pycomad 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pycomad-0.1.0/PKG-INFO +176 -0
- pycomad-0.1.0/README.md +144 -0
- pycomad-0.1.0/pycomad/__init__.py +51 -0
- pycomad-0.1.0/pycomad/_http.py +133 -0
- pycomad-0.1.0/pycomad/client.py +951 -0
- pycomad-0.1.0/pycomad/exceptions.py +23 -0
- pycomad-0.1.0/pycomad/models.py +342 -0
- pycomad-0.1.0/pycomad.egg-info/PKG-INFO +176 -0
- pycomad-0.1.0/pycomad.egg-info/SOURCES.txt +16 -0
- pycomad-0.1.0/pycomad.egg-info/dependency_links.txt +1 -0
- pycomad-0.1.0/pycomad.egg-info/requires.txt +13 -0
- pycomad-0.1.0/pycomad.egg-info/top_level.txt +1 -0
- pycomad-0.1.0/pyproject.toml +68 -0
- pycomad-0.1.0/setup.cfg +4 -0
- pycomad-0.1.0/tests/test_client.py +632 -0
- pycomad-0.1.0/tests/test_exceptions.py +34 -0
- pycomad-0.1.0/tests/test_http.py +136 -0
- pycomad-0.1.0/tests/test_models.py +255 -0
pycomad-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pycomad
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python client library for the Comad DAM (Digital Asset Management) API
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/ullav-dev/pycomad
|
|
7
|
+
Project-URL: Documentation, https://ullav-dev.github.io/pycomad/
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/ullav-dev/pycomad/issues
|
|
9
|
+
Keywords: dam,digital asset management,comad,ullav
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: requests>=2.28
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov>=4; extra == "dev"
|
|
25
|
+
Requires-Dist: responses>=0.23; extra == "dev"
|
|
26
|
+
Requires-Dist: ruff; extra == "dev"
|
|
27
|
+
Requires-Dist: mypy; extra == "dev"
|
|
28
|
+
Requires-Dist: types-requests; extra == "dev"
|
|
29
|
+
Provides-Extra: docs
|
|
30
|
+
Requires-Dist: mkdocs-material>=9; extra == "docs"
|
|
31
|
+
Requires-Dist: mkdocstrings[python]>=0.25; extra == "docs"
|
|
32
|
+
|
|
33
|
+
# pycomad
|
|
34
|
+
|
|
35
|
+
Python client library for the [Comad DAM (Digital Asset Management)](https://github.com/colinmanning/ullav-dam-server) API.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install pycomad
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from pycomad import ComadClient
|
|
47
|
+
|
|
48
|
+
client = ComadClient(
|
|
49
|
+
api_url="http://localhost:8080",
|
|
50
|
+
auth_url="http://localhost:8081", # ullav-user-management service
|
|
51
|
+
)
|
|
52
|
+
client.login(email="user@example.com", password="secret")
|
|
53
|
+
|
|
54
|
+
# Upload a file (creates the record and uploads in one step)
|
|
55
|
+
asset = client.assets.upload("photo.jpg", creator="alice", is_private=False)
|
|
56
|
+
|
|
57
|
+
# Or create a record first, then upload the file
|
|
58
|
+
record = client.assets.create("Report", "application/pdf")
|
|
59
|
+
client.assets.upload_file(record.id, "report.pdf")
|
|
60
|
+
|
|
61
|
+
# Download and thumbnail
|
|
62
|
+
raw_bytes = client.assets.download(asset.id)
|
|
63
|
+
thumb_png = client.assets.thumbnail(asset.id)
|
|
64
|
+
|
|
65
|
+
# Categories
|
|
66
|
+
cat = client.categories.create("Architecture", access_level="Global")
|
|
67
|
+
client.assets.add_category(asset.id, cat.id)
|
|
68
|
+
detail = client.assets.get(asset.id) # AssetWithCategories
|
|
69
|
+
|
|
70
|
+
# Search
|
|
71
|
+
results = client.search.search(q="photo", creator="alice")
|
|
72
|
+
nearby = client.search.nearby(lat=53.3, lon=-6.2, radius_km=5)
|
|
73
|
+
|
|
74
|
+
# Metadata (EXIF / IPTC / XMP)
|
|
75
|
+
meta = client.metadata.get(asset.id)
|
|
76
|
+
if meta.exif:
|
|
77
|
+
print(meta.exif.get("camera_make"))
|
|
78
|
+
|
|
79
|
+
# Usage quotas
|
|
80
|
+
usage = client.assets.usage()
|
|
81
|
+
print(f"{usage.asset_count} assets, {usage.used_bytes // 1024**2} MB used")
|
|
82
|
+
|
|
83
|
+
# ZIP batch import
|
|
84
|
+
result = client.zip.upload("photos.zip", creator="alice")
|
|
85
|
+
print(f"Imported {len(result.assets)} assets into {len(result.categories)} categories")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Example: upload a file
|
|
89
|
+
|
|
90
|
+
Print asset details after uploading a local file:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import os
|
|
94
|
+
from pycomad import ComadClient
|
|
95
|
+
|
|
96
|
+
client = ComadClient(
|
|
97
|
+
api_url=os.environ["COMAD_API_URL"],
|
|
98
|
+
auth_url=os.environ.get("COMAD_AUTH_URL", os.environ["COMAD_API_URL"]),
|
|
99
|
+
)
|
|
100
|
+
client.login(email=os.environ["COMAD_EMAIL"], password=os.environ["COMAD_PASSWORD"])
|
|
101
|
+
|
|
102
|
+
asset = client.assets.upload("photo.jpg", creator="alice")
|
|
103
|
+
print(f"{asset.name} ({asset.asset_type}, {asset.size:,} bytes) id={asset.id}")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
A runnable version of this script is at `examples/upload_asset.py`.
|
|
107
|
+
|
|
108
|
+
## Authentication
|
|
109
|
+
|
|
110
|
+
`ComadClient` authenticates against the `ullav-user-management` service, which
|
|
111
|
+
issues the JWT accepted by the Comad DAM server.
|
|
112
|
+
|
|
113
|
+
- `api_url` — Comad DAM server base URL (e.g. `http://comad-server:8080`)
|
|
114
|
+
- `auth_url` — auth service base URL (e.g. `http://ullav-user-management:8081`);
|
|
115
|
+
omit if both services are behind the same proxy
|
|
116
|
+
|
|
117
|
+
Call `client.login(email, password)` before any other method. Tokens expire
|
|
118
|
+
according to the server's configuration; call `login()` again to refresh.
|
|
119
|
+
|
|
120
|
+
## Resource clients
|
|
121
|
+
|
|
122
|
+
| Attribute | Resource |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `client.assets` | Asset CRUD, file upload/download/thumbnail, category links, usage |
|
|
125
|
+
| `client.metadata` | EXIF/IPTC/XMP metadata get and refresh |
|
|
126
|
+
| `client.search` | Full-text and metadata-driven search, geo search |
|
|
127
|
+
| `client.categories` | Category CRUD (hierarchical tree) |
|
|
128
|
+
| `client.custom_field_schemas` | Team custom field schema CRUD |
|
|
129
|
+
| `client.zip` | ZIP batch import |
|
|
130
|
+
|
|
131
|
+
## File uploads
|
|
132
|
+
|
|
133
|
+
`client.assets.upload()` and `client.assets.upload_file()` accept:
|
|
134
|
+
|
|
135
|
+
- A file path as a `str` or `pathlib.Path`
|
|
136
|
+
- An already-open binary file object (`IO[bytes]`)
|
|
137
|
+
|
|
138
|
+
The MIME type is inferred from the file extension if `asset_type` is omitted.
|
|
139
|
+
|
|
140
|
+
## Error handling
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from pycomad import ComadAuthError, ComadNotFoundError, ComadValidationError
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
asset = client.assets.get("non-existent-id")
|
|
147
|
+
except ComadNotFoundError:
|
|
148
|
+
print("not found")
|
|
149
|
+
except ComadAuthError:
|
|
150
|
+
client.login(email, password) # token expired — re-authenticate
|
|
151
|
+
except ComadValidationError as e:
|
|
152
|
+
print("bad request:", e)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
| Exception | HTTP status |
|
|
156
|
+
|---|---|
|
|
157
|
+
| `ComadAuthError` | 401 / 403, or `login()` not called |
|
|
158
|
+
| `ComadNotFoundError` | 404 |
|
|
159
|
+
| `ComadValidationError` | 400 |
|
|
160
|
+
| `ComadServerError` | 5xx |
|
|
161
|
+
| `ComadError` | base class |
|
|
162
|
+
|
|
163
|
+
## Access levels
|
|
164
|
+
|
|
165
|
+
Use the `AccessLevel` enumeration or plain strings when creating categories:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from pycomad import AccessLevel
|
|
169
|
+
|
|
170
|
+
client.categories.create("Press", access_level=AccessLevel.GLOBAL)
|
|
171
|
+
client.categories.create("Internal", access_level="Private") # equivalent
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Licence
|
|
175
|
+
|
|
176
|
+
MIT
|
pycomad-0.1.0/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# pycomad
|
|
2
|
+
|
|
3
|
+
Python client library for the [Comad DAM (Digital Asset Management)](https://github.com/colinmanning/ullav-dam-server) API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install pycomad
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from pycomad import ComadClient
|
|
15
|
+
|
|
16
|
+
client = ComadClient(
|
|
17
|
+
api_url="http://localhost:8080",
|
|
18
|
+
auth_url="http://localhost:8081", # ullav-user-management service
|
|
19
|
+
)
|
|
20
|
+
client.login(email="user@example.com", password="secret")
|
|
21
|
+
|
|
22
|
+
# Upload a file (creates the record and uploads in one step)
|
|
23
|
+
asset = client.assets.upload("photo.jpg", creator="alice", is_private=False)
|
|
24
|
+
|
|
25
|
+
# Or create a record first, then upload the file
|
|
26
|
+
record = client.assets.create("Report", "application/pdf")
|
|
27
|
+
client.assets.upload_file(record.id, "report.pdf")
|
|
28
|
+
|
|
29
|
+
# Download and thumbnail
|
|
30
|
+
raw_bytes = client.assets.download(asset.id)
|
|
31
|
+
thumb_png = client.assets.thumbnail(asset.id)
|
|
32
|
+
|
|
33
|
+
# Categories
|
|
34
|
+
cat = client.categories.create("Architecture", access_level="Global")
|
|
35
|
+
client.assets.add_category(asset.id, cat.id)
|
|
36
|
+
detail = client.assets.get(asset.id) # AssetWithCategories
|
|
37
|
+
|
|
38
|
+
# Search
|
|
39
|
+
results = client.search.search(q="photo", creator="alice")
|
|
40
|
+
nearby = client.search.nearby(lat=53.3, lon=-6.2, radius_km=5)
|
|
41
|
+
|
|
42
|
+
# Metadata (EXIF / IPTC / XMP)
|
|
43
|
+
meta = client.metadata.get(asset.id)
|
|
44
|
+
if meta.exif:
|
|
45
|
+
print(meta.exif.get("camera_make"))
|
|
46
|
+
|
|
47
|
+
# Usage quotas
|
|
48
|
+
usage = client.assets.usage()
|
|
49
|
+
print(f"{usage.asset_count} assets, {usage.used_bytes // 1024**2} MB used")
|
|
50
|
+
|
|
51
|
+
# ZIP batch import
|
|
52
|
+
result = client.zip.upload("photos.zip", creator="alice")
|
|
53
|
+
print(f"Imported {len(result.assets)} assets into {len(result.categories)} categories")
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Example: upload a file
|
|
57
|
+
|
|
58
|
+
Print asset details after uploading a local file:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import os
|
|
62
|
+
from pycomad import ComadClient
|
|
63
|
+
|
|
64
|
+
client = ComadClient(
|
|
65
|
+
api_url=os.environ["COMAD_API_URL"],
|
|
66
|
+
auth_url=os.environ.get("COMAD_AUTH_URL", os.environ["COMAD_API_URL"]),
|
|
67
|
+
)
|
|
68
|
+
client.login(email=os.environ["COMAD_EMAIL"], password=os.environ["COMAD_PASSWORD"])
|
|
69
|
+
|
|
70
|
+
asset = client.assets.upload("photo.jpg", creator="alice")
|
|
71
|
+
print(f"{asset.name} ({asset.asset_type}, {asset.size:,} bytes) id={asset.id}")
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A runnable version of this script is at `examples/upload_asset.py`.
|
|
75
|
+
|
|
76
|
+
## Authentication
|
|
77
|
+
|
|
78
|
+
`ComadClient` authenticates against the `ullav-user-management` service, which
|
|
79
|
+
issues the JWT accepted by the Comad DAM server.
|
|
80
|
+
|
|
81
|
+
- `api_url` — Comad DAM server base URL (e.g. `http://comad-server:8080`)
|
|
82
|
+
- `auth_url` — auth service base URL (e.g. `http://ullav-user-management:8081`);
|
|
83
|
+
omit if both services are behind the same proxy
|
|
84
|
+
|
|
85
|
+
Call `client.login(email, password)` before any other method. Tokens expire
|
|
86
|
+
according to the server's configuration; call `login()` again to refresh.
|
|
87
|
+
|
|
88
|
+
## Resource clients
|
|
89
|
+
|
|
90
|
+
| Attribute | Resource |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `client.assets` | Asset CRUD, file upload/download/thumbnail, category links, usage |
|
|
93
|
+
| `client.metadata` | EXIF/IPTC/XMP metadata get and refresh |
|
|
94
|
+
| `client.search` | Full-text and metadata-driven search, geo search |
|
|
95
|
+
| `client.categories` | Category CRUD (hierarchical tree) |
|
|
96
|
+
| `client.custom_field_schemas` | Team custom field schema CRUD |
|
|
97
|
+
| `client.zip` | ZIP batch import |
|
|
98
|
+
|
|
99
|
+
## File uploads
|
|
100
|
+
|
|
101
|
+
`client.assets.upload()` and `client.assets.upload_file()` accept:
|
|
102
|
+
|
|
103
|
+
- A file path as a `str` or `pathlib.Path`
|
|
104
|
+
- An already-open binary file object (`IO[bytes]`)
|
|
105
|
+
|
|
106
|
+
The MIME type is inferred from the file extension if `asset_type` is omitted.
|
|
107
|
+
|
|
108
|
+
## Error handling
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from pycomad import ComadAuthError, ComadNotFoundError, ComadValidationError
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
asset = client.assets.get("non-existent-id")
|
|
115
|
+
except ComadNotFoundError:
|
|
116
|
+
print("not found")
|
|
117
|
+
except ComadAuthError:
|
|
118
|
+
client.login(email, password) # token expired — re-authenticate
|
|
119
|
+
except ComadValidationError as e:
|
|
120
|
+
print("bad request:", e)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
| Exception | HTTP status |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `ComadAuthError` | 401 / 403, or `login()` not called |
|
|
126
|
+
| `ComadNotFoundError` | 404 |
|
|
127
|
+
| `ComadValidationError` | 400 |
|
|
128
|
+
| `ComadServerError` | 5xx |
|
|
129
|
+
| `ComadError` | base class |
|
|
130
|
+
|
|
131
|
+
## Access levels
|
|
132
|
+
|
|
133
|
+
Use the `AccessLevel` enumeration or plain strings when creating categories:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from pycomad import AccessLevel
|
|
137
|
+
|
|
138
|
+
client.categories.create("Press", access_level=AccessLevel.GLOBAL)
|
|
139
|
+
client.categories.create("Internal", access_level="Private") # equivalent
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Licence
|
|
143
|
+
|
|
144
|
+
MIT
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""pycomad — Python client library for the Comad DAM (Digital Asset Management) API."""
|
|
2
|
+
|
|
3
|
+
from .client import ComadClient
|
|
4
|
+
from .exceptions import (
|
|
5
|
+
ComadAuthError,
|
|
6
|
+
ComadError,
|
|
7
|
+
ComadNotFoundError,
|
|
8
|
+
ComadServerError,
|
|
9
|
+
ComadValidationError,
|
|
10
|
+
)
|
|
11
|
+
from .models import (
|
|
12
|
+
AccessLevel,
|
|
13
|
+
Asset,
|
|
14
|
+
AssetMetadata,
|
|
15
|
+
AssetMetadataSummary,
|
|
16
|
+
AssetSearchResult,
|
|
17
|
+
AssetWithCategories,
|
|
18
|
+
Category,
|
|
19
|
+
CategoryWithChildren,
|
|
20
|
+
CustomFieldSchema,
|
|
21
|
+
FieldType,
|
|
22
|
+
LoginInfo,
|
|
23
|
+
UsageSummary,
|
|
24
|
+
ZipUploadResult,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
# Client
|
|
29
|
+
"ComadClient",
|
|
30
|
+
# Exceptions
|
|
31
|
+
"ComadError",
|
|
32
|
+
"ComadAuthError",
|
|
33
|
+
"ComadNotFoundError",
|
|
34
|
+
"ComadValidationError",
|
|
35
|
+
"ComadServerError",
|
|
36
|
+
# Enumerations
|
|
37
|
+
"AccessLevel",
|
|
38
|
+
"FieldType",
|
|
39
|
+
# Models
|
|
40
|
+
"LoginInfo",
|
|
41
|
+
"Asset",
|
|
42
|
+
"AssetWithCategories",
|
|
43
|
+
"AssetMetadata",
|
|
44
|
+
"AssetMetadataSummary",
|
|
45
|
+
"AssetSearchResult",
|
|
46
|
+
"Category",
|
|
47
|
+
"CategoryWithChildren",
|
|
48
|
+
"CustomFieldSchema",
|
|
49
|
+
"UsageSummary",
|
|
50
|
+
"ZipUploadResult",
|
|
51
|
+
]
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Internal HTTP session: request dispatch and error mapping."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
from .exceptions import (
|
|
11
|
+
ComadAuthError,
|
|
12
|
+
ComadError,
|
|
13
|
+
ComadNotFoundError,
|
|
14
|
+
ComadServerError,
|
|
15
|
+
ComadValidationError,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _compact(d: Dict[str, Any]) -> Dict[str, Any]:
|
|
20
|
+
"""Return a copy of *d* with all ``None``-valued keys removed."""
|
|
21
|
+
return {k: v for k, v in d.items() if v is not None}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _str_id(value: Any) -> Optional[str]:
|
|
25
|
+
"""Coerce a ``uuid.UUID`` or string identifier to ``str``, pass ``None`` through."""
|
|
26
|
+
if value is None:
|
|
27
|
+
return None
|
|
28
|
+
return str(value) if isinstance(value, uuid.UUID) else value
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class _HttpSession:
|
|
32
|
+
"""Thin wrapper around :class:`requests.Session` that handles auth headers and
|
|
33
|
+
maps non-2xx responses to typed exceptions.
|
|
34
|
+
|
|
35
|
+
Content-Type is NOT set as a session default so that multipart and binary
|
|
36
|
+
requests are unaffected. JSON methods pass ``json=`` to requests, which
|
|
37
|
+
sets the header automatically per-call.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, api_url: str, auth_url: str) -> None:
|
|
41
|
+
self._api_url = api_url.rstrip("/")
|
|
42
|
+
self._auth_url = auth_url.rstrip("/")
|
|
43
|
+
self._session = requests.Session()
|
|
44
|
+
self._token: Optional[str] = None
|
|
45
|
+
|
|
46
|
+
def set_token(self, token: str) -> None:
|
|
47
|
+
"""Store the JWT and attach it to all subsequent requests."""
|
|
48
|
+
self._token = token
|
|
49
|
+
self._session.headers["Authorization"] = f"Bearer {token}"
|
|
50
|
+
|
|
51
|
+
def _require_auth(self) -> None:
|
|
52
|
+
if self._token is None:
|
|
53
|
+
raise ComadAuthError("Not authenticated — call ComadClient.login() first.")
|
|
54
|
+
|
|
55
|
+
def _raise_for_status(self, response: requests.Response) -> None:
|
|
56
|
+
if response.ok:
|
|
57
|
+
return
|
|
58
|
+
try:
|
|
59
|
+
body = response.json()
|
|
60
|
+
message = body.get("error") or body.get("message") or response.text
|
|
61
|
+
except Exception:
|
|
62
|
+
message = response.text
|
|
63
|
+
if response.status_code == 401:
|
|
64
|
+
raise ComadAuthError(message)
|
|
65
|
+
if response.status_code == 403:
|
|
66
|
+
raise ComadAuthError(f"Forbidden: {message}")
|
|
67
|
+
if response.status_code == 404:
|
|
68
|
+
raise ComadNotFoundError(message)
|
|
69
|
+
if response.status_code == 400:
|
|
70
|
+
raise ComadValidationError(message)
|
|
71
|
+
if response.status_code >= 500:
|
|
72
|
+
raise ComadServerError(f"Server error {response.status_code}: {message}")
|
|
73
|
+
raise ComadError(f"HTTP {response.status_code}: {message}")
|
|
74
|
+
|
|
75
|
+
# ── auth service ──────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
def post_auth(self, path: str, json: Any) -> Any:
|
|
78
|
+
"""POST to the authentication service (no JWT header required)."""
|
|
79
|
+
response = self._session.post(f"{self._auth_url}{path}", json=json)
|
|
80
|
+
self._raise_for_status(response)
|
|
81
|
+
return response.json()
|
|
82
|
+
|
|
83
|
+
# ── DAM API ───────────────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
|
|
86
|
+
self._require_auth()
|
|
87
|
+
response = self._session.get(f"{self._api_url}{path}", params=params)
|
|
88
|
+
self._raise_for_status(response)
|
|
89
|
+
return response.json()
|
|
90
|
+
|
|
91
|
+
def get_bytes(self, path: str) -> bytes:
|
|
92
|
+
"""GET a binary response (download, thumbnail)."""
|
|
93
|
+
self._require_auth()
|
|
94
|
+
response = self._session.get(f"{self._api_url}{path}")
|
|
95
|
+
self._raise_for_status(response)
|
|
96
|
+
return response.content
|
|
97
|
+
|
|
98
|
+
def post(self, path: str, json: Any = None) -> Any:
|
|
99
|
+
self._require_auth()
|
|
100
|
+
response = self._session.post(f"{self._api_url}{path}", json=json)
|
|
101
|
+
self._raise_for_status(response)
|
|
102
|
+
return response.json() if response.status_code != 204 else None
|
|
103
|
+
|
|
104
|
+
def post_multipart(
|
|
105
|
+
self,
|
|
106
|
+
path: str,
|
|
107
|
+
files: Dict[str, Any],
|
|
108
|
+
data: Optional[Dict[str, str]] = None,
|
|
109
|
+
) -> Any:
|
|
110
|
+
"""POST multipart/form-data (file uploads).
|
|
111
|
+
|
|
112
|
+
Does not set Content-Type — requests computes the boundary automatically
|
|
113
|
+
when ``files`` is passed.
|
|
114
|
+
"""
|
|
115
|
+
self._require_auth()
|
|
116
|
+
headers = {"Authorization": f"Bearer {self._token}"}
|
|
117
|
+
response = requests.post(
|
|
118
|
+
f"{self._api_url}{path}", files=files, data=data, headers=headers
|
|
119
|
+
)
|
|
120
|
+
self._raise_for_status(response)
|
|
121
|
+
return response.json() if response.status_code != 204 else None
|
|
122
|
+
|
|
123
|
+
def put(self, path: str, json: Any = None) -> Any:
|
|
124
|
+
self._require_auth()
|
|
125
|
+
response = self._session.put(f"{self._api_url}{path}", json=json)
|
|
126
|
+
self._raise_for_status(response)
|
|
127
|
+
return response.json() if response.status_code != 204 else None
|
|
128
|
+
|
|
129
|
+
def delete(self, path: str) -> Any:
|
|
130
|
+
self._require_auth()
|
|
131
|
+
response = self._session.delete(f"{self._api_url}{path}")
|
|
132
|
+
self._raise_for_status(response)
|
|
133
|
+
return response.json() if response.status_code != 204 else None
|