synapse-sdk 1.0.0a11__py3-none-any.whl → 2026.1.1b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of synapse-sdk might be problematic. Click here for more details.
- synapse_sdk/__init__.py +24 -0
- synapse_sdk/cli/__init__.py +9 -8
- synapse_sdk/cli/agent/__init__.py +25 -0
- synapse_sdk/cli/agent/config.py +104 -0
- synapse_sdk/cli/agent/select.py +197 -0
- synapse_sdk/cli/auth.py +104 -0
- synapse_sdk/cli/main.py +1025 -0
- synapse_sdk/cli/plugin/__init__.py +58 -0
- synapse_sdk/cli/plugin/create.py +566 -0
- synapse_sdk/cli/plugin/job.py +196 -0
- synapse_sdk/cli/plugin/publish.py +322 -0
- synapse_sdk/cli/plugin/run.py +131 -0
- synapse_sdk/cli/plugin/test.py +200 -0
- synapse_sdk/clients/README.md +239 -0
- synapse_sdk/clients/__init__.py +5 -0
- synapse_sdk/clients/_template.py +266 -0
- synapse_sdk/clients/agent/__init__.py +84 -29
- synapse_sdk/clients/agent/async_ray.py +289 -0
- synapse_sdk/clients/agent/container.py +83 -0
- synapse_sdk/clients/agent/plugin.py +101 -0
- synapse_sdk/clients/agent/ray.py +296 -39
- synapse_sdk/clients/backend/__init__.py +152 -12
- synapse_sdk/clients/backend/annotation.py +164 -22
- synapse_sdk/clients/backend/core.py +101 -0
- synapse_sdk/clients/backend/data_collection.py +292 -0
- synapse_sdk/clients/backend/hitl.py +87 -0
- synapse_sdk/clients/backend/integration.py +374 -46
- synapse_sdk/clients/backend/ml.py +134 -22
- synapse_sdk/clients/backend/models.py +247 -0
- synapse_sdk/clients/base.py +538 -59
- synapse_sdk/clients/exceptions.py +35 -7
- synapse_sdk/clients/pipeline/__init__.py +5 -0
- synapse_sdk/clients/pipeline/client.py +636 -0
- synapse_sdk/clients/protocols.py +178 -0
- synapse_sdk/clients/utils.py +86 -8
- synapse_sdk/clients/validation.py +58 -0
- synapse_sdk/enums.py +76 -0
- synapse_sdk/exceptions.py +168 -0
- synapse_sdk/integrations/__init__.py +74 -0
- synapse_sdk/integrations/_base.py +119 -0
- synapse_sdk/integrations/_context.py +53 -0
- synapse_sdk/integrations/ultralytics/__init__.py +78 -0
- synapse_sdk/integrations/ultralytics/_callbacks.py +126 -0
- synapse_sdk/integrations/ultralytics/_patches.py +124 -0
- synapse_sdk/loggers.py +476 -95
- synapse_sdk/mcp/MCP.md +69 -0
- synapse_sdk/mcp/__init__.py +48 -0
- synapse_sdk/mcp/__main__.py +6 -0
- synapse_sdk/mcp/config.py +349 -0
- synapse_sdk/mcp/prompts/__init__.py +4 -0
- synapse_sdk/mcp/resources/__init__.py +4 -0
- synapse_sdk/mcp/server.py +1352 -0
- synapse_sdk/mcp/tools/__init__.py +6 -0
- synapse_sdk/plugins/__init__.py +133 -9
- synapse_sdk/plugins/action.py +229 -0
- synapse_sdk/plugins/actions/__init__.py +82 -0
- synapse_sdk/plugins/actions/dataset/__init__.py +37 -0
- synapse_sdk/plugins/actions/dataset/action.py +471 -0
- synapse_sdk/plugins/actions/export/__init__.py +55 -0
- synapse_sdk/plugins/actions/export/action.py +183 -0
- synapse_sdk/plugins/actions/export/context.py +59 -0
- synapse_sdk/plugins/actions/inference/__init__.py +84 -0
- synapse_sdk/plugins/actions/inference/action.py +285 -0
- synapse_sdk/plugins/actions/inference/context.py +81 -0
- synapse_sdk/plugins/actions/inference/deployment.py +322 -0
- synapse_sdk/plugins/actions/inference/serve.py +252 -0
- synapse_sdk/plugins/actions/train/__init__.py +54 -0
- synapse_sdk/plugins/actions/train/action.py +326 -0
- synapse_sdk/plugins/actions/train/context.py +57 -0
- synapse_sdk/plugins/actions/upload/__init__.py +49 -0
- synapse_sdk/plugins/actions/upload/action.py +165 -0
- synapse_sdk/plugins/actions/upload/context.py +61 -0
- synapse_sdk/plugins/config.py +98 -0
- synapse_sdk/plugins/context/__init__.py +109 -0
- synapse_sdk/plugins/context/env.py +113 -0
- synapse_sdk/plugins/datasets/__init__.py +113 -0
- synapse_sdk/plugins/datasets/converters/__init__.py +76 -0
- synapse_sdk/plugins/datasets/converters/base.py +347 -0
- synapse_sdk/plugins/datasets/converters/yolo/__init__.py +9 -0
- synapse_sdk/plugins/datasets/converters/yolo/from_dm.py +468 -0
- synapse_sdk/plugins/datasets/converters/yolo/to_dm.py +381 -0
- synapse_sdk/plugins/datasets/formats/__init__.py +82 -0
- synapse_sdk/plugins/datasets/formats/dm.py +351 -0
- synapse_sdk/plugins/datasets/formats/yolo.py +240 -0
- synapse_sdk/plugins/decorators.py +83 -0
- synapse_sdk/plugins/discovery.py +790 -0
- synapse_sdk/plugins/docs/ACTION_DEV_GUIDE.md +933 -0
- synapse_sdk/plugins/docs/ARCHITECTURE.md +1225 -0
- synapse_sdk/plugins/docs/LOGGING_SYSTEM.md +683 -0
- synapse_sdk/plugins/docs/OVERVIEW.md +531 -0
- synapse_sdk/plugins/docs/PIPELINE_GUIDE.md +145 -0
- synapse_sdk/plugins/docs/README.md +513 -0
- synapse_sdk/plugins/docs/STEP.md +656 -0
- synapse_sdk/plugins/enums.py +70 -10
- synapse_sdk/plugins/errors.py +92 -0
- synapse_sdk/plugins/executors/__init__.py +43 -0
- synapse_sdk/plugins/executors/local.py +99 -0
- synapse_sdk/plugins/executors/ray/__init__.py +18 -0
- synapse_sdk/plugins/executors/ray/base.py +282 -0
- synapse_sdk/plugins/executors/ray/job.py +298 -0
- synapse_sdk/plugins/executors/ray/jobs_api.py +511 -0
- synapse_sdk/plugins/executors/ray/packaging.py +137 -0
- synapse_sdk/plugins/executors/ray/pipeline.py +792 -0
- synapse_sdk/plugins/executors/ray/task.py +257 -0
- synapse_sdk/plugins/models/__init__.py +26 -0
- synapse_sdk/plugins/models/logger.py +173 -0
- synapse_sdk/plugins/models/pipeline.py +25 -0
- synapse_sdk/plugins/pipelines/__init__.py +81 -0
- synapse_sdk/plugins/pipelines/action_pipeline.py +417 -0
- synapse_sdk/plugins/pipelines/context.py +107 -0
- synapse_sdk/plugins/pipelines/display.py +311 -0
- synapse_sdk/plugins/runner.py +114 -0
- synapse_sdk/plugins/schemas/__init__.py +19 -0
- synapse_sdk/plugins/schemas/results.py +152 -0
- synapse_sdk/plugins/steps/__init__.py +63 -0
- synapse_sdk/plugins/steps/base.py +128 -0
- synapse_sdk/plugins/steps/context.py +90 -0
- synapse_sdk/plugins/steps/orchestrator.py +128 -0
- synapse_sdk/plugins/steps/registry.py +103 -0
- synapse_sdk/plugins/steps/utils/__init__.py +20 -0
- synapse_sdk/plugins/steps/utils/logging.py +85 -0
- synapse_sdk/plugins/steps/utils/timing.py +71 -0
- synapse_sdk/plugins/steps/utils/validation.py +68 -0
- synapse_sdk/plugins/templates/__init__.py +50 -0
- synapse_sdk/plugins/templates/base/.gitignore.j2 +26 -0
- synapse_sdk/plugins/templates/base/.synapseignore.j2 +11 -0
- synapse_sdk/plugins/templates/base/README.md.j2 +26 -0
- synapse_sdk/plugins/templates/base/plugin/__init__.py.j2 +1 -0
- synapse_sdk/plugins/templates/base/pyproject.toml.j2 +14 -0
- synapse_sdk/plugins/templates/base/requirements.txt.j2 +1 -0
- synapse_sdk/plugins/templates/custom/plugin/main.py.j2 +18 -0
- synapse_sdk/plugins/templates/data_validation/plugin/validate.py.j2 +32 -0
- synapse_sdk/plugins/templates/export/plugin/export.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/inference.py.j2 +36 -0
- synapse_sdk/plugins/templates/neural_net/plugin/train.py.j2 +33 -0
- synapse_sdk/plugins/templates/post_annotation/plugin/post_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/pre_annotation/plugin/pre_annotate.py.j2 +32 -0
- synapse_sdk/plugins/templates/smart_tool/plugin/auto_label.py.j2 +44 -0
- synapse_sdk/plugins/templates/upload/plugin/upload.py.j2 +35 -0
- synapse_sdk/plugins/testing/__init__.py +25 -0
- synapse_sdk/plugins/testing/sample_actions.py +98 -0
- synapse_sdk/plugins/types.py +206 -0
- synapse_sdk/plugins/upload.py +595 -64
- synapse_sdk/plugins/utils.py +325 -37
- synapse_sdk/shared/__init__.py +25 -0
- synapse_sdk/utils/__init__.py +1 -0
- synapse_sdk/utils/auth.py +74 -0
- synapse_sdk/utils/file/__init__.py +58 -0
- synapse_sdk/utils/file/archive.py +449 -0
- synapse_sdk/utils/file/checksum.py +167 -0
- synapse_sdk/utils/file/download.py +286 -0
- synapse_sdk/utils/file/io.py +129 -0
- synapse_sdk/utils/file/requirements.py +36 -0
- synapse_sdk/utils/network.py +168 -0
- synapse_sdk/utils/storage/__init__.py +238 -0
- synapse_sdk/utils/storage/config.py +188 -0
- synapse_sdk/utils/storage/errors.py +52 -0
- synapse_sdk/utils/storage/providers/__init__.py +13 -0
- synapse_sdk/utils/storage/providers/base.py +76 -0
- synapse_sdk/utils/storage/providers/gcs.py +168 -0
- synapse_sdk/utils/storage/providers/http.py +250 -0
- synapse_sdk/utils/storage/providers/local.py +126 -0
- synapse_sdk/utils/storage/providers/s3.py +177 -0
- synapse_sdk/utils/storage/providers/sftp.py +208 -0
- synapse_sdk/utils/storage/registry.py +125 -0
- synapse_sdk/utils/websocket.py +99 -0
- synapse_sdk-2026.1.1b2.dist-info/METADATA +715 -0
- synapse_sdk-2026.1.1b2.dist-info/RECORD +172 -0
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/WHEEL +1 -1
- synapse_sdk-2026.1.1b2.dist-info/licenses/LICENSE +201 -0
- locale/en/LC_MESSAGES/messages.mo +0 -0
- locale/en/LC_MESSAGES/messages.po +0 -39
- locale/ko/LC_MESSAGES/messages.mo +0 -0
- locale/ko/LC_MESSAGES/messages.po +0 -34
- synapse_sdk/cli/create_plugin.py +0 -10
- synapse_sdk/clients/agent/core.py +0 -7
- synapse_sdk/clients/agent/service.py +0 -15
- synapse_sdk/clients/backend/dataset.py +0 -51
- synapse_sdk/clients/ray/__init__.py +0 -6
- synapse_sdk/clients/ray/core.py +0 -22
- synapse_sdk/clients/ray/serve.py +0 -20
- synapse_sdk/i18n.py +0 -35
- synapse_sdk/plugins/categories/__init__.py +0 -0
- synapse_sdk/plugins/categories/base.py +0 -235
- synapse_sdk/plugins/categories/data_validation/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/actions/validation.py +0 -10
- synapse_sdk/plugins/categories/data_validation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/data_validation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/data_validation/templates/plugin/validation.py +0 -5
- synapse_sdk/plugins/categories/decorators.py +0 -13
- synapse_sdk/plugins/categories/export/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/export/actions/export.py +0 -10
- synapse_sdk/plugins/categories/import/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/import/actions/import.py +0 -10
- synapse_sdk/plugins/categories/neural_net/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/actions/deployment.py +0 -45
- synapse_sdk/plugins/categories/neural_net/actions/inference.py +0 -18
- synapse_sdk/plugins/categories/neural_net/actions/test.py +0 -10
- synapse_sdk/plugins/categories/neural_net/actions/train.py +0 -143
- synapse_sdk/plugins/categories/neural_net/templates/config.yaml +0 -12
- synapse_sdk/plugins/categories/neural_net/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/neural_net/templates/plugin/inference.py +0 -4
- synapse_sdk/plugins/categories/neural_net/templates/plugin/test.py +0 -2
- synapse_sdk/plugins/categories/neural_net/templates/plugin/train.py +0 -14
- synapse_sdk/plugins/categories/post_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/actions/post_annotation.py +0 -10
- synapse_sdk/plugins/categories/post_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/post_annotation/templates/plugin/post_annotation.py +0 -3
- synapse_sdk/plugins/categories/pre_annotation/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/actions/pre_annotation.py +0 -10
- synapse_sdk/plugins/categories/pre_annotation/templates/config.yaml +0 -3
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/pre_annotation/templates/plugin/pre_annotation.py +0 -3
- synapse_sdk/plugins/categories/registry.py +0 -16
- synapse_sdk/plugins/categories/smart_tool/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/actions/auto_label.py +0 -37
- synapse_sdk/plugins/categories/smart_tool/templates/config.yaml +0 -7
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/__init__.py +0 -0
- synapse_sdk/plugins/categories/smart_tool/templates/plugin/auto_label.py +0 -11
- synapse_sdk/plugins/categories/templates.py +0 -32
- synapse_sdk/plugins/cli/__init__.py +0 -21
- synapse_sdk/plugins/cli/publish.py +0 -37
- synapse_sdk/plugins/cli/run.py +0 -67
- synapse_sdk/plugins/exceptions.py +0 -22
- synapse_sdk/plugins/models.py +0 -121
- synapse_sdk/plugins/templates/cookiecutter.json +0 -11
- synapse_sdk/plugins/templates/hooks/post_gen_project.py +0 -3
- synapse_sdk/plugins/templates/hooks/pre_prompt.py +0 -21
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.env.dist +0 -24
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.gitignore +0 -27
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/.pre-commit-config.yaml +0 -7
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/README.md +0 -5
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/config.yaml +0 -6
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/main.py +0 -4
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/plugin/__init__.py +0 -0
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/pyproject.toml +0 -13
- synapse_sdk/plugins/templates/synapse-{{cookiecutter.plugin_code}}-plugin/requirements.txt +0 -1
- synapse_sdk/shared/enums.py +0 -8
- synapse_sdk/utils/debug.py +0 -5
- synapse_sdk/utils/file.py +0 -87
- synapse_sdk/utils/module_loading.py +0 -29
- synapse_sdk/utils/pydantic/__init__.py +0 -0
- synapse_sdk/utils/pydantic/config.py +0 -4
- synapse_sdk/utils/pydantic/errors.py +0 -33
- synapse_sdk/utils/pydantic/validators.py +0 -7
- synapse_sdk/utils/storage.py +0 -91
- synapse_sdk/utils/string.py +0 -11
- synapse_sdk-1.0.0a11.dist-info/LICENSE +0 -21
- synapse_sdk-1.0.0a11.dist-info/METADATA +0 -43
- synapse_sdk-1.0.0a11.dist-info/RECORD +0 -111
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/entry_points.txt +0 -0
- {synapse_sdk-1.0.0a11.dist-info → synapse_sdk-2026.1.1b2.dist-info}/top_level.txt +0 -0
synapse_sdk/clients/base.py
CHANGED
|
@@ -1,93 +1,572 @@
|
|
|
1
|
+
"""Base HTTP client classes for sync and async operations.
|
|
2
|
+
|
|
3
|
+
This module provides BaseClient (sync) and AsyncBaseClient (async) classes
|
|
4
|
+
that serve as the foundation for all API clients in the SDK.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
1
9
|
import json
|
|
2
|
-
import
|
|
10
|
+
from contextlib import ExitStack
|
|
3
11
|
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
4
13
|
|
|
14
|
+
import httpx
|
|
5
15
|
import requests
|
|
16
|
+
from requests.adapters import HTTPAdapter
|
|
17
|
+
from urllib3.util.retry import Retry
|
|
6
18
|
|
|
7
|
-
from synapse_sdk.clients.
|
|
19
|
+
from synapse_sdk.clients.utils import build_url, extract_error_detail, parse_json_response
|
|
20
|
+
from synapse_sdk.clients.validation import ValidationMixin
|
|
21
|
+
from synapse_sdk.exceptions import (
|
|
22
|
+
ClientConnectionError,
|
|
23
|
+
ClientError,
|
|
24
|
+
ClientTimeoutError,
|
|
25
|
+
ServerError,
|
|
26
|
+
raise_for_status,
|
|
27
|
+
)
|
|
8
28
|
from synapse_sdk.utils.file import files_url_to_path_from_objs
|
|
9
29
|
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from pydantic import BaseModel
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BaseClient(ValidationMixin):
|
|
35
|
+
"""Synchronous HTTP client base using requests.
|
|
36
|
+
|
|
37
|
+
This class provides a foundation for building API clients with
|
|
38
|
+
session management, retry logic, and request/response handling.
|
|
10
39
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
40
|
+
Attributes:
|
|
41
|
+
name: Client name for error messages.
|
|
42
|
+
page_size: Default page size for paginated requests.
|
|
43
|
+
"""
|
|
14
44
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
requests_session = requests.Session()
|
|
18
|
-
self.requests_session = requests_session
|
|
45
|
+
name: str | None = None
|
|
46
|
+
page_size: int = 100
|
|
19
47
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
return os.path.join(self.base_url, path)
|
|
23
|
-
return path
|
|
48
|
+
def __init__(self, base_url: str, timeout: dict[str, int] | None = None):
|
|
49
|
+
"""Initialize the base client.
|
|
24
50
|
|
|
25
|
-
|
|
51
|
+
Args:
|
|
52
|
+
base_url: The base URL for all API requests.
|
|
53
|
+
timeout: Optional timeout configuration with 'connect' and 'read' keys.
|
|
54
|
+
"""
|
|
55
|
+
self.base_url = base_url.rstrip('/')
|
|
56
|
+
self.timeout = timeout or {
|
|
57
|
+
'connect': 5,
|
|
58
|
+
'read': 15,
|
|
59
|
+
}
|
|
60
|
+
self._session: requests.Session | None = None
|
|
61
|
+
self._retry_config = {
|
|
62
|
+
'total': 3,
|
|
63
|
+
'backoff_factor': 1,
|
|
64
|
+
'status_forcelist': [502, 503, 504],
|
|
65
|
+
'allowed_methods': ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
def _create_session(self) -> requests.Session:
|
|
69
|
+
"""Create a new requests session with retry strategy."""
|
|
70
|
+
session = requests.Session()
|
|
71
|
+
retry_strategy = Retry(**self._retry_config)
|
|
72
|
+
adapter = HTTPAdapter(max_retries=retry_strategy)
|
|
73
|
+
session.mount('http://', adapter)
|
|
74
|
+
session.mount('https://', adapter)
|
|
75
|
+
return session
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def requests_session(self) -> requests.Session:
|
|
79
|
+
"""Get or create the requests session."""
|
|
80
|
+
if self._session is None:
|
|
81
|
+
self._session = self._create_session()
|
|
82
|
+
return self._session
|
|
83
|
+
|
|
84
|
+
def _get_headers(self) -> dict[str, str]:
|
|
85
|
+
"""Return headers for requests. Override in subclasses."""
|
|
26
86
|
return {}
|
|
27
87
|
|
|
28
|
-
def _request(self, method, path, **kwargs):
|
|
29
|
-
|
|
88
|
+
def _request(self, method: str, path: str, **kwargs) -> dict | str | None:
|
|
89
|
+
"""Request handler for all HTTP methods.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
method: HTTP method (get, post, put, patch, delete).
|
|
93
|
+
path: URL path to request.
|
|
94
|
+
**kwargs: Additional arguments passed to requests.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Parsed response data.
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ClientTimeoutError: If the request times out.
|
|
101
|
+
ClientConnectionError: If connection fails.
|
|
102
|
+
HTTPError subclasses: For HTTP error responses.
|
|
103
|
+
"""
|
|
104
|
+
url = build_url(self.base_url, path)
|
|
30
105
|
headers = self._get_headers()
|
|
106
|
+
headers.update(kwargs.pop('headers', {}))
|
|
107
|
+
|
|
108
|
+
if 'timeout' not in kwargs:
|
|
109
|
+
kwargs['timeout'] = (self.timeout['connect'], self.timeout['read'])
|
|
110
|
+
|
|
111
|
+
with ExitStack() as stack:
|
|
112
|
+
if method in ('post', 'put', 'patch'):
|
|
113
|
+
self._prepare_request_body(kwargs, headers, stack)
|
|
31
114
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
115
|
+
try:
|
|
116
|
+
response = getattr(self.requests_session, method)(url, headers=headers, **kwargs)
|
|
117
|
+
if not response.ok:
|
|
118
|
+
raise_for_status(response.status_code, extract_error_detail(response))
|
|
119
|
+
except (
|
|
120
|
+
ClientError,
|
|
121
|
+
ClientTimeoutError,
|
|
122
|
+
ClientConnectionError,
|
|
123
|
+
):
|
|
124
|
+
raise
|
|
125
|
+
except requests.exceptions.ConnectTimeout:
|
|
126
|
+
raise ClientTimeoutError(f'{self.name} connection timeout (>{self.timeout["connect"]}s)')
|
|
127
|
+
except requests.exceptions.ReadTimeout:
|
|
128
|
+
raise ClientTimeoutError(f'{self.name} read timeout (>{self.timeout["read"]}s)')
|
|
129
|
+
except requests.exceptions.ConnectionError as e:
|
|
130
|
+
error_str = str(e)
|
|
131
|
+
if 'Name or service not known' in error_str or 'nodename nor servname provided' in error_str:
|
|
132
|
+
raise ClientConnectionError(f'{self.name} host unreachable')
|
|
133
|
+
elif 'Connection refused' in error_str:
|
|
134
|
+
raise ClientConnectionError(f'{self.name} connection refused')
|
|
135
|
+
else:
|
|
136
|
+
raise ClientConnectionError(f'{self.name} connection error: {error_str[:100]}')
|
|
137
|
+
except requests.exceptions.RequestException as e:
|
|
138
|
+
raise ServerError(500, f'{self.name} request failed: {str(e)[:100]}')
|
|
139
|
+
|
|
140
|
+
return parse_json_response(response)
|
|
141
|
+
|
|
142
|
+
def _prepare_request_body(self, kwargs: dict, headers: dict, stack: ExitStack) -> None:
|
|
143
|
+
"""Prepare request body, handling files and JSON serialization."""
|
|
144
|
+
if kwargs.get('files') is not None:
|
|
145
|
+
for name, file in kwargs['files'].items():
|
|
146
|
+
if isinstance(file, (str, Path)):
|
|
147
|
+
file = Path(file)
|
|
148
|
+
opened_file = stack.enter_context(file.open(mode='rb'))
|
|
149
|
+
kwargs['files'][name] = (file.name, opened_file)
|
|
150
|
+
if 'data' in kwargs:
|
|
36
151
|
for name, value in kwargs['data'].items():
|
|
37
152
|
if isinstance(value, dict):
|
|
38
153
|
kwargs['data'][name] = json.dumps(value)
|
|
154
|
+
else:
|
|
155
|
+
headers['Content-Type'] = 'application/json'
|
|
156
|
+
if 'data' in kwargs:
|
|
157
|
+
kwargs['data'] = json.dumps(kwargs['data'])
|
|
158
|
+
|
|
159
|
+
def _get(
|
|
160
|
+
self,
|
|
161
|
+
path: str,
|
|
162
|
+
url_conversion: dict | None = None,
|
|
163
|
+
response_model: type[BaseModel] | None = None,
|
|
164
|
+
**kwargs,
|
|
165
|
+
) -> Any:
|
|
166
|
+
"""Perform a GET request.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
path: URL path to request.
|
|
170
|
+
url_conversion: Optional URL conversion config for file paths.
|
|
171
|
+
response_model: Optional Pydantic model for response validation.
|
|
172
|
+
**kwargs: Additional arguments passed to requests.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Parsed and optionally validated response data.
|
|
176
|
+
"""
|
|
177
|
+
response = self._request('get', path, **kwargs)
|
|
178
|
+
|
|
179
|
+
if url_conversion and isinstance(response, dict):
|
|
180
|
+
is_list = url_conversion.get('is_list', False)
|
|
181
|
+
if is_list:
|
|
182
|
+
files_url_to_path_from_objs(response['results'], **url_conversion, is_async=True)
|
|
39
183
|
else:
|
|
40
|
-
|
|
41
|
-
if 'data' in kwargs:
|
|
42
|
-
kwargs['data'] = json.dumps(kwargs['data'])
|
|
184
|
+
files_url_to_path_from_objs(response, **url_conversion)
|
|
43
185
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
186
|
+
if response_model:
|
|
187
|
+
return self._validate_response(response, response_model)
|
|
188
|
+
return response
|
|
189
|
+
|
|
190
|
+
def _mutate(
|
|
191
|
+
self,
|
|
192
|
+
method: str,
|
|
193
|
+
path: str,
|
|
194
|
+
request_model: type[BaseModel] | None = None,
|
|
195
|
+
response_model: type[BaseModel] | None = None,
|
|
196
|
+
**kwargs,
|
|
197
|
+
) -> Any:
|
|
198
|
+
"""Perform a mutating request (POST, PUT, PATCH, DELETE).
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
method: HTTP method.
|
|
202
|
+
path: URL path to request.
|
|
203
|
+
request_model: Optional Pydantic model for request validation.
|
|
204
|
+
response_model: Optional Pydantic model for response validation.
|
|
205
|
+
**kwargs: Additional arguments passed to requests.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Parsed and optionally validated response data.
|
|
209
|
+
"""
|
|
210
|
+
if kwargs.get('data') and request_model:
|
|
211
|
+
kwargs['data'] = self._validate_request(kwargs['data'], request_model)
|
|
212
|
+
|
|
213
|
+
response = self._request(method, path, **kwargs)
|
|
214
|
+
|
|
215
|
+
if response_model:
|
|
216
|
+
return self._validate_response(response, response_model)
|
|
217
|
+
return response
|
|
218
|
+
|
|
219
|
+
def _post(
|
|
220
|
+
self,
|
|
221
|
+
path: str,
|
|
222
|
+
request_model: type[BaseModel] | None = None,
|
|
223
|
+
response_model: type[BaseModel] | None = None,
|
|
224
|
+
**kwargs,
|
|
225
|
+
) -> Any:
|
|
226
|
+
"""Perform a POST request."""
|
|
227
|
+
return self._mutate('post', path, request_model, response_model, **kwargs)
|
|
228
|
+
|
|
229
|
+
def _put(
|
|
230
|
+
self,
|
|
231
|
+
path: str,
|
|
232
|
+
request_model: type[BaseModel] | None = None,
|
|
233
|
+
response_model: type[BaseModel] | None = None,
|
|
234
|
+
**kwargs,
|
|
235
|
+
) -> Any:
|
|
236
|
+
"""Perform a PUT request."""
|
|
237
|
+
return self._mutate('put', path, request_model, response_model, **kwargs)
|
|
238
|
+
|
|
239
|
+
def _patch(
|
|
240
|
+
self,
|
|
241
|
+
path: str,
|
|
242
|
+
request_model: type[BaseModel] | None = None,
|
|
243
|
+
response_model: type[BaseModel] | None = None,
|
|
244
|
+
**kwargs,
|
|
245
|
+
) -> Any:
|
|
246
|
+
"""Perform a PATCH request."""
|
|
247
|
+
return self._mutate('patch', path, request_model, response_model, **kwargs)
|
|
248
|
+
|
|
249
|
+
def _delete(
|
|
250
|
+
self,
|
|
251
|
+
path: str,
|
|
252
|
+
request_model: type[BaseModel] | None = None,
|
|
253
|
+
response_model: type[BaseModel] | None = None,
|
|
254
|
+
**kwargs,
|
|
255
|
+
) -> Any:
|
|
256
|
+
"""Perform a DELETE request."""
|
|
257
|
+
return self._mutate('delete', path, request_model, response_model, **kwargs)
|
|
258
|
+
|
|
259
|
+
def _list(
|
|
260
|
+
self,
|
|
261
|
+
path: str,
|
|
262
|
+
url_conversion: dict | None = None,
|
|
263
|
+
list_all: bool = False,
|
|
264
|
+
params: dict | None = None,
|
|
265
|
+
**kwargs,
|
|
266
|
+
) -> dict | tuple[Any, int]:
|
|
267
|
+
"""List resources from a paginated API endpoint.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
path: URL path to request.
|
|
271
|
+
url_conversion: Optional URL conversion config for file paths.
|
|
272
|
+
list_all: If True, return a generator for all pages.
|
|
273
|
+
params: Optional query parameters.
|
|
274
|
+
**kwargs: Additional arguments passed to requests.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Response dict, or tuple of (generator, count) if list_all=True.
|
|
278
|
+
"""
|
|
279
|
+
if params is None:
|
|
280
|
+
params = {}
|
|
281
|
+
|
|
282
|
+
if list_all:
|
|
283
|
+
response = self._get(path, params=params, **kwargs)
|
|
284
|
+
return self._list_all(path, url_conversion, params=params, **kwargs), response.get('count')
|
|
285
|
+
return self._get(path, params=params, **kwargs)
|
|
286
|
+
|
|
287
|
+
def _list_all(self, path: str, url_conversion: dict | None = None, params: dict | None = None, **kwargs):
|
|
288
|
+
"""Generator yielding all results from a paginated endpoint."""
|
|
289
|
+
if params is None:
|
|
290
|
+
params = {}
|
|
291
|
+
|
|
292
|
+
request_params = params.copy()
|
|
293
|
+
if 'page_size' not in request_params:
|
|
294
|
+
request_params['page_size'] = self.page_size
|
|
295
|
+
|
|
296
|
+
next_url = path
|
|
297
|
+
is_first_request = True
|
|
298
|
+
|
|
299
|
+
while next_url:
|
|
300
|
+
if is_first_request:
|
|
301
|
+
response = self._get(next_url, url_conversion, params=request_params, **kwargs)
|
|
302
|
+
is_first_request = False
|
|
303
|
+
else:
|
|
304
|
+
response = self._get(next_url, url_conversion, **kwargs)
|
|
305
|
+
|
|
306
|
+
yield from response['results']
|
|
307
|
+
next_url = response.get('next')
|
|
308
|
+
|
|
309
|
+
def exists(self, api: str, *args, **kwargs) -> bool:
|
|
310
|
+
"""Check if any results exist for the given API method."""
|
|
311
|
+
return getattr(self, api)(*args, **kwargs)['count'] > 0
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class AsyncBaseClient(ValidationMixin):
|
|
315
|
+
"""Asynchronous HTTP client base using httpx.
|
|
316
|
+
|
|
317
|
+
This class provides a foundation for building async API clients with
|
|
318
|
+
connection management and request/response handling.
|
|
319
|
+
|
|
320
|
+
Attributes:
|
|
321
|
+
name: Client name for error messages.
|
|
322
|
+
page_size: Default page size for paginated requests.
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
name: str | None = None
|
|
326
|
+
page_size: int = 100
|
|
327
|
+
|
|
328
|
+
def __init__(
|
|
329
|
+
self,
|
|
330
|
+
base_url: str,
|
|
331
|
+
timeout: float | httpx.Timeout | None = None,
|
|
332
|
+
):
|
|
333
|
+
"""Initialize the async base client.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
base_url: The base URL for all API requests.
|
|
337
|
+
timeout: Optional timeout configuration.
|
|
338
|
+
"""
|
|
339
|
+
self.base_url = base_url.rstrip('/')
|
|
340
|
+
self.timeout = timeout if timeout is not None else httpx.Timeout(15.0, connect=5.0)
|
|
341
|
+
self._client: httpx.AsyncClient | None = None
|
|
52
342
|
|
|
53
|
-
|
|
343
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
344
|
+
"""Get or create the async HTTP client."""
|
|
345
|
+
if self._client is None or self._client.is_closed:
|
|
346
|
+
self._client = httpx.AsyncClient(
|
|
347
|
+
base_url=self.base_url,
|
|
348
|
+
timeout=self.timeout,
|
|
349
|
+
)
|
|
350
|
+
return self._client
|
|
351
|
+
|
|
352
|
+
async def close(self) -> None:
|
|
353
|
+
"""Close the HTTP client."""
|
|
354
|
+
if self._client and not self._client.is_closed:
|
|
355
|
+
await self._client.aclose()
|
|
356
|
+
self._client = None
|
|
357
|
+
|
|
358
|
+
async def __aenter__(self) -> AsyncBaseClient:
|
|
359
|
+
return self
|
|
360
|
+
|
|
361
|
+
async def __aexit__(self, *args) -> None:
|
|
362
|
+
await self.close()
|
|
363
|
+
|
|
364
|
+
def _get_headers(self) -> dict[str, str]:
|
|
365
|
+
"""Return headers for requests. Override in subclasses."""
|
|
366
|
+
return {}
|
|
367
|
+
|
|
368
|
+
async def _request(self, method: str, path: str, **kwargs) -> dict | str | None:
|
|
369
|
+
"""Request handler for all HTTP methods.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
method: HTTP method (GET, POST, PUT, PATCH, DELETE).
|
|
373
|
+
path: URL path to request.
|
|
374
|
+
**kwargs: Additional arguments passed to httpx.
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
Parsed response data.
|
|
378
|
+
|
|
379
|
+
Raises:
|
|
380
|
+
ClientTimeoutError: If the request times out.
|
|
381
|
+
ClientConnectionError: If connection fails.
|
|
382
|
+
HTTPError subclasses: For HTTP error responses.
|
|
383
|
+
"""
|
|
384
|
+
client = await self._get_client()
|
|
385
|
+
# Handle full URLs (e.g., pagination next links) vs relative paths
|
|
386
|
+
if path.startswith(('http://', 'https://')):
|
|
387
|
+
url = path
|
|
388
|
+
else:
|
|
389
|
+
url = path.lstrip('/')
|
|
390
|
+
headers = self._get_headers()
|
|
391
|
+
headers.update(kwargs.pop('headers', {}))
|
|
54
392
|
|
|
55
|
-
def _post_response(self, response):
|
|
56
393
|
try:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
394
|
+
response = await client.request(method, url, headers=headers, **kwargs)
|
|
395
|
+
if not response.is_success:
|
|
396
|
+
raise_for_status(response.status_code, extract_error_detail(response))
|
|
397
|
+
except (
|
|
398
|
+
ClientError,
|
|
399
|
+
ClientTimeoutError,
|
|
400
|
+
ClientConnectionError,
|
|
401
|
+
):
|
|
402
|
+
raise
|
|
403
|
+
except httpx.ConnectTimeout:
|
|
404
|
+
raise ClientTimeoutError(f'{self.name} connection timeout')
|
|
405
|
+
except httpx.ReadTimeout:
|
|
406
|
+
raise ClientTimeoutError(f'{self.name} read timeout')
|
|
407
|
+
except httpx.ConnectError as e:
|
|
408
|
+
raise ClientConnectionError(f'{self.name} connection error: {str(e)[:100]}')
|
|
409
|
+
except httpx.HTTPStatusError as e:
|
|
410
|
+
raise_for_status(e.response.status_code, extract_error_detail(e.response))
|
|
411
|
+
raise # unreachable, but helps type checker
|
|
412
|
+
except httpx.HTTPError as e:
|
|
413
|
+
raise ServerError(500, f'{self.name} request failed: {str(e)[:100]}')
|
|
60
414
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
415
|
+
return parse_json_response(response)
|
|
416
|
+
|
|
417
|
+
async def _get(
|
|
418
|
+
self,
|
|
419
|
+
path: str,
|
|
420
|
+
url_conversion: dict | None = None,
|
|
421
|
+
response_model: type[BaseModel] | None = None,
|
|
422
|
+
**kwargs,
|
|
423
|
+
) -> Any:
|
|
424
|
+
"""Perform a GET request.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
path: URL path to request.
|
|
428
|
+
url_conversion: Optional URL conversion config for file paths.
|
|
429
|
+
response_model: Optional Pydantic model for response validation.
|
|
430
|
+
**kwargs: Additional arguments passed to httpx.
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
Parsed and optionally validated response data.
|
|
434
|
+
"""
|
|
435
|
+
response = await self._request('GET', path, **kwargs)
|
|
436
|
+
|
|
437
|
+
if url_conversion and isinstance(response, dict):
|
|
438
|
+
is_list = url_conversion.get('is_list', False)
|
|
439
|
+
if is_list:
|
|
440
|
+
files_url_to_path_from_objs(response['results'], **url_conversion, is_async=True)
|
|
66
441
|
else:
|
|
67
442
|
files_url_to_path_from_objs(response, **url_conversion)
|
|
443
|
+
|
|
444
|
+
if response_model:
|
|
445
|
+
return self._validate_response(response, response_model)
|
|
446
|
+
return response
|
|
447
|
+
|
|
448
|
+
async def _mutate(
|
|
449
|
+
self,
|
|
450
|
+
method: str,
|
|
451
|
+
path: str,
|
|
452
|
+
request_model: type[BaseModel] | None = None,
|
|
453
|
+
response_model: type[BaseModel] | None = None,
|
|
454
|
+
**kwargs,
|
|
455
|
+
) -> Any:
|
|
456
|
+
"""Perform a mutating request (POST, PUT, PATCH, DELETE).
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
method: HTTP method.
|
|
460
|
+
path: URL path to request.
|
|
461
|
+
request_model: Optional Pydantic model for request validation.
|
|
462
|
+
response_model: Optional Pydantic model for response validation.
|
|
463
|
+
**kwargs: Additional arguments passed to httpx.
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
Parsed and optionally validated response data.
|
|
467
|
+
"""
|
|
468
|
+
if kwargs.get('json') and request_model:
|
|
469
|
+
kwargs['json'] = self._validate_request(kwargs['json'], request_model)
|
|
470
|
+
|
|
471
|
+
response = await self._request(method, path, **kwargs)
|
|
472
|
+
|
|
473
|
+
if response_model:
|
|
474
|
+
return self._validate_response(response, response_model)
|
|
68
475
|
return response
|
|
69
476
|
|
|
70
|
-
def _post(
|
|
71
|
-
|
|
477
|
+
async def _post(
|
|
478
|
+
self,
|
|
479
|
+
path: str,
|
|
480
|
+
request_model: type[BaseModel] | None = None,
|
|
481
|
+
response_model: type[BaseModel] | None = None,
|
|
482
|
+
**kwargs,
|
|
483
|
+
) -> Any:
|
|
484
|
+
"""Perform a POST request."""
|
|
485
|
+
return await self._mutate('POST', path, request_model, response_model, **kwargs)
|
|
486
|
+
|
|
487
|
+
async def _put(
|
|
488
|
+
self,
|
|
489
|
+
path: str,
|
|
490
|
+
request_model: type[BaseModel] | None = None,
|
|
491
|
+
response_model: type[BaseModel] | None = None,
|
|
492
|
+
**kwargs,
|
|
493
|
+
) -> Any:
|
|
494
|
+
"""Perform a PUT request."""
|
|
495
|
+
return await self._mutate('PUT', path, request_model, response_model, **kwargs)
|
|
496
|
+
|
|
497
|
+
async def _patch(
|
|
498
|
+
self,
|
|
499
|
+
path: str,
|
|
500
|
+
request_model: type[BaseModel] | None = None,
|
|
501
|
+
response_model: type[BaseModel] | None = None,
|
|
502
|
+
**kwargs,
|
|
503
|
+
) -> Any:
|
|
504
|
+
"""Perform a PATCH request."""
|
|
505
|
+
return await self._mutate('PATCH', path, request_model, response_model, **kwargs)
|
|
72
506
|
|
|
73
|
-
def
|
|
74
|
-
|
|
507
|
+
async def _delete(
|
|
508
|
+
self,
|
|
509
|
+
path: str,
|
|
510
|
+
request_model: type[BaseModel] | None = None,
|
|
511
|
+
response_model: type[BaseModel] | None = None,
|
|
512
|
+
**kwargs,
|
|
513
|
+
) -> Any:
|
|
514
|
+
"""Perform a DELETE request."""
|
|
515
|
+
return await self._mutate('DELETE', path, request_model, response_model, **kwargs)
|
|
75
516
|
|
|
76
|
-
def
|
|
77
|
-
|
|
517
|
+
async def _list(
|
|
518
|
+
self,
|
|
519
|
+
path: str,
|
|
520
|
+
url_conversion: dict | None = None,
|
|
521
|
+
list_all: bool = False,
|
|
522
|
+
params: dict | None = None,
|
|
523
|
+
**kwargs,
|
|
524
|
+
) -> dict | tuple[Any, int]:
|
|
525
|
+
"""List resources from a paginated API endpoint.
|
|
526
|
+
|
|
527
|
+
Args:
|
|
528
|
+
path: URL path to request.
|
|
529
|
+
url_conversion: Optional URL conversion config for file paths.
|
|
530
|
+
list_all: If True, return a generator for all pages.
|
|
531
|
+
params: Optional query parameters.
|
|
532
|
+
**kwargs: Additional arguments passed to httpx.
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
Response dict, or tuple of (generator, count) if list_all=True.
|
|
536
|
+
"""
|
|
537
|
+
if params is None:
|
|
538
|
+
params = {}
|
|
78
539
|
|
|
79
|
-
def _list(self, path, url_conversion=None, list_all=False, **kwargs):
|
|
80
|
-
response = self._get(path, url_conversion, **kwargs)
|
|
81
540
|
if list_all:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
541
|
+
response = await self._get(path, params=params, **kwargs)
|
|
542
|
+
return self._list_all(path, url_conversion, params=params, **kwargs), response.get('count')
|
|
543
|
+
return await self._get(path, params=params, **kwargs)
|
|
85
544
|
|
|
86
|
-
def _list_all(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
545
|
+
async def _list_all(
|
|
546
|
+
self,
|
|
547
|
+
path: str,
|
|
548
|
+
url_conversion: dict | None = None,
|
|
549
|
+
params: dict | None = None,
|
|
550
|
+
**kwargs,
|
|
551
|
+
):
|
|
552
|
+
"""Async generator yielding all results from a paginated endpoint."""
|
|
553
|
+
if params is None:
|
|
554
|
+
params = {}
|
|
91
555
|
|
|
92
|
-
|
|
93
|
-
|
|
556
|
+
request_params = params.copy()
|
|
557
|
+
if 'page_size' not in request_params:
|
|
558
|
+
request_params['page_size'] = self.page_size
|
|
559
|
+
|
|
560
|
+
next_url: str | None = path
|
|
561
|
+
is_first_request = True
|
|
562
|
+
|
|
563
|
+
while next_url:
|
|
564
|
+
if is_first_request:
|
|
565
|
+
response = await self._get(next_url, url_conversion, params=request_params, **kwargs)
|
|
566
|
+
is_first_request = False
|
|
567
|
+
else:
|
|
568
|
+
response = await self._get(next_url, url_conversion, **kwargs)
|
|
569
|
+
|
|
570
|
+
for item in response['results']:
|
|
571
|
+
yield item
|
|
572
|
+
next_url = response.get('next')
|
|
@@ -1,8 +1,36 @@
|
|
|
1
|
-
|
|
2
|
-
status = None
|
|
3
|
-
reason = None
|
|
1
|
+
from __future__ import annotations
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
# Re-export from the shared exceptions module for backwards compatibility
|
|
4
|
+
from synapse_sdk.exceptions import (
|
|
5
|
+
AuthenticationError,
|
|
6
|
+
AuthorizationError,
|
|
7
|
+
ClientConnectionError,
|
|
8
|
+
ClientError,
|
|
9
|
+
ClientTimeoutError,
|
|
10
|
+
HTTPError,
|
|
11
|
+
NotFoundError,
|
|
12
|
+
RateLimitError,
|
|
13
|
+
ServerError,
|
|
14
|
+
StreamError,
|
|
15
|
+
StreamLimitExceededError,
|
|
16
|
+
ValidationError,
|
|
17
|
+
WebSocketError,
|
|
18
|
+
raise_for_status,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
'ClientError',
|
|
23
|
+
'ClientConnectionError',
|
|
24
|
+
'ClientTimeoutError',
|
|
25
|
+
'HTTPError',
|
|
26
|
+
'AuthenticationError',
|
|
27
|
+
'AuthorizationError',
|
|
28
|
+
'NotFoundError',
|
|
29
|
+
'ValidationError',
|
|
30
|
+
'RateLimitError',
|
|
31
|
+
'ServerError',
|
|
32
|
+
'StreamError',
|
|
33
|
+
'StreamLimitExceededError',
|
|
34
|
+
'WebSocketError',
|
|
35
|
+
'raise_for_status',
|
|
36
|
+
]
|