initial commit after automated creating
This commit is contained in:
commit
1cf124a5a3
48 changed files with 12041 additions and 0 deletions
14
.env.example
Normal file
14
.env.example
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Copy this file to .env and fill in your values.
|
||||
# All variables are prefixed with AGRAVITY_.
|
||||
|
||||
# Required: your x-functions-key API key
|
||||
AGRAVITY_API_KEY=your-api-key-here
|
||||
|
||||
# Optional: override the base API URL
|
||||
# AGRAVITY_BASE_URL=https://devagravitypublic.azurewebsites.net/api
|
||||
|
||||
# Optional: HTTP request timeout in seconds
|
||||
# AGRAVITY_TIMEOUT=60.0
|
||||
|
||||
# Optional: set to false to disable SSL certificate verification (not recommended)
|
||||
# AGRAVITY_VERIFY_SSL=true
|
||||
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.pyo
|
||||
*.pyd
|
||||
*.egg
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
.eggs/
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
venv/
|
||||
env/
|
||||
|
||||
# Type checking
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Linting
|
||||
.ruff_cache/
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
|
||||
# pytest
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
141
README.md
Normal file
141
README.md
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# agravity-client
|
||||
|
||||
A fully **Pythonic, Pydantic v2–driven, async** REST client for the
|
||||
[Agravity DAM API](https://agravity.io) (v10.3.0).
|
||||
|
||||
Built on [`httpx`](https://www.python-httpx.org/) and
|
||||
[Pydantic](https://docs.pydantic.dev/), this library gives you:
|
||||
|
||||
- **Typed return values** – every endpoint returns a validated Pydantic model.
|
||||
- **Async-first** – all network calls are `async def` using `httpx.AsyncClient`.
|
||||
- **Context-manager support** – clean connection lifecycle.
|
||||
- **Auto-configuration** – reads `AGRAVITY_*` env vars or a `.env` file via
|
||||
`pydantic-settings`.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install agravity-client
|
||||
```
|
||||
|
||||
_Or in development mode:_
|
||||
|
||||
```bash
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
|
||||
## Quick start
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agravity_client import AgravityClient, AgravityConfig
|
||||
|
||||
async def main():
|
||||
config = AgravityConfig(api_key="YOUR_API_KEY") # or set AGRAVITY_API_KEY env var
|
||||
|
||||
async with AgravityClient(config) as client:
|
||||
# ------------------------------------------------------------------
|
||||
# Version & capabilities
|
||||
# ------------------------------------------------------------------
|
||||
version = await client.general.get_version()
|
||||
print(version.version)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# List assets in a collection
|
||||
# ------------------------------------------------------------------
|
||||
page = await client.assets.list_assets(
|
||||
collection_id="your-collection-id",
|
||||
limit=25,
|
||||
)
|
||||
for asset in page.assets or []:
|
||||
print(asset.id, asset.name)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Upload an asset
|
||||
# ------------------------------------------------------------------
|
||||
with open("photo.jpg", "rb") as fh:
|
||||
new_asset = await client.assets.upload_asset(
|
||||
fh.read(),
|
||||
"photo.jpg",
|
||||
name="My photo",
|
||||
collection_id="your-collection-id",
|
||||
)
|
||||
print("Created:", new_asset.id)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Search
|
||||
# ------------------------------------------------------------------
|
||||
from agravity_client.models import AzSearchOptions
|
||||
results = await client.search.search(
|
||||
AzSearchOptions(searchterm="sunset", limit=10)
|
||||
)
|
||||
print(results.count, "results")
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
| Setting | Env variable | Default |
|
||||
|---------|-------------|---------|
|
||||
| `base_url` | `AGRAVITY_BASE_URL` | `https://devagravitypublic.azurewebsites.net/api` |
|
||||
| `api_key` | `AGRAVITY_API_KEY` | _(empty)_ |
|
||||
| `timeout` | `AGRAVITY_TIMEOUT` | `60.0` |
|
||||
| `verify_ssl` | `AGRAVITY_VERIFY_SSL` | `true` |
|
||||
|
||||
Copy `.env.example` to `.env` and fill in your values.
|
||||
|
||||
|
||||
## API modules
|
||||
|
||||
| Attribute | Endpoints covered |
|
||||
|-----------|-------------------|
|
||||
| `client.assets` | `/assets`, `/assetsupload`, `/assetsbulkupdate`, all sub-resources |
|
||||
| `client.collections` | `/collections` CRUD, ancestors, descendants, preview, bynames |
|
||||
| `client.collection_types` | `/collectiontypes` |
|
||||
| `client.relations` | `/relations`, `/assetrelationtypes` |
|
||||
| `client.search` | `/search`, `/search/facette`, `/search/adminstatus`, `/savedsearch` |
|
||||
| `client.sharing` | `/sharing`, `/sharing/quickshares` |
|
||||
| `client.portals` | `/portals` |
|
||||
| `client.workspaces` | `/workspaces` |
|
||||
| `client.auth` | `/auth/containerwrite`, `/auth/inbox`, `/auth/users` |
|
||||
| `client.download_formats` | `/downloadformats` |
|
||||
| `client.static_lists` | `/staticdefinedlists` |
|
||||
| `client.translations` | `/translations` |
|
||||
| `client.publishing` | `/publish` |
|
||||
| `client.secure_upload` | `/secureupload` |
|
||||
| `client.helper` | `/helper/*` |
|
||||
| `client.webappdata` | `/webappdata` |
|
||||
| `client.general` | `/version`, `/deleted`, `/durable`, `/negotiate`, `/public`, `/config` |
|
||||
| `client.ai` | `/ai/reverseassetsearch` |
|
||||
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Create and activate a virtual environment
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate # Windows
|
||||
# source .venv/bin/activate # Unix
|
||||
|
||||
# Install in editable mode with all dev dependencies
|
||||
pip install -e ".[dev]"
|
||||
|
||||
# Lint & format
|
||||
ruff check .
|
||||
ruff format .
|
||||
|
||||
# Type-check
|
||||
mypy agravity_client
|
||||
|
||||
# Run tests
|
||||
pytest
|
||||
```
|
||||
|
||||
|
||||
## Licence
|
||||
|
||||
MIT
|
||||
120
agravity_client.code-workspace
Normal file
120
agravity_client.code-workspace
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
// Python environment
|
||||
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/Scripts/python.exe",
|
||||
"python.terminal.activateEnvironment": true,
|
||||
|
||||
// Editor
|
||||
"editor.formatOnSave": true,
|
||||
"editor.rulers": [100],
|
||||
"editor.tabSize": 4,
|
||||
|
||||
// Ruff (linter + formatter)
|
||||
"[python]": {
|
||||
"editor.defaultFormatter": "charliermarsh.ruff",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.ruff": "explicit",
|
||||
"source.organizeImports.ruff": "explicit"
|
||||
}
|
||||
},
|
||||
|
||||
// Pylance / Pyright
|
||||
"python.analysis.typeCheckingMode": "basic",
|
||||
"python.analysis.autoImportCompletions": true,
|
||||
"python.analysis.indexing": true,
|
||||
|
||||
// Test discovery
|
||||
"python.testing.pytestEnabled": true,
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestArgs": ["tests"],
|
||||
|
||||
// File associations
|
||||
"files.associations": {
|
||||
"*.env.example": "dotenv"
|
||||
},
|
||||
|
||||
// Exclude noise from explorer
|
||||
"files.exclude": {
|
||||
"**/__pycache__": true,
|
||||
"**/*.pyc": true,
|
||||
"**/.mypy_cache": true,
|
||||
"**/.ruff_cache": true,
|
||||
"**/*.egg-info": true,
|
||||
".venv": false
|
||||
}
|
||||
},
|
||||
"extensions": {
|
||||
"recommendations": [
|
||||
"ms-python.python",
|
||||
"ms-python.vscode-pylance",
|
||||
"charliermarsh.ruff",
|
||||
"ms-python.mypy-type-checker",
|
||||
"tamasfe.even-better-toml",
|
||||
"mikestead.dotenv"
|
||||
]
|
||||
},
|
||||
"launch": {
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: Current File",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"console": "integratedTerminal",
|
||||
"justMyCode": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"tasks": {
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Install (editable)",
|
||||
"type": "shell",
|
||||
"command": "pip install -e \".[dev]\"",
|
||||
"group": "build",
|
||||
"presentation": { "reveal": "always" },
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Ruff: lint",
|
||||
"type": "shell",
|
||||
"command": "ruff check .",
|
||||
"group": "test",
|
||||
"presentation": { "reveal": "always" },
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Ruff: format",
|
||||
"type": "shell",
|
||||
"command": "ruff format .",
|
||||
"group": "build",
|
||||
"presentation": { "reveal": "always" },
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "mypy: type-check",
|
||||
"type": "shell",
|
||||
"command": "mypy agravity_client",
|
||||
"group": "test",
|
||||
"presentation": { "reveal": "always" },
|
||||
"problemMatcher": ["$mypy"]
|
||||
},
|
||||
{
|
||||
"label": "pytest",
|
||||
"type": "shell",
|
||||
"command": "pytest -v",
|
||||
"group": { "kind": "test", "isDefault": true },
|
||||
"presentation": { "reveal": "always" },
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
29
agravity_client/__init__.py
Normal file
29
agravity_client/__init__.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
"""Agravity DAM Python client – public API surface."""
|
||||
from __future__ import annotations
|
||||
|
||||
from agravity_client.client import AgravityClient
|
||||
from agravity_client.config import AgravityConfig
|
||||
from agravity_client.exceptions import (
|
||||
AgravityAPIError,
|
||||
AgravityAuthenticationError,
|
||||
AgravityConnectionError,
|
||||
AgravityError,
|
||||
AgravityNotFoundError,
|
||||
AgravityTimeoutError,
|
||||
AgravityValidationError,
|
||||
)
|
||||
from agravity_client.models import * # noqa: F403 – re-export all model symbols
|
||||
|
||||
__version__ = "0.1.0"
|
||||
__all__ = [
|
||||
"AgravityClient",
|
||||
"AgravityConfig",
|
||||
# Exceptions
|
||||
"AgravityError",
|
||||
"AgravityAPIError",
|
||||
"AgravityAuthenticationError",
|
||||
"AgravityConnectionError",
|
||||
"AgravityNotFoundError",
|
||||
"AgravityTimeoutError",
|
||||
"AgravityValidationError",
|
||||
]
|
||||
42
agravity_client/api/__init__.py
Normal file
42
agravity_client/api/__init__.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""Agravity API client sub-modules."""
|
||||
from __future__ import annotations
|
||||
|
||||
from agravity_client.api.ai import AiApi
|
||||
from agravity_client.api.assets import AssetsApi
|
||||
from agravity_client.api.auth import AuthApi
|
||||
from agravity_client.api.collection_types import CollectionTypesApi
|
||||
from agravity_client.api.collections import CollectionsApi
|
||||
from agravity_client.api.download_formats import DownloadFormatsApi
|
||||
from agravity_client.api.general import GeneralApi
|
||||
from agravity_client.api.helper import HelperApi
|
||||
from agravity_client.api.portals import PortalsApi
|
||||
from agravity_client.api.publishing import PublishingApi
|
||||
from agravity_client.api.relations import RelationsApi
|
||||
from agravity_client.api.search import SearchApi
|
||||
from agravity_client.api.secure_upload import SecureUploadApi
|
||||
from agravity_client.api.sharing import SharingApi
|
||||
from agravity_client.api.static_lists import StaticListsApi
|
||||
from agravity_client.api.translations import TranslationsApi
|
||||
from agravity_client.api.webappdata import WebAppDataApi
|
||||
from agravity_client.api.workspaces import WorkspacesApi
|
||||
|
||||
__all__ = [
|
||||
"AiApi",
|
||||
"AssetsApi",
|
||||
"AuthApi",
|
||||
"CollectionTypesApi",
|
||||
"CollectionsApi",
|
||||
"DownloadFormatsApi",
|
||||
"GeneralApi",
|
||||
"HelperApi",
|
||||
"PortalsApi",
|
||||
"PublishingApi",
|
||||
"RelationsApi",
|
||||
"SearchApi",
|
||||
"SecureUploadApi",
|
||||
"SharingApi",
|
||||
"StaticListsApi",
|
||||
"TranslationsApi",
|
||||
"WebAppDataApi",
|
||||
"WorkspacesApi",
|
||||
]
|
||||
37
agravity_client/api/ai.py
Normal file
37
agravity_client/api/ai.py
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
"""AI Operations API module (reverse asset search)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.asset import Asset
|
||||
|
||||
|
||||
class AiApi(AgravityBaseApi):
|
||||
"""Covers all /ai endpoints."""
|
||||
|
||||
async def reverse_asset_search(
|
||||
self,
|
||||
image_file: bytes,
|
||||
filename: str,
|
||||
*,
|
||||
limit: Optional[int] = None,
|
||||
content_type: str = "application/octet-stream",
|
||||
) -> list[Asset]:
|
||||
"""POST /ai/reverseassetsearch – find similar assets by image.
|
||||
|
||||
Args:
|
||||
image_file: Raw image bytes to use as the search query.
|
||||
filename: Original filename for the image.
|
||||
limit: Maximum number of results to return.
|
||||
content_type: MIME type of the uploaded image.
|
||||
|
||||
Returns:
|
||||
A list of matching :class:`~agravity_client.models.asset.Asset` objects.
|
||||
"""
|
||||
files = {"image_file": (filename, image_file, content_type)}
|
||||
data = {}
|
||||
if limit is not None:
|
||||
data["limit"] = str(limit)
|
||||
resp = await self._post("/ai/reverseassetsearch", data=data, files=files)
|
||||
return [Asset.model_validate(item) for item in resp.json()]
|
||||
420
agravity_client/api/assets.py
Normal file
420
agravity_client/api/assets.py
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
"""Asset Management and Operations API module."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.asset import (
|
||||
Asset,
|
||||
AssetAvailability,
|
||||
AssetBlob,
|
||||
AssetBulkUpdate,
|
||||
AssetIdFormat,
|
||||
AssetPageResult,
|
||||
)
|
||||
from agravity_client.models.collection import Collection
|
||||
from agravity_client.models.common import AgravityInfoResponse
|
||||
from agravity_client.models.download import DynamicImageOperation
|
||||
from agravity_client.models.publish import PublishEntity, PublishedAsset
|
||||
from agravity_client.models.relation import AssetRelation
|
||||
from agravity_client.models.versioning import VersionedAsset, VersionEntity
|
||||
|
||||
|
||||
class AssetsApi(AgravityBaseApi):
|
||||
"""Covers all /assets endpoints."""
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Asset CRUD
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def list_assets(
|
||||
self,
|
||||
*,
|
||||
collection_id: Optional[str] = None,
|
||||
collection_type_id: Optional[str] = None,
|
||||
fields: Optional[str] = None,
|
||||
expose: Optional[bool] = None,
|
||||
continuation_token: Optional[str] = None,
|
||||
limit: Optional[int] = None,
|
||||
orderby: Optional[str] = None,
|
||||
filter: Optional[str] = None,
|
||||
items: Optional[bool] = None,
|
||||
translations: Optional[bool] = None,
|
||||
accept_language: Optional[str] = None,
|
||||
) -> AssetPageResult:
|
||||
"""GET /assets – list assets (optionally filtered/paginated)."""
|
||||
params: dict[str, Any] = {
|
||||
"collectionid": collection_id,
|
||||
"collectiontypeid": collection_type_id,
|
||||
"fields": fields,
|
||||
"expose": expose,
|
||||
"continuation_token": continuation_token,
|
||||
"limit": limit,
|
||||
"orderby": orderby,
|
||||
"filter": filter,
|
||||
"items": items,
|
||||
"translations": translations,
|
||||
}
|
||||
headers = {"Accept-Language": accept_language} if accept_language else None
|
||||
resp = await self._get("/assets", params=params, extra_headers=headers)
|
||||
return AssetPageResult.model_validate(resp.json())
|
||||
|
||||
async def create_asset(self, payload: dict[str, Any]) -> Asset:
|
||||
"""POST /assets – create a new asset."""
|
||||
resp = await self._post("/assets", json=payload)
|
||||
return Asset.model_validate(resp.json())
|
||||
|
||||
async def get_asset(
|
||||
self,
|
||||
asset_id: str,
|
||||
*,
|
||||
fields: Optional[str] = None,
|
||||
items: Optional[bool] = None,
|
||||
translations: Optional[bool] = None,
|
||||
accept_language: Optional[str] = None,
|
||||
) -> Asset:
|
||||
"""GET /assets/{id}."""
|
||||
params: dict[str, Any] = {
|
||||
"fields": fields,
|
||||
"items": items,
|
||||
"translations": translations,
|
||||
}
|
||||
headers = {"Accept-Language": accept_language} if accept_language else None
|
||||
resp = await self._get(f"/assets/{asset_id}", params=params, extra_headers=headers)
|
||||
return Asset.model_validate(resp.json())
|
||||
|
||||
async def update_asset(self, asset_id: str, payload: dict[str, Any]) -> Asset:
|
||||
"""POST /assets/{id} – update asset metadata."""
|
||||
resp = await self._post(f"/assets/{asset_id}", json=payload)
|
||||
return Asset.model_validate(resp.json())
|
||||
|
||||
async def delete_asset(self, asset_id: str) -> None:
|
||||
"""DELETE /assets/{id}."""
|
||||
await self._delete(f"/assets/{asset_id}")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Upload
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def upload_asset(
|
||||
self,
|
||||
file: bytes,
|
||||
filename: str,
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
collection_id: Optional[str] = None,
|
||||
preview_of: Optional[str] = None,
|
||||
content_type: str = "application/octet-stream",
|
||||
) -> Asset:
|
||||
"""POST /assetsupload – upload a new asset file (multipart)."""
|
||||
files: dict[str, Any] = {
|
||||
"file": (filename, file, content_type),
|
||||
}
|
||||
data: dict[str, Any] = {}
|
||||
if name:
|
||||
data["name"] = name
|
||||
if collection_id:
|
||||
data["collectionid"] = collection_id
|
||||
if filename:
|
||||
data["filename"] = filename
|
||||
if preview_of:
|
||||
data["previewof"] = preview_of
|
||||
resp = await self._post("/assetsupload", data=data, files=files)
|
||||
return Asset.model_validate(resp.json())
|
||||
|
||||
async def upload_asset_from_path(
|
||||
self,
|
||||
path: Path | str,
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
collection_id: Optional[str] = None,
|
||||
preview_of: Optional[str] = None,
|
||||
) -> Asset:
|
||||
"""Convenience wrapper: read a local file and call :meth:`upload_asset`."""
|
||||
path = Path(path)
|
||||
return await self.upload_asset(
|
||||
path.read_bytes(),
|
||||
path.name,
|
||||
name=name,
|
||||
collection_id=collection_id,
|
||||
preview_of=preview_of,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Bulk update
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def bulk_update_assets(self, payload: AssetBulkUpdate) -> AgravityInfoResponse:
|
||||
"""POST /assetsbulkupdate."""
|
||||
resp = await self._post("/assetsbulkupdate", json=payload.model_dump(by_alias=True, exclude_none=True))
|
||||
return AgravityInfoResponse.model_validate(resp.json())
|
||||
|
||||
async def bulk_upsert_assets(self, payload: AssetBulkUpdate) -> AgravityInfoResponse:
|
||||
"""PUT /assetsbulkupdate."""
|
||||
resp = await self._put("/assetsbulkupdate", json=payload.model_dump(by_alias=True, exclude_none=True))
|
||||
return AgravityInfoResponse.model_validate(resp.json())
|
||||
|
||||
async def bulk_delete_assets(self, asset_ids: list[str]) -> AgravityInfoResponse:
|
||||
"""DELETE /assetsbulkupdate."""
|
||||
resp = await self._delete("/assetsbulkupdate", params={"ids": ",".join(asset_ids)})
|
||||
return AgravityInfoResponse.model_validate(resp.json())
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Blobs / binary operations
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def get_asset_blob(
|
||||
self,
|
||||
asset_id: str,
|
||||
c: str,
|
||||
*,
|
||||
portal_id: Optional[str] = None,
|
||||
key: Optional[str] = None,
|
||||
) -> AssetBlob:
|
||||
"""GET /assets/{id}/blobs – get a specific blob by format key."""
|
||||
resp = await self._get(
|
||||
f"/assets/{asset_id}/blobs",
|
||||
params={"c": c, "portal_id": portal_id, "key": key},
|
||||
)
|
||||
return AssetBlob.model_validate(resp.json())
|
||||
|
||||
async def get_shared_asset_blob(
|
||||
self,
|
||||
asset_id: str,
|
||||
share_id: str,
|
||||
format: str,
|
||||
*,
|
||||
password: Optional[str] = None,
|
||||
) -> AssetBlob:
|
||||
"""GET /assets/{id}/blob – retrieve a blob via share token."""
|
||||
headers = {"ay-password": password} if password else None
|
||||
resp = await self._get(
|
||||
f"/assets/{asset_id}/blob",
|
||||
params={"share_id": share_id, "format": format},
|
||||
extra_headers=headers,
|
||||
)
|
||||
return AssetBlob.model_validate(resp.json())
|
||||
|
||||
async def download_asset(
|
||||
self,
|
||||
asset_id: str,
|
||||
*,
|
||||
format: Optional[str] = None,
|
||||
c: Optional[str] = None,
|
||||
) -> AssetBlob:
|
||||
"""GET /assets/{id}/download – resolve download URL for an asset."""
|
||||
resp = await self._get(
|
||||
f"/assets/{asset_id}/download",
|
||||
params={"format": format, "c": c},
|
||||
)
|
||||
return AssetBlob.model_validate(resp.json())
|
||||
|
||||
async def resize_asset(
|
||||
self,
|
||||
asset_id: str,
|
||||
*,
|
||||
width: Optional[int] = None,
|
||||
height: Optional[int] = None,
|
||||
mode: Optional[str] = None,
|
||||
format: Optional[str] = None,
|
||||
bgcolor: Optional[str] = None,
|
||||
dpi: Optional[int] = None,
|
||||
depth: Optional[int] = None,
|
||||
) -> bytes:
|
||||
"""GET /assets/{id}/resize – return resized image bytes."""
|
||||
resp = await self._get(
|
||||
f"/assets/{asset_id}/resize",
|
||||
params={
|
||||
"width": width,
|
||||
"height": height,
|
||||
"mode": mode,
|
||||
"format": format,
|
||||
"bgcolor": bgcolor,
|
||||
"dpi": dpi,
|
||||
"depth": depth,
|
||||
},
|
||||
)
|
||||
return resp.content
|
||||
|
||||
async def imageedit_asset(
|
||||
self,
|
||||
asset_id: str,
|
||||
*,
|
||||
width: Optional[int] = None,
|
||||
height: Optional[int] = None,
|
||||
mode: Optional[str] = None,
|
||||
target: Optional[str] = None,
|
||||
bgcolor: Optional[str] = None,
|
||||
dpi: Optional[int] = None,
|
||||
depth: Optional[int] = None,
|
||||
quality: Optional[int] = None,
|
||||
colorspace: Optional[str] = None,
|
||||
crop_x: Optional[int] = None,
|
||||
crop_y: Optional[int] = None,
|
||||
crop_width: Optional[int] = None,
|
||||
crop_height: Optional[int] = None,
|
||||
filter: Optional[str] = None,
|
||||
original: Optional[bool] = None,
|
||||
) -> bytes:
|
||||
"""GET /assets/{id}/imageedit – apply image transformations."""
|
||||
resp = await self._get(
|
||||
f"/assets/{asset_id}/imageedit",
|
||||
params={
|
||||
"width": width, "height": height, "mode": mode, "target": target,
|
||||
"bgcolor": bgcolor, "dpi": dpi, "depth": depth, "quality": quality,
|
||||
"colorspace": colorspace, "crop_x": crop_x, "crop_y": crop_y,
|
||||
"crop_width": crop_width, "crop_height": crop_height,
|
||||
"filter": filter, "original": original,
|
||||
},
|
||||
)
|
||||
return resp.content
|
||||
|
||||
async def imageedit_asset_operations(
|
||||
self,
|
||||
asset_id: str,
|
||||
operations: list[DynamicImageOperation],
|
||||
) -> bytes:
|
||||
"""POST /assets/{id}/imageedit – apply a sequence of image operations."""
|
||||
payload = [op.model_dump(by_alias=True, exclude_none=True) for op in operations]
|
||||
resp = await self._post(f"/assets/{asset_id}/imageedit", json=payload)
|
||||
return resp.content
|
||||
|
||||
async def imageedit_asset_by_format(
|
||||
self,
|
||||
asset_id: str,
|
||||
download_format_id: str,
|
||||
) -> bytes:
|
||||
"""GET /assets/{id}/imageedit/{download_format_id}."""
|
||||
resp = await self._get(f"/assets/{asset_id}/imageedit/{download_format_id}")
|
||||
return resp.content
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Collections membership
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def get_asset_collections(self, asset_id: str) -> list[Collection]:
|
||||
"""GET /assets/{id}/collections."""
|
||||
resp = await self._get(f"/assets/{asset_id}/collections")
|
||||
return [Collection.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def add_asset_to_collection(
|
||||
self,
|
||||
asset_id: str,
|
||||
*,
|
||||
collection_id: Optional[str] = None,
|
||||
) -> None:
|
||||
"""POST /assets/{id}/tocollection."""
|
||||
await self._post(
|
||||
f"/assets/{asset_id}/tocollection",
|
||||
params={"collectionid": collection_id},
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Availability
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def update_asset_availability(
|
||||
self, asset_id: str, availability: AssetAvailability
|
||||
) -> AssetAvailability:
|
||||
"""PUT /assets/{id}/availability."""
|
||||
resp = await self._put(
|
||||
f"/assets/{asset_id}/availability",
|
||||
json=availability.model_dump(by_alias=True, exclude_none=True),
|
||||
)
|
||||
return AssetAvailability.model_validate(resp.json())
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Relations
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def get_asset_relations(self, asset_id: str) -> list[AssetRelation]:
|
||||
"""GET /assets/{id}/relations."""
|
||||
resp = await self._get(f"/assets/{asset_id}/relations")
|
||||
return [AssetRelation.model_validate(item) for item in resp.json()]
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Publishing (per-asset)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def get_asset_publish(self, asset_id: str) -> PublishEntity:
|
||||
"""GET /assets/{id}/publish."""
|
||||
resp = await self._get(f"/assets/{asset_id}/publish")
|
||||
return PublishEntity.model_validate(resp.json())
|
||||
|
||||
async def create_asset_publish(
|
||||
self,
|
||||
asset_id: str,
|
||||
payload: Optional[dict[str, Any]] = None,
|
||||
) -> PublishedAsset:
|
||||
"""POST /assets/{id}/publish."""
|
||||
resp = await self._post(f"/assets/{asset_id}/publish", json=payload)
|
||||
return PublishedAsset.model_validate(resp.json())
|
||||
|
||||
async def get_asset_published_by_id(
|
||||
self, asset_id: str, pub_id: str
|
||||
) -> PublishedAsset:
|
||||
"""GET /assets/{id}/publish/{pid}."""
|
||||
resp = await self._get(f"/assets/{asset_id}/publish/{pub_id}")
|
||||
return PublishedAsset.model_validate(resp.json())
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Versioning (per-asset)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def get_asset_versions(self, asset_id: str) -> VersionEntity:
|
||||
"""GET /assets/{id}/versions."""
|
||||
resp = await self._get(f"/assets/{asset_id}/versions")
|
||||
return VersionEntity.model_validate(resp.json())
|
||||
|
||||
async def create_asset_version(
|
||||
self,
|
||||
asset_id: str,
|
||||
payload: Optional[dict[str, Any]] = None,
|
||||
) -> VersionedAsset:
|
||||
"""POST /assets/{id}/versions – create a new version snapshot."""
|
||||
resp = await self._post(f"/assets/{asset_id}/versions", json=payload)
|
||||
return VersionedAsset.model_validate(resp.json())
|
||||
|
||||
async def upload_asset_version(
|
||||
self,
|
||||
asset_id: str,
|
||||
file: bytes,
|
||||
filename: str,
|
||||
*,
|
||||
content_type: str = "application/octet-stream",
|
||||
) -> VersionedAsset:
|
||||
"""POST /assets/{id}/versionsupload – upload a new version file."""
|
||||
files = {"file": (filename, file, content_type)}
|
||||
resp = await self._post(f"/assets/{asset_id}/versionsupload", files=files)
|
||||
return VersionedAsset.model_validate(resp.json())
|
||||
|
||||
async def restore_asset_version(
|
||||
self, asset_id: str, version_nr: int
|
||||
) -> dict[str, Any]:
|
||||
"""POST /assets/{id}/versions/{vNr}/restore."""
|
||||
resp = await self._post(f"/assets/{asset_id}/versions/{version_nr}/restore")
|
||||
return resp.json()
|
||||
|
||||
async def delete_asset_version(self, asset_id: str, version_nr: int) -> None:
|
||||
"""DELETE /assets/{id}/versions/{vNr}."""
|
||||
await self._delete(f"/assets/{asset_id}/versions/{version_nr}")
|
||||
|
||||
async def update_asset_version(
|
||||
self,
|
||||
asset_id: str,
|
||||
version_nr: int,
|
||||
payload: Optional[dict[str, Any]] = None,
|
||||
) -> VersionedAsset:
|
||||
"""POST /assets/{id}/versions/{vNr} – update version metadata."""
|
||||
resp = await self._post(
|
||||
f"/assets/{asset_id}/versions/{version_nr}", json=payload
|
||||
)
|
||||
return VersionedAsset.model_validate(resp.json())
|
||||
|
||||
async def get_asset_version_blob(
|
||||
self, asset_id: str, version_nr: int
|
||||
) -> AssetBlob:
|
||||
"""GET /assets/{id}/versions/{vNr}/blobs."""
|
||||
resp = await self._get(f"/assets/{asset_id}/versions/{version_nr}/blobs")
|
||||
return AssetBlob.model_validate(resp.json())
|
||||
35
agravity_client/api/auth.py
Normal file
35
agravity_client/api/auth.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
"""Auth API module (SAS tokens, users)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.auth import AgravityUser, SasToken
|
||||
|
||||
|
||||
class AuthApi(AgravityBaseApi):
|
||||
"""Covers all /auth endpoints."""
|
||||
|
||||
async def get_container_write_sas_token(
|
||||
self,
|
||||
container: str,
|
||||
blob: str,
|
||||
*,
|
||||
permission: Optional[str] = None,
|
||||
) -> SasToken:
|
||||
"""GET /auth/containerwrite."""
|
||||
resp = await self._get(
|
||||
"/auth/containerwrite",
|
||||
params={"container": container, "blob": blob, "permission": permission},
|
||||
)
|
||||
return SasToken.model_validate(resp.json())
|
||||
|
||||
async def get_inbox_sas_token(self) -> SasToken:
|
||||
"""GET /auth/inbox (deprecated)."""
|
||||
resp = await self._get("/auth/inbox")
|
||||
return SasToken.model_validate(resp.json())
|
||||
|
||||
async def get_users(self) -> list[AgravityUser]:
|
||||
"""GET /auth/users."""
|
||||
resp = await self._get("/auth/users")
|
||||
return [AgravityUser.model_validate(item) for item in resp.json()]
|
||||
160
agravity_client/api/base.py
Normal file
160
agravity_client/api/base.py
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
"""Base HTTP client shared by all Agravity API modules."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
import httpx
|
||||
|
||||
from agravity_client.config import AgravityConfig
|
||||
from agravity_client.exceptions import (
|
||||
AgravityAPIError,
|
||||
AgravityAuthenticationError,
|
||||
AgravityConnectionError,
|
||||
AgravityNotFoundError,
|
||||
AgravityTimeoutError,
|
||||
AgravityValidationError,
|
||||
)
|
||||
from agravity_client.models.common import AgravityErrorResponse
|
||||
|
||||
|
||||
class AgravityBaseApi:
|
||||
"""Low-level async HTTP wrapper around ``httpx.AsyncClient``.
|
||||
|
||||
Every high-level API module subclasses this and calls the
|
||||
``_get`` / ``_post`` / ``_put`` / ``_delete`` / ``_patch`` helpers,
|
||||
which centralise auth header injection and error mapping.
|
||||
"""
|
||||
|
||||
def __init__(self, http: httpx.AsyncClient, config: AgravityConfig) -> None:
|
||||
self._http = http
|
||||
self._config = config
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Private helpers
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _auth_headers(self) -> dict[str, str]:
|
||||
"""Return the authentication headers for every request."""
|
||||
if self._config.api_key:
|
||||
return {"x-functions-key": self._config.api_key}
|
||||
return {}
|
||||
|
||||
def _full_url(self, path: str) -> str:
|
||||
"""Combine base URL with a relative path."""
|
||||
return f"{self._config.base_url}/{path.lstrip('/')}"
|
||||
|
||||
@staticmethod
|
||||
def _raise_for_status(response: httpx.Response) -> None:
|
||||
"""Map HTTP error codes to typed exceptions."""
|
||||
if response.is_success:
|
||||
return
|
||||
|
||||
error_response: Optional[AgravityErrorResponse] = None
|
||||
raw_body: Optional[str] = None
|
||||
|
||||
try:
|
||||
data = response.json()
|
||||
error_response = AgravityErrorResponse.model_validate(data)
|
||||
except Exception:
|
||||
raw_body = response.text
|
||||
|
||||
status = response.status_code
|
||||
if status == 401:
|
||||
raise AgravityAuthenticationError(status, error_response, raw_body)
|
||||
if status == 404:
|
||||
raise AgravityNotFoundError(status, error_response, raw_body)
|
||||
if status in (400, 422):
|
||||
raise AgravityValidationError(status, error_response, raw_body)
|
||||
raise AgravityAPIError(status, error_response, raw_body)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# HTTP verbs
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def _request(
|
||||
self,
|
||||
method: str,
|
||||
path: str,
|
||||
*,
|
||||
params: Optional[dict[str, Any]] = None,
|
||||
json: Optional[Any] = None,
|
||||
data: Optional[dict[str, Any]] = None,
|
||||
files: Optional[dict[str, Any]] = None,
|
||||
content: Optional[bytes] = None,
|
||||
extra_headers: Optional[dict[str, str]] = None,
|
||||
) -> httpx.Response:
|
||||
"""Dispatch a request and raise on error."""
|
||||
headers = {**self._auth_headers(), **(extra_headers or {})}
|
||||
# Remove None-valued query params
|
||||
if params:
|
||||
params = {k: v for k, v in params.items() if v is not None}
|
||||
|
||||
try:
|
||||
response = await self._http.request(
|
||||
method,
|
||||
self._full_url(path),
|
||||
params=params or None,
|
||||
json=json,
|
||||
data=data,
|
||||
files=files,
|
||||
content=content,
|
||||
headers=headers,
|
||||
timeout=self._config.timeout,
|
||||
)
|
||||
except httpx.TimeoutException as exc:
|
||||
raise AgravityTimeoutError(str(exc)) from exc
|
||||
except httpx.TransportError as exc:
|
||||
raise AgravityConnectionError(str(exc)) from exc
|
||||
|
||||
self._raise_for_status(response)
|
||||
return response
|
||||
|
||||
async def _get(
|
||||
self,
|
||||
path: str,
|
||||
params: Optional[dict[str, Any]] = None,
|
||||
extra_headers: Optional[dict[str, str]] = None,
|
||||
) -> httpx.Response:
|
||||
return await self._request("GET", path, params=params, extra_headers=extra_headers)
|
||||
|
||||
async def _post(
|
||||
self,
|
||||
path: str,
|
||||
json: Optional[Any] = None,
|
||||
data: Optional[dict[str, Any]] = None,
|
||||
files: Optional[dict[str, Any]] = None,
|
||||
content: Optional[bytes] = None,
|
||||
params: Optional[dict[str, Any]] = None,
|
||||
extra_headers: Optional[dict[str, str]] = None,
|
||||
) -> httpx.Response:
|
||||
return await self._request(
|
||||
"POST", path,
|
||||
json=json, data=data, files=files, content=content,
|
||||
params=params, extra_headers=extra_headers,
|
||||
)
|
||||
|
||||
async def _put(
|
||||
self,
|
||||
path: str,
|
||||
json: Optional[Any] = None,
|
||||
params: Optional[dict[str, Any]] = None,
|
||||
extra_headers: Optional[dict[str, str]] = None,
|
||||
) -> httpx.Response:
|
||||
return await self._request("PUT", path, json=json, params=params, extra_headers=extra_headers)
|
||||
|
||||
async def _patch(
|
||||
self,
|
||||
path: str,
|
||||
json: Optional[Any] = None,
|
||||
params: Optional[dict[str, Any]] = None,
|
||||
extra_headers: Optional[dict[str, str]] = None,
|
||||
) -> httpx.Response:
|
||||
return await self._request("PATCH", path, json=json, params=params, extra_headers=extra_headers)
|
||||
|
||||
async def _delete(
|
||||
self,
|
||||
path: str,
|
||||
params: Optional[dict[str, Any]] = None,
|
||||
extra_headers: Optional[dict[str, str]] = None,
|
||||
) -> httpx.Response:
|
||||
return await self._request("DELETE", path, params=params, extra_headers=extra_headers)
|
||||
24
agravity_client/api/collection_types.py
Normal file
24
agravity_client/api/collection_types.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
"""Collection Type Management API module."""
|
||||
from __future__ import annotations
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.collection import CollectionType, CollTypeItem
|
||||
|
||||
|
||||
class CollectionTypesApi(AgravityBaseApi):
|
||||
"""Covers all /collectiontypes endpoints."""
|
||||
|
||||
async def list_collection_types(self) -> list[CollectionType]:
|
||||
"""GET /collectiontypes."""
|
||||
resp = await self._get("/collectiontypes")
|
||||
return [CollectionType.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def get_collection_type(self, type_id: str) -> CollectionType:
|
||||
"""GET /collectiontypes/{id}."""
|
||||
resp = await self._get(f"/collectiontypes/{type_id}")
|
||||
return CollectionType.model_validate(resp.json())
|
||||
|
||||
async def get_collection_type_items(self, type_id: str) -> list[CollTypeItem]:
|
||||
"""GET /collectiontypes/{id}/items."""
|
||||
resp = await self._get(f"/collectiontypes/{type_id}/items")
|
||||
return [CollTypeItem.model_validate(item) for item in resp.json()]
|
||||
106
agravity_client/api/collections.py
Normal file
106
agravity_client/api/collections.py
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
"""Collection Management API module."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.collection import (
|
||||
Collection,
|
||||
EntityListResult,
|
||||
EntityNamesRequest,
|
||||
MoveCollectionBody,
|
||||
)
|
||||
|
||||
|
||||
class CollectionsApi(AgravityBaseApi):
|
||||
"""Covers all /collections endpoints."""
|
||||
|
||||
async def list_collections(
|
||||
self,
|
||||
*,
|
||||
accept_language: Optional[str] = None,
|
||||
) -> list[Collection]:
|
||||
"""GET /collections."""
|
||||
headers = {"Accept-Language": accept_language} if accept_language else None
|
||||
resp = await self._get("/collections", extra_headers=headers)
|
||||
return [Collection.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def create_collection(
|
||||
self, payload: dict[str, Any]
|
||||
) -> Collection:
|
||||
"""POST /collections."""
|
||||
resp = await self._post("/collections", json=payload)
|
||||
return Collection.model_validate(resp.json())
|
||||
|
||||
async def get_collection(
|
||||
self,
|
||||
collection_id: str,
|
||||
*,
|
||||
accept_language: Optional[str] = None,
|
||||
) -> Collection:
|
||||
"""GET /collections/{id}."""
|
||||
headers = {"Accept-Language": accept_language} if accept_language else None
|
||||
resp = await self._get(f"/collections/{collection_id}", extra_headers=headers)
|
||||
return Collection.model_validate(resp.json())
|
||||
|
||||
async def update_collection(
|
||||
self, collection_id: str, payload: dict[str, Any]
|
||||
) -> Collection:
|
||||
"""POST /collections/{id}."""
|
||||
resp = await self._post(f"/collections/{collection_id}", json=payload)
|
||||
return Collection.model_validate(resp.json())
|
||||
|
||||
async def delete_collection(self, collection_id: str) -> None:
|
||||
"""DELETE /collections/{id}."""
|
||||
await self._delete(f"/collections/{collection_id}")
|
||||
|
||||
async def get_collection_ancestors(
|
||||
self,
|
||||
collection_id: str,
|
||||
*,
|
||||
accept_language: Optional[str] = None,
|
||||
) -> list[Collection]:
|
||||
"""GET /collections/{id}/ancestors."""
|
||||
headers = {"Accept-Language": accept_language} if accept_language else None
|
||||
resp = await self._get(f"/collections/{collection_id}/ancestors", extra_headers=headers)
|
||||
return [Collection.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def get_collection_descendants(
|
||||
self,
|
||||
collection_id: str,
|
||||
*,
|
||||
accept_language: Optional[str] = None,
|
||||
) -> list[Collection]:
|
||||
"""GET /collections/{id}/descendants."""
|
||||
headers = {"Accept-Language": accept_language} if accept_language else None
|
||||
resp = await self._get(f"/collections/{collection_id}/descendants", extra_headers=headers)
|
||||
return [Collection.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def get_collection_preview(
|
||||
self,
|
||||
collection_id: str,
|
||||
) -> bytes:
|
||||
"""GET /collections/{id}/preview – returns image bytes."""
|
||||
resp = await self._get(f"/collections/{collection_id}/preview")
|
||||
return resp.content
|
||||
|
||||
async def get_collections_by_names(
|
||||
self,
|
||||
request: EntityNamesRequest,
|
||||
) -> EntityListResult:
|
||||
"""POST /collections/bynames."""
|
||||
resp = await self._post(
|
||||
"/collections/bynames",
|
||||
json=request.model_dump(by_alias=True, exclude_none=True),
|
||||
)
|
||||
return EntityListResult.model_validate(resp.json())
|
||||
|
||||
async def move_collection(
|
||||
self,
|
||||
payload: MoveCollectionBody,
|
||||
) -> None:
|
||||
"""POST /movecolltocoll – move a collection to another parent."""
|
||||
await self._post(
|
||||
"/movecolltocoll",
|
||||
json=payload.model_dump(by_alias=True, exclude_none=True),
|
||||
)
|
||||
21
agravity_client/api/download_formats.py
Normal file
21
agravity_client/api/download_formats.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
"""Download Format Management API module."""
|
||||
from __future__ import annotations
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.download import DownloadFormat
|
||||
|
||||
|
||||
class DownloadFormatsApi(AgravityBaseApi):
|
||||
"""Covers all /downloadformats endpoints."""
|
||||
|
||||
async def list_download_formats(self) -> list[DownloadFormat]:
|
||||
"""GET /downloadformats."""
|
||||
resp = await self._get("/downloadformats")
|
||||
return [DownloadFormat.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def list_download_formats_from_shared(
|
||||
self, share_id: str
|
||||
) -> list[DownloadFormat]:
|
||||
"""GET /downloadformats/{shareId} – formats available for a share token."""
|
||||
resp = await self._get(f"/downloadformats/{share_id}")
|
||||
return [DownloadFormat.model_validate(item) for item in resp.json()]
|
||||
50
agravity_client/api/general.py
Normal file
50
agravity_client/api/general.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"""General / utility endpoints (version, deleted, durable, signalR, public)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.auth import AgravityVersion
|
||||
from agravity_client.models.common import DeletedEntities, SignalRConnectionInfo
|
||||
|
||||
|
||||
class GeneralApi(AgravityBaseApi):
|
||||
"""Covers miscellaneous top-level endpoints."""
|
||||
|
||||
async def get_version(self) -> AgravityVersion:
|
||||
"""GET /version – API version and capabilities."""
|
||||
resp = await self._get("/version")
|
||||
return AgravityVersion.model_validate(resp.json())
|
||||
|
||||
async def get_deleted_entities(self) -> list[DeletedEntities]:
|
||||
"""GET /deleted – list recently deleted entities."""
|
||||
resp = await self._get("/deleted")
|
||||
return [DeletedEntities.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def get_durable_status(
|
||||
self,
|
||||
instance_id: Optional[str] = None,
|
||||
) -> dict[str, Any]:
|
||||
"""GET /durable – check status of a durable function instance."""
|
||||
resp = await self._get("/durable", params={"instanceId": instance_id})
|
||||
return resp.json()
|
||||
|
||||
async def get_signalr_connection_info(self) -> SignalRConnectionInfo:
|
||||
"""GET /negotiate – retrieve SignalR connection details."""
|
||||
resp = await self._get("/negotiate")
|
||||
return SignalRConnectionInfo.model_validate(resp.json())
|
||||
|
||||
async def get_public_asset(self, asset_id: str) -> dict[str, Any]:
|
||||
"""GET /public/{id} – publicly accessible asset metadata."""
|
||||
resp = await self._get(f"/public/{asset_id}")
|
||||
return resp.json()
|
||||
|
||||
async def view_public_asset(self, asset_id: str) -> bytes:
|
||||
"""GET /public/{id}/view – publicly accessible asset binary."""
|
||||
resp = await self._get(f"/public/{asset_id}/view")
|
||||
return resp.content
|
||||
|
||||
async def get_frontend_config(self) -> list[dict[str, Any]]:
|
||||
"""GET /config – retrieve frontend configuration entries."""
|
||||
resp = await self._get("/config")
|
||||
return resp.json()
|
||||
38
agravity_client/api/helper.py
Normal file
38
agravity_client/api/helper.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
"""Helper Tools API module (searchable/filterable item metadata)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.search import SearchableItem
|
||||
|
||||
|
||||
class HelperApi(AgravityBaseApi):
|
||||
"""Covers all /helper endpoints."""
|
||||
|
||||
async def get_user_defined_lists(
|
||||
self,
|
||||
*,
|
||||
collection_type_id: Optional[str] = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""GET /helper/userdefinedlists."""
|
||||
resp = await self._get(
|
||||
"/helper/userdefinedlists",
|
||||
params={"collectiontypeid": collection_type_id},
|
||||
)
|
||||
return resp.json()
|
||||
|
||||
async def get_searchable_item_names(self) -> list[str]:
|
||||
"""GET /helper/searchableitemnames – list names of searchable fields."""
|
||||
resp = await self._get("/helper/searchableitemnames")
|
||||
return resp.json()
|
||||
|
||||
async def get_searchable_items(self) -> list[SearchableItem]:
|
||||
"""GET /helper/searchableitems – full searchable field definitions."""
|
||||
resp = await self._get("/helper/searchableitems")
|
||||
return [SearchableItem.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def get_filterable_items(self) -> list[SearchableItem]:
|
||||
"""GET /helper/filterableitems – fields that can be used as filters."""
|
||||
resp = await self._get("/helper/filterableitems")
|
||||
return [SearchableItem.model_validate(item) for item in resp.json()]
|
||||
73
agravity_client/api/portals.py
Normal file
73
agravity_client/api/portals.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
"""Portal Management API module."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.download import DownloadZipRequest, DownloadZipStatus
|
||||
from agravity_client.models.portal import Portal, PortalConfiguration
|
||||
|
||||
|
||||
class PortalsApi(AgravityBaseApi):
|
||||
"""Covers all /portals endpoints."""
|
||||
|
||||
async def list_portals(self) -> list[Portal]:
|
||||
"""GET /portals."""
|
||||
resp = await self._get("/portals")
|
||||
return [Portal.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def get_portal(self, portal_id: str) -> Portal:
|
||||
"""GET /portals/{id}."""
|
||||
resp = await self._get(f"/portals/{portal_id}")
|
||||
return Portal.model_validate(resp.json())
|
||||
|
||||
async def get_portal_config(self, portal_id: str) -> PortalConfiguration:
|
||||
"""GET /portals/{id}/config."""
|
||||
resp = await self._get(f"/portals/{portal_id}/config")
|
||||
return PortalConfiguration.model_validate(resp.json())
|
||||
|
||||
async def enhance_portal_token(
|
||||
self,
|
||||
portal_id: str,
|
||||
payload: Optional[dict[str, Any]] = None,
|
||||
) -> dict[str, Any]:
|
||||
"""POST /portals/{id}/enhancetoken – augment the auth token claims."""
|
||||
resp = await self._post(
|
||||
f"/portals/{portal_id}/enhancetoken",
|
||||
json=payload,
|
||||
)
|
||||
return resp.json()
|
||||
|
||||
async def save_portal_user_attributes(
|
||||
self,
|
||||
portal_id: str,
|
||||
payload: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""POST /portals/{id}/saveuserattributes."""
|
||||
resp = await self._post(
|
||||
f"/portals/{portal_id}/saveuserattributes",
|
||||
json=payload,
|
||||
)
|
||||
return resp.json()
|
||||
|
||||
async def get_portal_zip_status(self, portal_id: str) -> DownloadZipStatus:
|
||||
"""GET /portals/{id}/zip."""
|
||||
resp = await self._get(f"/portals/{portal_id}/zip")
|
||||
return DownloadZipStatus.model_validate(resp.json())
|
||||
|
||||
async def create_portal_zip(
|
||||
self,
|
||||
portal_id: str,
|
||||
payload: DownloadZipRequest,
|
||||
) -> DownloadZipStatus:
|
||||
"""POST /portals/{id}/zip."""
|
||||
resp = await self._post(
|
||||
f"/portals/{portal_id}/zip",
|
||||
json=payload.model_dump(by_alias=True, exclude_none=True),
|
||||
)
|
||||
return DownloadZipStatus.model_validate(resp.json())
|
||||
|
||||
async def get_portal_asset_ids(self, portal_id: str) -> list[str]:
|
||||
"""GET /portals/{id}/assetids."""
|
||||
resp = await self._get(f"/portals/{portal_id}/assetids")
|
||||
return resp.json()
|
||||
14
agravity_client/api/publishing.py
Normal file
14
agravity_client/api/publishing.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"""Publishing API module (global published assets list)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.publish import PublishEntity
|
||||
|
||||
|
||||
class PublishingApi(AgravityBaseApi):
|
||||
"""Covers the /publish endpoint."""
|
||||
|
||||
async def list_published(self) -> list[PublishEntity]:
|
||||
"""GET /publish – list all published entities across assets."""
|
||||
resp = await self._get("/publish")
|
||||
return [PublishEntity.model_validate(item) for item in resp.json()]
|
||||
57
agravity_client/api/relations.py
Normal file
57
agravity_client/api/relations.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
"""Asset Relation and Asset Relation Type API modules."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.relation import AssetRelation, AssetRelationType
|
||||
|
||||
|
||||
class RelationsApi(AgravityBaseApi):
|
||||
"""Covers /relations and /assetrelationtypes endpoints."""
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Asset Relations (CRUD)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def list_relations(self) -> list[AssetRelation]:
|
||||
"""GET /relations."""
|
||||
resp = await self._get("/relations")
|
||||
return [AssetRelation.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def create_relation(self, payload: dict[str, Any]) -> AssetRelation:
|
||||
"""POST /relations."""
|
||||
resp = await self._post("/relations", json=payload)
|
||||
return AssetRelation.model_validate(resp.json())
|
||||
|
||||
async def get_relation(self, relation_id: str) -> AssetRelation:
|
||||
"""GET /relations/{id}."""
|
||||
resp = await self._get(f"/relations/{relation_id}")
|
||||
return AssetRelation.model_validate(resp.json())
|
||||
|
||||
async def update_relation(
|
||||
self,
|
||||
relation_id: str,
|
||||
payload: dict[str, Any],
|
||||
) -> AssetRelation:
|
||||
"""POST /relations/{id}."""
|
||||
resp = await self._post(f"/relations/{relation_id}", json=payload)
|
||||
return AssetRelation.model_validate(resp.json())
|
||||
|
||||
async def delete_relation(self, relation_id: str) -> None:
|
||||
"""DELETE /relations/{id}."""
|
||||
await self._delete(f"/relations/{relation_id}")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Asset Relation Types (read-only)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def list_relation_types(self) -> list[AssetRelationType]:
|
||||
"""GET /assetrelationtypes."""
|
||||
resp = await self._get("/assetrelationtypes")
|
||||
return [AssetRelationType.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def get_relation_type(self, type_id: str) -> AssetRelationType:
|
||||
"""GET /assetrelationtypes/{id}."""
|
||||
resp = await self._get(f"/assetrelationtypes/{type_id}")
|
||||
return AssetRelationType.model_validate(resp.json())
|
||||
42
agravity_client/api/search.py
Normal file
42
agravity_client/api/search.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""Search Management API module."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.search import (
|
||||
AzSearchOptions,
|
||||
SavedSearch,
|
||||
SearchAdminStatus,
|
||||
SearchResult,
|
||||
)
|
||||
|
||||
|
||||
class SearchApi(AgravityBaseApi):
|
||||
"""Covers /search and /savedsearch endpoints."""
|
||||
|
||||
async def search(self, options: AzSearchOptions) -> SearchResult:
|
||||
"""POST /search – perform a full-text/faceted search."""
|
||||
resp = await self._post(
|
||||
"/search",
|
||||
json=options.model_dump(by_alias=True, exclude_none=True),
|
||||
)
|
||||
return SearchResult.model_validate(resp.json())
|
||||
|
||||
async def search_facette(self, options: AzSearchOptions) -> SearchResult:
|
||||
"""POST /search/facette – faceted search."""
|
||||
resp = await self._post(
|
||||
"/search/facette",
|
||||
json=options.model_dump(by_alias=True, exclude_none=True),
|
||||
)
|
||||
return SearchResult.model_validate(resp.json())
|
||||
|
||||
async def get_search_admin_status(self) -> SearchAdminStatus:
|
||||
"""GET /search/adminstatus."""
|
||||
resp = await self._get("/search/adminstatus")
|
||||
return SearchAdminStatus.model_validate(resp.json())
|
||||
|
||||
async def list_saved_searches(self) -> list[SavedSearch]:
|
||||
"""GET /savedsearch."""
|
||||
resp = await self._get("/savedsearch")
|
||||
return [SavedSearch.model_validate(item) for item in resp.json()]
|
||||
28
agravity_client/api/secure_upload.py
Normal file
28
agravity_client/api/secure_upload.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
"""Secure Upload API module."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.secure_upload import SecureUploadEntity
|
||||
|
||||
|
||||
class SecureUploadApi(AgravityBaseApi):
|
||||
"""Covers all /secureupload endpoints."""
|
||||
|
||||
async def check_secure_upload(
|
||||
self,
|
||||
*,
|
||||
token: Optional[str] = None,
|
||||
) -> dict:
|
||||
"""GET /secureupload – check status / retrieve upload config."""
|
||||
resp = await self._get("/secureupload", params={"token": token})
|
||||
return resp.json()
|
||||
|
||||
async def create_secure_upload(
|
||||
self,
|
||||
payload: Optional[dict] = None,
|
||||
) -> SecureUploadEntity:
|
||||
"""POST /secureupload – create a secure upload entity."""
|
||||
resp = await self._post("/secureupload", json=payload)
|
||||
return SecureUploadEntity.model_validate(resp.json())
|
||||
79
agravity_client/api/sharing.py
Normal file
79
agravity_client/api/sharing.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
"""Sharing Management API module."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.download import DownloadZipRequest, DownloadZipStatus
|
||||
from agravity_client.models.sharing import QuickShareFull, SharedCollectionFull
|
||||
|
||||
|
||||
class SharingApi(AgravityBaseApi):
|
||||
"""Covers all /sharing endpoints."""
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Shared collections
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def list_shared_collections(self) -> list[SharedCollectionFull]:
|
||||
"""GET /sharing."""
|
||||
resp = await self._get("/sharing")
|
||||
return [SharedCollectionFull.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def get_shared_collection(
|
||||
self,
|
||||
token: str,
|
||||
*,
|
||||
password: Optional[str] = None,
|
||||
) -> SharedCollectionFull:
|
||||
"""GET /sharing/{token}."""
|
||||
headers = {"ay-password": password} if password else None
|
||||
resp = await self._get(f"/sharing/{token}", extra_headers=headers)
|
||||
return SharedCollectionFull.model_validate(resp.json())
|
||||
|
||||
async def get_shared_collection_zip_status(
|
||||
self,
|
||||
token: str,
|
||||
*,
|
||||
password: Optional[str] = None,
|
||||
) -> DownloadZipStatus:
|
||||
"""GET /sharing/{token}/zip."""
|
||||
headers = {"ay-password": password} if password else None
|
||||
resp = await self._get(f"/sharing/{token}/zip", extra_headers=headers)
|
||||
return DownloadZipStatus.model_validate(resp.json())
|
||||
|
||||
async def create_shared_collection_zip(
|
||||
self,
|
||||
token: str,
|
||||
payload: DownloadZipRequest,
|
||||
*,
|
||||
password: Optional[str] = None,
|
||||
) -> DownloadZipStatus:
|
||||
"""POST /sharing/{token}/zip."""
|
||||
headers = {"ay-password": password} if password else None
|
||||
resp = await self._post(
|
||||
f"/sharing/{token}/zip",
|
||||
json=payload.model_dump(by_alias=True, exclude_none=True),
|
||||
extra_headers=headers,
|
||||
)
|
||||
return DownloadZipStatus.model_validate(resp.json())
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Quick shares
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def list_quickshares(self) -> list[QuickShareFull]:
|
||||
"""GET /sharing/quickshares."""
|
||||
resp = await self._get("/sharing/quickshares")
|
||||
return [QuickShareFull.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def get_quickshare(
|
||||
self,
|
||||
token: str,
|
||||
*,
|
||||
password: Optional[str] = None,
|
||||
) -> QuickShareFull:
|
||||
"""GET /sharing/quickshares/{token}."""
|
||||
headers = {"ay-password": password} if password else None
|
||||
resp = await self._get(f"/sharing/quickshares/{token}", extra_headers=headers)
|
||||
return QuickShareFull.model_validate(resp.json())
|
||||
30
agravity_client/api/static_lists.py
Normal file
30
agravity_client/api/static_lists.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
"""Static Defined Lists API module."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.static_lists import StaticDefinedList
|
||||
|
||||
|
||||
class StaticListsApi(AgravityBaseApi):
|
||||
"""Covers all /staticdefinedlists endpoints."""
|
||||
|
||||
async def list_static_defined_lists(self) -> list[StaticDefinedList]:
|
||||
"""GET /staticdefinedlists."""
|
||||
resp = await self._get("/staticdefinedlists")
|
||||
return [StaticDefinedList.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def get_static_defined_list(self, list_id: str) -> StaticDefinedList:
|
||||
"""GET /staticdefinedlists/{id}."""
|
||||
resp = await self._get(f"/staticdefinedlists/{list_id}")
|
||||
return StaticDefinedList.model_validate(resp.json())
|
||||
|
||||
async def update_static_defined_list(
|
||||
self,
|
||||
list_id: str,
|
||||
payload: dict[str, Any],
|
||||
) -> StaticDefinedList:
|
||||
"""PUT /staticdefinedlists/{id}."""
|
||||
resp = await self._put(f"/staticdefinedlists/{list_id}", json=payload)
|
||||
return StaticDefinedList.model_validate(resp.json())
|
||||
43
agravity_client/api/translations.py
Normal file
43
agravity_client/api/translations.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
"""Translation Management API module."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.common import DictionaryObject
|
||||
|
||||
|
||||
class TranslationsApi(AgravityBaseApi):
|
||||
"""Covers all /translations endpoints."""
|
||||
|
||||
async def get_translation(self, entity_id: str) -> DictionaryObject:
|
||||
"""GET /translations/{id} – retrieve all translations for an entity."""
|
||||
resp = await self._get(f"/translations/{entity_id}")
|
||||
return DictionaryObject.model_validate(resp.json())
|
||||
|
||||
async def update_translation_property(
|
||||
self,
|
||||
entity_id: str,
|
||||
property_name: str,
|
||||
payload: dict[str, Any],
|
||||
) -> DictionaryObject:
|
||||
"""PUT /translations/{id}/{property}."""
|
||||
resp = await self._put(
|
||||
f"/translations/{entity_id}/{property_name}",
|
||||
json=payload,
|
||||
)
|
||||
return DictionaryObject.model_validate(resp.json())
|
||||
|
||||
async def update_translation_custom_field(
|
||||
self,
|
||||
entity_id: str,
|
||||
property_name: str,
|
||||
custom_field: str,
|
||||
payload: dict[str, Any],
|
||||
) -> DictionaryObject:
|
||||
"""PUT /translations/{id}/{property}/{custom_field}."""
|
||||
resp = await self._put(
|
||||
f"/translations/{entity_id}/{property_name}/{custom_field}",
|
||||
json=payload,
|
||||
)
|
||||
return DictionaryObject.model_validate(resp.json())
|
||||
45
agravity_client/api/webappdata.py
Normal file
45
agravity_client/api/webappdata.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
"""Web App Data API module."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional, Union
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.search import AllWebAppData, GroupAllAppData
|
||||
|
||||
|
||||
class WebAppDataApi(AgravityBaseApi):
|
||||
"""Covers all /webappdata endpoints."""
|
||||
|
||||
async def get_web_app_data(
|
||||
self,
|
||||
*,
|
||||
collection_type_id: Optional[str] = None,
|
||||
accept_language: Optional[str] = None,
|
||||
) -> Union[AllWebAppData, GroupAllAppData]:
|
||||
"""GET /webappdata – retrieve structured data for web app initialization.
|
||||
|
||||
If *collection_type_id* is supplied the API may return a
|
||||
:class:`GroupAllAppData` instead of :class:`AllWebAppData`.
|
||||
"""
|
||||
params = {"collectiontypeid": collection_type_id}
|
||||
headers = {"Accept-Language": accept_language} if accept_language else None
|
||||
resp = await self._get("/webappdata", params=params, extra_headers=headers)
|
||||
data = resp.json()
|
||||
# Heuristic: GroupAllAppData has a "collection_type" key
|
||||
if "collection_type" in data or "collectionType" in data:
|
||||
return GroupAllAppData.model_validate(data)
|
||||
return AllWebAppData.model_validate(data)
|
||||
|
||||
async def get_web_app_data_by_collection_type(
|
||||
self,
|
||||
collection_type_id: str,
|
||||
*,
|
||||
accept_language: Optional[str] = None,
|
||||
) -> GroupAllAppData:
|
||||
"""GET /webappdata/{collectionTypeId}."""
|
||||
headers = {"Accept-Language": accept_language} if accept_language else None
|
||||
resp = await self._get(
|
||||
f"/webappdata/{collection_type_id}",
|
||||
extra_headers=headers,
|
||||
)
|
||||
return GroupAllAppData.model_validate(resp.json())
|
||||
19
agravity_client/api/workspaces.py
Normal file
19
agravity_client/api/workspaces.py
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
"""Workspaces API module."""
|
||||
from __future__ import annotations
|
||||
|
||||
from agravity_client.api.base import AgravityBaseApi
|
||||
from agravity_client.models.workspace import Workspace
|
||||
|
||||
|
||||
class WorkspacesApi(AgravityBaseApi):
|
||||
"""Covers all /workspaces endpoints."""
|
||||
|
||||
async def list_workspaces(self) -> list[Workspace]:
|
||||
"""GET /workspaces."""
|
||||
resp = await self._get("/workspaces")
|
||||
return [Workspace.model_validate(item) for item in resp.json()]
|
||||
|
||||
async def get_workspace(self, workspace_id: str) -> Workspace:
|
||||
"""GET /workspaces/{id}."""
|
||||
resp = await self._get(f"/workspaces/{workspace_id}")
|
||||
return Workspace.model_validate(resp.json())
|
||||
114
agravity_client/client.py
Normal file
114
agravity_client/client.py
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
"""Top-level AgravityClient – the single entry point for users."""
|
||||
from __future__ import annotations
|
||||
|
||||
from types import TracebackType
|
||||
from typing import Optional, Type
|
||||
|
||||
import httpx
|
||||
|
||||
from agravity_client.api.ai import AiApi
|
||||
from agravity_client.api.assets import AssetsApi
|
||||
from agravity_client.api.auth import AuthApi
|
||||
from agravity_client.api.collection_types import CollectionTypesApi
|
||||
from agravity_client.api.collections import CollectionsApi
|
||||
from agravity_client.api.download_formats import DownloadFormatsApi
|
||||
from agravity_client.api.general import GeneralApi
|
||||
from agravity_client.api.helper import HelperApi
|
||||
from agravity_client.api.portals import PortalsApi
|
||||
from agravity_client.api.publishing import PublishingApi
|
||||
from agravity_client.api.relations import RelationsApi
|
||||
from agravity_client.api.search import SearchApi
|
||||
from agravity_client.api.secure_upload import SecureUploadApi
|
||||
from agravity_client.api.sharing import SharingApi
|
||||
from agravity_client.api.static_lists import StaticListsApi
|
||||
from agravity_client.api.translations import TranslationsApi
|
||||
from agravity_client.api.webappdata import WebAppDataApi
|
||||
from agravity_client.api.workspaces import WorkspacesApi
|
||||
from agravity_client.config import AgravityConfig
|
||||
|
||||
|
||||
class AgravityClient:
|
||||
"""Fully async Agravity DAM API client.
|
||||
|
||||
Usage example::
|
||||
|
||||
import asyncio
|
||||
from agravity_client import AgravityClient, AgravityConfig
|
||||
|
||||
async def main():
|
||||
config = AgravityConfig(api_key="your-key")
|
||||
async with AgravityClient(config) as client:
|
||||
version = await client.general.get_version()
|
||||
print(version)
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
The client can also be used without the context-manager idiom – just
|
||||
remember to call :meth:`aclose` when you are done.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Optional[AgravityConfig] = None,
|
||||
*,
|
||||
http_client: Optional[httpx.AsyncClient] = None,
|
||||
) -> None:
|
||||
self._config = config or AgravityConfig()
|
||||
self._http = http_client or httpx.AsyncClient(
|
||||
verify=self._config.verify_ssl,
|
||||
timeout=self._config.timeout,
|
||||
)
|
||||
self._init_api_modules()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Context-manager support
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
async def __aenter__(self) -> "AgravityClient":
|
||||
return self
|
||||
|
||||
async def __aexit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
exc_val: Optional[BaseException],
|
||||
exc_tb: Optional[TracebackType],
|
||||
) -> None:
|
||||
await self.aclose()
|
||||
|
||||
async def aclose(self) -> None:
|
||||
"""Close the underlying HTTP transport."""
|
||||
await self._http.aclose()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# API module initialisation
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _init_api_modules(self) -> None:
|
||||
args = (self._http, self._config)
|
||||
self.ai = AiApi(*args)
|
||||
self.assets = AssetsApi(*args)
|
||||
self.auth = AuthApi(*args)
|
||||
self.collection_types = CollectionTypesApi(*args)
|
||||
self.collections = CollectionsApi(*args)
|
||||
self.download_formats = DownloadFormatsApi(*args)
|
||||
self.general = GeneralApi(*args)
|
||||
self.helper = HelperApi(*args)
|
||||
self.portals = PortalsApi(*args)
|
||||
self.publishing = PublishingApi(*args)
|
||||
self.relations = RelationsApi(*args)
|
||||
self.search = SearchApi(*args)
|
||||
self.secure_upload = SecureUploadApi(*args)
|
||||
self.sharing = SharingApi(*args)
|
||||
self.static_lists = StaticListsApi(*args)
|
||||
self.translations = TranslationsApi(*args)
|
||||
self.webappdata = WebAppDataApi(*args)
|
||||
self.workspaces = WorkspacesApi(*args)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Convenience read-only properties
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@property
|
||||
def config(self) -> AgravityConfig:
|
||||
"""The current client configuration."""
|
||||
return self._config
|
||||
48
agravity_client/config.py
Normal file
48
agravity_client/config.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
"""Agravity client configuration using Pydantic Settings."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class AgravityConfig(BaseSettings):
|
||||
"""Configuration for the Agravity API client.
|
||||
|
||||
Values can be provided via environment variables or directly in code.
|
||||
|
||||
Environment variable names:
|
||||
AGRAVITY_BASE_URL
|
||||
AGRAVITY_API_KEY
|
||||
AGRAVITY_TIMEOUT
|
||||
AGRAVITY_VERIFY_SSL
|
||||
"""
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="AGRAVITY_",
|
||||
env_file=".env",
|
||||
env_file_encoding="utf-8",
|
||||
case_sensitive=False,
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
base_url: str = Field(
|
||||
default="https://devagravitypublic.azurewebsites.net/api",
|
||||
description="The base URL for the Agravity API (without trailing slash).",
|
||||
)
|
||||
api_key: str = Field(
|
||||
default="",
|
||||
description="The API key (x-functions-key) used for authentication.",
|
||||
)
|
||||
timeout: float = Field(
|
||||
default=60.0,
|
||||
description="Default HTTP request timeout in seconds.",
|
||||
)
|
||||
verify_ssl: bool = Field(
|
||||
default=True,
|
||||
description="Whether to verify SSL certificates.",
|
||||
)
|
||||
|
||||
@field_validator("base_url")
|
||||
@classmethod
|
||||
def strip_trailing_slash(cls, v: str) -> str:
|
||||
return v.rstrip("/")
|
||||
59
agravity_client/exceptions.py
Normal file
59
agravity_client/exceptions.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"""Custom exceptions for the Agravity API client."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from agravity_client.models.common import AgravityErrorResponse
|
||||
|
||||
|
||||
class AgravityError(Exception):
|
||||
"""Base exception for all Agravity client errors."""
|
||||
|
||||
|
||||
class AgravityAPIError(AgravityError):
|
||||
"""Raised when the API returns a non-2xx response.
|
||||
|
||||
Attributes:
|
||||
status_code: The HTTP status code returned by the server.
|
||||
error_response: The parsed AgravityErrorResponse body, if available.
|
||||
raw_body: The raw response text, if parsing failed.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
status_code: int,
|
||||
error_response: Optional[AgravityErrorResponse] = None,
|
||||
raw_body: Optional[str] = None,
|
||||
) -> None:
|
||||
self.status_code = status_code
|
||||
self.error_response = error_response
|
||||
self.raw_body = raw_body
|
||||
|
||||
if error_response and error_response.error_message:
|
||||
msg = f"HTTP {status_code}: {error_response.error_message}"
|
||||
elif raw_body:
|
||||
msg = f"HTTP {status_code}: {raw_body[:200]}"
|
||||
else:
|
||||
msg = f"HTTP {status_code}"
|
||||
|
||||
super().__init__(msg)
|
||||
|
||||
|
||||
class AgravityAuthenticationError(AgravityAPIError):
|
||||
"""Raised when the API returns a 401 Unauthorized response."""
|
||||
|
||||
|
||||
class AgravityNotFoundError(AgravityAPIError):
|
||||
"""Raised when the API returns a 404 Not Found response."""
|
||||
|
||||
|
||||
class AgravityValidationError(AgravityAPIError):
|
||||
"""Raised when the API returns a 400 Bad Request / 422 response."""
|
||||
|
||||
|
||||
class AgravityTimeoutError(AgravityError):
|
||||
"""Raised when an HTTP request times out."""
|
||||
|
||||
|
||||
class AgravityConnectionError(AgravityError):
|
||||
"""Raised when a network-level connection error occurs."""
|
||||
138
agravity_client/models/__init__.py
Normal file
138
agravity_client/models/__init__.py
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
"""Agravity DAM – Pydantic models (re-exported from sub-modules)."""
|
||||
|
||||
from agravity_client.models.asset import (
|
||||
Asset,
|
||||
AssetAvailability,
|
||||
AssetBlob,
|
||||
AssetBlobOrientation,
|
||||
AssetBlobType,
|
||||
AssetBulkUpdate,
|
||||
AssetCheckout,
|
||||
AssetIconRule,
|
||||
AssetIdFormat,
|
||||
AssetPageResult,
|
||||
AssetRole,
|
||||
)
|
||||
from agravity_client.models.auth import (
|
||||
AgravityUser,
|
||||
AgravityUserOnlineStatus,
|
||||
AgravityVersion,
|
||||
SasToken,
|
||||
)
|
||||
from agravity_client.models.collection import (
|
||||
CollTypeItem,
|
||||
Collection,
|
||||
CollectionType,
|
||||
CollectionUDL,
|
||||
CollectionUDLListEntity,
|
||||
CollectionUDLReference,
|
||||
EntityListResult,
|
||||
EntityNamesRequest,
|
||||
MoveCollectionBody,
|
||||
PermissionEntity,
|
||||
PermissionRole,
|
||||
)
|
||||
from agravity_client.models.common import (
|
||||
AgravityErrorResponse,
|
||||
AgravityInfoResponse,
|
||||
DataResult,
|
||||
DeletedEntities,
|
||||
DictionaryObject,
|
||||
EntityId,
|
||||
EntityIdName,
|
||||
FrontendAppConfig,
|
||||
SignalRConnectionInfo,
|
||||
WhereParam,
|
||||
WhereParamOperator,
|
||||
WhereParamValueType,
|
||||
)
|
||||
from agravity_client.models.download import (
|
||||
DistZipResponse,
|
||||
DownloadFormat,
|
||||
DownloadZipRequest,
|
||||
DownloadZipStatus,
|
||||
DynamicImageOperation,
|
||||
SharedAllowedFormat,
|
||||
ZipType,
|
||||
)
|
||||
from agravity_client.models.portal import (
|
||||
Portal,
|
||||
PortalAuthentication,
|
||||
PortalAuthMethod,
|
||||
PortalConfiguration,
|
||||
PortalFields,
|
||||
PortalLinks,
|
||||
PortalTheme,
|
||||
PortalUserContext,
|
||||
)
|
||||
from agravity_client.models.publish import PublishedAsset, PublishEntity
|
||||
from agravity_client.models.relation import AssetRelation, AssetRelationType, RelatedAsset
|
||||
from agravity_client.models.search import (
|
||||
AllWebAppData,
|
||||
AzSearchOptions,
|
||||
GroupAllAppData,
|
||||
SavedSearch,
|
||||
SearchableItem,
|
||||
SearchAdminDataSourceStatus,
|
||||
SearchAdminIndexerLastRun,
|
||||
SearchAdminIndexerStatus,
|
||||
SearchAdminIndexStatus,
|
||||
SearchAdminSkillStatus,
|
||||
SearchAdminStatistics,
|
||||
SearchAdminStatus,
|
||||
SearchFacet,
|
||||
SearchFacetEntity,
|
||||
SearchResult,
|
||||
)
|
||||
from agravity_client.models.secure_upload import SecureUploadEntity
|
||||
from agravity_client.models.sharing import (
|
||||
QuickShareFull,
|
||||
SharedAsset,
|
||||
SharedCollectionFull,
|
||||
)
|
||||
from agravity_client.models.static_lists import StaticDefinedList
|
||||
from agravity_client.models.versioning import VersionedAsset, VersionEntity
|
||||
from agravity_client.models.workspace import Workspace
|
||||
|
||||
__all__ = [
|
||||
# asset
|
||||
"Asset", "AssetAvailability", "AssetBlob", "AssetBlobOrientation",
|
||||
"AssetBlobType", "AssetBulkUpdate", "AssetCheckout", "AssetIconRule",
|
||||
"AssetIdFormat", "AssetPageResult", "AssetRole",
|
||||
# auth
|
||||
"AgravityUser", "AgravityUserOnlineStatus", "AgravityVersion", "SasToken",
|
||||
# collection
|
||||
"CollTypeItem", "Collection", "CollectionType", "CollectionUDL",
|
||||
"CollectionUDLListEntity", "CollectionUDLReference", "EntityListResult",
|
||||
"EntityNamesRequest", "MoveCollectionBody", "PermissionEntity", "PermissionRole",
|
||||
# common
|
||||
"AgravityErrorResponse", "AgravityInfoResponse", "DataResult", "DeletedEntities",
|
||||
"DictionaryObject", "EntityId", "EntityIdName", "FrontendAppConfig",
|
||||
"SignalRConnectionInfo", "WhereParam", "WhereParamOperator", "WhereParamValueType",
|
||||
# download
|
||||
"DistZipResponse", "DownloadFormat", "DownloadZipRequest", "DownloadZipStatus",
|
||||
"DynamicImageOperation", "SharedAllowedFormat", "ZipType",
|
||||
# portal
|
||||
"Portal", "PortalAuthentication", "PortalAuthMethod", "PortalConfiguration",
|
||||
"PortalFields", "PortalLinks", "PortalTheme", "PortalUserContext",
|
||||
# publish
|
||||
"PublishedAsset", "PublishEntity",
|
||||
# relation
|
||||
"AssetRelation", "AssetRelationType", "RelatedAsset",
|
||||
# search
|
||||
"AllWebAppData", "AzSearchOptions", "GroupAllAppData", "SavedSearch",
|
||||
"SearchableItem", "SearchAdminDataSourceStatus", "SearchAdminIndexerLastRun",
|
||||
"SearchAdminIndexerStatus", "SearchAdminIndexStatus", "SearchAdminSkillStatus",
|
||||
"SearchAdminStatistics", "SearchAdminStatus", "SearchFacet", "SearchFacetEntity",
|
||||
"SearchResult",
|
||||
# secure upload
|
||||
"SecureUploadEntity",
|
||||
# sharing
|
||||
"QuickShareFull", "SharedAsset", "SharedCollectionFull",
|
||||
# static lists
|
||||
"StaticDefinedList",
|
||||
# versioning
|
||||
"VersionedAsset", "VersionEntity",
|
||||
# workspace
|
||||
"Workspace",
|
||||
]
|
||||
208
agravity_client/models/asset.py
Normal file
208
agravity_client/models/asset.py
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
"""Asset-related Pydantic models."""
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from agravity_client.models.common import DictionaryObject, WhereParam, _Base
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Enums
|
||||
# ---------------------------------------------------------------------------
|
||||
class AssetBlobType(str, Enum):
|
||||
unknown = "UNKNOWN"
|
||||
image = "IMAGE"
|
||||
video = "VIDEO"
|
||||
audio = "AUDIO"
|
||||
document = "DOCUMENT"
|
||||
text = "TEXT"
|
||||
other = "OTHER"
|
||||
|
||||
|
||||
class AssetBlobOrientation(str, Enum):
|
||||
portrait = "PORTRAIT"
|
||||
landscape = "LANDSCAPE"
|
||||
square = "SQUARE"
|
||||
|
||||
|
||||
class AssetRole(str, Enum):
|
||||
none = "NONE"
|
||||
viewer = "VIEWER"
|
||||
editor = "EDITOR"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AssetBlob
|
||||
# ---------------------------------------------------------------------------
|
||||
class AssetBlob(_Base):
|
||||
blob_type: Optional[AssetBlobType] = AssetBlobType.unknown
|
||||
name: Optional[str] = None
|
||||
container: Optional[str] = None
|
||||
size: Optional[int] = None
|
||||
extension: Optional[str] = None
|
||||
content_type: Optional[str] = None
|
||||
md5: Optional[str] = None
|
||||
add_data: Optional[dict[str, Any]] = None
|
||||
|
||||
# image metadata
|
||||
width: Optional[int] = None
|
||||
height: Optional[int] = None
|
||||
maxwidthheight: Optional[int] = None
|
||||
quality: Optional[float] = None
|
||||
orientation: Optional[AssetBlobOrientation] = AssetBlobOrientation.portrait
|
||||
colorspace: Optional[str] = None
|
||||
profile: Optional[str] = None
|
||||
transparency: Optional[bool] = None
|
||||
mode: Optional[str] = None
|
||||
target: Optional[str] = None
|
||||
filter: Optional[str] = None
|
||||
dpi_x: Optional[float] = None
|
||||
dpi_y: Optional[float] = None
|
||||
perhash: Optional[str] = None
|
||||
dominantcolor: Optional[str] = None
|
||||
depth: Optional[int] = None
|
||||
animated: Optional[bool] = None
|
||||
|
||||
# video metadata
|
||||
duration: Optional[int] = None
|
||||
videocodec: Optional[str] = None
|
||||
videobitrate: Optional[int] = None
|
||||
fps: Optional[float] = None
|
||||
colormode: Optional[str] = None
|
||||
|
||||
# audio metadata
|
||||
audiocodec: Optional[str] = None
|
||||
audiosamplerate: Optional[str] = None
|
||||
audiochanneloutput: Optional[str] = None
|
||||
audiobitrate: Optional[int] = None
|
||||
|
||||
# document metadata
|
||||
author: Optional[str] = None
|
||||
title: Optional[str] = None
|
||||
language: Optional[str] = None
|
||||
wordcount: Optional[int] = None
|
||||
pages: Optional[int] = None
|
||||
encoding_name: Optional[str] = None
|
||||
encoding_code: Optional[str] = None
|
||||
|
||||
# URL / download info
|
||||
url: Optional[str] = None
|
||||
size_readable: Optional[str] = None
|
||||
downloadable: Optional[bool] = None
|
||||
expires: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
uploaded_date: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
uploaded_by: Optional[str] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AssetCheckout
|
||||
# ---------------------------------------------------------------------------
|
||||
class AssetCheckout(_Base):
|
||||
user_id: Optional[str] = None
|
||||
checkout_date: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AssetIconRule
|
||||
# ---------------------------------------------------------------------------
|
||||
class AssetIconRule(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
path: Optional[str] = None
|
||||
color: Optional[str] = None
|
||||
value: Optional[str] = None
|
||||
icon: Optional[str] = None
|
||||
operator: Optional[str] = None
|
||||
translations: Optional[dict[str, DictionaryObject]] = None
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
add_properties: Optional[dict[str, Any]] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Asset (core entity)
|
||||
# ---------------------------------------------------------------------------
|
||||
class Asset(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
asset_type: Optional[str] = None
|
||||
duplicates: Optional[list[str]] = None
|
||||
keywords: Optional[list[str]] = None
|
||||
orig_blob: Optional[AssetBlob] = None
|
||||
blobs: Optional[list[AssetBlob]] = None
|
||||
collections: Optional[list[str]] = None
|
||||
failed_reason: Optional[str] = None
|
||||
quality_gate: Optional[list[str]] = None
|
||||
region_of_origin: Optional[str] = None
|
||||
availability: Optional[str] = None
|
||||
available_from: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
available_to: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
checkout: Optional[AssetCheckout] = None
|
||||
fs_synced: Optional[bool] = None
|
||||
custom: Optional[dict[str, Any]] = None
|
||||
items: Optional[list[Any]] = None # List[CollTypeItem] – to avoid circular import
|
||||
translations: Optional[dict[str, DictionaryObject]] = None
|
||||
role: Optional[AssetRole] = AssetRole.none
|
||||
description: Optional[str] = None
|
||||
add_properties: Optional[dict[str, Any]] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AssetAvailability
|
||||
# ---------------------------------------------------------------------------
|
||||
class AssetAvailability(_Base):
|
||||
availability: Optional[str] = None
|
||||
available_from: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
available_to: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AssetBulkUpdate
|
||||
# ---------------------------------------------------------------------------
|
||||
class AssetBulkUpdate(_Base):
|
||||
collection_id: Optional[str] = None
|
||||
ref_asset: Optional[Asset] = None
|
||||
asset_ids: Optional[list[str]] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AssetPageResult
|
||||
# ---------------------------------------------------------------------------
|
||||
class AssetPageResult(_Base):
|
||||
page: Optional[list[Asset]] = None
|
||||
page_size: Optional[int] = None
|
||||
size: Optional[int] = None
|
||||
continuation_token: Optional[str] = None
|
||||
filter: Optional[list[WhereParam]] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AssetIdFormat (used in QuickShareFull)
|
||||
# ---------------------------------------------------------------------------
|
||||
class AssetIdFormat(_Base):
|
||||
id: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
format: Optional[str] = None
|
||||
58
agravity_client/models/auth.py
Normal file
58
agravity_client/models/auth.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
"""Auth-related Pydantic models."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from agravity_client.models.common import _Base
|
||||
|
||||
|
||||
class AgravityUserOnlineStatus(_Base):
|
||||
status: Optional[str] = None
|
||||
last_connection: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
|
||||
|
||||
class AgravityUser(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
impersonation: Optional[str] = None
|
||||
apikey: Optional[str] = None
|
||||
online_status: Optional[AgravityUserOnlineStatus] = None
|
||||
roles: Optional[list[str]] = None
|
||||
groups: Optional[list[str]] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
class AgravityVersion(_Base):
|
||||
name: Optional[str] = None
|
||||
company: Optional[str] = None
|
||||
customer: Optional[str] = None
|
||||
contact: Optional[str] = None
|
||||
updated: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
client_id: Optional[str] = None
|
||||
tenant_id: Optional[str] = None
|
||||
subscription_id: Optional[str] = None
|
||||
base: Optional[str] = None
|
||||
version: Optional[str] = None
|
||||
enabled_features: Optional[list[str]] = None
|
||||
region: Optional[str] = None
|
||||
|
||||
|
||||
class SasToken(_Base):
|
||||
token: Optional[str] = None
|
||||
container: Optional[str] = None
|
||||
blob: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
fulltoken: Optional[str] = None
|
||||
expires: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
166
agravity_client/models/collection.py
Normal file
166
agravity_client/models/collection.py
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
"""Collection-related Pydantic models."""
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from agravity_client.models.common import DictionaryObject, _Base
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Enums
|
||||
# ---------------------------------------------------------------------------
|
||||
class PermissionRole(str, Enum):
|
||||
none = "NONE"
|
||||
viewer = "VIEWER"
|
||||
editor = "EDITOR"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PermissionEntity
|
||||
# ---------------------------------------------------------------------------
|
||||
class PermissionEntity(_Base):
|
||||
id: Optional[str] = None
|
||||
role: Optional[PermissionRole] = PermissionRole.none
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CollTypeItem
|
||||
# ---------------------------------------------------------------------------
|
||||
class CollTypeItem(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
item_type: Optional[str] = None
|
||||
format: Optional[str] = None
|
||||
label: Optional[str] = None
|
||||
default_value: Optional[Any] = None
|
||||
mandatory: Optional[bool] = None
|
||||
searchable: Optional[bool] = None
|
||||
onlyasset: Optional[bool] = None
|
||||
multi: Optional[bool] = None
|
||||
md5: Optional[str] = None
|
||||
group: Optional[str] = None
|
||||
order: Optional[int] = None
|
||||
translations: Optional[dict[str, DictionaryObject]] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Collection
|
||||
# ---------------------------------------------------------------------------
|
||||
class Collection(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
parent: Optional[str] = None
|
||||
path: Optional[str] = None
|
||||
level: Optional[int] = None
|
||||
custom: Optional[dict[str, Any]] = None
|
||||
items: Optional[list[CollTypeItem]] = None
|
||||
translations: Optional[dict[str, DictionaryObject]] = None
|
||||
role: Optional[PermissionRole] = PermissionRole.none
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
add_properties: Optional[dict[str, Any]] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CollectionType
|
||||
# ---------------------------------------------------------------------------
|
||||
class CollectionType(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
items: Optional[list[CollTypeItem]] = None
|
||||
translations: Optional[dict[str, DictionaryObject]] = None
|
||||
order: Optional[int] = None
|
||||
permissions: Optional[list[PermissionEntity]] = None
|
||||
permissionless: Optional[bool] = None
|
||||
role: Optional[PermissionRole] = PermissionRole.none
|
||||
description: Optional[str] = None
|
||||
add_properties: Optional[dict[str, Any]] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CollectionUDL (user-defined list)
|
||||
# ---------------------------------------------------------------------------
|
||||
class CollectionUDL(_Base):
|
||||
id: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
translations: Optional[dict[str, DictionaryObject]] = None
|
||||
children: Optional[list[Any]] = None # List[EntityIdName]
|
||||
|
||||
|
||||
class CollectionUDLReference(_Base):
|
||||
parent: Optional[str] = None
|
||||
coll_types: Optional[list[str]] = None
|
||||
permissions: Optional[list[PermissionEntity]] = None
|
||||
|
||||
|
||||
class CollectionUDLListEntity(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
udl_refs: Optional[list[CollectionUDLReference]] = None
|
||||
udl_entries: Optional[list[CollectionUDL]] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# EntityNamesRequest / EntityListResult
|
||||
# ---------------------------------------------------------------------------
|
||||
class EntityNamesRequest(_Base):
|
||||
names: Optional[list[str]] = None
|
||||
filter: Optional[str] = None
|
||||
|
||||
|
||||
class EntityListResult(_Base):
|
||||
entities: Optional[list[Any]] = None # List[EntityIdName]
|
||||
notfounds: Optional[list[str]] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# MoveCollectionBody
|
||||
# ---------------------------------------------------------------------------
|
||||
class MoveCollectionBody(_Base):
|
||||
from_collection_id: Optional[str] = None
|
||||
to_collection_id: Optional[str] = None
|
||||
operation: Optional[str] = None
|
||||
131
agravity_client/models/common.py
Normal file
131
agravity_client/models/common.py
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
"""Common / shared Pydantic models for the Agravity API."""
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Shared Config mixin: allow extra fields (the API may return undocumented ones)
|
||||
# ---------------------------------------------------------------------------
|
||||
class _Base(BaseModel):
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Error / Info responses
|
||||
# ---------------------------------------------------------------------------
|
||||
class AgravityErrorResponse(_Base):
|
||||
error_id: Optional[str] = None
|
||||
error_message: Optional[str] = None
|
||||
exception: Optional[str] = None
|
||||
|
||||
|
||||
class AgravityInfoResponse(_Base):
|
||||
info_id: Optional[str] = None
|
||||
info_message: Optional[str] = None
|
||||
info_object: Optional[Any] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dictionary helper
|
||||
# ---------------------------------------------------------------------------
|
||||
class DictionaryObject(_Base):
|
||||
"""A freeform dictionary of key -> any value (used for translations etc.)"""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return dict(self.model_extra or {})
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# EntityId / EntityIdName
|
||||
# ---------------------------------------------------------------------------
|
||||
class EntityId(_Base):
|
||||
id: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
|
||||
|
||||
class EntityIdName(_Base):
|
||||
id: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
translations: Optional[dict[str, DictionaryObject]] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Frontend config
|
||||
# ---------------------------------------------------------------------------
|
||||
class FrontendAppConfig(_Base):
|
||||
key: Optional[str] = None
|
||||
value: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
contentType: Optional[str] = None
|
||||
sinceApiVersion: Optional[str] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SignalR
|
||||
# ---------------------------------------------------------------------------
|
||||
class SignalRConnectionInfo(_Base):
|
||||
url: Optional[str] = None
|
||||
accessToken: Optional[str] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Deleted Entities
|
||||
# ---------------------------------------------------------------------------
|
||||
class DeletedEntities(_Base):
|
||||
id: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
deleted: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
entity_type: Optional[str] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DataResult (search sub-model)
|
||||
# ---------------------------------------------------------------------------
|
||||
class DataResult(_Base):
|
||||
asset: Optional[list[Any]] = None # List[Asset] – forward ref resolved later
|
||||
sum_asset_results: Optional[int] = None
|
||||
collection: Optional[list[Any]] = None # List[Collection]
|
||||
sum_collection_results: Optional[int] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# WhereParam (filter parameter in AssetPageResult)
|
||||
# ---------------------------------------------------------------------------
|
||||
class WhereParamOperator(str, Enum):
|
||||
equals = "Equals"
|
||||
not_equals = "NotEquals"
|
||||
greater_than = "GreaterThan"
|
||||
less_than = "LessThan"
|
||||
greater_than_or_equal = "GreaterThanOrEqual"
|
||||
less_than_or_equal = "LessThanOrEqual"
|
||||
contains = "Contains"
|
||||
starts_with = "StartsWith"
|
||||
array_contains = "ArrayContains"
|
||||
array_contains_partial = "ArrayContainsPartial"
|
||||
is_defined = "IsDefined"
|
||||
is_not_defined = "IsNotDefined"
|
||||
is_empty = "IsEmpty"
|
||||
is_not_empty = "IsNotEmpty"
|
||||
|
||||
|
||||
class WhereParamValueType(str, Enum):
|
||||
string = "String"
|
||||
bool = "Bool"
|
||||
number = "Number"
|
||||
|
||||
|
||||
class WhereParam(_Base):
|
||||
operator: Optional[WhereParamOperator] = WhereParamOperator.equals
|
||||
field: Optional[str] = None
|
||||
value: Optional[Any] = None
|
||||
notPrefix: Optional[bool] = None
|
||||
valueType: Optional[WhereParamValueType] = WhereParamValueType.string
|
||||
104
agravity_client/models/download.py
Normal file
104
agravity_client/models/download.py
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
"""Download-format and ZIP-related Pydantic models."""
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from agravity_client.models.common import DictionaryObject, _Base
|
||||
from agravity_client.models.collection import PermissionEntity
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Enums
|
||||
# ---------------------------------------------------------------------------
|
||||
class ZipType(str, Enum):
|
||||
download = "DOWNLOAD"
|
||||
shared = "SHARED"
|
||||
quickshare = "QUICKSHARE"
|
||||
portal = "PORTAL"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DynamicImageOperation
|
||||
# ---------------------------------------------------------------------------
|
||||
class DynamicImageOperation(_Base):
|
||||
operation: Optional[str] = None
|
||||
params: Optional[list[Any]] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SharedAllowedFormat
|
||||
# ---------------------------------------------------------------------------
|
||||
class SharedAllowedFormat(_Base):
|
||||
asset_type: Optional[str] = None
|
||||
format: Optional[str] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DownloadFormat
|
||||
# ---------------------------------------------------------------------------
|
||||
class DownloadFormat(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
operations: Optional[list[DynamicImageOperation]] = None
|
||||
extension: Optional[str] = None
|
||||
asset_type: Optional[str] = None
|
||||
origin: Optional[str] = None
|
||||
fallback_thumb: Optional[bool] = None
|
||||
target_filename: Optional[str] = None
|
||||
translations: Optional[dict[str, DictionaryObject]] = None
|
||||
permissions: Optional[list[PermissionEntity]] = None
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
add_properties: Optional[dict[str, Any]] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DownloadZipRequest
|
||||
# ---------------------------------------------------------------------------
|
||||
class DownloadZipRequest(_Base):
|
||||
id: Optional[str] = None
|
||||
zip_type: Optional[ZipType] = ZipType.download
|
||||
asset_ids: Optional[list[str]] = None
|
||||
allowed_formats: Optional[list[SharedAllowedFormat]] = None
|
||||
zipname: Optional[str] = None
|
||||
email_to: Optional[list[str]] = None
|
||||
message: Optional[str] = None
|
||||
valid_until: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DownloadZipStatus
|
||||
# ---------------------------------------------------------------------------
|
||||
class DownloadZipStatus(_Base):
|
||||
id: Optional[str] = None
|
||||
user: Optional[str] = None
|
||||
percent: Optional[float] = None
|
||||
part: Optional[float] = None
|
||||
count: Optional[int] = None
|
||||
message: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
zip_type: Optional[ZipType] = ZipType.download
|
||||
zipname: Optional[str] = None
|
||||
size: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DistZipResponse (used in GroupAllAppData)
|
||||
# ---------------------------------------------------------------------------
|
||||
class DistZipResponse(_Base):
|
||||
url: Optional[str] = None
|
||||
modified_date: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
size: Optional[int] = None
|
||||
175
agravity_client/models/portal.py
Normal file
175
agravity_client/models/portal.py
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
"""Portal-related Pydantic models."""
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from agravity_client.models.collection import CollTypeItem, PermissionEntity
|
||||
from agravity_client.models.common import DictionaryObject, FrontendAppConfig, _Base
|
||||
from agravity_client.models.download import SharedAllowedFormat
|
||||
from agravity_client.models.asset import AssetIconRule
|
||||
from agravity_client.models.static_lists import StaticDefinedList
|
||||
from agravity_client.models.collection import CollectionUDL
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Enums
|
||||
# ---------------------------------------------------------------------------
|
||||
class PortalAuthMethod(str, Enum):
|
||||
undefined = "UNDEFINED"
|
||||
none = "NONE"
|
||||
password = "PASSWORD"
|
||||
eeid = "EEID"
|
||||
auth0 = "AUTH0"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PortalAuthentication
|
||||
# ---------------------------------------------------------------------------
|
||||
class PortalAuthentication(_Base):
|
||||
method: Optional[PortalAuthMethod] = PortalAuthMethod.undefined
|
||||
issuer: Optional[str] = None
|
||||
client_id: Optional[str] = None
|
||||
tenant_id: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PortalUserContext
|
||||
# ---------------------------------------------------------------------------
|
||||
class PortalUserContext(_Base):
|
||||
key: Optional[str] = None
|
||||
mandatory: Optional[bool] = None
|
||||
mapping: Optional[dict[str, str]] = None
|
||||
options: Optional[list[str]] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PortalFields
|
||||
# ---------------------------------------------------------------------------
|
||||
class PortalFields(_Base):
|
||||
name: Optional[str] = None
|
||||
detail_order: Optional[int] = None
|
||||
facet_order: Optional[int] = None
|
||||
labels: Optional[dict[str, str]] = None
|
||||
user_context: Optional[PortalUserContext] = None
|
||||
format: Optional[str] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PortalLinks
|
||||
# ---------------------------------------------------------------------------
|
||||
class PortalLinks(_Base):
|
||||
conditions: Optional[str] = None
|
||||
privacy: Optional[str] = None
|
||||
impressum: Optional[str] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PortalTheme
|
||||
# ---------------------------------------------------------------------------
|
||||
class PortalTheme(_Base):
|
||||
logo_url: Optional[str] = None
|
||||
topbar_color: Optional[str] = None
|
||||
background_url: Optional[str] = None
|
||||
fav_icon: Optional[str] = None
|
||||
icon_empty: Optional[str] = None
|
||||
icon_active: Optional[str] = None
|
||||
colors: Optional[dict[str, Any]] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Portal (base entity)
|
||||
# ---------------------------------------------------------------------------
|
||||
class _PortalBase(_Base):
|
||||
"""Shared fields between Portal and PortalConfiguration."""
|
||||
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
authentication: Optional[PortalAuthentication] = None
|
||||
languages: Optional[str] = None
|
||||
fields: Optional[list[PortalFields]] = None
|
||||
filter: Optional[str] = None
|
||||
limit_ids: Optional[list[str]] = None
|
||||
allowed_formats: Optional[list[SharedAllowedFormat]] = None
|
||||
asset_icon_rules: Optional[list[AssetIconRule]] = None
|
||||
allowed_origins: Optional[list[str]] = None
|
||||
links: Optional[PortalLinks] = None
|
||||
theme: Optional[PortalTheme] = None
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
add_properties: Optional[dict[str, Any]] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
class Portal(_PortalBase):
|
||||
pass
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PortalConfiguration (extends Portal with full config data)
|
||||
# ---------------------------------------------------------------------------
|
||||
class PortalConfiguration(_PortalBase):
|
||||
download_formats: Optional[list[Any]] = None # List[DownloadFormat]
|
||||
sdls: Optional[list[StaticDefinedList]] = None
|
||||
udls: Optional[list[CollectionUDL]] = None
|
||||
items: Optional[list[CollTypeItem]] = None
|
||||
configs: Optional[list[FrontendAppConfig]] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Custom claims provider response models (token issuance / attribute collection)
|
||||
# ---------------------------------------------------------------------------
|
||||
class CustomClaimsProviderClaims(_Base):
|
||||
userContext: Optional[list[str]] = None
|
||||
role: Optional[str] = None
|
||||
|
||||
|
||||
class CustomClaimsProviderActionTokenIssuanceStart(_Base):
|
||||
claims: Optional[CustomClaimsProviderClaims] = None
|
||||
odata_type: Optional[str] = Field(None, alias="@odata.type")
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
class CustomClaimsProviderActionAttributeCollectionSubmit(_Base):
|
||||
odata_type: Optional[str] = Field(None, alias="@odata.type")
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
class CustomClaimsProviderDataTokenIssuanceStart(_Base):
|
||||
actions: Optional[list[CustomClaimsProviderActionTokenIssuanceStart]] = None
|
||||
odata_type: Optional[str] = Field(None, alias="@odata.type")
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
class CustomClaimsProviderDataAttributeCollectionSubmit(_Base):
|
||||
actions: Optional[list[CustomClaimsProviderActionAttributeCollectionSubmit]] = None
|
||||
odata_type: Optional[str] = Field(None, alias="@odata.type")
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
class CustomClaimsProviderResponseContentTokenIssuanceStart(_Base):
|
||||
data: Optional[CustomClaimsProviderDataTokenIssuanceStart] = None
|
||||
|
||||
|
||||
class CustomClaimsProviderResponseContentAttributeCollectionSubmit(_Base):
|
||||
data: Optional[CustomClaimsProviderDataAttributeCollectionSubmit] = None
|
||||
|
||||
|
||||
# Convenience alias used by the API client
|
||||
from typing import Union as _Union # noqa: E402
|
||||
|
||||
CustomClaimsProviderResponse = _Union[
|
||||
CustomClaimsProviderResponseContentTokenIssuanceStart,
|
||||
CustomClaimsProviderResponseContentAttributeCollectionSubmit,
|
||||
]
|
||||
39
agravity_client/models/publish.py
Normal file
39
agravity_client/models/publish.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
"""Publish-related Pydantic models."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from agravity_client.models.common import _Base
|
||||
|
||||
|
||||
class PublishedAsset(_Base):
|
||||
id: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
target: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
usecases: Optional[list[str]] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
cdn: Optional[str] = None
|
||||
status_table_id: Optional[str] = None
|
||||
format: Optional[str] = None
|
||||
properties: Optional[dict[str, Any]] = None
|
||||
|
||||
|
||||
class PublishEntity(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
published: Optional[list[PublishedAsset]] = None
|
||||
region_of_origin: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
51
agravity_client/models/relation.py
Normal file
51
agravity_client/models/relation.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
"""Asset relation Pydantic models."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from agravity_client.models.collection import PermissionEntity
|
||||
from agravity_client.models.common import DictionaryObject, _Base
|
||||
|
||||
|
||||
class RelatedAsset(_Base):
|
||||
id: Optional[str] = None
|
||||
parent: Optional[bool] = None
|
||||
|
||||
|
||||
class AssetRelation(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
assets: Optional[list[RelatedAsset]] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
class AssetRelationType(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
hierarchical: Optional[bool] = None
|
||||
sequential: Optional[bool] = None
|
||||
unique_per_asset: Optional[bool] = None
|
||||
translations: Optional[dict[str, DictionaryObject]] = None
|
||||
permissions: Optional[list[PermissionEntity]] = None
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
add_properties: Optional[dict[str, Any]] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
172
agravity_client/models/search.py
Normal file
172
agravity_client/models/search.py
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
"""Search-related Pydantic models (includes web-app data and saved searches)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from agravity_client.models.asset import Asset
|
||||
from agravity_client.models.collection import Collection, CollectionType
|
||||
from agravity_client.models.common import DictionaryObject, _Base
|
||||
from agravity_client.models.download import DistZipResponse
|
||||
from agravity_client.models.publish import PublishedAsset
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AzSearchOptions
|
||||
# ---------------------------------------------------------------------------
|
||||
class AzSearchOptions(_Base):
|
||||
searchString: Optional[str] = None
|
||||
limit: Optional[int] = None
|
||||
skip: Optional[int] = None
|
||||
collectiontypeid: Optional[str] = None
|
||||
collectionid: Optional[str] = None
|
||||
filter: Optional[str] = None
|
||||
orderby: Optional[str] = None
|
||||
mode: Optional[str] = None
|
||||
broadness: Optional[int] = None
|
||||
rel_id: Optional[str] = None
|
||||
ids: Optional[str] = None
|
||||
portal_id: Optional[str] = None
|
||||
scopefilter: Optional[str] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SearchFacet
|
||||
# ---------------------------------------------------------------------------
|
||||
class SearchFacetEntity(_Base):
|
||||
count: Optional[int] = None
|
||||
value: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class SearchFacet(_Base):
|
||||
name: Optional[str] = None
|
||||
entities: Optional[list[SearchFacetEntity]] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DataResult → SearchResult
|
||||
# ---------------------------------------------------------------------------
|
||||
class _DataResult(_Base):
|
||||
asset: Optional[list[Asset]] = None
|
||||
sum_asset_results: Optional[int] = None
|
||||
collection: Optional[list[Collection]] = None
|
||||
sum_collection_results: Optional[int] = None
|
||||
|
||||
|
||||
class SearchResult(_Base):
|
||||
data_result: Optional[_DataResult] = None
|
||||
options: Optional[AzSearchOptions] = None
|
||||
facets: Optional[list[SearchFacet]] = None
|
||||
count: Optional[int] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SearchableItem
|
||||
# ---------------------------------------------------------------------------
|
||||
class SearchableItem(_Base):
|
||||
name: Optional[str] = None
|
||||
is_key: Optional[bool] = None
|
||||
filterable: Optional[bool] = None
|
||||
hidden: Optional[bool] = None
|
||||
searchable: Optional[bool] = None
|
||||
facetable: Optional[bool] = None
|
||||
sortable: Optional[bool] = None
|
||||
is_collection: Optional[bool] = None
|
||||
searchtype: Optional[str] = None
|
||||
fields: Optional[list["SearchableItem"]] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SearchAdmin* models
|
||||
# ---------------------------------------------------------------------------
|
||||
class SearchAdminStatistics(_Base):
|
||||
documentcount: Optional[int] = None
|
||||
storagesizebytes: Optional[int] = None
|
||||
|
||||
|
||||
class SearchAdminIndexStatus(_Base):
|
||||
name: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
statistics: Optional[SearchAdminStatistics] = None
|
||||
|
||||
|
||||
class SearchAdminIndexerLastRun(_Base):
|
||||
status: Optional[str] = None
|
||||
starttime: Optional[str] = None
|
||||
endtime: Optional[str] = None
|
||||
itemcount: Optional[int] = None
|
||||
faileditemcount: Optional[int] = None
|
||||
|
||||
|
||||
class SearchAdminIndexerStatus(_Base):
|
||||
name: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
lastrun: Optional[SearchAdminIndexerLastRun] = None
|
||||
history: Optional[list[SearchAdminIndexerLastRun]] = None
|
||||
|
||||
|
||||
class SearchAdminDataSourceStatus(_Base):
|
||||
name: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
|
||||
|
||||
class SearchAdminSkillStatus(_Base):
|
||||
name: Optional[str] = None
|
||||
skills: Optional[list[str]] = None
|
||||
|
||||
|
||||
class SearchAdminStatus(_Base):
|
||||
index: Optional[SearchAdminIndexStatus] = None
|
||||
indexer: Optional[SearchAdminIndexerStatus] = None
|
||||
datasource: Optional[SearchAdminDataSourceStatus] = None
|
||||
skillsets: Optional[list[SearchAdminSkillStatus]] = None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SavedSearch
|
||||
# ---------------------------------------------------------------------------
|
||||
class SavedSearch(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
searchstring: Optional[str] = None
|
||||
external: Optional[bool] = None
|
||||
translations: Optional[dict[str, DictionaryObject]] = None
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
add_properties: Optional[dict[str, Any]] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Web App Data
|
||||
# ---------------------------------------------------------------------------
|
||||
class AllWebAppData(_Base):
|
||||
root_collection: Optional[Collection] = None
|
||||
subcollections: Optional[list[Collection]] = None
|
||||
assets: Optional[list[Asset]] = None
|
||||
pub_assets: Optional[list[PublishedAsset]] = None
|
||||
created_date: Optional[str] = None
|
||||
|
||||
|
||||
class KeyValuePairObject(_Base):
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
|
||||
class GroupAllAppData(_Base):
|
||||
collection_type: Optional[CollectionType] = None
|
||||
collections: Optional[list[Collection]] = None
|
||||
assets: Optional[list[Asset]] = None
|
||||
created_date: Optional[str] = None
|
||||
add_info: Optional[list[KeyValuePairObject]] = None
|
||||
dist: Optional[DistZipResponse] = None
|
||||
35
agravity_client/models/secure_upload.py
Normal file
35
agravity_client/models/secure_upload.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
"""Secure Upload Pydantic model."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from agravity_client.models.common import _Base
|
||||
|
||||
|
||||
class CreateSftpUserResult(_Base):
|
||||
url: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
|
||||
|
||||
class SecureUploadEntity(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
collection_id: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
valid_until: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
password: Optional[str] = None
|
||||
asset_tags: Optional[list[str]] = None
|
||||
message: Optional[str] = None
|
||||
sftp_connection: Optional[CreateSftpUserResult] = None
|
||||
check_name_for_version: Optional[bool] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
71
agravity_client/models/sharing.py
Normal file
71
agravity_client/models/sharing.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
"""Sharing-related Pydantic models (shared collections and quick shares)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from agravity_client.models.asset import AssetBlob, AssetIdFormat
|
||||
from agravity_client.models.common import EntityId, _Base
|
||||
from agravity_client.models.download import SharedAllowedFormat
|
||||
|
||||
|
||||
class SharedAsset(_Base):
|
||||
id: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
asset_type: Optional[str] = None
|
||||
orig_blob: Optional[AssetBlob] = None
|
||||
blobs: Optional[list[AssetBlob]] = None
|
||||
|
||||
|
||||
class SharedCollectionFull(_Base):
|
||||
page: Optional[list[SharedAsset]] = None
|
||||
page_size: Optional[int] = None
|
||||
size: Optional[int] = None
|
||||
continuation_token: Optional[str] = None
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
collection_id: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
valid_until: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
valid_for: Optional[str] = None
|
||||
message: Optional[str] = None
|
||||
global_: Optional[bool] = Field(None, alias="global")
|
||||
allowed_formats: Optional[list[SharedAllowedFormat]] = None
|
||||
password: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
|
||||
|
||||
class QuickShareFull(_Base):
|
||||
page: Optional[list[SharedAsset]] = None
|
||||
page_size: Optional[int] = None
|
||||
size: Optional[int] = None
|
||||
continuation_token: Optional[str] = None
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
assets: Optional[list[AssetIdFormat]] = None
|
||||
users: Optional[list[EntityId]] = None
|
||||
expires: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
url: Optional[str] = None
|
||||
zip_url: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
27
agravity_client/models/static_lists.py
Normal file
27
agravity_client/models/static_lists.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
"""Static Defined List Pydantic model."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from agravity_client.models.common import DictionaryObject, _Base
|
||||
|
||||
|
||||
class StaticDefinedList(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
translations: Optional[dict[str, DictionaryObject]] = None
|
||||
values: Optional[list[str]] = None
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
add_properties: Optional[dict[str, Any]] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
36
agravity_client/models/versioning.py
Normal file
36
agravity_client/models/versioning.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"""Asset versioning Pydantic models."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from agravity_client.models.asset import AssetBlob
|
||||
from agravity_client.models.common import _Base
|
||||
|
||||
|
||||
class VersionedAsset(_Base):
|
||||
version_nr: Optional[int] = None
|
||||
until_date: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
version_info: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
blob_data: Optional[AssetBlob] = None
|
||||
blob_uploaded: Optional[str] = Field(None, description="ISO 8601 date-time string")
|
||||
mime_type: Optional[str] = None
|
||||
|
||||
|
||||
class VersionEntity(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
versions: Optional[list[VersionedAsset]] = None
|
||||
region_of_origin: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
30
agravity_client/models/workspace.py
Normal file
30
agravity_client/models/workspace.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
"""Workspace Pydantic model."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
from pydantic import ConfigDict, Field
|
||||
|
||||
from agravity_client.models.collection import CollectionType, PermissionEntity
|
||||
from agravity_client.models.common import DictionaryObject, _Base
|
||||
|
||||
|
||||
class Workspace(_Base):
|
||||
id: Optional[str] = None
|
||||
entity_type: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
collection_types: Optional[list[CollectionType]] = None
|
||||
translations: Optional[dict[str, DictionaryObject]] = None
|
||||
order: Optional[int] = None
|
||||
permissions: Optional[list[PermissionEntity]] = None
|
||||
description: Optional[str] = None
|
||||
add_properties: Optional[dict[str, Any]] = None
|
||||
status: Optional[str] = None
|
||||
created_date: Optional[str] = None
|
||||
created_by: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_by: Optional[str] = None
|
||||
pk: Optional[str] = None
|
||||
etag: Optional[str] = Field(None, alias="_etag")
|
||||
|
||||
model_config = ConfigDict(extra="allow", populate_by_name=True)
|
||||
70
pyproject.toml
Normal file
70
pyproject.toml
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "agravity-client"
|
||||
version = "0.1.0"
|
||||
description = "Pythonic, Pydantic-driven async REST client for the Agravity DAM API (v10.3.0)."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
license = { text = "MIT" }
|
||||
keywords = ["agravity", "dam", "rest", "api", "client", "pydantic", "httpx"]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Software Development :: Libraries :: Python Modules",
|
||||
"Typing :: Typed",
|
||||
]
|
||||
dependencies = [
|
||||
"httpx>=0.27",
|
||||
"pydantic>=2.6",
|
||||
"pydantic-settings>=2.2",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=8.1",
|
||||
"pytest-asyncio>=0.23",
|
||||
"pytest-httpx>=0.30",
|
||||
"ruff>=0.4",
|
||||
"mypy>=1.10",
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["agravity_client"]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ruff
|
||||
# ---------------------------------------------------------------------------
|
||||
[tool.ruff]
|
||||
target-version = "py310"
|
||||
line-length = 100
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "I", "N", "UP", "ANN", "B", "C4", "T20"]
|
||||
ignore = [
|
||||
"ANN101", # missing type annotation for self
|
||||
"ANN102", # missing type annotation for cls
|
||||
"ANN401", # Dynamically typed expressions (Any) are disallowed
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# mypy
|
||||
# ---------------------------------------------------------------------------
|
||||
[tool.mypy]
|
||||
python_version = "3.10"
|
||||
strict = true
|
||||
ignore_missing_imports = true
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# pytest
|
||||
# ---------------------------------------------------------------------------
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
testpaths = ["tests"]
|
||||
8501
swagger.json
Normal file
8501
swagger.json
Normal file
File diff suppressed because one or more lines are too long
1
tests/__init__.py
Normal file
1
tests/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# tests package
|
||||
31
tests/conftest.py
Normal file
31
tests/conftest.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
"""Shared pytest fixtures for the agravity-client test suite."""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
import httpx
|
||||
|
||||
from agravity_client.client import AgravityClient
|
||||
from agravity_client.config import AgravityConfig
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def config() -> AgravityConfig:
|
||||
"""Return a test AgravityConfig with a dummy API key."""
|
||||
return AgravityConfig(
|
||||
api_key="test-api-key",
|
||||
base_url="https://api.example.local/api",
|
||||
)
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def client(config: AgravityConfig) -> AgravityClient:
|
||||
"""Return an AgravityClient backed by a real (but not-yet-connected) transport.
|
||||
|
||||
Tests that need to mock HTTP responses should use ``pytest-httpx``'s
|
||||
``httpx_mock`` fixture and pass a transport via::
|
||||
|
||||
client._http = httpx.AsyncClient(transport=httpx_mock.get_async_handler())
|
||||
"""
|
||||
async with AgravityClient(config) as c:
|
||||
yield c
|
||||
72
tests/test_models.py
Normal file
72
tests/test_models.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
"""Basic smoke tests for Pydantic model validation."""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from agravity_client.models import (
|
||||
Asset,
|
||||
AssetBlob,
|
||||
AssetBlobType,
|
||||
AssetPageResult,
|
||||
Collection,
|
||||
AgravityVersion,
|
||||
SearchResult,
|
||||
AzSearchOptions,
|
||||
)
|
||||
|
||||
|
||||
class TestAssetModel:
|
||||
def test_minimal_asset(self):
|
||||
asset = Asset.model_validate({"id": "abc123"})
|
||||
assert asset.id == "abc123"
|
||||
|
||||
def test_asset_with_blob_type_enum(self):
|
||||
asset = Asset.model_validate({
|
||||
"id": "abc",
|
||||
"asset_type": "IMAGE",
|
||||
})
|
||||
assert asset.asset_type == "IMAGE"
|
||||
|
||||
def test_asset_page_result(self):
|
||||
result = AssetPageResult.model_validate({
|
||||
"assets": [{"id": "a1"}, {"id": "a2"}],
|
||||
"size": 2,
|
||||
})
|
||||
assert len(result.assets) == 2
|
||||
assert result.assets[0].id == "a1"
|
||||
|
||||
def test_extra_fields_allowed(self):
|
||||
"""Unknown API fields must not raise ValidationError."""
|
||||
asset = Asset.model_validate({"id": "x", "future_field": "value"})
|
||||
assert asset.id == "x"
|
||||
|
||||
|
||||
class TestCollectionModel:
|
||||
def test_minimal_collection(self):
|
||||
coll = Collection.model_validate({"id": "c1", "name": "Root"})
|
||||
assert coll.id == "c1"
|
||||
assert coll.name == "Root"
|
||||
|
||||
|
||||
class TestVersionModel:
|
||||
def test_agravity_version(self):
|
||||
v = AgravityVersion.model_validate({
|
||||
"name": "Agravity",
|
||||
"version": "10.3.0",
|
||||
})
|
||||
assert v.version == "10.3.0"
|
||||
|
||||
|
||||
class TestSearchModels:
|
||||
def test_search_options_defaults(self):
|
||||
opts = AzSearchOptions()
|
||||
assert opts.limit is None
|
||||
|
||||
def test_search_options_with_values(self):
|
||||
opts = AzSearchOptions(searchterm="hello", limit=5)
|
||||
dumped = opts.model_dump(exclude_none=True)
|
||||
assert dumped["searchterm"] == "hello"
|
||||
assert dumped["limit"] == 5
|
||||
|
||||
def test_search_result_minimal(self):
|
||||
result = SearchResult.model_validate({})
|
||||
assert result.count is None
|
||||
Loading…
Add table
Add a link
Reference in a new issue