Implemented Art Collection relation calls and tests

Implemented art collection relation calls and test script.
And some other minor sanity checks.
master
Peery 1 year ago
parent a03549c635
commit f841643d5f

@ -7,7 +7,7 @@ import sqlalchemy
from sqlalchemy.orm import declarative_base, relationship, load_only
from database.models import Art, ArtnoID, Artist, Presence, TagCategory, TagCategorynoID, TagNoID, Tag, \
Topic, TopicNoId, Collection, CollectionNoID
Topic, TopicNoId, Collection, CollectionNoID, Art2CollRelationNoID, Art2CollRelation
# TODO read sensitive data from environment file or similar
SQLALCHEMY_DATABASE_URL = "postgresql://artnet_editor:G606Rm9sFEXe6wfTLxVu@[::1]/test_artnet"
@ -23,7 +23,7 @@ art_to_tag_table = Table('art_to_tag', Base.metadata,
art_to_art_collection = Table('art_to_art_collection', Base.metadata,
Column('collection_id', Integer, ForeignKey('art_collection.id')),
Column('art_id', Integer, ForeignKey('art.id')),
Column('ranking', String))
Column('ranking', String))"""
artist_to_topic_table = Table('artist_to_topic', Base.metadata,
Column('artist_id', Integer, ForeignKey('artist.id')),
@ -68,7 +68,7 @@ class DBArt(Base):
description = Column(String, nullable=True)
tags = relationship("DBTag", secondary=art_to_tag_table, back_populates="art", viewonly=True)
collections = relationship("DBCollection", secondary=art_to_art_collection, back_populates="art")
collections = relationship("DBCollection", secondary="art_to_art_collection", back_populates="art")
presences = relationship("DBPresence", secondary="art_to_presence", back_populates="arts")
@ -104,7 +104,15 @@ class DBCollection(Base):
name = Column(String, unique=True, nullable=False)
description = Column(String)
art = relationship("DBArt", secondary=art_to_art_collection, back_populates="collections")
art = relationship("DBArt", secondary="art_to_art_collection", back_populates="collections")
class DBCollection2Art(Base):
__tablename__ = "art_to_art_collection"
collection_id = Column(Integer, ForeignKey(DBCollection.id, ondelete="CASCADE"), primary_key=True)
art_id = Column(Integer, ForeignKey(DBArt.id, ondelete="CASCADE"), primary_key=True)
ranking = Column(String)
class DBTopic(Base): # as of now unused
@ -155,7 +163,6 @@ class Database:
try:
return Database.__db
finally:
##Database.__db.close()
pass
def __del__(self):
@ -188,6 +195,8 @@ class Database:
if art.presences is not None:
for presence in art.presences:
if self.get_presence(presence.name, presence.domain) is None:
raise ValueError("Used presence did not exist!")
db_art_presence = DBArt2Presence(presence_name=presence.name, presence_domain=presence.domain,
art_id=db_art.id)
db.add(db_art_presence)
@ -213,6 +222,14 @@ class Database:
db_art.path = art.path if art.path is not None else db_art.path
db_art.description = art.description if art.description is not None else db_art.description
if art.presences is not None:
for presence in art.presences:
db_art.presences.append(self.get_presence(presence.name, presence.domain))
if art.collections is not None:
for collection in art.collections:
db_art.collections.append(self.get_collection_by_id(collection.id))
self.__get_db().commit()
def delete_art_by_id(self, art_id: int):
@ -616,4 +633,43 @@ class Database:
self.__get_db().delete(db_collection)
self.__get_db().commit()
# Art -> Collection
def get_collection_art(self, collection_id: int):
return self.__get_db().query(DBCollection2Art).filter(DBCollection2Art.collection_id == collection_id).all()
# Art <-> Collection
def get_art_collection_relation(self, art_id: int, collection_id: int) -> DBCollection2Art:
return self.__get_db().query(DBCollection2Art).filter(DBCollection2Art.collection_id == collection_id,
DBCollection2Art.art_id == art_id).first()
def create_art_collection_relation(self, art2collection: Art2CollRelation):
db_art2collection = DBCollection2Art(art_id=art2collection.art_id,
collection_id=art2collection.collection_id,
ranking=art2collection.ranking)
self.__get_db().add(db_art2collection)
self.__get_db().commit()
def update_art_collection_relation_by_model(self, art_collection_relation: Art2CollRelation):
db_art2collection = self.get_art_collection_relation(art_id=art_collection_relation.art_id,
collection_id=art_collection_relation.collection_id)
db_art2collection.ranking = art_collection_relation.ranking
self.__get_db().commit()
def delete_art_collection_relation(self, art_id: int, collection_id: int):
db_art2collection = self.get_art_collection_relation(art_id=art_id, collection_id=collection_id)
if db_art2collection is None:
raise ValueError("Could not find art-collection relation with these IDs!")
self.__get_db().delete(db_art2collection)
self.__get_db().commit()
# Collection -> Art
def get_art_collections(self, art_id: int):
return self.__get_db().query(DBCollection2Art).filter(
DBCollection2Art.art_id == art_id).all()
# TODO throws error
# TODO SAWarning: SELECT statement has a cartesian product between FROM element(s) "art_to_art_collection" and FROM element "art_collection". Apply join condition(s) between each element to resolve.
# return self.__get_db().query(DBCollection).filter(DBCollection2Art.art_id == art_id).all()

@ -78,8 +78,10 @@ class ArtnoID(BaseModel):
title: Optional[str] = None
link: Optional[str] = None
description: Optional[str]
presences: Optional[List[Presence]]
tags: Optional[List[Tag]]
collections: Optional[List[Collection]]
class Config:
orm_mode = True
@ -92,16 +94,17 @@ class Art(ArtnoID):
orm_mode = True
class CollectionNoID(BaseModel):
name: str
description: Optional[str]
class Art2CollRelationNoID(BaseModel):
ranking: Optional[str]
class Config:
orm_mode = True
class Collection(CollectionNoID):
id: int
class Art2CollRelation(Art2CollRelationNoID):
collection_id: int
art_id: int
class Config:
orm_mode = True

@ -4,16 +4,16 @@ import uvicorn
from typing import List, Tuple
from database.database import Database
from database.database import DBPresence, DBArt2Presence
from database.database import DBPresence, DBArt2Presence, DBCollection2Art
from database.models import Art, ArtnoID, Presence, ArtistNoId, Tag, TagNoID, TagCategory, TagCategorynoID, TopicNoId, \
Topic, CollectionNoID, Collection
Topic, CollectionNoID, Collection, Art2CollRelationNoID, Art2CollRelation
app = FastAPI()
db = Database()
@app.get("/artnet/metadata/art")
async def art(id: int = None, hash: str = None, tag_id: int = None):
async def art(id: int = None, hash: str = None, tag_id: int = None, collection_id: int = None):
hash = unquote(hash) if hash is not None else None
print(f"Received GET on /artnet/metadata/art (id={id}, hash={hash}, tag_id={tag_id})")
@ -24,6 +24,8 @@ async def art(id: int = None, hash: str = None, tag_id: int = None):
result = db.get_art_by_hash(hash)
elif tag_id is not None:
result = db.get_tag_art(tag_id)
elif collection_id is not None:
result = db.get_collection_art(collection_id)
if id is None and hash is None and tag_id is None: # TODO fix against listing enormous amounts of art (because "all" could be large)
result = db.get_art_list()
@ -60,6 +62,9 @@ async def art(art: ArtnoID, id: int = None):
if art.presences is not None:
updated_art.presences = art.presences
if art.collections is not None:
updated_art.collections = art.collections
db.update_art_by_model(updated_art)
return True
@ -350,10 +355,10 @@ def category(id: int):
@app.get("/artnet/metadata/collection")
def collection(id: int = None, name: str = None):
def collection(id: int = None, name: str = None, art_id: int = None):
print(f"Received GET on /artnet/metadata/collection (id={id}, name={name})")
result = None
if id is None and name is None:
if id is None and name is None and art_id is None:
result = db.get_collection_list()
if id is not None and name is not None:
@ -365,9 +370,14 @@ def collection(id: int = None, name: str = None):
if name is not None:
result = db.get_collections_by_name(name)
if art_id is not None:
result = db.get_art_collections(art_id)
if result is not None:
if isinstance(result, list):
for i in range(len(result)):
if isinstance(result[i], DBCollection2Art):
continue
result[i].name = result[i].name.strip()
else:
result.name = result.name.strip()
@ -396,5 +406,41 @@ async def collection(id: int):
raise HTTPException(status_code=404, detail=str(e))
@app.get("/artnet/metadata/collection_entry")
async def collection_entry(art_id: int, collection_id: int):
print(f"Received GET on /artnet/metadata/collection_entry (art_id={art_id}, collection_id={collection_id})")
result = db.get_art_collection_relation(art_id=art_id, collection_id=collection_id)
if result is None:
raise HTTPException(status_code=404, detail="Can not find a art-collection-relation with these IDs!")
return result
@app.post("/artnet/metadata/collection_entry")
async def collection_entry(art2collection: Art2CollRelationNoID, art_id: int = None, collection_id: int = None):
print(f"Received POST on /artnet/metadata/collection_entry")
if art_id is None and collection_id is None: # create new relation, impossible!
raise HTTPException(status_code=406, detail="You can not create a relation without IDs!")
elif art_id is not None and collection_id is not None:
art2collection_id = Art2CollRelation(art_id=art_id, collection_id=collection_id)
art2collection_id.ranking = art2collection.ranking
db.update_art_collection_relation_by_model(art2collection_id)
return
raise HTTPException(status_code=406, detail="You can not only specify only one ID!")
@app.delete("/artnet/metadata/collection_entry")
async def collection_entry(art_id: int, collection_id: int):
print(f"Received DELETE on /artnet/metadata/collection_entry (art_id={art_id}, collection_id={collection_id})")
try:
db.delete_art_collection_relation(art_id=art_id, collection_id=collection_id)
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e))
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000)

@ -10,13 +10,13 @@ from tests.createAPI.create_delete_presence import test_presence_entries, list_p
test_art_entries = [
{"hash": "hash1", "path": "artist1/image1", "title": "Image Title 1", "link": "http://localhost/artist1/image1.png",
"description": "description Nr. 1",
"presences": [(test_presence_entries[0]["name"], test_presence_entries[0]["domain"])]},
"presences": [(test_presence_entries[2]["name"], test_presence_entries[2]["domain"])]},
{"hash": "hash2", "path": "artist1/image2", "title": "Image Title 2", "link": "http://localhost/artist1/image2.png",
"description": "description Nr. 2",
"presences": [(test_presence_entries[1]["name"], test_presence_entries[1]["domain"])]},
"presences": [(test_presence_entries[2]["name"], test_presence_entries[2]["domain"])]},
{"hash": "hash3", "path": "artist2/image1", "title": "Image Title 3", "link": "http://localhost/artist2/image3.png",
"description": "description Nr. 3",
"presences": [(test_presence_entries[0]["name"], test_presence_entries[0]["domain"]),
"presences": [(test_presence_entries[2]["name"], test_presence_entries[2]["domain"]),
(test_presence_entries[1]["name"], test_presence_entries[1]["domain"])]},
]
@ -35,6 +35,15 @@ def list_art(url: str, port: int):
return art
def get_art(url: str, port: int, id: int):
r = requests.get(f"http://{url}:{port}/artnet/metadata/art")
if r.status_code != 200:
print(f"Failed to query for art! Status: {r.status_code} Reason: {r.text}")
raise Exception("Failed querying for art!")
art = json.loads(r.text)
return art
def create_art_entries(url: str, port: int):
"""
Creates many art entries for testing purposes

