diff --git a/database/models.py b/database/models.py index af45df3..747f378 100644 --- a/database/models.py +++ b/database/models.py @@ -15,7 +15,7 @@ class Topic(TopicNoId): class ArtistNoId(BaseModel): - description: str + name: str topics: Optional[List[int]] class Config: @@ -50,6 +50,34 @@ class ArtnoID(BaseModel): orm_mode = True +class TagnoID(BaseModel): + name: Optional[str] + description: Optional[str] + category_id: Optional[TagCategory] + + class Config: + orm_mode = True + + +class Tag(TagnoID): + tag_ID: int + + class Config: + orm_mode = True + + +class ArtnoID(BaseModel): + hash: Optional[str] + path: Optional[str] + title: Optional[str] = None + link: Optional[str] = None + presences: Optional[List[Presence]] + tags: Optional[List[Tag]] + + class Config: + orm_mode = True + + class Art(ArtnoID): id: int diff --git a/main.py b/main.py index 0e67d84..ebcbd72 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +from urllib.parse import unquote from fastapi import FastAPI, HTTPException import uvicorn from typing import List, Tuple @@ -11,9 +12,8 @@ db = Database() @app.get("/artnet/metadata/art") async def art(id: int = None, hash: str = None, tag_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})") - #if id is None and hash is None and tag_id is None: - #raise HTTPException(status_code=406, detail="requires id or hash param") result = None if id is not None: @@ -38,46 +38,59 @@ async def art(art: ArtnoID, id: int = None): if art.presences is None: # tried to create art without any presence raise HTTPException(status_code=422, detail="No presences were listed") try: - for presence in art.presences: - r = db.get_presence(name=presence.name, domain=presence.domain) - #if db.get_presence(name=presence.name, domain=presence.domain) is None: - # raise HTTPException(status_code=422, - # detail=f"Presence ({presence.name}, {presence.domain}) is unknown!") - #db.create # create presence-art relationship - # TODO validate & create presence-art relationship - - new_id = db.create_art_by_model(art) + new_id = db.create_art_by_model(art).id return {"id": new_id} + except ValueError as e: raise HTTPException(status_code=422, detail=str(e)) + else: # update existing art entry if db.get_art_by_id(id) is None: - return HTTPException(status_code=404, detail="The specified art could not be found!") + raise HTTPException(status_code=404, detail="The specified art could not be found!") - # TODO implement linking to one or multiple presences - # TODO enforcing at least one presence updated_art = Art(id=id) updated_art.hash = art.hash updated_art.link = art.link updated_art.title = art.title updated_art.path = art.path + if art.presences is not None: + updated_art.presences = art.presences + db.update_art_by_model(updated_art) return True @app.delete("/artnet/metadata/art") -async def art(id: int): - print(f"Received DELETE on /artnet/metadata/art (id={id})") +async def art(id: int, presence_name: str = None, presence_domain: str = None): + presence_name = unquote(presence_name) if presence_name is not None else None + presence_domain = unquote(presence_domain) if presence_domain is not None else None + print(f"Received DELETE on /artnet/metadata/art (id={id}, presence_name={presence_name}, " + f"presence_domain={presence_domain})") + print("All art available is", [a.id for a in db.get_art_list()]) if db.get_art_by_id(id) is None: raise HTTPException(status_code=404, detail="Art has not been found!") - db.delete_art_by_id(id) + + if id is not None and presence_name is None and presence_domain is None: + db.delete_art_by_id(id) + return + elif id is not None and presence_name is not None and presence_domain is not None: + try: + db.delete_art_presences_by_id(art_id=id, presence_name=presence_name, presence_domain=presence_domain) + except ValueError: + raise HTTPException(status_code=404, detail="The art-presence relation could not be found!") + return + + raise HTTPException(status_code=422, detail="Unknown parameter combination!") @app.get("/artnet/metadata/presence") async def presence(name: str = None, domain: str = None, artist_id: int = None, art_id: int = None, art_md5: str = None): + name = unquote(name) if name is not None else None + domain = unquote(domain) if domain is not None else None + art_md5 = unquote(art_md5) if art_md5 is not None else None print(f"Received GET on /artnet/metadata/presence (name={name}, domain={domain}, artist_id={artist_id}" f", art_id={art_id}, art_md5={art_md5})") result = None @@ -127,18 +140,19 @@ async def presence(name: str, domain: str): @app.get("/artnet/metadata/artist") -async def artist(id: int = None, topic_id: int = None, description: str = None): - print(f"Received GET on /artnet/metadata/artist (id={id}, topic_id={topic_id}, description={description})") +async def artist(id: int = None, topic_id: int = None, name: str = None): + name = unquote(name) if name is not None else None + print(f"Received GET on /artnet/metadata/artist (id={id}, topic_id={topic_id}, name={name})") result = None - if id is None and topic_id is None and description is None: + if id is None and topic_id is None and name is None: result = db.get_artist_list() if id is not None: result = db.get_artist(id) elif topic_id is not None: result = db.get_topic_artists(topic_id) - elif description is not None: - result = db.search_artist(description) + elif name is not None: + result = db.search_artist(name) if result is not None: return result @@ -149,12 +163,12 @@ async def artist(id: int = None, topic_id: int = None, description: str = None): async def artist(artist: ArtistNoId, id: int = None): print(f"Received POST on /artnet/metadata/artist (id={id}) body: artist={artist}") if id is None: # create new artist - db_artist = db.create_artist(name=artist.description, topics=artist.topics) + db_artist = db.create_artist(name=artist.name, topics=artist.topics) return db_artist.id else: if db.get_artist(id) is not None: - db.update_artist(artist_id=id, name=artist.description, topics=artist.topics) + db.update_artist(artist_id=id, name=artist.name, topics=artist.topics) else: raise HTTPException(status_code=404, detail="Tried to edit unknown artist. ID was not found!") @@ -167,9 +181,9 @@ async def artist(id: int): db.delete_artist(id) - @app.get("/artnet/metadata/topic") async def topic(name: str = None, id: int = None, artist_id: int = None): + name = unquote(name) if name is not None else None print(f"Received GET on /artnet/metadata/topic (name={name}, id={id}, artist_id={artist_id})") if name is None and id is None and artist_id is None: raise HTTPException(status_code=406) @@ -191,6 +205,8 @@ async def topic(name: str = None, id: int = None, artist_id: int = None): @app.get("/artnet/metadata/tag") async def tag(id: int = None, name: str = None, fuzzy: bool = False, category: int = None): + name = unquote(name) if name is not None else None + print(f"Received GET on /artnet/metadata/tag (name={name}, id={id}, name={category}, fuzzy={fuzzy})") if id is None and name is None and category is None: raise HTTPException(status_code=406) @@ -216,8 +232,11 @@ async def tag(id: int = None, name: str = None, fuzzy: bool = False, category: i @app.get("/artnet/metadata/category") async def category(name: str = None, id: int = None): + name = unquote(name) if name is not None else None + print(f"Received GET on /artnet/metadata/category (name={name}, id={id})") if name is None and id is None: - raise HTTPException(status_code=406) + result = db.get_category_list() + return result result = None if id is not None: diff --git a/tests/createAPI/create_delete_art.py b/tests/createAPI/create_delete_art.py index 82c6e32..ba4efed 100644 --- a/tests/createAPI/create_delete_art.py +++ b/tests/createAPI/create_delete_art.py @@ -1,4 +1,5 @@ import requests +import urllib.parse import json from typing import List, Tuple @@ -63,7 +64,7 @@ def update_art_entries(url: str, port: int): raise Exception("Update Art Entry Test: FAILED") for i in range(len(test_art_entries)): - r = requests.get(f"http://{url}:{port}/artnet/metadata/art?id={test_art_entries[i]['ID']}") + r = requests.get(f"http://{url}:{port}/artnet/metadata/art?id={urllib.parse.quote(str(test_art_entries[i]['ID']))}") if json.loads(r.text)["title"].split(" ")[-1] != "updated": raise Exception("Update Art Entry Test: Failed (unexpected or no updated title)") if json.loads(r.text)["link"] != test_art_entries[i]["link"]: @@ -79,10 +80,7 @@ def delete_art_entries(url: str, port: int): if "id" in arts[i].keys(): r = delete_art_by_id(url, port, art_id=arts[i]["id"]) else: - try: - r = delete_art_by_hash(url, port, md5_hash=arts[i]["hash"]) - except ValueError: - r = None + r = delete_art_by_hash(url, port, md5_hash=arts[i]["hash"]) if r is not None and r.status_code != 200: raise Exception(f"Could not delete the art! {r.status_code}, {r.text}") @@ -112,20 +110,20 @@ def update_art(url: str, port: int, art_id: int, md5_hash: str = None, path: str if presences is not None: body["presences"] = [{"name": presence[0], "domain": presence[1]} for presence in presences] - r = requests.post(f"http://{url}:{port}/artnet/metadata/art?id={art_id}", + r = requests.post(f"http://{url}:{port}/artnet/metadata/art?id={urllib.parse.quote(str(art_id))}", json=body) return r def delete_art_by_id(url: str, port: int, art_id: int): - r = requests.delete(f"http://{url}:{port}/artnet/metadata/art?id={art_id}") + r = requests.delete(f"http://{url}:{port}/artnet/metadata/art?id={urllib.parse.quote(str(art_id))}") return r def delete_art_by_hash(url: str, port: int, md5_hash: str): - r1 = requests.get(f"http://{url}:{port}/artnet/metadata/art?hash={md5_hash}") + r1 = requests.get(f"http://{url}:{port}/artnet/metadata/art?hash={urllib.parse.quote(md5_hash)}") if r1.status_code != 200: raise ValueError("Could not delete art because it could not be found!") data = json.loads(str(r1.text)) @@ -154,7 +152,7 @@ def run_art_test(url: str, port: int): print(f"Creating {len(test_art_entries)} art entries ...") create_art_entries(url, port) - create_art_result = False if not len(list_art(url, port)) == 3 else True + create_art_result = False if not len(list_art(url, port)) == len(test_art_entries) else True print(f"Found {len(list_art(url, port))} art entries!") if not create_art_result: print(f"Failure! Unexpected number of art entries!") @@ -164,8 +162,6 @@ def run_art_test(url: str, port: int): update_art_result = True # given, the update function would raise an exception on error print(f"Found {len(list_art(url, port))} art entries!") - # TODO write test for updating presence-art relation - print("Deleting all art entries ...") delete_art_entries(url, port) delete_art_result = False if not len(list_art(url, port)) == 0 else True diff --git a/tests/createAPI/create_delete_artist.py b/tests/createAPI/create_delete_artist.py index 215ddfa..02d40f7 100644 --- a/tests/createAPI/create_delete_artist.py +++ b/tests/createAPI/create_delete_artist.py @@ -1,4 +1,5 @@ import requests +import urllib.parse from typing import List import json @@ -56,7 +57,7 @@ def create_artist(url: str, port: int, name: str, topics: List[int]): def delete_artist(url: str, port: int, id: int): - r = requests.delete(f"http://{url}:{port}/artnet/metadata/artist?id={id}") + r = requests.delete(f"http://{url}:{port}/artnet/metadata/artist?id={urllib.parse.quote(str(id))}") return r diff --git a/tests/createAPI/create_delete_presence.py b/tests/createAPI/create_delete_presence.py index a697158..7cebb2c 100644 --- a/tests/createAPI/create_delete_presence.py +++ b/tests/createAPI/create_delete_presence.py @@ -1,4 +1,5 @@ import requests +import urllib.parse import json from typing import List, Tuple @@ -30,7 +31,8 @@ def create_presence(url: str, port: int, name: str, domain: str, artist_id: int) def delete_presence(url: str, port: int, name: str, domain: str): - r = requests.delete(f"http://{url}:{port}/artnet/metadata/presence?name={name}&domain={domain}") + r = requests.delete(f"http://{url}:{port}/artnet/metadata/presence?name={urllib.parse.quote(name)}" + f"&domain={urllib.parse.quote(domain)}") if r.status_code != 200: print(f"Deleting Presence entry {name}@{domain}: FAILED") diff --git a/tests/relationAPI/relate_art.py b/tests/relationAPI/relate_art.py index 95e2754..8dd36c9 100644 --- a/tests/relationAPI/relate_art.py +++ b/tests/relationAPI/relate_art.py @@ -7,10 +7,10 @@ from tests.createAPI.create_delete_presence import test_presence_entries, list_p from tests.createAPI.create_delete_art import test_art_entries, list_art, create_art_entries, delete_art_entries test_art_presence_relations = [ - {"art_hash": test_art_entries[0]["hash"], "presence_name": test_presence_entries[0]["name"], "presence_domain": test_presence_entries[0]["domain"]}, - {"art_hash": test_art_entries[1]["hash"], "presence_name": test_presence_entries[0]["name"], "presence_domain": test_presence_entries[0]["domain"]}, - {"art_hash": test_art_entries[2]["hash"], "presence_name": test_presence_entries[0]["name"], "presence_domain": test_presence_entries[0]["domain"]}, - {"art_hash": test_art_entries[2]["hash"], "presence_name": test_presence_entries[1]["name"], "presence_domain": test_presence_entries[1]["domain"]}, + {"art_hash": test_art_entries[0]["hash"], "presence_name": test_art_entries[0]["presences"][0][0], "presence_domain": test_art_entries[0]["presences"][0][1]}, + {"art_hash": test_art_entries[1]["hash"], "presence_name": test_art_entries[1]["presences"][0][0], "presence_domain": test_art_entries[1]["presences"][0][1]}, + {"art_hash": test_art_entries[2]["hash"], "presence_name": test_art_entries[2]["presences"][0][0], "presence_domain": test_art_entries[1]["presences"][0][1]}, + {"art_hash": test_art_entries[2]["hash"], "presence_name": test_art_entries[2]["presences"][1][0], "presence_domain": test_art_entries[2]["presences"][1][1]}, ] @@ -38,6 +38,8 @@ def list_art_to_presence_relations(url: str, port: int): def art_hash_to_id(arts: List[dict]): """ Converts a list of arts into the format of {hash: id, hash1: id1} + + This allows reverse mapping of the usual ID->hash to hash->ID. :param arts: :return: """ @@ -77,6 +79,35 @@ def create_art_presence_relation(url: str, port: int, presence_name: str, presen return r +def delete_art_presence_relations(url: str, port: int): + arts = list_art(url, port) + art_hash_id = art_hash_to_id(arts) + for i in range(len(test_art_presence_relations)): + print(f"Deleting art-presence relation with id:{art_hash_id[test_art_presence_relations[i]['art_hash']]} " + f"presence_name:{test_art_presence_relations[i]['presence_name']} " + f"presence_domain:{test_art_presence_relations[i]['presence_domain']}") + r = delete_art_presence_relation(url, port, art_id=art_hash_id[test_art_presence_relations[i]["art_hash"]], + presence_name=test_art_presence_relations[i]["presence_name"], + presence_domain=test_art_presence_relations[i]["presence_domain"]) + if r.status_code != 200: + print(f"Tried to delete art-presence relation with name:{test_art_presence_relations[i]['presence_name']} " + f"domain:{test_art_presence_relations[i]['presence_domain']}" + f" art_id:{art_hash_id[test_art_presence_relations[i]['art_hash']]}") + print(f"Deleting Relation failed with {r.status_code} and reason {r.text}!") + raise Exception("Couldn't delete art-presence relation!") + + l = list_art_to_presence_relations(url, port) + if len(l[0]) == 0 and len(l[1]) == 0 and len(l[2]) == 0: + return True + else: + return False + + +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}") + + def run_art_presence_relation_test(url, port): print() print("----------------") @@ -87,7 +118,7 @@ def run_art_presence_relation_test(url, port): print("Deleting leftover art entries ...") delete_art_entries(url, port) - print(f"Starting presence test with " + print(f"Leftover entries for the test are " f"({len(list_presences(url, port))}) presences, " f"({len(list_art(url, port))}) art!") @@ -96,6 +127,10 @@ def run_art_presence_relation_test(url, port): create_presence_entries(url, port) create_art_entries(url, port) + print(f"Starting art2presence test with " + f"({len(list_presences(url, port))}) presences, " + f"({len(list_art(url, port))}) art!") + art = list_art(url, port) # debug presences = list_presences(url, port) # debug @@ -109,6 +144,14 @@ def run_art_presence_relation_test(url, port): create_result = create_art_presence_relations(url, port) print(f"Found {sum([len(x) for x in list_art_to_presence_relations(url, port)])} art-presence relations!") - # TODO delete art-presence relation test + delete_result = delete_art_presence_relations(url, port) + print(f"Found {sum([len(x) for x in list_art_to_presence_relations(url, port)])} art-presence relations!") - return create_result + delete_art_entries(url, port) + delete_artist_entries(url, port) + + print(f"Ending art2presence test with " + f"({len(list_presences(url, port))}) presences, " + f"({len(list_art(url, port))}) art!") + + return create_result, delete_result diff --git a/tests/run_tests.py b/tests/run_tests.py index c0def89..039e4d3 100644 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -1,6 +1,7 @@ from tests.createAPI.create_delete_art import run_art_test, list_art, delete_art_entries from tests.createAPI.create_delete_presence import run_presence_test, list_presences from tests.createAPI.create_delete_artist import delete_artist_entries, list_artists, run_artist_test +from tests.createAPI.create_delete_tag_category import delete_category_entries, list_tag_categories, run_tag_category_test from tests.relationAPI.relate_art import run_art_presence_relation_test @@ -18,10 +19,15 @@ def run_tests(url: str, port: int): if len(list_art(url, port)) > 0: print("Deleting leftover art ...") delete_art_entries(url, port) + if len(list_tag_categories(url, port)) > 0: + print("Deleting leftover tag categories ...") + delete_category_entries(url, port) create_artist_result, delete_artist_result = run_artist_test(url, port) create_presence_result, delete_presence_result, cascade_delete_presence_result = run_presence_test(url, port) create_art_result, update_art_result, delete_art_result = run_art_test(url, port) + create_art2presence_result, delete_art2presence_result = run_art_presence_relation_test(url, port) + create_category_result, update_category_result, delete_category_result = run_tag_category_test(url, port) print() print("-------- Test Results ---------") @@ -32,16 +38,32 @@ def run_tests(url: str, port: int): print(f"Presences: {len(r)}, {r}") r = list_art(url, port) print(f"Art: {len(r)}, {r}") + r = list_tag_categories(url, port) + print(f"Tag Categories: {len(r)}, {r}") print() print("Functions:") - print(f"Artists: \tCreate: {create_artist_result} \tUpdate: {'N/A'} \tDelete: {delete_artist_result} \t(Direct)") - print(f"Presences: \tCreate: {create_presence_result} \tUpdate: {'N/A'} " + print(f"Artists: \t\t\t\tCreate: {create_artist_result} \tUpdate: {'N/A'} " + f"\tDelete: {delete_artist_result} \t(Direct)") + print(f"Presences: \t\t\t\tCreate: {create_presence_result} \tUpdate: {'N/A'} " f"\tDelete: {delete_presence_result} \t(Direct), {cascade_delete_presence_result} \t(cascade@artist)") - print(f"Art: \t\tCreate: {create_art_result} \tUpdate: {update_art_result} " + print(f"Art: \t\t\t\t\tCreate: {create_art_result} \tUpdate: {update_art_result} " f"\tDelete: {delete_art_result} \t(Direct)") + print(f"Art2Presence: \t\t\tCreate: {create_art2presence_result} \tUpdate: {'N/A'} " + f"\tDelete: {delete_art2presence_result} \t(Direct)") + print(f"Tag Category: \t\t\tCreate: {create_category_result} \tUpdate: {update_category_result} " + f"\tDelete: {delete_category_result} \t(Direct)") + print(f"Tag: \t\t\t\t\tN/A") + print(f"(Artist) Topic: \t\tN/A") + print(f"Art Collection: \t\tN/A") + print(f"Art2Art Collection: \tN/A") + print(f"Artist2Topic: \t\t\tN/A") + print(f"Art2Tag: \t\t\t\tN/A") + print(f"Tag Alias: \t\t\t\tN/A") + print(f"Tag Implication: \t\tN/A") + print("-------------------------------") if __name__ == "__main__": - #run_tests("127.0.0.1", 8000) - run_art_presence_relation_test("127.0.0.1", 8000) + run_tests("127.0.0.1", 8000) +