Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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="[email protected]", password="password")
client = DirectusClient(url="http://localhost:8055", email="[email protected]", password="password")
```

### Logging in and out of the client

```python
client = DirectusClient_V9(url="http://localhost:8055", email="[email protected]", password="password")
client = DirectusClient(url="http://localhost:8055", email="[email protected]", password="password")

# Log out and use static token instead
client.logout()
Expand Down
3 changes: 0 additions & 3 deletions directus/__init__.py

This file was deleted.

20 changes: 20 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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/"]
4 changes: 0 additions & 4 deletions requirements.txt

This file was deleted.

12 changes: 0 additions & 12 deletions setup.py

This file was deleted.

1 change: 1 addition & 0 deletions src/directus_sdk/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .clients import DirectusClient_V9
130 changes: 55 additions & 75 deletions directus/clients.py → src/directus_sdk/clients.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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']
Expand All @@ -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):
'''
Expand All @@ -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
]
Expand All @@ -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:
'''
Expand All @@ -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)
Expand Down