@ -105,7 +105,7 @@ def delete_art_presence_relations(url: str, port: int):
def delete_art_presence_relation(url: str, port: int, presence_name: str, presence_domain: str, art_id: int):
return requests.delete(f"http://{url}:{port}/artnet/metadata/art?id={art_id}&presence_name={presence_name}"
f"&presence_domain={presence_domain}")
f"&presence_domain={presence_domain}")
def run_art_presence_relation_test(url, port):

@ -0,0 +1,128 @@
import requests
import json
from tests.createAPI.create_delete_art import get_art, list_art, create_art_entries, delete_art_entries, \
test_art_entries
from tests.createAPI.create_delete_presence import create_presence_entries
from tests.createAPI.create_delete_artist import create_artist_entries, delete_artist_entries
from tests.createAPI.create_delete_collection import get_collection_by_id, list_collections, create_collection_entries, \
delete_collection_entries, test_collection_entries
test_art_2_collection_entries = [{"art_id": 0, "collection_id": 0, "ranking": "aa"},
{"art_id": 1, "collection_id": 0, "ranking": "ab"},
{"art_id": 2, "collection_id": 1, "ranking": "aaa"},
{"art_id": 1, "collection_id": 1, "ranking": "ac"}]
def create_art2collections(url: str, port: int):
for relation in test_art_2_collection_entries:
add_art_to_collection(url, port, art_id=test_art_entries[relation["art_id"]]["ID"],
collection_id=test_collection_entries[relation["collection_id"]]["id"])
get_art2collection(url, port, art_id=test_art_entries[relation["art_id"]]["ID"],
collection_id=test_collection_entries[relation["collection_id"]]["id"])
return True
def update_art2collections(url: str, port: int):
for relation in test_art_2_collection_entries:
update_art2collection(url, port, art_id=test_art_entries[relation["art_id"]]["ID"],
collection_id=test_collection_entries[relation["collection_id"]]["id"],
ranking=relation["ranking"])
c = get_art2collection(url, port, art_id=test_art_entries[relation["art_id"]]["ID"],
collection_id=test_collection_entries[relation["collection_id"]]["id"])
if c["ranking"] != relation["ranking"]:
print(f"Failed to update the ARt2Collection relation! Got: {c['ranking']} Expected: {relation['ranking']}")
raise Exception("Failed to update the ranking in Art2Collection")
return True
def delete_art2collections(url: str, port: int):
for relation in test_art_2_collection_entries:
delete_art2collection(url, port, art_id=test_art_entries[relation["art_id"]]["ID"],
collection_id=test_collection_entries[relation["collection_id"]]["id"])
return True
def add_art_to_collection(url: str, port: int, art_id: int, collection_id: int):
prev_colls = get_art2collections_by_art_id(url, port, art_id)
r = requests.post(f"http://{url}:{port}/artnet/metadata/art?id={art_id}",
json={"collections": [{"id": c["collection_id"]} for c in prev_colls] + [{"id": collection_id}]})
if r.status_code != 200:
print(f"Failed to set Art2Collection. Status: {r.status_code} Reason: {r.text}")
raise Exception("Failed to set Art2Collection relation!")
def update_art2collection(url: str, port: int, art_id: int, collection_id: int, ranking: str):
r = requests.post(f"http://{url}:{port}/artnet/metadata/collection_entry?art_id={art_id}"
f"&collection_id={collection_id}",
json={"ranking": ranking})
if r.status_code != 200:
print(f"Failed to update Art2Collection relation! Status: {r.status_code} Reason: {r.text}")
raise Exception("Failed to update Art2Collection relation!")
def get_art2collection(url: str, port: int, art_id: int, collection_id: int):
r = requests.get(f"http://{url}:{port}/artnet/metadata/collection_entry?art_id={art_id}"
f"&collection_id={collection_id}")
if r.status_code != 200:
print(f"Failed to fetch specific art-collection relation! Status: {r.status_code} Reason: {r.text}")
raise Exception("Failed to fetch Art2Collection!")
result = json.loads(r.text)
if result["art_id"] != art_id or result["collection_id"] != collection_id:
print(f"Got: ({result['art_id']}, {result['collection_id']}) Expected: ({art_id}, {collection_id})")
raise Exception("The returned relation does not fit the requested one!")
return result
def delete_art2collection(url: str, port: int, art_id: int, collection_id: int):
r = requests.delete(f"http://{url}:{port}/artnet/metadata/collection_entry?art_id={art_id}"
f"&collection_id={collection_id}")
if r.status_code != 200:
print(f"Failed to delete art-collection relation! Status: {r.status_code} Reason: {r.text}")
raise Exception("Failed to delete art-collection relation!")
def get_art2collections_by_art_id(url: str, port: int, art_id: int):
r = requests.get(f"http://{url}:{port}/artnet/metadata/collection?art_id={art_id}")
if r.status_code != 200:
print(f"Failed to query for Art2Collection relation via art_id. Status: {r.status_code} Reason: {r.text}")
raise Exception("Failed to query Art2Collection relations!")
return json.loads(r.text)
def run_art2collections_tests(url: str, port: int):
print()
print("----------------")
print(f"Starting art2collection test with ({len(list_collections(url, port))}) collections and "
f"({len(list_art(url, port))}) art!")
print("Setting up art, presence, artists and collections for the tests ...")
create_collection_entries(url, port)
create_artist_entries(url, port)
create_presence_entries(url, port)
create_art_entries(url, port)
print(f"Done with the setup! Now have ({len(list_collections(url, port))}) collections and "
f"({len(list_art(url, port))}) art!")
print("Adding art2collection relation ...")
create_result = create_art2collections(url, port)
print("Updating rankings ...")
update_result = update_art2collections(url, port)
print("Removing relations ...")
delete_result = delete_art2collections(url, port)
print("Done with the art2collection relation test!")
print("Cleaning up the setup ...")
delete_collection_entries(url, port)
delete_artist_entries(url, port)
delete_art_entries(url, port)
return create_result, update_result, delete_result

