sammler-sdk 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sammler_sdk-1.0.0/PKG-INFO +50 -0
- sammler_sdk-1.0.0/README.md +41 -0
- sammler_sdk-1.0.0/pyproject.toml +21 -0
- sammler_sdk-1.0.0/sammler/__init__.py +15 -0
- sammler_sdk-1.0.0/sammler/client.py +61 -0
- sammler_sdk-1.0.0/sammler/graphql/__init__.py +42 -0
- sammler_sdk-1.0.0/sammler/graphql/async_base_client.py +395 -0
- sammler_sdk-1.0.0/sammler/graphql/base_model.py +30 -0
- sammler_sdk-1.0.0/sammler/graphql/client.py +128 -0
- sammler_sdk-1.0.0/sammler/graphql/enums.py +3 -0
- sammler_sdk-1.0.0/sammler/graphql/exceptions.py +85 -0
- sammler_sdk-1.0.0/sammler/graphql/get_collection.py +41 -0
- sammler_sdk-1.0.0/sammler/graphql/get_collections.py +25 -0
- sammler_sdk-1.0.0/sammler/graphql/get_item.py +38 -0
- sammler_sdk-1.0.0/sammler/graphql/get_items.py +30 -0
- sammler_sdk-1.0.0/sammler/graphql/input_types.py +3 -0
- sammler_sdk-1.0.0/sammler_sdk.egg-info/PKG-INFO +50 -0
- sammler_sdk-1.0.0/sammler_sdk.egg-info/SOURCES.txt +20 -0
- sammler_sdk-1.0.0/sammler_sdk.egg-info/dependency_links.txt +1 -0
- sammler_sdk-1.0.0/sammler_sdk.egg-info/requires.txt +2 -0
- sammler_sdk-1.0.0/sammler_sdk.egg-info/top_level.txt +1 -0
- sammler_sdk-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sammler-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python SDK for interacting with Sammler GraphQL API
|
|
5
|
+
Requires-Python: >=3.8
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: httpx>=0.24.0
|
|
8
|
+
Requires-Dist: pydantic>=2.0
|
|
9
|
+
|
|
10
|
+
# Sammler Python SDK
|
|
11
|
+
|
|
12
|
+
Python client for interacting with the Sammler GraphQL API.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install .
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Code Generation
|
|
21
|
+
|
|
22
|
+
To regenerate the client using `ariadne-codegen`:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Set up a virtual environment
|
|
26
|
+
python3 -m venv .venv
|
|
27
|
+
source .venv/bin/activate
|
|
28
|
+
|
|
29
|
+
# Install tools
|
|
30
|
+
pip install ariadne-codegen[http]
|
|
31
|
+
|
|
32
|
+
# Generate client
|
|
33
|
+
ariadne-codegen
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import asyncio
|
|
40
|
+
from sammler import SammlerClient
|
|
41
|
+
|
|
42
|
+
async def main():
|
|
43
|
+
async with SammlerClient(api_key="your_api_key") as client:
|
|
44
|
+
collections = await client.get_collections()
|
|
45
|
+
for col in collections:
|
|
46
|
+
print(col.name)
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
asyncio.run(main())
|
|
50
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Sammler Python SDK
|
|
2
|
+
|
|
3
|
+
Python client for interacting with the Sammler GraphQL API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install .
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Code Generation
|
|
12
|
+
|
|
13
|
+
To regenerate the client using `ariadne-codegen`:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Set up a virtual environment
|
|
17
|
+
python3 -m venv .venv
|
|
18
|
+
source .venv/bin/activate
|
|
19
|
+
|
|
20
|
+
# Install tools
|
|
21
|
+
pip install ariadne-codegen[http]
|
|
22
|
+
|
|
23
|
+
# Generate client
|
|
24
|
+
ariadne-codegen
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import asyncio
|
|
31
|
+
from sammler import SammlerClient
|
|
32
|
+
|
|
33
|
+
async def main():
|
|
34
|
+
async with SammlerClient(api_key="your_api_key") as client:
|
|
35
|
+
collections = await client.get_collections()
|
|
36
|
+
for col in collections:
|
|
37
|
+
print(col.name)
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
asyncio.run(main())
|
|
41
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sammler-sdk"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Python SDK for interacting with Sammler GraphQL API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"httpx>=0.24.0",
|
|
13
|
+
"pydantic>=2.0"
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[tool.ariadne-codegen]
|
|
17
|
+
remote_schema_url = "https://sammler-api-3f60d.web.app"
|
|
18
|
+
queries_path = "../graphql/operations.graphql"
|
|
19
|
+
target_package_name = "graphql"
|
|
20
|
+
target_package_path = "sammler"
|
|
21
|
+
client_type = "async"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .client import SammlerClient
|
|
2
|
+
from .graphql.get_collections import GetCollectionsCollections
|
|
3
|
+
from .graphql.get_collection import GetCollectionCollection, GetCollectionCollectionItems
|
|
4
|
+
from .graphql.get_items import GetItemsItems
|
|
5
|
+
from .graphql.get_item import GetItemItem, GetItemItemCollection
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"SammlerClient",
|
|
9
|
+
"GetCollectionsCollections",
|
|
10
|
+
"GetCollectionCollection",
|
|
11
|
+
"GetCollectionCollectionItems",
|
|
12
|
+
"GetItemsItems",
|
|
13
|
+
"GetItemItem",
|
|
14
|
+
"GetItemItemCollection",
|
|
15
|
+
]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
from typing import List, Optional, Any
|
|
3
|
+
from .graphql.client import Client
|
|
4
|
+
from .graphql.get_collections import GetCollectionsCollections
|
|
5
|
+
from .graphql.get_collection import GetCollectionCollection
|
|
6
|
+
from .graphql.get_items import GetItemsItems
|
|
7
|
+
from .graphql.get_item import GetItemItem
|
|
8
|
+
|
|
9
|
+
class SammlerClient:
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
api_key: str,
|
|
13
|
+
endpoint: str = "https://sammler-api-3f60d.web.app",
|
|
14
|
+
headers: Optional[dict] = None,
|
|
15
|
+
):
|
|
16
|
+
self.endpoint = endpoint
|
|
17
|
+
req_headers = {
|
|
18
|
+
"x-api-key": api_key,
|
|
19
|
+
**(headers or {})
|
|
20
|
+
}
|
|
21
|
+
self.http_client = httpx.AsyncClient(headers=req_headers)
|
|
22
|
+
self.client = Client(url=self.endpoint, http_client=self.http_client)
|
|
23
|
+
|
|
24
|
+
async def __aenter__(self):
|
|
25
|
+
return self
|
|
26
|
+
|
|
27
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
28
|
+
await self.close()
|
|
29
|
+
|
|
30
|
+
async def close(self):
|
|
31
|
+
await self.http_client.aclose()
|
|
32
|
+
|
|
33
|
+
async def get_collections(self) -> List[GetCollectionsCollections]:
|
|
34
|
+
"""Retrieve all collections belonging to the authenticated developer profile."""
|
|
35
|
+
response = await self.client.get_collections()
|
|
36
|
+
return response.collections
|
|
37
|
+
|
|
38
|
+
async def get_collection(self, id: str) -> Optional[GetCollectionCollection]:
|
|
39
|
+
"""Retrieve a single collection by its ID, including all items inside it."""
|
|
40
|
+
response = await self.client.get_collection(id=id)
|
|
41
|
+
return response.collection
|
|
42
|
+
|
|
43
|
+
async def get_items(self) -> List[GetItemsItems]:
|
|
44
|
+
"""Retrieve all collection items belonging to the authenticated developer profile."""
|
|
45
|
+
response = await self.client.get_items()
|
|
46
|
+
return response.items
|
|
47
|
+
|
|
48
|
+
async def get_item(self, id: str) -> Optional[GetItemItem]:
|
|
49
|
+
"""Retrieve a single collection item by its ID, including its associated collection."""
|
|
50
|
+
response = await self.client.get_item(id=id)
|
|
51
|
+
return response.item
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def raw_client(self) -> Client:
|
|
55
|
+
"""Exposes the raw underlying generated SDK client methods."""
|
|
56
|
+
return self.client
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def httpx_client(self) -> httpx.AsyncClient:
|
|
60
|
+
"""Exposes the underlying httpx.AsyncClient instance."""
|
|
61
|
+
return self.http_client
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Generated by ariadne-codegen
|
|
2
|
+
|
|
3
|
+
from .async_base_client import AsyncBaseClient
|
|
4
|
+
from .base_model import BaseModel, Upload
|
|
5
|
+
from .client import Client
|
|
6
|
+
from .exceptions import (
|
|
7
|
+
GraphQLClientError,
|
|
8
|
+
GraphQLClientGraphQLError,
|
|
9
|
+
GraphQLClientGraphQLMultiError,
|
|
10
|
+
GraphQLClientHttpError,
|
|
11
|
+
GraphQLClientInvalidResponseError,
|
|
12
|
+
)
|
|
13
|
+
from .get_collection import (
|
|
14
|
+
GetCollection,
|
|
15
|
+
GetCollectionCollection,
|
|
16
|
+
GetCollectionCollectionItems,
|
|
17
|
+
)
|
|
18
|
+
from .get_collections import GetCollections, GetCollectionsCollections
|
|
19
|
+
from .get_item import GetItem, GetItemItem, GetItemItemCollection
|
|
20
|
+
from .get_items import GetItems, GetItemsItems
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"AsyncBaseClient",
|
|
24
|
+
"BaseModel",
|
|
25
|
+
"Client",
|
|
26
|
+
"GetCollection",
|
|
27
|
+
"GetCollectionCollection",
|
|
28
|
+
"GetCollectionCollectionItems",
|
|
29
|
+
"GetCollections",
|
|
30
|
+
"GetCollectionsCollections",
|
|
31
|
+
"GetItem",
|
|
32
|
+
"GetItemItem",
|
|
33
|
+
"GetItemItemCollection",
|
|
34
|
+
"GetItems",
|
|
35
|
+
"GetItemsItems",
|
|
36
|
+
"GraphQLClientError",
|
|
37
|
+
"GraphQLClientGraphQLError",
|
|
38
|
+
"GraphQLClientGraphQLMultiError",
|
|
39
|
+
"GraphQLClientHttpError",
|
|
40
|
+
"GraphQLClientInvalidResponseError",
|
|
41
|
+
"Upload",
|
|
42
|
+
]
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
# Generated by ariadne-codegen
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import enum
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import AsyncIterator
|
|
7
|
+
from typing import IO, Any, Optional, TypeVar, cast
|
|
8
|
+
from uuid import uuid4
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
from pydantic_core import to_jsonable_python
|
|
13
|
+
|
|
14
|
+
from .base_model import UNSET, Upload
|
|
15
|
+
from .exceptions import (
|
|
16
|
+
GraphQLClientError,
|
|
17
|
+
GraphQLClientGraphQLMultiError,
|
|
18
|
+
GraphQLClientHttpError,
|
|
19
|
+
GraphQLClientInvalidMessageFormat,
|
|
20
|
+
GraphQLClientInvalidResponseError,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from websockets import ( # type: ignore[import-not-found,unused-ignore]
|
|
25
|
+
ClientConnection,
|
|
26
|
+
)
|
|
27
|
+
from websockets import ( # type: ignore[import-not-found,unused-ignore]
|
|
28
|
+
connect as ws_connect,
|
|
29
|
+
)
|
|
30
|
+
from websockets.typing import ( # type: ignore[import-not-found,unused-ignore]
|
|
31
|
+
Data,
|
|
32
|
+
Origin,
|
|
33
|
+
Subprotocol,
|
|
34
|
+
)
|
|
35
|
+
except ImportError:
|
|
36
|
+
from contextlib import asynccontextmanager
|
|
37
|
+
|
|
38
|
+
@asynccontextmanager # type: ignore
|
|
39
|
+
async def ws_connect(*args, **kwargs):
|
|
40
|
+
raise NotImplementedError("Subscriptions require 'websockets' package.")
|
|
41
|
+
yield
|
|
42
|
+
|
|
43
|
+
ClientConnection = Any # type: ignore[misc,assignment,unused-ignore]
|
|
44
|
+
Data = Any # type: ignore[misc,assignment,unused-ignore]
|
|
45
|
+
Origin = Any # type: ignore[misc,assignment,unused-ignore]
|
|
46
|
+
|
|
47
|
+
def Subprotocol(*args, **kwargs): # type: ignore # noqa: N802, N803
|
|
48
|
+
raise NotImplementedError("Subscriptions require 'websockets' package.")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
Self = TypeVar("Self", bound="AsyncBaseClient")
|
|
52
|
+
|
|
53
|
+
GRAPHQL_TRANSPORT_WS = "graphql-transport-ws"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class GraphQLTransportWSMessageType(str, enum.Enum):
|
|
57
|
+
CONNECTION_INIT = "connection_init"
|
|
58
|
+
CONNECTION_ACK = "connection_ack"
|
|
59
|
+
PING = "ping"
|
|
60
|
+
PONG = "pong"
|
|
61
|
+
SUBSCRIBE = "subscribe"
|
|
62
|
+
NEXT = "next"
|
|
63
|
+
ERROR = "error"
|
|
64
|
+
COMPLETE = "complete"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class AsyncBaseClient:
|
|
68
|
+
def __init__(
|
|
69
|
+
self,
|
|
70
|
+
url: str = "",
|
|
71
|
+
headers: Optional[dict[str, str]] = None,
|
|
72
|
+
http_client: Optional[httpx.AsyncClient] = None,
|
|
73
|
+
ws_url: str = "",
|
|
74
|
+
ws_headers: Optional[dict[str, Any]] = None,
|
|
75
|
+
ws_origin: Optional[str] = None,
|
|
76
|
+
ws_connection_init_payload: Optional[dict[str, Any]] = None,
|
|
77
|
+
) -> None:
|
|
78
|
+
self.url = url
|
|
79
|
+
self.headers = headers
|
|
80
|
+
self.http_client = (
|
|
81
|
+
http_client if http_client else httpx.AsyncClient(headers=headers)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
self.ws_url = ws_url
|
|
85
|
+
self.ws_headers = ws_headers or {}
|
|
86
|
+
self.ws_origin = Origin(ws_origin) if ws_origin else None
|
|
87
|
+
self.ws_connection_init_payload = ws_connection_init_payload
|
|
88
|
+
|
|
89
|
+
async def __aenter__(self: Self) -> Self:
|
|
90
|
+
return self
|
|
91
|
+
|
|
92
|
+
async def __aexit__(
|
|
93
|
+
self,
|
|
94
|
+
exc_type: object,
|
|
95
|
+
exc_val: object,
|
|
96
|
+
exc_tb: object,
|
|
97
|
+
) -> None:
|
|
98
|
+
await self.http_client.aclose()
|
|
99
|
+
|
|
100
|
+
async def execute(
|
|
101
|
+
self,
|
|
102
|
+
query: str,
|
|
103
|
+
operation_name: Optional[str] = None,
|
|
104
|
+
variables: Optional[dict[str, Any]] = None,
|
|
105
|
+
**kwargs: Any,
|
|
106
|
+
) -> httpx.Response:
|
|
107
|
+
processed_variables, files, files_map = self._process_variables(variables)
|
|
108
|
+
|
|
109
|
+
if files and files_map:
|
|
110
|
+
return await self._execute_multipart(
|
|
111
|
+
query=query,
|
|
112
|
+
operation_name=operation_name,
|
|
113
|
+
variables=processed_variables,
|
|
114
|
+
files=files,
|
|
115
|
+
files_map=files_map,
|
|
116
|
+
**kwargs,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return await self._execute_json(
|
|
120
|
+
query=query,
|
|
121
|
+
operation_name=operation_name,
|
|
122
|
+
variables=processed_variables,
|
|
123
|
+
**kwargs,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def get_data(self, response: httpx.Response) -> dict[str, Any]:
|
|
127
|
+
if not response.is_success:
|
|
128
|
+
raise GraphQLClientHttpError(
|
|
129
|
+
status_code=response.status_code, response=response
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
try:
|
|
133
|
+
response_json = response.json()
|
|
134
|
+
except ValueError as exc:
|
|
135
|
+
raise GraphQLClientInvalidResponseError(response=response) from exc
|
|
136
|
+
|
|
137
|
+
if (not isinstance(response_json, dict)) or (
|
|
138
|
+
"data" not in response_json and "errors" not in response_json
|
|
139
|
+
):
|
|
140
|
+
raise GraphQLClientInvalidResponseError(response=response)
|
|
141
|
+
|
|
142
|
+
data = response_json.get("data")
|
|
143
|
+
errors = response_json.get("errors")
|
|
144
|
+
|
|
145
|
+
if errors:
|
|
146
|
+
raise GraphQLClientGraphQLMultiError.from_errors_dicts(
|
|
147
|
+
errors_dicts=errors, data=data
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return cast(dict[str, Any], data)
|
|
151
|
+
|
|
152
|
+
async def execute_ws(
|
|
153
|
+
self,
|
|
154
|
+
query: str,
|
|
155
|
+
operation_name: Optional[str] = None,
|
|
156
|
+
variables: Optional[dict[str, Any]] = None,
|
|
157
|
+
**kwargs: Any,
|
|
158
|
+
) -> AsyncIterator[dict[str, Any]]:
|
|
159
|
+
headers = self.ws_headers.copy()
|
|
160
|
+
headers.update(kwargs.pop("additional_headers", {}))
|
|
161
|
+
|
|
162
|
+
merged_kwargs: dict[str, Any] = {"origin": self.ws_origin}
|
|
163
|
+
merged_kwargs.update(kwargs)
|
|
164
|
+
merged_kwargs["additional_headers"] = headers
|
|
165
|
+
|
|
166
|
+
operation_id = str(uuid4())
|
|
167
|
+
async with ws_connect(
|
|
168
|
+
self.ws_url,
|
|
169
|
+
subprotocols=[Subprotocol(GRAPHQL_TRANSPORT_WS)],
|
|
170
|
+
**merged_kwargs,
|
|
171
|
+
) as websocket:
|
|
172
|
+
await self._send_connection_init(websocket)
|
|
173
|
+
# Wait for connection_ack; some servers (e.g. Hasura) send ping before
|
|
174
|
+
# connection_ack, so we loop and handle pings until we get ack.
|
|
175
|
+
try:
|
|
176
|
+
await asyncio.wait_for(
|
|
177
|
+
self._wait_for_connection_ack(websocket),
|
|
178
|
+
timeout=5.0,
|
|
179
|
+
)
|
|
180
|
+
except asyncio.TimeoutError as exc:
|
|
181
|
+
raise GraphQLClientError(
|
|
182
|
+
"Connection ack not received within 5 seconds"
|
|
183
|
+
) from exc
|
|
184
|
+
await self._send_subscribe(
|
|
185
|
+
websocket,
|
|
186
|
+
operation_id=operation_id,
|
|
187
|
+
query=query,
|
|
188
|
+
operation_name=operation_name,
|
|
189
|
+
variables=variables,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
async for message in websocket:
|
|
193
|
+
data = await self._handle_ws_message(message, websocket)
|
|
194
|
+
if data and "connection_ack" not in data:
|
|
195
|
+
yield data
|
|
196
|
+
|
|
197
|
+
def _process_variables(
|
|
198
|
+
self, variables: Optional[dict[str, Any]]
|
|
199
|
+
) -> tuple[
|
|
200
|
+
dict[str, Any], dict[str, tuple[str, IO[bytes], str]], dict[str, list[str]]
|
|
201
|
+
]:
|
|
202
|
+
if not variables:
|
|
203
|
+
return {}, {}, {}
|
|
204
|
+
|
|
205
|
+
serializable_variables = self._convert_dict_to_json_serializable(variables)
|
|
206
|
+
return self._get_files_from_variables(serializable_variables)
|
|
207
|
+
|
|
208
|
+
def _convert_dict_to_json_serializable(
|
|
209
|
+
self, dict_: dict[str, Any]
|
|
210
|
+
) -> dict[str, Any]:
|
|
211
|
+
return {
|
|
212
|
+
key: self._convert_value(value)
|
|
213
|
+
for key, value in dict_.items()
|
|
214
|
+
if value is not UNSET
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
def _convert_value(self, value: Any) -> Any:
|
|
218
|
+
if isinstance(value, BaseModel):
|
|
219
|
+
return value.model_dump(by_alias=True, exclude_unset=True)
|
|
220
|
+
if isinstance(value, list):
|
|
221
|
+
return [self._convert_value(item) for item in value]
|
|
222
|
+
return value
|
|
223
|
+
|
|
224
|
+
def _get_files_from_variables(
|
|
225
|
+
self, variables: dict[str, Any]
|
|
226
|
+
) -> tuple[
|
|
227
|
+
dict[str, Any], dict[str, tuple[str, IO[bytes], str]], dict[str, list[str]]
|
|
228
|
+
]:
|
|
229
|
+
files_map: dict[str, list[str]] = {}
|
|
230
|
+
files_list: list[Upload] = []
|
|
231
|
+
|
|
232
|
+
def separate_files(path: str, obj: Any) -> Any:
|
|
233
|
+
if isinstance(obj, list):
|
|
234
|
+
nulled_list = []
|
|
235
|
+
for index, value in enumerate(obj):
|
|
236
|
+
value = separate_files(f"{path}.{index}", value)
|
|
237
|
+
nulled_list.append(value)
|
|
238
|
+
return nulled_list
|
|
239
|
+
|
|
240
|
+
if isinstance(obj, dict):
|
|
241
|
+
nulled_dict = {}
|
|
242
|
+
for key, value in obj.items():
|
|
243
|
+
value = separate_files(f"{path}.{key}", value)
|
|
244
|
+
nulled_dict[key] = value
|
|
245
|
+
return nulled_dict
|
|
246
|
+
|
|
247
|
+
if isinstance(obj, Upload):
|
|
248
|
+
if obj in files_list:
|
|
249
|
+
file_index = files_list.index(obj)
|
|
250
|
+
files_map[str(file_index)].append(path)
|
|
251
|
+
else:
|
|
252
|
+
file_index = len(files_list)
|
|
253
|
+
files_list.append(obj)
|
|
254
|
+
files_map[str(file_index)] = [path]
|
|
255
|
+
return None
|
|
256
|
+
|
|
257
|
+
return obj
|
|
258
|
+
|
|
259
|
+
nulled_variables = separate_files("variables", variables)
|
|
260
|
+
files: dict[str, tuple[str, IO[bytes], str]] = {
|
|
261
|
+
str(i): (file_.filename, cast(IO[bytes], file_.content), file_.content_type)
|
|
262
|
+
for i, file_ in enumerate(files_list)
|
|
263
|
+
}
|
|
264
|
+
return nulled_variables, files, files_map
|
|
265
|
+
|
|
266
|
+
async def _execute_multipart(
|
|
267
|
+
self,
|
|
268
|
+
query: str,
|
|
269
|
+
operation_name: Optional[str],
|
|
270
|
+
variables: dict[str, Any],
|
|
271
|
+
files: dict[str, tuple[str, IO[bytes], str]],
|
|
272
|
+
files_map: dict[str, list[str]],
|
|
273
|
+
**kwargs: Any,
|
|
274
|
+
) -> httpx.Response:
|
|
275
|
+
data = {
|
|
276
|
+
"operations": json.dumps(
|
|
277
|
+
{
|
|
278
|
+
"query": query,
|
|
279
|
+
"operationName": operation_name,
|
|
280
|
+
"variables": variables,
|
|
281
|
+
},
|
|
282
|
+
default=to_jsonable_python,
|
|
283
|
+
),
|
|
284
|
+
"map": json.dumps(files_map, default=to_jsonable_python),
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return await self.http_client.post(
|
|
288
|
+
url=self.url, data=data, files=files, **kwargs
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
async def _execute_json(
|
|
292
|
+
self,
|
|
293
|
+
query: str,
|
|
294
|
+
operation_name: Optional[str],
|
|
295
|
+
variables: dict[str, Any],
|
|
296
|
+
**kwargs: Any,
|
|
297
|
+
) -> httpx.Response:
|
|
298
|
+
headers: dict[str, str] = {"Content-type": "application/json"}
|
|
299
|
+
headers.update(kwargs.get("headers", {}))
|
|
300
|
+
|
|
301
|
+
merged_kwargs: dict[str, Any] = kwargs.copy()
|
|
302
|
+
merged_kwargs["headers"] = headers
|
|
303
|
+
|
|
304
|
+
return await self.http_client.post(
|
|
305
|
+
url=self.url,
|
|
306
|
+
content=json.dumps(
|
|
307
|
+
{
|
|
308
|
+
"query": query,
|
|
309
|
+
"operationName": operation_name,
|
|
310
|
+
"variables": variables,
|
|
311
|
+
},
|
|
312
|
+
default=to_jsonable_python,
|
|
313
|
+
),
|
|
314
|
+
**merged_kwargs,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
async def _send_connection_init(self, websocket: ClientConnection) -> None:
|
|
318
|
+
payload: dict[str, Any] = {
|
|
319
|
+
"type": GraphQLTransportWSMessageType.CONNECTION_INIT.value
|
|
320
|
+
}
|
|
321
|
+
if self.ws_connection_init_payload:
|
|
322
|
+
payload["payload"] = self.ws_connection_init_payload
|
|
323
|
+
await websocket.send(json.dumps(payload))
|
|
324
|
+
|
|
325
|
+
async def _wait_for_connection_ack(self, websocket: ClientConnection) -> None:
|
|
326
|
+
"""Read messages until connection_ack; handle ping/pong in between."""
|
|
327
|
+
async for message in websocket:
|
|
328
|
+
data = await self._handle_ws_message(message, websocket)
|
|
329
|
+
if data is not None and "connection_ack" in data:
|
|
330
|
+
return
|
|
331
|
+
|
|
332
|
+
async def _send_subscribe(
|
|
333
|
+
self,
|
|
334
|
+
websocket: ClientConnection,
|
|
335
|
+
operation_id: str,
|
|
336
|
+
query: str,
|
|
337
|
+
operation_name: Optional[str] = None,
|
|
338
|
+
variables: Optional[dict[str, Any]] = None,
|
|
339
|
+
) -> None:
|
|
340
|
+
payload_inner: dict[str, Any] = {
|
|
341
|
+
"query": query,
|
|
342
|
+
"operationName": operation_name,
|
|
343
|
+
}
|
|
344
|
+
if variables:
|
|
345
|
+
payload_inner["variables"] = self._convert_dict_to_json_serializable(
|
|
346
|
+
variables
|
|
347
|
+
)
|
|
348
|
+
payload: dict[str, Any] = {
|
|
349
|
+
"id": operation_id,
|
|
350
|
+
"type": GraphQLTransportWSMessageType.SUBSCRIBE.value,
|
|
351
|
+
"payload": payload_inner,
|
|
352
|
+
}
|
|
353
|
+
await websocket.send(json.dumps(payload))
|
|
354
|
+
|
|
355
|
+
async def _handle_ws_message(
|
|
356
|
+
self,
|
|
357
|
+
message: Data,
|
|
358
|
+
websocket: ClientConnection,
|
|
359
|
+
expected_type: Optional[GraphQLTransportWSMessageType] = None,
|
|
360
|
+
) -> Optional[dict[str, Any]]:
|
|
361
|
+
try:
|
|
362
|
+
message_dict = json.loads(message)
|
|
363
|
+
except json.JSONDecodeError as exc:
|
|
364
|
+
raise GraphQLClientInvalidMessageFormat(message=message) from exc
|
|
365
|
+
|
|
366
|
+
type_ = message_dict.get("type")
|
|
367
|
+
payload = message_dict.get("payload", {})
|
|
368
|
+
|
|
369
|
+
if not type_ or type_ not in {t.value for t in GraphQLTransportWSMessageType}:
|
|
370
|
+
raise GraphQLClientInvalidMessageFormat(message=message)
|
|
371
|
+
|
|
372
|
+
if expected_type and expected_type != type_:
|
|
373
|
+
raise GraphQLClientInvalidMessageFormat(
|
|
374
|
+
f"Invalid message received. Expected: {expected_type.value}"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
if type_ == GraphQLTransportWSMessageType.NEXT:
|
|
378
|
+
if "data" not in payload:
|
|
379
|
+
raise GraphQLClientInvalidMessageFormat(message=message)
|
|
380
|
+
return cast(dict[str, Any], payload["data"])
|
|
381
|
+
|
|
382
|
+
if type_ == GraphQLTransportWSMessageType.COMPLETE:
|
|
383
|
+
await websocket.close()
|
|
384
|
+
elif type_ == GraphQLTransportWSMessageType.PING:
|
|
385
|
+
await websocket.send(
|
|
386
|
+
json.dumps({"type": GraphQLTransportWSMessageType.PONG.value})
|
|
387
|
+
)
|
|
388
|
+
elif type_ == GraphQLTransportWSMessageType.ERROR:
|
|
389
|
+
raise GraphQLClientGraphQLMultiError.from_errors_dicts(
|
|
390
|
+
errors_dicts=payload, data=message_dict
|
|
391
|
+
)
|
|
392
|
+
elif type_ == GraphQLTransportWSMessageType.CONNECTION_ACK:
|
|
393
|
+
return {"connection_ack": True}
|
|
394
|
+
|
|
395
|
+
return None
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Generated by ariadne-codegen
|
|
2
|
+
|
|
3
|
+
from io import IOBase
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel as PydanticBaseModel
|
|
6
|
+
from pydantic import ConfigDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class UnsetType:
|
|
10
|
+
def __bool__(self) -> bool:
|
|
11
|
+
return False
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
UNSET = UnsetType()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseModel(PydanticBaseModel):
|
|
18
|
+
model_config = ConfigDict(
|
|
19
|
+
populate_by_name=True,
|
|
20
|
+
validate_assignment=True,
|
|
21
|
+
arbitrary_types_allowed=True,
|
|
22
|
+
protected_namespaces=(),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Upload:
|
|
27
|
+
def __init__(self, filename: str, content: IOBase, content_type: str):
|
|
28
|
+
self.filename = filename
|
|
29
|
+
self.content = content
|
|
30
|
+
self.content_type = content_type
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Generated by ariadne-codegen
|
|
2
|
+
# Source: ../graphql/operations.graphql
|
|
3
|
+
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .async_base_client import AsyncBaseClient
|
|
7
|
+
from .get_collection import GetCollection
|
|
8
|
+
from .get_collections import GetCollections
|
|
9
|
+
from .get_item import GetItem
|
|
10
|
+
from .get_items import GetItems
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def gql(q: str) -> str:
|
|
14
|
+
return q
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Client(AsyncBaseClient):
|
|
18
|
+
async def get_collections(self, **kwargs: Any) -> GetCollections:
|
|
19
|
+
query = gql("""
|
|
20
|
+
query GetCollections {
|
|
21
|
+
collections {
|
|
22
|
+
id
|
|
23
|
+
userId
|
|
24
|
+
name
|
|
25
|
+
description
|
|
26
|
+
type
|
|
27
|
+
createdAt
|
|
28
|
+
updatedAt
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
""")
|
|
32
|
+
variables: dict[str, object] = {}
|
|
33
|
+
response = await self.execute(
|
|
34
|
+
query=query, operation_name="GetCollections", variables=variables, **kwargs
|
|
35
|
+
)
|
|
36
|
+
data = self.get_data(response)
|
|
37
|
+
return GetCollections.model_validate(data)
|
|
38
|
+
|
|
39
|
+
async def get_collection(self, id: str, **kwargs: Any) -> GetCollection:
|
|
40
|
+
query = gql("""
|
|
41
|
+
query GetCollection($id: ID!) {
|
|
42
|
+
collection(id: $id) {
|
|
43
|
+
id
|
|
44
|
+
userId
|
|
45
|
+
name
|
|
46
|
+
description
|
|
47
|
+
type
|
|
48
|
+
createdAt
|
|
49
|
+
updatedAt
|
|
50
|
+
items {
|
|
51
|
+
id
|
|
52
|
+
collectionId
|
|
53
|
+
name
|
|
54
|
+
description
|
|
55
|
+
quantity
|
|
56
|
+
value
|
|
57
|
+
condition
|
|
58
|
+
notes
|
|
59
|
+
createdAt
|
|
60
|
+
updatedAt
|
|
61
|
+
photoRef
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
""")
|
|
66
|
+
variables: dict[str, object] = {"id": id}
|
|
67
|
+
response = await self.execute(
|
|
68
|
+
query=query, operation_name="GetCollection", variables=variables, **kwargs
|
|
69
|
+
)
|
|
70
|
+
data = self.get_data(response)
|
|
71
|
+
return GetCollection.model_validate(data)
|
|
72
|
+
|
|
73
|
+
async def get_items(self, **kwargs: Any) -> GetItems:
|
|
74
|
+
query = gql("""
|
|
75
|
+
query GetItems {
|
|
76
|
+
items {
|
|
77
|
+
id
|
|
78
|
+
userId
|
|
79
|
+
collectionId
|
|
80
|
+
name
|
|
81
|
+
description
|
|
82
|
+
quantity
|
|
83
|
+
value
|
|
84
|
+
condition
|
|
85
|
+
notes
|
|
86
|
+
createdAt
|
|
87
|
+
updatedAt
|
|
88
|
+
photoRef
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
""")
|
|
92
|
+
variables: dict[str, object] = {}
|
|
93
|
+
response = await self.execute(
|
|
94
|
+
query=query, operation_name="GetItems", variables=variables, **kwargs
|
|
95
|
+
)
|
|
96
|
+
data = self.get_data(response)
|
|
97
|
+
return GetItems.model_validate(data)
|
|
98
|
+
|
|
99
|
+
async def get_item(self, id: str, **kwargs: Any) -> GetItem:
|
|
100
|
+
query = gql("""
|
|
101
|
+
query GetItem($id: ID!) {
|
|
102
|
+
item(id: $id) {
|
|
103
|
+
id
|
|
104
|
+
userId
|
|
105
|
+
collectionId
|
|
106
|
+
name
|
|
107
|
+
description
|
|
108
|
+
quantity
|
|
109
|
+
value
|
|
110
|
+
condition
|
|
111
|
+
notes
|
|
112
|
+
createdAt
|
|
113
|
+
updatedAt
|
|
114
|
+
photoRef
|
|
115
|
+
collection {
|
|
116
|
+
id
|
|
117
|
+
name
|
|
118
|
+
type
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
""")
|
|
123
|
+
variables: dict[str, object] = {"id": id}
|
|
124
|
+
response = await self.execute(
|
|
125
|
+
query=query, operation_name="GetItem", variables=variables, **kwargs
|
|
126
|
+
)
|
|
127
|
+
data = self.get_data(response)
|
|
128
|
+
return GetItem.model_validate(data)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Generated by ariadne-codegen
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional, Union
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GraphQLClientError(Exception):
|
|
9
|
+
"""Base exception."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GraphQLClientHttpError(GraphQLClientError):
|
|
13
|
+
def __init__(self, status_code: int, response: httpx.Response) -> None:
|
|
14
|
+
self.status_code = status_code
|
|
15
|
+
self.response = response
|
|
16
|
+
|
|
17
|
+
def __str__(self) -> str:
|
|
18
|
+
return f"HTTP status code: {self.status_code}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GraphQLClientInvalidResponseError(GraphQLClientError):
|
|
22
|
+
def __init__(self, response: httpx.Response) -> None:
|
|
23
|
+
self.response = response
|
|
24
|
+
|
|
25
|
+
def __str__(self) -> str:
|
|
26
|
+
return "Invalid response format."
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class GraphQLClientGraphQLError(GraphQLClientError):
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
message: str,
|
|
33
|
+
locations: Optional[list[dict[str, int]]] = None,
|
|
34
|
+
path: Optional[list[str]] = None,
|
|
35
|
+
extensions: Optional[dict[str, object]] = None,
|
|
36
|
+
original: Optional[dict[str, object]] = None,
|
|
37
|
+
):
|
|
38
|
+
self.message = message
|
|
39
|
+
self.locations = locations
|
|
40
|
+
self.path = path
|
|
41
|
+
self.extensions = extensions
|
|
42
|
+
self.original = original
|
|
43
|
+
|
|
44
|
+
def __str__(self) -> str:
|
|
45
|
+
return self.message
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_dict(cls, error: dict[str, Any]) -> "GraphQLClientGraphQLError":
|
|
49
|
+
return cls(
|
|
50
|
+
message=error["message"],
|
|
51
|
+
locations=error.get("locations"),
|
|
52
|
+
path=error.get("path"),
|
|
53
|
+
extensions=error.get("extensions"),
|
|
54
|
+
original=error,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class GraphQLClientGraphQLMultiError(GraphQLClientError):
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
errors: list[GraphQLClientGraphQLError],
|
|
62
|
+
data: Optional[dict[str, Any]] = None,
|
|
63
|
+
):
|
|
64
|
+
self.errors = errors
|
|
65
|
+
self.data = data
|
|
66
|
+
|
|
67
|
+
def __str__(self) -> str:
|
|
68
|
+
return "; ".join(str(e) for e in self.errors)
|
|
69
|
+
|
|
70
|
+
@classmethod
|
|
71
|
+
def from_errors_dicts(
|
|
72
|
+
cls, errors_dicts: list[dict[str, Any]], data: Optional[dict[str, Any]] = None
|
|
73
|
+
) -> "GraphQLClientGraphQLMultiError":
|
|
74
|
+
return cls(
|
|
75
|
+
errors=[GraphQLClientGraphQLError.from_dict(e) for e in errors_dicts],
|
|
76
|
+
data=data,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class GraphQLClientInvalidMessageFormat(GraphQLClientError): # noqa: N818
|
|
81
|
+
def __init__(self, message: Union[str, bytes]) -> None:
|
|
82
|
+
self.message = message
|
|
83
|
+
|
|
84
|
+
def __str__(self) -> str:
|
|
85
|
+
return "Invalid message format."
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Generated by ariadne-codegen
|
|
2
|
+
# Source: ../graphql/operations.graphql
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from .base_model import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GetCollection(BaseModel):
|
|
12
|
+
collection: Optional["GetCollectionCollection"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GetCollectionCollection(BaseModel):
|
|
16
|
+
id: str
|
|
17
|
+
user_id: str = Field(alias="userId")
|
|
18
|
+
name: str
|
|
19
|
+
description: Optional[str]
|
|
20
|
+
type_: str = Field(alias="type")
|
|
21
|
+
created_at: str = Field(alias="createdAt")
|
|
22
|
+
updated_at: str = Field(alias="updatedAt")
|
|
23
|
+
items: list["GetCollectionCollectionItems"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GetCollectionCollectionItems(BaseModel):
|
|
27
|
+
id: str
|
|
28
|
+
collection_id: str = Field(alias="collectionId")
|
|
29
|
+
name: str
|
|
30
|
+
description: Optional[str]
|
|
31
|
+
quantity: int
|
|
32
|
+
value: Optional[float]
|
|
33
|
+
condition: Optional[str]
|
|
34
|
+
notes: Optional[str]
|
|
35
|
+
created_at: str = Field(alias="createdAt")
|
|
36
|
+
updated_at: str = Field(alias="updatedAt")
|
|
37
|
+
photo_ref: Optional[str] = Field(alias="photoRef")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
GetCollection.model_rebuild()
|
|
41
|
+
GetCollectionCollection.model_rebuild()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Generated by ariadne-codegen
|
|
2
|
+
# Source: ../graphql/operations.graphql
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from .base_model import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GetCollections(BaseModel):
|
|
12
|
+
collections: list["GetCollectionsCollections"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GetCollectionsCollections(BaseModel):
|
|
16
|
+
id: str
|
|
17
|
+
user_id: str = Field(alias="userId")
|
|
18
|
+
name: str
|
|
19
|
+
description: Optional[str]
|
|
20
|
+
type_: str = Field(alias="type")
|
|
21
|
+
created_at: str = Field(alias="createdAt")
|
|
22
|
+
updated_at: str = Field(alias="updatedAt")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
GetCollections.model_rebuild()
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Generated by ariadne-codegen
|
|
2
|
+
# Source: ../graphql/operations.graphql
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from .base_model import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GetItem(BaseModel):
|
|
12
|
+
item: Optional["GetItemItem"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GetItemItem(BaseModel):
|
|
16
|
+
id: str
|
|
17
|
+
user_id: str = Field(alias="userId")
|
|
18
|
+
collection_id: str = Field(alias="collectionId")
|
|
19
|
+
name: str
|
|
20
|
+
description: Optional[str]
|
|
21
|
+
quantity: int
|
|
22
|
+
value: Optional[float]
|
|
23
|
+
condition: Optional[str]
|
|
24
|
+
notes: Optional[str]
|
|
25
|
+
created_at: str = Field(alias="createdAt")
|
|
26
|
+
updated_at: str = Field(alias="updatedAt")
|
|
27
|
+
photo_ref: Optional[str] = Field(alias="photoRef")
|
|
28
|
+
collection: "GetItemItemCollection"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class GetItemItemCollection(BaseModel):
|
|
32
|
+
id: str
|
|
33
|
+
name: str
|
|
34
|
+
type_: str = Field(alias="type")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
GetItem.model_rebuild()
|
|
38
|
+
GetItemItem.model_rebuild()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Generated by ariadne-codegen
|
|
2
|
+
# Source: ../graphql/operations.graphql
|
|
3
|
+
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from .base_model import BaseModel
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GetItems(BaseModel):
|
|
12
|
+
items: list["GetItemsItems"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class GetItemsItems(BaseModel):
|
|
16
|
+
id: str
|
|
17
|
+
user_id: str = Field(alias="userId")
|
|
18
|
+
collection_id: str = Field(alias="collectionId")
|
|
19
|
+
name: str
|
|
20
|
+
description: Optional[str]
|
|
21
|
+
quantity: int
|
|
22
|
+
value: Optional[float]
|
|
23
|
+
condition: Optional[str]
|
|
24
|
+
notes: Optional[str]
|
|
25
|
+
created_at: str = Field(alias="createdAt")
|
|
26
|
+
updated_at: str = Field(alias="updatedAt")
|
|
27
|
+
photo_ref: Optional[str] = Field(alias="photoRef")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
GetItems.model_rebuild()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sammler-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Python SDK for interacting with Sammler GraphQL API
|
|
5
|
+
Requires-Python: >=3.8
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: httpx>=0.24.0
|
|
8
|
+
Requires-Dist: pydantic>=2.0
|
|
9
|
+
|
|
10
|
+
# Sammler Python SDK
|
|
11
|
+
|
|
12
|
+
Python client for interacting with the Sammler GraphQL API.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install .
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Code Generation
|
|
21
|
+
|
|
22
|
+
To regenerate the client using `ariadne-codegen`:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Set up a virtual environment
|
|
26
|
+
python3 -m venv .venv
|
|
27
|
+
source .venv/bin/activate
|
|
28
|
+
|
|
29
|
+
# Install tools
|
|
30
|
+
pip install ariadne-codegen[http]
|
|
31
|
+
|
|
32
|
+
# Generate client
|
|
33
|
+
ariadne-codegen
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import asyncio
|
|
40
|
+
from sammler import SammlerClient
|
|
41
|
+
|
|
42
|
+
async def main():
|
|
43
|
+
async with SammlerClient(api_key="your_api_key") as client:
|
|
44
|
+
collections = await client.get_collections()
|
|
45
|
+
for col in collections:
|
|
46
|
+
print(col.name)
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
asyncio.run(main())
|
|
50
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
sammler/__init__.py
|
|
4
|
+
sammler/client.py
|
|
5
|
+
sammler/graphql/__init__.py
|
|
6
|
+
sammler/graphql/async_base_client.py
|
|
7
|
+
sammler/graphql/base_model.py
|
|
8
|
+
sammler/graphql/client.py
|
|
9
|
+
sammler/graphql/enums.py
|
|
10
|
+
sammler/graphql/exceptions.py
|
|
11
|
+
sammler/graphql/get_collection.py
|
|
12
|
+
sammler/graphql/get_collections.py
|
|
13
|
+
sammler/graphql/get_item.py
|
|
14
|
+
sammler/graphql/get_items.py
|
|
15
|
+
sammler/graphql/input_types.py
|
|
16
|
+
sammler_sdk.egg-info/PKG-INFO
|
|
17
|
+
sammler_sdk.egg-info/SOURCES.txt
|
|
18
|
+
sammler_sdk.egg-info/dependency_links.txt
|
|
19
|
+
sammler_sdk.egg-info/requires.txt
|
|
20
|
+
sammler_sdk.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sammler
|