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 sqlalchemy.orm import declarative_base, relationship, load_only
from database.models import Art, ArtnoID, Artist, Presence, TagCategory, TagCategorynoID, TagNoID, Tag, \ 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 # TODO read sensitive data from environment file or similar
SQLALCHEMY_DATABASE_URL = "postgresql://artnet_editor:G606Rm9sFEXe6wfTLxVu@[::1]/test_artnet" 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, art_to_art_collection = Table('art_to_art_collection', Base.metadata,
Column('collection_id', Integer, ForeignKey('art_collection.id')), Column('collection_id', Integer, ForeignKey('art_collection.id')),
Column('art_id', Integer, ForeignKey('art.id')), Column('art_id', Integer, ForeignKey('art.id')),
Column('ranking', String)) Column('ranking', String))"""
artist_to_topic_table = Table('artist_to_topic', Base.metadata, artist_to_topic_table = Table('artist_to_topic', Base.metadata,
Column('artist_id', Integer, ForeignKey('artist.id')), Column('artist_id', Integer, ForeignKey('artist.id')),
@ -68,7 +68,7 @@ class DBArt(Base):
description = Column(String, nullable=True) description = Column(String, nullable=True)
tags = relationship("DBTag", secondary=art_to_tag_table, back_populates="art", viewonly=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") presences = relationship("DBPresence", secondary="art_to_presence", back_populates="arts")
@ -104,7 +104,15 @@ class DBCollection(Base):
name = Column(String, unique=True, nullable=False) name = Column(String, unique=True, nullable=False)
description = Column(String) 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 class DBTopic(Base): # as of now unused
@ -155,7 +163,6 @@ class Database:
try: try:
return Database.__db return Database.__db
finally: finally:
##Database.__db.close()
pass pass
def __del__(self): def __del__(self):
@ -188,6 +195,8 @@ class Database:
if art.presences is not None: if art.presences is not None:
for presence in art.presences: 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, db_art_presence = DBArt2Presence(presence_name=presence.name, presence_domain=presence.domain,
art_id=db_art.id) art_id=db_art.id)
db.add(db_art_presence) 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.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 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() self.__get_db().commit()
def delete_art_by_id(self, art_id: int): def delete_art_by_id(self, art_id: int):
@ -616,4 +633,43 @@ class Database:
self.__get_db().delete(db_collection) self.__get_db().delete(db_collection)
self.__get_db().commit() 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 title: Optional[str] = None
link: Optional[str] = None link: Optional[str] = None
description: Optional[str] description: Optional[str]
presences: Optional[List[Presence]] presences: Optional[List[Presence]]
tags: Optional[List[Tag]] tags: Optional[List[Tag]]
collections: Optional[List[Collection]]
class Config: class Config:
orm_mode = True orm_mode = True
@ -92,16 +94,17 @@ class Art(ArtnoID):
orm_mode = True orm_mode = True
class CollectionNoID(BaseModel): class Art2CollRelationNoID(BaseModel):
name: str ranking: Optional[str]
description: Optional[str]
class Config: class Config:
orm_mode = True orm_mode = True
class Collection(CollectionNoID): class Art2CollRelation(Art2CollRelationNoID):
id: int collection_id: int
art_id: int
class Config: class Config:
orm_mode = True orm_mode = True

@ -4,16 +4,16 @@ import uvicorn
from typing import List, Tuple from typing import List, Tuple
from database.database import Database 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, \ from database.models import Art, ArtnoID, Presence, ArtistNoId, Tag, TagNoID, TagCategory, TagCategorynoID, TopicNoId, \
Topic, CollectionNoID, Collection Topic, CollectionNoID, Collection, Art2CollRelationNoID, Art2CollRelation
app = FastAPI() app = FastAPI()
db = Database() db = Database()
@app.get("/artnet/metadata/art") @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 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})") 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) result = db.get_art_by_hash(hash)
elif tag_id is not None: elif tag_id is not None:
result = db.get_tag_art(tag_id) 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) 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() result = db.get_art_list()
@ -60,6 +62,9 @@ async def art(art: ArtnoID, id: int = None):
if art.presences is not None: if art.presences is not None:
updated_art.presences = art.presences updated_art.presences = art.presences
if art.collections is not None:
updated_art.collections = art.collections
db.update_art_by_model(updated_art) db.update_art_by_model(updated_art)
return True return True
@ -350,10 +355,10 @@ def category(id: int):
@app.get("/artnet/metadata/collection") @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})") print(f"Received GET on /artnet/metadata/collection (id={id}, name={name})")
result = None 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() result = db.get_collection_list()
if id is not None and name is not None: 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: if name is not None:
result = db.get_collections_by_name(name) 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 result is not None:
if isinstance(result, list): if isinstance(result, list):
for i in range(len(result)): for i in range(len(result)):
if isinstance(result[i], DBCollection2Art):
continue
result[i].name = result[i].name.strip() result[i].name = result[i].name.strip()
else: else:
result.name = result.name.strip() result.name = result.name.strip()
@ -396,5 +406,41 @@ async def collection(id: int):
raise HTTPException(status_code=404, detail=str(e)) 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__": if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=8000) 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 = [ test_art_entries = [
{"hash": "hash1", "path": "artist1/image1", "title": "Image Title 1", "link": "http://localhost/artist1/image1.png", {"hash": "hash1", "path": "artist1/image1", "title": "Image Title 1", "link": "http://localhost/artist1/image1.png",
"description": "description Nr. 1", "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", {"hash": "hash2", "path": "artist1/image2", "title": "Image Title 2", "link": "http://localhost/artist1/image2.png",
"description": "description Nr. 2", "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", {"hash": "hash3", "path": "artist2/image1", "title": "Image Title 3", "link": "http://localhost/artist2/image3.png",
"description": "description Nr. 3", "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"])]}, (test_presence_entries[1]["name"], test_presence_entries[1]["domain"])]},
] ]
@ -35,6 +35,15 @@ def list_art(url: str, port: int):
return art 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): def create_art_entries(url: str, port: int):
""" """
Creates many art entries for testing purposes 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): 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}" 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): 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.createAPI.create_delete_collection import run_collection_tests
from tests.relationAPI.relate_art import run_art_presence_relation_test 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): 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_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_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_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()
print("-------- Test Results ---------") print("-------- Test Results ---------")
@ -73,7 +76,8 @@ def run_tests(url: str, port: int):
f"\tDelete: {delete_topic_result} \t(Direct)") f"\tDelete: {delete_topic_result} \t(Direct)")
print(f"Art Collection: \t\tCreate: {create_collection_result} \tUpdate: {update_collection_result} " print(f"Art Collection: \t\tCreate: {create_collection_result} \tUpdate: {update_collection_result} "
f"\tDelete: {delete_collection_result} \t(Direct)") 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"Artist2Topic: \t\t\tN/A")
print(f"Art2Tag: \t\t\t\tN/A") print(f"Art2Tag: \t\t\t\tN/A")
print(f"Tag Alias: \t\t\t\tN/A") print(f"Tag Alias: \t\t\t\tN/A")

Loading…
Cancel
Save