@ -8,6 +8,7 @@ from tests.createAPI.create_delete_topic import list_topics, run_topic_tests, de
from tests.createAPI.create_delete_collection import run_collection_tests
from tests.relationAPI.relate_art import run_art_presence_relation_test
from tests.relationAPI.relate_art_to_collection import run_art2collections_tests
def run_tests(url: str, port: int):
@ -38,6 +39,8 @@ def run_tests(url: str, port: int):
create_tag_result, update_tag_result, delete_tag_result = run_tag_test(url, port)
create_topic_result, update_topic_result, delete_topic_result = run_topic_tests(url, port)
create_collection_result, update_collection_result, delete_collection_result = run_collection_tests(url, port)
create_art2collection_result, update_art2collection_result, delete_art2collection_result = \
run_art2collections_tests(url, port)
print()
print("-------- Test Results ---------")
@ -73,7 +76,8 @@ def run_tests(url: str, port: int):
f"\tDelete: {delete_topic_result} \t(Direct)")
print(f"Art Collection: \t\tCreate: {create_collection_result} \tUpdate: {update_collection_result} "
f"\tDelete: {delete_collection_result} \t(Direct)")
print(f"Art2Art Collection: \tN/A")
print(f"Art2Art Collection: \tCreate: {create_art2collection_result} \tUpdate: {update_art2collection_result}"
f"\tDelete: {delete_art2collection_result} \t(Direct)")
print(f"Artist2Topic: \t\t\tN/A")
print(f"Art2Tag: \t\t\t\tN/A")
print(f"Tag Alias: \t\t\t\tN/A")

Loading…
Cancel
Save