diff --git a/README.md b/README.md index 656f567..990a98e 100644 --- a/README.md +++ b/README.md @@ -16,22 +16,22 @@ pip install -e . ## Usage -### Initializa directus client +### Initialize directus client ```python -from directus.clients import DirectusClient_V9 +from directus_sdk import DirectusClient_V9 as DirectusClient # Create a directus client connection with user static token -client = DirectusClient_V9(url="http://localhost:8055", token="admin-token") +client = DirectusClient(url="http://localhost:8055", token="admin-token") # Or create a directus client connection with email and password -client = DirectusClient_V9(url="http://localhost:8055", email="user@example.com", password="password") +client = DirectusClient(url="http://localhost:8055", email="user@example.com", password="password") ``` ### Logging in and out of the client ```python -client = DirectusClient_V9(url="http://localhost:8055", email="user@example.com", password="password") +client = DirectusClient(url="http://localhost:8055", email="user@example.com", password="password") # Log out and use static token instead client.logout() diff --git a/directus/__init__.py b/directus/__init__.py deleted file mode 100644 index eeb4c96..0000000 --- a/directus/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from directus.clients import DirectusClient_V9 - -__version__ = '1.0.0' \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7452c3b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[project] +name = "directus-sdk-python" +version = "1.0.0" +description = "python SDK for directus client wiht convenience functions" +authors = [ + {name = "Jason Cheng"}, +] + +readme = "README.md" +requires-python = ">=3.6" +dependencies = [ + "requests>=2.27.1", +] + +[project.urls] +Repository = "https://github.com/Jason-CKY/directus-sdk-python" +Issues = "https://github.com/Jason-CKY/directus-sdk-python/issues" + +[tool.setuptools.packages.find] +where = ["src/"] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 8649921..0000000 --- a/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -requests -uuid -pytest -pytest-cov \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index d8f5b09..0000000 --- a/setup.py +++ /dev/null @@ -1,12 +0,0 @@ -from setuptools import setup, find_packages -from directus import __version__ - -setup( - name='directus-sdk', - version=__version__, - description='python SDK for directus client wiht convenience functions', - url='https://github.com/Jason-CKY/directus-sdk-python', - author='Jason Cheng', - packages=find_packages(exclude=['examples']), - install_requires=['requests'] -) \ No newline at end of file diff --git a/src/directus_sdk/__init__.py b/src/directus_sdk/__init__.py new file mode 100644 index 0000000..fc253c1 --- /dev/null +++ b/src/directus_sdk/__init__.py @@ -0,0 +1 @@ +from .clients import DirectusClient_V9 \ No newline at end of file diff --git a/directus/clients.py b/src/directus_sdk/clients.py similarity index 65% rename from directus/clients.py rename to src/directus_sdk/clients.py index b48cb5a..849f9e3 100644 --- a/directus/clients.py +++ b/src/directus_sdk/clients.py @@ -1,14 +1,8 @@ -import requests -from urllib3.exceptions import InsecureRequestWarning +import httpx, json +from httpx import Response class DirectusClient_V9(): - def __init__(self, url: str, token: str = None, email: str = None, password: str = None, verify: bool = False): - self.verify = verify - if not self.verify: - requests.packages.urllib3.disable_warnings( - category=InsecureRequestWarning - ) - + def __init__(self, url: str, token: str = None, email: str = None, password: str = None): self.url = url if token is not None: self.static_token = token @@ -34,7 +28,7 @@ def login(self, email: str = None, password: str = None) -> tuple: self.email = email self.password = password - auth = requests.post( + auth = httpx.post( f"{self.url}/auth/login", json={ "email": email, @@ -52,10 +46,9 @@ def logout(self, refresh_token: str = None) -> None: ''' if refresh_token is None: refresh_token = self.refresh_token - auth = requests.post( + auth = httpx.post( f"{self.url}/auth/logout", - json={"refresh_token": refresh_token}, - verify=self.verify + json={"refresh_token": refresh_token} ) self.temporary_token = None self.refresh_token = None @@ -66,12 +59,11 @@ def refresh(self, refresh_token: str = None) -> None: ''' if refresh_token is None: refresh_token = self.refresh_token - auth = requests.post( + auth = httpx.post( f"{self.url}/auth/refresh", json={ "refresh_token": refresh_token - }, - verify=self.verify + } ).json()['data'] self.temporary_token = auth['access_token'] @@ -90,80 +82,79 @@ def get_token(self): token = "" return token - def get(self, path, output_type: str = "json", **kwargs): - data = requests.get( - f"{self.url}{path}", + def request(self, method, path, **kwargs) -> Response: + response: Response = httpx.request( + method=method, + url=f"{self.url}/{path}", headers={"Authorization": f"Bearer {self.get_token()}"}, - verify=self.verify, **kwargs ) - if 'errors' in data.text: - raise AssertionError(data.json()['errors']) - if output_type == 'csv': - return data.text - - return data.json()['data'] + return response + + def get(self, path, **kwargs) -> Response: + response: Response = httpx.get( + f"{self.url}/{path}", + headers={"Authorization": f"Bearer {self.get_token()}"}, + **kwargs + ) + return response - def post(self, path, **kwargs): - x = requests.post( - f"{self.url}{path}", + def post(self, path, **kwargs) -> Response: + response: Response = httpx.post( + f"{self.url}/{path}", headers={"Authorization": f"Bearer {self.get_token()}"}, - verify=self.verify, **kwargs ) - if x.status_code != 200: - raise AssertionError(x.text) - - return x.json() + return response - def delete(self, path, **kwargs): - x = requests.delete( - f"{self.url}{path}", + def delete(self, path, **kwargs) -> Response: + print(f"{self.url}/{path}") + response: Response = httpx.delete( + f"{self.url}/{path}", headers={"Authorization": f"Bearer {self.get_token()}"}, - verify=self.verify, **kwargs ) - if x.status_code != 204: - raise AssertionError(x.text) + + return response - def patch(self, path, **kwargs): - x = requests.patch( - f"{self.url}{path}", + def patch(self, path, **kwargs) -> Response: + response: Response = httpx.patch( + f"{self.url}/{path}", headers={"Authorization": f"Bearer {self.get_token()}"}, - verify=self.verify, **kwargs ) - - if x.status_code not in [200, 204]: - raise AssertionError(x.text) - return x.json() + return response - def bulk_insert(self, collection_name: str, items: list, interval: int = 100, verbose: bool = False) -> None: + def bulk_insert(self, collection_name: str, items: list, interval: int = 500) -> None: ''' Post items is capped at 100 items. This function breaks up any list of items more than 100 long and bulk insert + Returns repsonse of last request ''' - length = len(items) - for i in range(0, length, interval): - if verbose: - print(f"Inserting {i}-{min(i+100, length)} out of {length}") - self.post(f"/items/{collection_name}", json=items[i:i + interval]) + if len(items) == 0: + return None + + for i in range(0, len(items), interval): + response: Response = self.post(f"items/{collection_name}", json=items[i:i + interval]) + if response.status_code in (400, 500): + print(response.content) + return None def duplicate_collection(self, collection_name: str, duplicate_collection_name: str) -> None: ''' Duplicate the collection with schema, fields, and data ''' - duplicate_collection = self.get(f"/collections/{collection_name}") + duplicate_collection = self.get(f"collections/{collection_name}") duplicate_collection['collection'] = duplicate_collection_name duplicate_collection['meta']['collection'] = duplicate_collection_name duplicate_collection['schema']['name'] = duplicate_collection_name - self.post("/collections", json=duplicate_collection) + self.post("collections", json=duplicate_collection) fields = [ field for field in self.get_all_fields(collection_name) if not field['schema']['is_primary_key'] ] for field in fields: - self.post(f"/fields/{duplicate_collection_name}", json=field) - self.bulk_insert(duplicate_collection_name, self.get(f"/items/{collection_name}", params={"limit": -1})) + self.post(f"fields/{duplicate_collection_name}", json=field) + self.bulk_insert(duplicate_collection_name, self.get(f"items/{collection_name}", params={"limit": -1})) def collection_exists(self, collection_name: str): ''' @@ -177,42 +168,31 @@ def delete_all_items(self, collection_name: str) -> None: Delete all items from the directus collection. Delete api from directus only able to delete based on ID. This helper function helps to delete every item within the collection. ''' - pk_name = self.get_pk_field(collection_name)['field'] - item_ids = [data['id'] for data in self.get(f"/items/{collection_name}?fields={pk_name}", params={"limit": -1})] - if len(item_ids) == 0: - raise AssertionError("No items to delete!") - for i in range(0, len(item_ids), 100): - self.delete(f"/items/{collection_name}", json=item_ids[i:i + 100]) + self.request("DELETE", f"items/{collection_name}", json={"query":{"limit": -1}}) def get_all_fields(self, collection_name: str) -> list: ''' Return all fields in the directus collection. Remove the id key in metya to avoid errors in inseting this directus field again ''' - fields = self.get(f"/fields/{collection_name}") + fields = self.get(f"fields/{collection_name}") for field in fields: if 'meta' in field and field['meta'] is not None and 'id' in field['meta']: field['meta'].pop('id') return fields - def get_pk_field(self, collection_name: str) -> dict: - ''' - Return the primary key field of the collection - ''' - return [field for field in self.get(f"/fields/{collection_name}") if field['schema']['is_primary_key']][0] - def get_all_user_created_collection_names(self) -> list: ''' Returns all user created collections. By default Directus GET /collections API will return system collections as well which may not always be useful. ''' - return [ col['collection'] for col in self.get('/collections') if not col['collection'].startswith('directus') ] + return [ col['collection'] for col in self.get('collections') if not col['collection'].startswith('directus') ] def get_all_fk_fields(self, collection_name: str) -> dict: ''' Return all foreign key fields in the directus collection ''' - return [ field for field in self.get(f"/fields/{collection_name}") + return [ field for field in self.get(f"fields/{collection_name}") if 'foreign_key_table' in field['schema'].keys() and field['schema']['foreign_key_table'] is not None ] @@ -225,7 +205,7 @@ def get_relations(self, collection_name: str) -> list: "collection": relation["collection"], "field": relation["field"], "related_collection": relation["related_collection"] - } for relation in self.get(f"/relations/{collection_name}")] + } for relation in self.get(f"relations/{collection_name}")] def post_relation(self, relation: dict) -> None: ''' @@ -234,7 +214,7 @@ def post_relation(self, relation: dict) -> None: ''' assert set(relation.keys()) == set(['collection', 'field', 'related_collection']) try: - self.post(f"/relations", json=relation) + self.post(f"relations", json=relation) except AssertionError as e: if '"id" has to be unique' in str(e): self.post_relation(relation)