reflectapi-runtime 0.1.0__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.
- reflectapi_runtime/__init__.py +109 -0
- reflectapi_runtime/auth.py +507 -0
- reflectapi_runtime/batch.py +165 -0
- reflectapi_runtime/client.py +924 -0
- reflectapi_runtime/exceptions.py +120 -0
- reflectapi_runtime/hypothesis_strategies.py +275 -0
- reflectapi_runtime/middleware.py +254 -0
- reflectapi_runtime/option.py +295 -0
- reflectapi_runtime/response.py +126 -0
- reflectapi_runtime/streaming.py +435 -0
- reflectapi_runtime/testing.py +380 -0
- reflectapi_runtime/types.py +32 -0
- reflectapi_runtime-0.1.0.dist-info/METADATA +36 -0
- reflectapi_runtime-0.1.0.dist-info/RECORD +15 -0
- reflectapi_runtime-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"""Testing utilities for ReflectAPI Python clients."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
8
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
9
|
+
|
|
10
|
+
import httpx
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
|
|
15
|
+
from .middleware import AsyncMiddleware, SyncMiddleware
|
|
16
|
+
from .response import ApiResponse, TransportMetadata
|
|
17
|
+
|
|
18
|
+
T = TypeVar("T")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CassetteMiddleware(SyncMiddleware):
|
|
22
|
+
"""Middleware for recording and replaying HTTP requests (sync version).
|
|
23
|
+
|
|
24
|
+
This middleware integrates with CassetteClient to provide VCR-like
|
|
25
|
+
functionality directly through the middleware chain, making recording
|
|
26
|
+
and playback seamless and transparent.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, cassette_client: CassetteClient):
|
|
30
|
+
self.cassette_client = cassette_client
|
|
31
|
+
|
|
32
|
+
def handle(self, request: httpx.Request, next_call: SyncNextHandler) -> httpx.Response:
|
|
33
|
+
"""Handle request through cassette recording/playback."""
|
|
34
|
+
# For now, just pass through to next handler
|
|
35
|
+
# Full implementation would integrate with cassette_client
|
|
36
|
+
return next_call(request)
|
|
37
|
+
|
|
38
|
+
def process_request(
|
|
39
|
+
self, request: httpx.Request, client: httpx.Client
|
|
40
|
+
) -> httpx.Response:
|
|
41
|
+
"""Process request through cassette recording/playback."""
|
|
42
|
+
if self.cassette_client.is_recording:
|
|
43
|
+
# Make the real request and record it
|
|
44
|
+
response = client.send(request)
|
|
45
|
+
self.cassette_client.record_interaction(request, response)
|
|
46
|
+
return response
|
|
47
|
+
else:
|
|
48
|
+
# Try to find a matching recorded response
|
|
49
|
+
recorded_response = self.cassette_client.find_matching_response(request)
|
|
50
|
+
if recorded_response:
|
|
51
|
+
return recorded_response
|
|
52
|
+
else:
|
|
53
|
+
# No matching response found - either make real request or raise error
|
|
54
|
+
if self.cassette_client.allow_new_requests:
|
|
55
|
+
response = client.send(request)
|
|
56
|
+
self.cassette_client.record_interaction(request, response)
|
|
57
|
+
return response
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
f"No recorded response found for {request.method} {request.url} "
|
|
61
|
+
"and new requests are not allowed"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class AsyncCassetteMiddleware(AsyncMiddleware):
|
|
66
|
+
"""Middleware for recording and replaying HTTP requests (async version)."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, cassette_client: CassetteClient):
|
|
69
|
+
self.cassette_client = cassette_client
|
|
70
|
+
|
|
71
|
+
async def handle(self, request: httpx.Request, next_call: AsyncNextHandler) -> httpx.Response:
|
|
72
|
+
"""Handle request through cassette recording/playback."""
|
|
73
|
+
# For now, just pass through to next handler
|
|
74
|
+
# Full implementation would integrate with cassette_client
|
|
75
|
+
return await next_call(request)
|
|
76
|
+
|
|
77
|
+
async def process_request(
|
|
78
|
+
self, request: httpx.Request, client: httpx.AsyncClient
|
|
79
|
+
) -> httpx.Response:
|
|
80
|
+
"""Process request through cassette recording/playback."""
|
|
81
|
+
if self.cassette_client.is_recording:
|
|
82
|
+
# Make the real request and record it
|
|
83
|
+
response = await client.send(request)
|
|
84
|
+
self.cassette_client.record_interaction(request, response)
|
|
85
|
+
return response
|
|
86
|
+
else:
|
|
87
|
+
# Try to find a matching recorded response
|
|
88
|
+
recorded_response = self.cassette_client.find_matching_response(request)
|
|
89
|
+
if recorded_response:
|
|
90
|
+
return recorded_response
|
|
91
|
+
else:
|
|
92
|
+
# No matching response found
|
|
93
|
+
if self.cassette_client.allow_new_requests:
|
|
94
|
+
response = await client.send(request)
|
|
95
|
+
self.cassette_client.record_interaction(request, response)
|
|
96
|
+
return response
|
|
97
|
+
else:
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"No recorded response found for {request.method} {request.url} "
|
|
100
|
+
"and new requests are not allowed"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class MockClient:
|
|
105
|
+
"""Mock client for testing that mimics the interface of generated clients."""
|
|
106
|
+
|
|
107
|
+
def __init__(self) -> None:
|
|
108
|
+
self._mock_responses: dict[str, Any] = {}
|
|
109
|
+
self._call_history: list[dict[str, Any]] = []
|
|
110
|
+
|
|
111
|
+
def __getattr__(self, name: str) -> Any:
|
|
112
|
+
"""Return a mock for any method call."""
|
|
113
|
+
if name not in self._mock_responses:
|
|
114
|
+
self._mock_responses[name] = MagicMock()
|
|
115
|
+
return self._mock_responses[name]
|
|
116
|
+
|
|
117
|
+
def set_response(self, method_name: str, response: Any) -> None:
|
|
118
|
+
"""Set a mock response for a specific method."""
|
|
119
|
+
mock = MagicMock(return_value=response)
|
|
120
|
+
self._mock_responses[method_name] = mock
|
|
121
|
+
setattr(self, method_name, mock)
|
|
122
|
+
|
|
123
|
+
def set_async_response(self, method_name: str, response: Any) -> None:
|
|
124
|
+
"""Set a mock async response for a specific method."""
|
|
125
|
+
mock = AsyncMock(return_value=response)
|
|
126
|
+
self._mock_responses[method_name] = mock
|
|
127
|
+
setattr(self, method_name, mock)
|
|
128
|
+
|
|
129
|
+
def get_call_history(self) -> list[dict[str, Any]]:
|
|
130
|
+
"""Get the history of all method calls."""
|
|
131
|
+
return self._call_history.copy()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def create_api_response(
|
|
135
|
+
value: T,
|
|
136
|
+
*,
|
|
137
|
+
status_code: int = 200,
|
|
138
|
+
headers: dict[str, str] | None = None,
|
|
139
|
+
timing: float = 0.1,
|
|
140
|
+
) -> ApiResponse[T]:
|
|
141
|
+
"""Create a mock ApiResponse for testing."""
|
|
142
|
+
import httpx
|
|
143
|
+
|
|
144
|
+
# Create a mock response
|
|
145
|
+
mock_response = MagicMock(spec=httpx.Response)
|
|
146
|
+
mock_response.status_code = status_code
|
|
147
|
+
mock_response.headers = httpx.Headers(headers or {})
|
|
148
|
+
mock_response.reason_phrase = "OK" if status_code < 400 else "Error"
|
|
149
|
+
|
|
150
|
+
metadata = TransportMetadata(
|
|
151
|
+
status_code=status_code,
|
|
152
|
+
headers=mock_response.headers,
|
|
153
|
+
timing=timing,
|
|
154
|
+
raw_response=mock_response,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
return ApiResponse(value, metadata)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class CassetteClient:
|
|
161
|
+
"""Client that records and replays HTTP requests for testing."""
|
|
162
|
+
|
|
163
|
+
def __init__(self, cassette_path: str | Path, allow_new_requests: bool = True) -> None:
|
|
164
|
+
self.cassette_path = Path(cassette_path)
|
|
165
|
+
self._recorded_interactions: list[dict[str, Any]] = []
|
|
166
|
+
self._playback_interactions: list[dict[str, Any]] = []
|
|
167
|
+
self._current_interaction = 0
|
|
168
|
+
self._mode = "record" # or "playback"
|
|
169
|
+
self.allow_new_requests = allow_new_requests
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def record(cls, cassette_path: str | Path) -> CassetteClient:
|
|
173
|
+
"""Create a client in record mode."""
|
|
174
|
+
client = cls(cassette_path)
|
|
175
|
+
client._mode = "record"
|
|
176
|
+
return client
|
|
177
|
+
|
|
178
|
+
@classmethod
|
|
179
|
+
def playback(cls, cassette_path: str | Path) -> CassetteClient:
|
|
180
|
+
"""Create a client in playback mode."""
|
|
181
|
+
client = cls(cassette_path)
|
|
182
|
+
client._mode = "playback"
|
|
183
|
+
client._load_cassette()
|
|
184
|
+
return client
|
|
185
|
+
|
|
186
|
+
def _load_cassette(self) -> None:
|
|
187
|
+
"""Load recorded interactions from the cassette file."""
|
|
188
|
+
if self.cassette_path.exists():
|
|
189
|
+
with open(self.cassette_path) as f:
|
|
190
|
+
data = json.load(f)
|
|
191
|
+
self._playback_interactions = data.get("interactions", [])
|
|
192
|
+
|
|
193
|
+
def save_cassette(self) -> None:
|
|
194
|
+
"""Save recorded interactions to the cassette file."""
|
|
195
|
+
self.cassette_path.parent.mkdir(parents=True, exist_ok=True)
|
|
196
|
+
|
|
197
|
+
with open(self.cassette_path, "w") as f:
|
|
198
|
+
json.dump(
|
|
199
|
+
{
|
|
200
|
+
"interactions": self._recorded_interactions,
|
|
201
|
+
"version": "1.0",
|
|
202
|
+
},
|
|
203
|
+
f,
|
|
204
|
+
indent=2,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def get_next_response(self, request: dict[str, Any]) -> Any: # noqa: ARG002
|
|
209
|
+
"""Get the next recorded response for playback."""
|
|
210
|
+
if self._mode != "playback":
|
|
211
|
+
return None
|
|
212
|
+
|
|
213
|
+
if self._current_interaction >= len(self._playback_interactions):
|
|
214
|
+
raise RuntimeError("No more recorded interactions available")
|
|
215
|
+
|
|
216
|
+
interaction = self._playback_interactions[self._current_interaction]
|
|
217
|
+
self._current_interaction += 1
|
|
218
|
+
|
|
219
|
+
# TODO: Match request against recorded request for validation
|
|
220
|
+
return interaction["response"]
|
|
221
|
+
|
|
222
|
+
@property
|
|
223
|
+
def is_recording(self) -> bool:
|
|
224
|
+
"""Check if the client is in recording mode."""
|
|
225
|
+
return self._mode == "record"
|
|
226
|
+
|
|
227
|
+
@property
|
|
228
|
+
def is_playback(self) -> bool:
|
|
229
|
+
"""Check if the client is in playback mode."""
|
|
230
|
+
return self._mode == "playback"
|
|
231
|
+
|
|
232
|
+
def record_interaction(self, request: httpx.Request, response: httpx.Response) -> None:
|
|
233
|
+
"""Record an HTTP request/response interaction."""
|
|
234
|
+
if not self.is_recording:
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
# Serialize request
|
|
238
|
+
request_data = {
|
|
239
|
+
"method": request.method,
|
|
240
|
+
"url": str(request.url),
|
|
241
|
+
"headers": dict(request.headers),
|
|
242
|
+
"content": request.content.decode('utf-8', errors='replace') if request.content else None,
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# Serialize response
|
|
246
|
+
response_data = {
|
|
247
|
+
"status_code": response.status_code,
|
|
248
|
+
"headers": dict(response.headers),
|
|
249
|
+
"content": response.content.decode('utf-8', errors='replace'),
|
|
250
|
+
"reason_phrase": getattr(response, 'reason_phrase', ''),
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
self._recorded_interactions.append({
|
|
254
|
+
"request": request_data,
|
|
255
|
+
"response": response_data,
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
def find_matching_response(self, request: httpx.Request) -> httpx.Response | None:
|
|
259
|
+
"""Find a recorded response that matches the given request."""
|
|
260
|
+
if not self.is_playback:
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
request_method = request.method
|
|
264
|
+
request_url = str(request.url)
|
|
265
|
+
|
|
266
|
+
# Simple matching by method and URL
|
|
267
|
+
# In a more sophisticated implementation, you might want to match headers, body, etc.
|
|
268
|
+
for interaction in self._playback_interactions:
|
|
269
|
+
recorded_request = interaction["request"]
|
|
270
|
+
if (recorded_request["method"] == request_method and
|
|
271
|
+
recorded_request["url"] == request_url):
|
|
272
|
+
|
|
273
|
+
# Create a mock response
|
|
274
|
+
response_data = interaction["response"]
|
|
275
|
+
|
|
276
|
+
# Create a mock httpx.Response
|
|
277
|
+
mock_response = MagicMock(spec=httpx.Response)
|
|
278
|
+
mock_response.status_code = response_data["status_code"]
|
|
279
|
+
mock_response.headers = httpx.Headers(response_data["headers"])
|
|
280
|
+
mock_response.content = response_data["content"].encode('utf-8')
|
|
281
|
+
mock_response.text = response_data["content"]
|
|
282
|
+
mock_response.reason_phrase = response_data.get("reason_phrase", "")
|
|
283
|
+
mock_response.json.return_value = json.loads(response_data["content"]) if response_data["content"] else {}
|
|
284
|
+
|
|
285
|
+
return mock_response
|
|
286
|
+
|
|
287
|
+
return None
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class TestClientMixin:
|
|
291
|
+
"""Mixin that adds testing capabilities to generated clients."""
|
|
292
|
+
|
|
293
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
294
|
+
# Extract testing-specific kwargs
|
|
295
|
+
self._dev_mode = kwargs.pop("dev_mode", False)
|
|
296
|
+
self._cassette_client: CassetteClient | None = kwargs.pop(
|
|
297
|
+
"cassette_client", None
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Only call super().__init__ if there are args/kwargs to pass
|
|
301
|
+
# or if the class has a custom __init__ that's not object.__init__
|
|
302
|
+
try:
|
|
303
|
+
super().__init__(*args, **kwargs)
|
|
304
|
+
except TypeError:
|
|
305
|
+
# If TypeError occurs, it's likely object.__init__ with extra args
|
|
306
|
+
# Only call it if there are no args/kwargs
|
|
307
|
+
if not args and not kwargs:
|
|
308
|
+
super().__init__()
|
|
309
|
+
# Otherwise, just skip the super() call
|
|
310
|
+
|
|
311
|
+
def save_requests_to_cassette(self, cassette_path: str | Path) -> None: # noqa: ARG002
|
|
312
|
+
"""Save recorded requests to a cassette file."""
|
|
313
|
+
if self._cassette_client:
|
|
314
|
+
self._cassette_client.save_cassette()
|
|
315
|
+
|
|
316
|
+
@classmethod
|
|
317
|
+
def playback_from_cassette(
|
|
318
|
+
cls,
|
|
319
|
+
cassette_path: str | Path,
|
|
320
|
+
**kwargs: Any,
|
|
321
|
+
) -> Any:
|
|
322
|
+
"""Create a client that replays requests from a cassette."""
|
|
323
|
+
cassette_client = CassetteClient.playback(cassette_path)
|
|
324
|
+
kwargs["cassette_client"] = cassette_client
|
|
325
|
+
return cls(base_url="http://test.local", **kwargs)
|
|
326
|
+
|
|
327
|
+
@classmethod
|
|
328
|
+
def record_to_cassette(
|
|
329
|
+
cls,
|
|
330
|
+
cassette_path: str | Path,
|
|
331
|
+
base_url: str,
|
|
332
|
+
**kwargs: Any,
|
|
333
|
+
) -> Any:
|
|
334
|
+
"""Create a client that records requests to a cassette."""
|
|
335
|
+
cassette_client = CassetteClient.record(cassette_path)
|
|
336
|
+
|
|
337
|
+
# Add cassette middleware to the middleware list
|
|
338
|
+
middleware = kwargs.get("middleware", [])
|
|
339
|
+
|
|
340
|
+
# Determine if this is an async client
|
|
341
|
+
if hasattr(cls, "__bases__") and any("Async" in base.__name__ for base in cls.__bases__):
|
|
342
|
+
cassette_middleware = AsyncCassetteMiddleware(cassette_client)
|
|
343
|
+
else:
|
|
344
|
+
cassette_middleware = CassetteMiddleware(cassette_client)
|
|
345
|
+
|
|
346
|
+
middleware.insert(0, cassette_middleware) # Add at the beginning of the chain
|
|
347
|
+
kwargs["middleware"] = middleware
|
|
348
|
+
kwargs["cassette_client"] = cassette_client
|
|
349
|
+
|
|
350
|
+
return cls(base_url=base_url, **kwargs)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# Hypothesis strategies for property-based testing
|
|
354
|
+
try:
|
|
355
|
+
from hypothesis import strategies as st # type: ignore[import-not-found]
|
|
356
|
+
from hypothesis.strategies import ( # type: ignore[import-not-found]
|
|
357
|
+
SearchStrategy, # noqa: TC002
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
def create_model_strategy(
|
|
361
|
+
model_class: type[BaseModel],
|
|
362
|
+
) -> SearchStrategy[BaseModel]:
|
|
363
|
+
"""Create a Hypothesis strategy for a Pydantic model."""
|
|
364
|
+
# This is a simplified implementation
|
|
365
|
+
# A full implementation would introspect the model fields
|
|
366
|
+
# and create appropriate strategies for each field type
|
|
367
|
+
|
|
368
|
+
def build_model(**kwargs: Any) -> BaseModel:
|
|
369
|
+
return model_class.model_validate(kwargs)
|
|
370
|
+
|
|
371
|
+
# Return a basic strategy that creates valid instances
|
|
372
|
+
return st.builds(build_model)
|
|
373
|
+
|
|
374
|
+
except ImportError:
|
|
375
|
+
# Hypothesis is not available
|
|
376
|
+
def create_model_strategy(model_class: type[BaseModel]) -> None: # type: ignore # noqa: ARG001
|
|
377
|
+
"""Hypothesis not available - strategy creation disabled."""
|
|
378
|
+
raise ImportError(
|
|
379
|
+
"hypothesis is required for property-based testing strategies"
|
|
380
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Common type definitions for ReflectAPI Python runtime."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, TypeVar, Union
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, ConfigDict
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .exceptions import ApiError
|
|
11
|
+
from .response import ApiResponse
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
|
|
15
|
+
# Type alias for batch operation results - avoids circular imports
|
|
16
|
+
BatchResult = Union["ApiResponse[T]", "ApiError"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ReflectapiEmpty(BaseModel):
|
|
20
|
+
"""Struct object with no fields.
|
|
21
|
+
|
|
22
|
+
This represents empty struct types from ReflectAPI schemas.
|
|
23
|
+
"""
|
|
24
|
+
model_config = ConfigDict(extra="ignore")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ReflectapiInfallible(BaseModel):
|
|
28
|
+
"""Error object which is expected to be never returned.
|
|
29
|
+
|
|
30
|
+
This represents infallible error types that should never occur.
|
|
31
|
+
"""
|
|
32
|
+
model_config = ConfigDict(extra="ignore")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reflectapi-runtime
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Runtime library for ReflectAPI Python clients
|
|
5
|
+
Project-URL: Homepage, https://github.com/thepartly/reflectapi
|
|
6
|
+
Project-URL: Repository, https://github.com/thepartly/reflectapi
|
|
7
|
+
Project-URL: Documentation, https://docs.rs/reflectapi/latest/reflectapi/
|
|
8
|
+
Author: Partly
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: api,client,codegen,http,rest
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.12
|
|
21
|
+
Requires-Dist: httpx>=0.25.0
|
|
22
|
+
Requires-Dist: pydantic>=2.0.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: hypothesis>=6.0.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: mypy>=1.5.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
30
|
+
Provides-Extra: test
|
|
31
|
+
Requires-Dist: hypothesis>=6.0.0; extra == 'test'
|
|
32
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'test'
|
|
33
|
+
Requires-Dist: pytest>=7.0.0; extra == 'test'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
See https://github.com/thepartly/reflectapi
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
reflectapi_runtime/__init__.py,sha256=uK53OB89osGbBFSAOMAYLOJH97o_ATFKrLsTj-BOyjI,2403
|
|
2
|
+
reflectapi_runtime/auth.py,sha256=fOWJv_x6EI4FHZzNUJiz2uBFHqQ4oDDX7qahr-tQWoQ,18047
|
|
3
|
+
reflectapi_runtime/batch.py,sha256=KW1qOhdvkLOv0Z27vjCGm1UBFwTlwnlJ54zFOA8x4P4,6347
|
|
4
|
+
reflectapi_runtime/client.py,sha256=QOdS9VWODS0pc_0KWINbLaDO18d38MntsuTvL9xCb9M,34461
|
|
5
|
+
reflectapi_runtime/exceptions.py,sha256=LIpF1_06j6EMn7L8oJClG7_PeQWS3pvllFSsHdnlj2A,3404
|
|
6
|
+
reflectapi_runtime/hypothesis_strategies.py,sha256=EDMQnasfxemx0ohS_58zGrbuv-Ti8CM3E7vuQL7vlqI,9875
|
|
7
|
+
reflectapi_runtime/middleware.py,sha256=ghwqpyYC5EcAGc1xeh_cgc3H5-aGFxHrl5Pe3AkTHPE,8338
|
|
8
|
+
reflectapi_runtime/option.py,sha256=MB5G1eD3dkwYZYsPEo8CoErcgJNVGwCIb-DIULR2izM,9112
|
|
9
|
+
reflectapi_runtime/response.py,sha256=XVH7WMnygpYr3E8H6XApxB2SSd13rliaY6XjouBVBIw,4146
|
|
10
|
+
reflectapi_runtime/streaming.py,sha256=NIB5s98ipRBk25R9GqmGUAeabmxP5aajYeGsYYzsh34,14867
|
|
11
|
+
reflectapi_runtime/testing.py,sha256=eoCs3aPhc8M-RaHI83WDhR-d5aM3N6WWxwTDi0IbT_k,14485
|
|
12
|
+
reflectapi_runtime/types.py,sha256=Oht5HXHaRkjvaJOaP337B74C_rsxHcbZRf2BQ7bn3EA,845
|
|
13
|
+
reflectapi_runtime-0.1.0.dist-info/METADATA,sha256=Vw1oXFUm1J0fzjqdL6L9c9XewU2OFUWmy_M7Rmwchok,1460
|
|
14
|
+
reflectapi_runtime-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
15
|
+
reflectapi_runtime-0.1.0.dist-info/RECORD,,
|