infralo 0.1.0.dev1__tar.gz → 0.1.0.dev2__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.
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/PKG-INFO +1 -1
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/pyproject.toml +1 -3
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/src/infralo/_models.py +1 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/src/infralo/client.py +127 -9
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/src/infralo/exporter.py +37 -9
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/src/infralo/trace.py +64 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/tests/test_sdk.py +100 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/uv.lock +2 -207
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/.gitignore +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/.pre-commit-config.yaml +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/README.md +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/README.md +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/agno/README.md +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/agno/agno_complex_nesting.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/agno/agno_integration.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/agno/pyproject.toml +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/agno/uv.lock +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/core/README.md +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/core/complex_nesting.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/core/complex_nesting_decorator.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/core/mcp_integration.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/core/parallel_execution.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/core/pattern_a_context_manager.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/core/pattern_b_decorator.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/core/pattern_c_manual.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/core/pyproject.toml +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/core/tool_first_execution.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/core/uv.lock +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/crewai/README.md +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/crewai/crewai_complex_nesting.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/crewai/crewai_integration.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/crewai/pyproject.toml +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/crewai/uv.lock +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/google_adk/README.md +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/google_adk/google_adk_complex_nesting.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/google_adk/google_adk_integration.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/google_adk/pyproject.toml +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/google_adk/uv.lock +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/langgraph/README.md +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/langgraph/langgraph_complex_nesting.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/langgraph/langgraph_integration.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/langgraph/pyproject.toml +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/examples/langgraph/uv.lock +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/src/infralo/__init__.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/src/infralo/integrations/__init__.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/src/infralo/integrations/agno.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/src/infralo/integrations/crewai.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/src/infralo/integrations/google_adk.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/src/infralo/integrations/langgraph.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/src/infralo/span.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/tests/conftest.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/tests/integrations/__init__.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/tests/integrations/test_agno.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/tests/integrations/test_crewai.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/tests/integrations/test_google_adk.py +0 -0
- {infralo-0.1.0.dev1 → infralo-0.1.0.dev2}/tests/integrations/test_langgraph.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "infralo"
|
|
7
|
-
version = "0.1.0.
|
|
7
|
+
version = "0.1.0.dev2"
|
|
8
8
|
description = "Infralo SDK"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -32,8 +32,6 @@ dev = [
|
|
|
32
32
|
"pytest>=8",
|
|
33
33
|
"pytest-asyncio>=0.23",
|
|
34
34
|
"respx>=0.21", # mock httpx
|
|
35
|
-
"build>=1.5.0",
|
|
36
|
-
"twine>=6.2.0",
|
|
37
35
|
]
|
|
38
36
|
|
|
39
37
|
[tool.hatch.build.targets.wheel]
|
|
@@ -18,8 +18,10 @@ import inspect
|
|
|
18
18
|
import uuid
|
|
19
19
|
from typing import Any, Callable, TypeVar
|
|
20
20
|
|
|
21
|
+
import httpx
|
|
22
|
+
|
|
21
23
|
from infralo.exporter import SpanExporter
|
|
22
|
-
from infralo.trace import Trace, _active_trace
|
|
24
|
+
from infralo.trace import Trace, _active_trace, get_active_trace
|
|
23
25
|
|
|
24
26
|
F = TypeVar("F", bound=Callable)
|
|
25
27
|
|
|
@@ -87,6 +89,7 @@ class Infralo:
|
|
|
87
89
|
trace_id=trace_id or str(uuid.uuid4()),
|
|
88
90
|
session_id=session_id,
|
|
89
91
|
exporter=self._exporter,
|
|
92
|
+
client=self,
|
|
90
93
|
)
|
|
91
94
|
|
|
92
95
|
# ── @tool decorator ──────────────────────────────────────────────────────
|
|
@@ -162,10 +165,8 @@ class Infralo:
|
|
|
162
165
|
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
163
166
|
trace = _active_trace.get()
|
|
164
167
|
if trace is None:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
"Wrap your code with `with infralo.start_trace(): ...`."
|
|
168
|
-
)
|
|
168
|
+
_pop_infralo_kwargs(kwargs)
|
|
169
|
+
return func(*args, **kwargs)
|
|
169
170
|
|
|
170
171
|
infralo_kwargs = _pop_infralo_kwargs(kwargs)
|
|
171
172
|
metadata: dict = infralo_kwargs.get("_infralo_metadata", {})
|
|
@@ -194,10 +195,8 @@ class Infralo:
|
|
|
194
195
|
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
195
196
|
trace = _active_trace.get()
|
|
196
197
|
if trace is None:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
"Wrap your code with `with infralo.start_trace(): ...`."
|
|
200
|
-
)
|
|
198
|
+
_pop_infralo_kwargs(kwargs)
|
|
199
|
+
return await func(*args, **kwargs)
|
|
201
200
|
|
|
202
201
|
infralo_kwargs = _pop_infralo_kwargs(kwargs)
|
|
203
202
|
metadata: dict = infralo_kwargs.get("_infralo_metadata", {})
|
|
@@ -232,6 +231,125 @@ class Infralo:
|
|
|
232
231
|
return decorator(fn)
|
|
233
232
|
return decorator
|
|
234
233
|
|
|
234
|
+
# ── Approvals ────────────────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
def create_approval(
|
|
237
|
+
self,
|
|
238
|
+
title: str,
|
|
239
|
+
callback_url: str,
|
|
240
|
+
*,
|
|
241
|
+
description: str | None = None,
|
|
242
|
+
review_data: dict[str, Any] | None = None,
|
|
243
|
+
callback_headers: dict[str, str] | None = None,
|
|
244
|
+
client_reference_id: str | None = None,
|
|
245
|
+
trace_id: str | None = None,
|
|
246
|
+
timeout: float = 15.0,
|
|
247
|
+
) -> dict[str, Any]:
|
|
248
|
+
"""
|
|
249
|
+
Create a human-in-the-loop approval request.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
title: Short summary shown to reviewers.
|
|
253
|
+
callback_url: Webhook URL called when the approval request is reviewed.
|
|
254
|
+
description: Detailed context for reviewers.
|
|
255
|
+
review_data: Arbitrary structured payload required for review.
|
|
256
|
+
callback_headers: Custom HTTP headers to pass to callback webhook.
|
|
257
|
+
client_reference_id: Opaque ID for client-side correlation.
|
|
258
|
+
trace_id: Associated trace ID (auto-populated from active trace if omitted).
|
|
259
|
+
timeout: HTTP request timeout in seconds (default: 15.0).
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
dict containing the approval object data returned by the backend.
|
|
263
|
+
"""
|
|
264
|
+
if trace_id is None:
|
|
265
|
+
active_trace = get_active_trace()
|
|
266
|
+
if active_trace is not None:
|
|
267
|
+
trace_id = active_trace.trace_id
|
|
268
|
+
|
|
269
|
+
payload: dict[str, Any] = {
|
|
270
|
+
"title": title,
|
|
271
|
+
"callback_url": callback_url,
|
|
272
|
+
}
|
|
273
|
+
if description is not None:
|
|
274
|
+
payload["description"] = description
|
|
275
|
+
if review_data is not None:
|
|
276
|
+
payload["review_data"] = review_data
|
|
277
|
+
if callback_headers is not None:
|
|
278
|
+
payload["callback_headers"] = callback_headers
|
|
279
|
+
if client_reference_id is not None:
|
|
280
|
+
payload["client_reference_id"] = client_reference_id
|
|
281
|
+
if trace_id is not None:
|
|
282
|
+
payload["trace_id"] = trace_id
|
|
283
|
+
|
|
284
|
+
resp = httpx.post(
|
|
285
|
+
f"{self._exporter.endpoint}/api/v1/approvals",
|
|
286
|
+
headers={
|
|
287
|
+
"Authorization": f"Bearer {self._exporter.api_key}",
|
|
288
|
+
"Content-Type": "application/json",
|
|
289
|
+
},
|
|
290
|
+
json=payload,
|
|
291
|
+
timeout=timeout,
|
|
292
|
+
)
|
|
293
|
+
resp.raise_for_status()
|
|
294
|
+
resp_json = resp.json()
|
|
295
|
+
if isinstance(resp_json, dict) and "data" in resp_json:
|
|
296
|
+
return resp_json["data"]
|
|
297
|
+
return resp_json
|
|
298
|
+
|
|
299
|
+
async def acreate_approval(
|
|
300
|
+
self,
|
|
301
|
+
title: str,
|
|
302
|
+
callback_url: str,
|
|
303
|
+
*,
|
|
304
|
+
description: str | None = None,
|
|
305
|
+
review_data: dict[str, Any] | None = None,
|
|
306
|
+
callback_headers: dict[str, str] | None = None,
|
|
307
|
+
client_reference_id: str | None = None,
|
|
308
|
+
trace_id: str | None = None,
|
|
309
|
+
timeout: float = 15.0,
|
|
310
|
+
) -> dict[str, Any]:
|
|
311
|
+
"""
|
|
312
|
+
Async version of `create_approval`. Create a human-in-the-loop approval request.
|
|
313
|
+
"""
|
|
314
|
+
if trace_id is None:
|
|
315
|
+
active_trace = get_active_trace()
|
|
316
|
+
if active_trace is not None:
|
|
317
|
+
trace_id = active_trace.trace_id
|
|
318
|
+
|
|
319
|
+
payload: dict[str, Any] = {
|
|
320
|
+
"title": title,
|
|
321
|
+
"callback_url": callback_url,
|
|
322
|
+
}
|
|
323
|
+
if description is not None:
|
|
324
|
+
payload["description"] = description
|
|
325
|
+
if review_data is not None:
|
|
326
|
+
payload["review_data"] = review_data
|
|
327
|
+
if callback_headers is not None:
|
|
328
|
+
payload["callback_headers"] = callback_headers
|
|
329
|
+
if client_reference_id is not None:
|
|
330
|
+
payload["client_reference_id"] = client_reference_id
|
|
331
|
+
if trace_id is not None:
|
|
332
|
+
payload["trace_id"] = trace_id
|
|
333
|
+
|
|
334
|
+
async with httpx.AsyncClient() as client:
|
|
335
|
+
resp = await client.post(
|
|
336
|
+
f"{self._exporter.endpoint}/api/v1/approvals",
|
|
337
|
+
headers={
|
|
338
|
+
"Authorization": f"Bearer {self._exporter.api_key}",
|
|
339
|
+
"Content-Type": "application/json",
|
|
340
|
+
},
|
|
341
|
+
json=payload,
|
|
342
|
+
timeout=timeout,
|
|
343
|
+
)
|
|
344
|
+
resp.raise_for_status()
|
|
345
|
+
resp_json = resp.json()
|
|
346
|
+
if isinstance(resp_json, dict) and "data" in resp_json:
|
|
347
|
+
return resp_json["data"]
|
|
348
|
+
return resp_json
|
|
349
|
+
|
|
350
|
+
request_approval = create_approval
|
|
351
|
+
arequest_approval = acreate_approval
|
|
352
|
+
|
|
235
353
|
# ── Utilities ────────────────────────────────────────────────────────────
|
|
236
354
|
|
|
237
355
|
def flush(self, timeout: float = 10.0) -> None:
|
|
@@ -135,6 +135,26 @@ class SpanExporter:
|
|
|
135
135
|
# Reset backoff on success
|
|
136
136
|
self._current_interval = self.flush_interval
|
|
137
137
|
|
|
138
|
+
except httpx.HTTPStatusError as exc:
|
|
139
|
+
status_code = exc.response.status_code
|
|
140
|
+
# Drop spans immediately for permanent client errors (400, 401, 403, 422)
|
|
141
|
+
# except temporary errors like 408 (timeout) and 429 (rate limit)
|
|
142
|
+
if 400 <= status_code < 500 and status_code not in (408, 429):
|
|
143
|
+
logger.error(
|
|
144
|
+
"infralo: fatal client error %d while flushing %d span(s): %s — "
|
|
145
|
+
"dropping spans",
|
|
146
|
+
status_code,
|
|
147
|
+
len(batch),
|
|
148
|
+
exc.response.text,
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
logger.warning(
|
|
152
|
+
"infralo: failed to flush %d span(s): %s — re-enqueueing for retry",
|
|
153
|
+
len(batch),
|
|
154
|
+
exc,
|
|
155
|
+
)
|
|
156
|
+
self._current_interval = min(self._current_interval * 2.0, self._max_backoff)
|
|
157
|
+
self._re_enqueue_spans(batch)
|
|
138
158
|
except Exception as exc:
|
|
139
159
|
logger.warning(
|
|
140
160
|
"infralo: failed to flush %d span(s): %s — re-enqueueing for retry",
|
|
@@ -143,12 +163,20 @@ class SpanExporter:
|
|
|
143
163
|
)
|
|
144
164
|
# Apply exponential backoff
|
|
145
165
|
self._current_interval = min(self._current_interval * 2.0, self._max_backoff)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
166
|
+
self._re_enqueue_spans(batch)
|
|
167
|
+
|
|
168
|
+
def _re_enqueue_spans(self, batch: list[ToolSpanData]) -> None:
|
|
169
|
+
"""Best-effort re-enqueue with max retry attempt checks (limit: 5)."""
|
|
170
|
+
for span in batch:
|
|
171
|
+
span.attempts += 1
|
|
172
|
+
if span.attempts > 5:
|
|
173
|
+
logger.error(
|
|
174
|
+
"infralo: dropping span %s after %d failed attempts",
|
|
175
|
+
span.span_id,
|
|
176
|
+
span.attempts - 1,
|
|
177
|
+
)
|
|
178
|
+
continue
|
|
179
|
+
try:
|
|
180
|
+
self._queue.put_nowait(span)
|
|
181
|
+
except queue.Full:
|
|
182
|
+
logger.warning("infralo: queue full during retry — dropping span %s", span.span_id)
|
|
@@ -38,6 +38,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
38
38
|
import httpx
|
|
39
39
|
|
|
40
40
|
if TYPE_CHECKING:
|
|
41
|
+
from infralo.client import Infralo
|
|
41
42
|
from infralo.exporter import SpanExporter
|
|
42
43
|
from infralo.span import ToolSpan
|
|
43
44
|
|
|
@@ -82,6 +83,7 @@ class Trace:
|
|
|
82
83
|
trace_id: str,
|
|
83
84
|
session_id: str,
|
|
84
85
|
exporter: "SpanExporter",
|
|
86
|
+
client: "Infralo | None" = None,
|
|
85
87
|
) -> None:
|
|
86
88
|
self.trace_id = trace_id
|
|
87
89
|
self.session_id = session_id
|
|
@@ -91,6 +93,7 @@ class Trace:
|
|
|
91
93
|
self.last_tool_span_id: str | None = None
|
|
92
94
|
|
|
93
95
|
self._exporter = exporter
|
|
96
|
+
self._client = client
|
|
94
97
|
self._token: Token | None = None
|
|
95
98
|
|
|
96
99
|
# Thread-safe monotone sequence counter.
|
|
@@ -384,6 +387,67 @@ class Trace:
|
|
|
384
387
|
async with self.parallel():
|
|
385
388
|
return await asyncio.gather(*coros)
|
|
386
389
|
|
|
390
|
+
# ── Approvals ────────────────────────────────────────────────────────────
|
|
391
|
+
|
|
392
|
+
def create_approval(
|
|
393
|
+
self,
|
|
394
|
+
title: str,
|
|
395
|
+
callback_url: str,
|
|
396
|
+
*,
|
|
397
|
+
description: str | None = None,
|
|
398
|
+
review_data: dict[str, Any] | None = None,
|
|
399
|
+
callback_headers: dict[str, str] | None = None,
|
|
400
|
+
client_reference_id: str | None = None,
|
|
401
|
+
timeout: float = 15.0,
|
|
402
|
+
) -> dict[str, Any]:
|
|
403
|
+
"""
|
|
404
|
+
Create a human-in-the-loop approval request associated with this trace.
|
|
405
|
+
Delegates directly to the parent Infralo client instance.
|
|
406
|
+
"""
|
|
407
|
+
if self._client is None:
|
|
408
|
+
raise RuntimeError("Trace instance is not bound to an Infralo client instance.")
|
|
409
|
+
return self._client.create_approval(
|
|
410
|
+
title=title,
|
|
411
|
+
callback_url=callback_url,
|
|
412
|
+
description=description,
|
|
413
|
+
review_data=review_data,
|
|
414
|
+
callback_headers=callback_headers,
|
|
415
|
+
client_reference_id=client_reference_id,
|
|
416
|
+
trace_id=self.trace_id,
|
|
417
|
+
timeout=timeout,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
async def acreate_approval(
|
|
421
|
+
self,
|
|
422
|
+
title: str,
|
|
423
|
+
callback_url: str,
|
|
424
|
+
*,
|
|
425
|
+
description: str | None = None,
|
|
426
|
+
review_data: dict[str, Any] | None = None,
|
|
427
|
+
callback_headers: dict[str, str] | None = None,
|
|
428
|
+
client_reference_id: str | None = None,
|
|
429
|
+
timeout: float = 15.0,
|
|
430
|
+
) -> dict[str, Any]:
|
|
431
|
+
"""
|
|
432
|
+
Async version of `create_approval`. Create an approval request associated with this trace.
|
|
433
|
+
Delegates directly to the parent Infralo client instance.
|
|
434
|
+
"""
|
|
435
|
+
if self._client is None:
|
|
436
|
+
raise RuntimeError("Trace instance is not bound to an Infralo client instance.")
|
|
437
|
+
return await self._client.acreate_approval(
|
|
438
|
+
title=title,
|
|
439
|
+
callback_url=callback_url,
|
|
440
|
+
description=description,
|
|
441
|
+
review_data=review_data,
|
|
442
|
+
callback_headers=callback_headers,
|
|
443
|
+
client_reference_id=client_reference_id,
|
|
444
|
+
trace_id=self.trace_id,
|
|
445
|
+
timeout=timeout,
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
request_approval = create_approval
|
|
449
|
+
arequest_approval = acreate_approval
|
|
450
|
+
|
|
387
451
|
|
|
388
452
|
_patched_httpx = False
|
|
389
453
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import json
|
|
2
3
|
import logging
|
|
3
4
|
|
|
4
5
|
import pytest
|
|
@@ -223,3 +224,102 @@ def test_tool_call_first_before_llm():
|
|
|
223
224
|
assert "x-infralo-parent-span-id" not in headers
|
|
224
225
|
|
|
225
226
|
infralo.shutdown()
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def test_create_approval_sync():
|
|
230
|
+
infralo = Infralo(api_key="vk_test", endpoint="http://localhost:12345", flush_interval=60)
|
|
231
|
+
with respx.mock:
|
|
232
|
+
route = respx.post("http://localhost:12345/api/v1/approvals").mock(
|
|
233
|
+
return_value=Response(
|
|
234
|
+
200,
|
|
235
|
+
json={
|
|
236
|
+
"message": "Approval request created successfully",
|
|
237
|
+
"data": {
|
|
238
|
+
"approval_id": "appr_123",
|
|
239
|
+
"title": "Deploy to prod",
|
|
240
|
+
"status": "pending",
|
|
241
|
+
"trace_id": "trace-99",
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
with infralo.start_trace(trace_id="trace-99"):
|
|
248
|
+
res = infralo.create_approval(
|
|
249
|
+
title="Deploy to prod",
|
|
250
|
+
callback_url="https://example.com/callback",
|
|
251
|
+
description="Deploy feature X",
|
|
252
|
+
review_data={"env": "prod"},
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
assert route.called
|
|
256
|
+
req_json = json.loads(route.calls.last.request.content)
|
|
257
|
+
assert req_json["title"] == "Deploy to prod"
|
|
258
|
+
assert req_json["callback_url"] == "https://example.com/callback"
|
|
259
|
+
assert req_json["trace_id"] == "trace-99"
|
|
260
|
+
assert req_json["review_data"] == {"env": "prod"}
|
|
261
|
+
|
|
262
|
+
assert res["approval_id"] == "appr_123"
|
|
263
|
+
assert res["status"] == "pending"
|
|
264
|
+
|
|
265
|
+
infralo.shutdown()
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@pytest.mark.asyncio
|
|
269
|
+
async def test_create_approval_async():
|
|
270
|
+
infralo = Infralo(api_key="vk_test", endpoint="http://localhost:12345", flush_interval=60)
|
|
271
|
+
with respx.mock:
|
|
272
|
+
route = respx.post("http://localhost:12345/api/v1/approvals").mock(
|
|
273
|
+
return_value=Response(
|
|
274
|
+
200,
|
|
275
|
+
json={
|
|
276
|
+
"message": "Approval request created successfully",
|
|
277
|
+
"data": {
|
|
278
|
+
"approval_id": "appr_456",
|
|
279
|
+
"title": "Publish blog",
|
|
280
|
+
"status": "pending",
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
res = await infralo.acreate_approval(
|
|
287
|
+
title="Publish blog",
|
|
288
|
+
callback_url="https://example.com/webhook",
|
|
289
|
+
client_reference_id="ref_789",
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
assert route.called
|
|
293
|
+
req_json = json.loads(route.calls.last.request.content)
|
|
294
|
+
assert req_json["title"] == "Publish blog"
|
|
295
|
+
assert req_json["client_reference_id"] == "ref_789"
|
|
296
|
+
assert res["approval_id"] == "appr_456"
|
|
297
|
+
|
|
298
|
+
infralo.shutdown()
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def test_trace_create_approval():
|
|
302
|
+
infralo = Infralo(api_key="vk_test", endpoint="http://localhost:12345", flush_interval=60)
|
|
303
|
+
with respx.mock:
|
|
304
|
+
route = respx.post("http://localhost:12345/api/v1/approvals").mock(
|
|
305
|
+
return_value=Response(
|
|
306
|
+
200,
|
|
307
|
+
json={
|
|
308
|
+
"message": "Approval request created successfully",
|
|
309
|
+
"data": {"approval_id": "appr_789"},
|
|
310
|
+
},
|
|
311
|
+
)
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
with infralo.start_trace(trace_id="trace-abc") as trace:
|
|
315
|
+
res = trace.create_approval(
|
|
316
|
+
title="Action approval",
|
|
317
|
+
callback_url="https://example.com/cb",
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
assert route.called
|
|
321
|
+
req_json = json.loads(route.calls.last.request.content)
|
|
322
|
+
assert req_json["trace_id"] == "trace-abc"
|
|
323
|
+
assert res["approval_id"] == "appr_789"
|
|
324
|
+
|
|
325
|
+
infralo.shutdown()
|
|
@@ -315,15 +315,6 @@ wheels = [
|
|
|
315
315
|
{ url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" },
|
|
316
316
|
]
|
|
317
317
|
|
|
318
|
-
[[package]]
|
|
319
|
-
name = "backports-tarfile"
|
|
320
|
-
version = "1.2.0"
|
|
321
|
-
source = { registry = "https://pypi.org/simple" }
|
|
322
|
-
sdist = { url = "https://files.pythonhosted.org/packages/86/72/cd9b395f25e290e633655a100af28cb253e4393396264a98bd5f5951d50f/backports_tarfile-1.2.0.tar.gz", hash = "sha256:d75e02c268746e1b8144c278978b6e98e85de6ad16f8e4b0844a154557eca991", size = 86406, upload-time = "2024-05-28T17:01:54.731Z" }
|
|
323
|
-
wheels = [
|
|
324
|
-
{ url = "https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl", hash = "sha256:77e284d754527b01fb1e6fa8a1afe577858ebe4e9dad8919e34c862cb399bc34", size = 30181, upload-time = "2024-05-28T17:01:53.112Z" },
|
|
325
|
-
]
|
|
326
|
-
|
|
327
318
|
[[package]]
|
|
328
319
|
name = "bcrypt"
|
|
329
320
|
version = "5.0.0"
|
|
@@ -878,15 +869,6 @@ wheels = [
|
|
|
878
869
|
{ url = "https://files.pythonhosted.org/packages/a7/5f/ed01f9a3cdffbd5a008556fc7b2a08ddb1cc6ace7effa7340604b1d16699/docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b", size = 22484, upload-time = "2026-04-14T04:09:18.638Z" },
|
|
879
870
|
]
|
|
880
871
|
|
|
881
|
-
[[package]]
|
|
882
|
-
name = "docutils"
|
|
883
|
-
version = "0.23"
|
|
884
|
-
source = { registry = "https://pypi.org/simple" }
|
|
885
|
-
sdist = { url = "https://files.pythonhosted.org/packages/39/a4/5180d9afc57e8fca05601dd652bdff19604c218814037fe90ffc7625a50a/docutils-0.23.tar.gz", hash = "sha256:746f5060322511280a1e50eb76846ed6bf2342984b2ac04dc42caa1a8d78799e", size = 2303823, upload-time = "2026-05-27T17:41:06.934Z" }
|
|
886
|
-
wheels = [
|
|
887
|
-
{ url = "https://files.pythonhosted.org/packages/32/91/30151a39f7570f448ed84529390628a651d7f27c87d73c9b887f8189695e/docutils-0.23-py3-none-any.whl", hash = "sha256:25d013af9bf23bc1c7b2b093dff4208166c53a94786c9e447808335ef1185fea", size = 634701, upload-time = "2026-05-27T17:40:58.442Z" },
|
|
888
|
-
]
|
|
889
|
-
|
|
890
872
|
[[package]]
|
|
891
873
|
name = "durationpy"
|
|
892
874
|
version = "0.10"
|
|
@@ -1485,18 +1467,6 @@ wheels = [
|
|
|
1485
1467
|
{ url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" },
|
|
1486
1468
|
]
|
|
1487
1469
|
|
|
1488
|
-
[[package]]
|
|
1489
|
-
name = "id"
|
|
1490
|
-
version = "1.6.1"
|
|
1491
|
-
source = { registry = "https://pypi.org/simple" }
|
|
1492
|
-
dependencies = [
|
|
1493
|
-
{ name = "urllib3" },
|
|
1494
|
-
]
|
|
1495
|
-
sdist = { url = "https://files.pythonhosted.org/packages/6d/04/c2156091427636080787aac190019dc64096e56a23b7364d3c1764ee3a06/id-1.6.1.tar.gz", hash = "sha256:d0732d624fb46fd4e7bc4e5152f00214450953b9e772c182c1c22964def1a069", size = 18088, upload-time = "2026-02-04T16:19:41.26Z" }
|
|
1496
|
-
wheels = [
|
|
1497
|
-
{ url = "https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl", hash = "sha256:f5ec41ed2629a508f5d0988eda142e190c9c6da971100612c4de9ad9f9b237ca", size = 14689, upload-time = "2026-02-04T16:19:40.051Z" },
|
|
1498
|
-
]
|
|
1499
|
-
|
|
1500
1470
|
[[package]]
|
|
1501
1471
|
name = "idna"
|
|
1502
1472
|
version = "3.18"
|
|
@@ -1511,7 +1481,7 @@ name = "importlib-metadata"
|
|
|
1511
1481
|
version = "9.0.0"
|
|
1512
1482
|
source = { registry = "https://pypi.org/simple" }
|
|
1513
1483
|
dependencies = [
|
|
1514
|
-
{ name = "zipp", marker = "python_full_version < '3.
|
|
1484
|
+
{ name = "zipp", marker = "python_full_version < '3.11'" },
|
|
1515
1485
|
]
|
|
1516
1486
|
sdist = { url = "https://files.pythonhosted.org/packages/a9/01/15bb152d77b21318514a96f43af312635eb2500c96b55398d020c93d86ea/importlib_metadata-9.0.0.tar.gz", hash = "sha256:a4f57ab599e6a2e3016d7595cfd72eb4661a5106e787a95bcc90c7105b831efc", size = 56405, upload-time = "2026-03-20T06:42:56.999Z" }
|
|
1517
1487
|
wheels = [
|
|
@@ -1529,7 +1499,7 @@ wheels = [
|
|
|
1529
1499
|
|
|
1530
1500
|
[[package]]
|
|
1531
1501
|
name = "infralo"
|
|
1532
|
-
version = "0.1.0.
|
|
1502
|
+
version = "0.1.0.dev2"
|
|
1533
1503
|
source = { editable = "." }
|
|
1534
1504
|
dependencies = [
|
|
1535
1505
|
{ name = "httpx" },
|
|
@@ -1552,13 +1522,11 @@ langgraph = [
|
|
|
1552
1522
|
|
|
1553
1523
|
[package.dev-dependencies]
|
|
1554
1524
|
dev = [
|
|
1555
|
-
{ name = "build" },
|
|
1556
1525
|
{ name = "prek" },
|
|
1557
1526
|
{ name = "pytest" },
|
|
1558
1527
|
{ name = "pytest-asyncio" },
|
|
1559
1528
|
{ name = "respx" },
|
|
1560
1529
|
{ name = "ruff" },
|
|
1561
|
-
{ name = "twine" },
|
|
1562
1530
|
]
|
|
1563
1531
|
|
|
1564
1532
|
[package.metadata]
|
|
@@ -1574,13 +1542,11 @@ provides-extras = ["agno", "langgraph", "crewai", "google-adk"]
|
|
|
1574
1542
|
|
|
1575
1543
|
[package.metadata.requires-dev]
|
|
1576
1544
|
dev = [
|
|
1577
|
-
{ name = "build", specifier = ">=1.5.0" },
|
|
1578
1545
|
{ name = "prek", specifier = "==0.3.11" },
|
|
1579
1546
|
{ name = "pytest", specifier = ">=8" },
|
|
1580
1547
|
{ name = "pytest-asyncio", specifier = ">=0.23" },
|
|
1581
1548
|
{ name = "respx", specifier = ">=0.21" },
|
|
1582
1549
|
{ name = "ruff", specifier = "==0.15.5" },
|
|
1583
|
-
{ name = "twine", specifier = ">=6.2.0" },
|
|
1584
1550
|
]
|
|
1585
1551
|
|
|
1586
1552
|
[[package]]
|
|
@@ -1614,51 +1580,6 @@ wheels = [
|
|
|
1614
1580
|
{ url = "https://files.pythonhosted.org/packages/80/8d/f668a30fff4d25b36533355e23aeb0b5724df4628eb974124ed64b7bcf8d/instructor-1.15.4-py3-none-any.whl", hash = "sha256:00e0ecda80fd9746fb6d082d3f9641e193adb1d8849f0775f91519a82aeff968", size = 252522, upload-time = "2026-06-28T07:36:36.863Z" },
|
|
1615
1581
|
]
|
|
1616
1582
|
|
|
1617
|
-
[[package]]
|
|
1618
|
-
name = "jaraco-classes"
|
|
1619
|
-
version = "3.4.0"
|
|
1620
|
-
source = { registry = "https://pypi.org/simple" }
|
|
1621
|
-
dependencies = [
|
|
1622
|
-
{ name = "more-itertools" },
|
|
1623
|
-
]
|
|
1624
|
-
sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780, upload-time = "2024-03-31T07:27:36.643Z" }
|
|
1625
|
-
wheels = [
|
|
1626
|
-
{ url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777, upload-time = "2024-03-31T07:27:34.792Z" },
|
|
1627
|
-
]
|
|
1628
|
-
|
|
1629
|
-
[[package]]
|
|
1630
|
-
name = "jaraco-context"
|
|
1631
|
-
version = "6.1.2"
|
|
1632
|
-
source = { registry = "https://pypi.org/simple" }
|
|
1633
|
-
dependencies = [
|
|
1634
|
-
{ name = "backports-tarfile", marker = "python_full_version < '3.12'" },
|
|
1635
|
-
]
|
|
1636
|
-
sdist = { url = "https://files.pythonhosted.org/packages/af/50/4763cd07e722bb6285316d390a164bc7e479db9d90daa769f22578f698b4/jaraco_context-6.1.2.tar.gz", hash = "sha256:f1a6c9d391e661cc5b8d39861ff077a7dc24dc23833ccee564b234b81c82dfe3", size = 16801, upload-time = "2026-03-20T22:13:33.922Z" }
|
|
1637
|
-
wheels = [
|
|
1638
|
-
{ url = "https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl", hash = "sha256:bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535", size = 7871, upload-time = "2026-03-20T22:13:32.808Z" },
|
|
1639
|
-
]
|
|
1640
|
-
|
|
1641
|
-
[[package]]
|
|
1642
|
-
name = "jaraco-functools"
|
|
1643
|
-
version = "4.6.0"
|
|
1644
|
-
source = { registry = "https://pypi.org/simple" }
|
|
1645
|
-
dependencies = [
|
|
1646
|
-
{ name = "more-itertools" },
|
|
1647
|
-
]
|
|
1648
|
-
sdist = { url = "https://files.pythonhosted.org/packages/6c/1f/c23395957d41ccf27c4e535c3d334c4051e5395b3752057ba4cbaec35c56/jaraco_functools-4.6.0.tar.gz", hash = "sha256:880c577ec9720b3a052d5bc611fb9f2269b3d87902ef42440df443b88e443280", size = 20837, upload-time = "2026-07-14T01:28:02.544Z" }
|
|
1649
|
-
wheels = [
|
|
1650
|
-
{ url = "https://files.pythonhosted.org/packages/02/36/ecc85bc96c273dc8a11273ed4782272975e6338d4a3e9228621175edf0e3/jaraco_functools-4.6.0-py3-none-any.whl", hash = "sha256:99e3dc0060c5cbe8fcd1cdb36258e2a65ca40f1566b2033b12abb1bb44dd3c30", size = 11677, upload-time = "2026-07-14T01:28:01.59Z" },
|
|
1651
|
-
]
|
|
1652
|
-
|
|
1653
|
-
[[package]]
|
|
1654
|
-
name = "jeepney"
|
|
1655
|
-
version = "0.9.0"
|
|
1656
|
-
source = { registry = "https://pypi.org/simple" }
|
|
1657
|
-
sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758, upload-time = "2025-02-27T18:51:01.684Z" }
|
|
1658
|
-
wheels = [
|
|
1659
|
-
{ url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" },
|
|
1660
|
-
]
|
|
1661
|
-
|
|
1662
1583
|
[[package]]
|
|
1663
1584
|
name = "jinja2"
|
|
1664
1585
|
version = "3.1.6"
|
|
@@ -1871,24 +1792,6 @@ wheels = [
|
|
|
1871
1792
|
{ url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
|
|
1872
1793
|
]
|
|
1873
1794
|
|
|
1874
|
-
[[package]]
|
|
1875
|
-
name = "keyring"
|
|
1876
|
-
version = "25.7.0"
|
|
1877
|
-
source = { registry = "https://pypi.org/simple" }
|
|
1878
|
-
dependencies = [
|
|
1879
|
-
{ name = "importlib-metadata", marker = "python_full_version < '3.12'" },
|
|
1880
|
-
{ name = "jaraco-classes" },
|
|
1881
|
-
{ name = "jaraco-context" },
|
|
1882
|
-
{ name = "jaraco-functools" },
|
|
1883
|
-
{ name = "jeepney", marker = "sys_platform == 'linux'" },
|
|
1884
|
-
{ name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
|
|
1885
|
-
{ name = "secretstorage", marker = "sys_platform == 'linux'" },
|
|
1886
|
-
]
|
|
1887
|
-
sdist = { url = "https://files.pythonhosted.org/packages/43/4b/674af6ef2f97d56f0ab5153bf0bfa28ccb6c3ed4d1babf4305449668807b/keyring-25.7.0.tar.gz", hash = "sha256:fe01bd85eb3f8fb3dd0405defdeac9a5b4f6f0439edbb3149577f244a2e8245b", size = 63516, upload-time = "2025-11-16T16:26:09.482Z" }
|
|
1888
|
-
wheels = [
|
|
1889
|
-
{ url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" },
|
|
1890
|
-
]
|
|
1891
|
-
|
|
1892
1795
|
[[package]]
|
|
1893
1796
|
name = "kubernetes"
|
|
1894
1797
|
version = "36.0.3"
|
|
@@ -2361,15 +2264,6 @@ wheels = [
|
|
|
2361
2264
|
{ url = "https://files.pythonhosted.org/packages/a0/0f/59204bf136d1201f8d7884cfbaf7498c5b4674e87a4c693f9bde63741ce1/mmh3-5.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:dfd51b4c56b673dfbc43d7d27ef857dd91124801e2806c69bb45585ce0fa019b", size = 40391, upload-time = "2026-03-05T15:55:56.697Z" },
|
|
2362
2265
|
]
|
|
2363
2266
|
|
|
2364
|
-
[[package]]
|
|
2365
|
-
name = "more-itertools"
|
|
2366
|
-
version = "11.1.0"
|
|
2367
|
-
source = { registry = "https://pypi.org/simple" }
|
|
2368
|
-
sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" }
|
|
2369
|
-
wheels = [
|
|
2370
|
-
{ url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" },
|
|
2371
|
-
]
|
|
2372
|
-
|
|
2373
2267
|
[[package]]
|
|
2374
2268
|
name = "mpmath"
|
|
2375
2269
|
version = "1.3.0"
|
|
@@ -2517,40 +2411,6 @@ wheels = [
|
|
|
2517
2411
|
{ url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" },
|
|
2518
2412
|
]
|
|
2519
2413
|
|
|
2520
|
-
[[package]]
|
|
2521
|
-
name = "nh3"
|
|
2522
|
-
version = "0.3.6"
|
|
2523
|
-
source = { registry = "https://pypi.org/simple" }
|
|
2524
|
-
sdist = { url = "https://files.pythonhosted.org/packages/5e/1b/ef84624f14954d270f74060a19fc550dd4f06656399447569afb584d8c06/nh3-0.3.6.tar.gz", hash = "sha256:f3736c9dd3d1856f80cd031715b84ca75cda2bbb1ac802c3da26bfce590838d7", size = 24684, upload-time = "2026-06-22T00:47:02.008Z" }
|
|
2525
|
-
wheels = [
|
|
2526
|
-
{ url = "https://files.pythonhosted.org/packages/99/3e/6506aa4f23dc7b7993a2d0a45dca3ce864ec48380adfe15a173e643c63e8/nh3-0.3.6-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2411e8c3cee81a1ddd62c2a5d50585c28aa5566d373ad1db92536b95ddb24ef2", size = 1421679, upload-time = "2026-06-22T00:46:20.248Z" },
|
|
2527
|
-
{ url = "https://files.pythonhosted.org/packages/e3/e1/e96e7864a7a53bd6b6fab7e9632467382a2a2c1f3fed951918ad131542fb/nh3-0.3.6-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e196fa70c2ff2eb4de7d3df3108f8f358c1d69dff20d45b11f20a5aa227ffb6d", size = 792570, upload-time = "2026-06-22T00:46:22.179Z" },
|
|
2528
|
-
{ url = "https://files.pythonhosted.org/packages/59/62/5b6108bedaef2b2637fed04c87bdbcb5967b9961758b41f0e466ef22a022/nh3-0.3.6-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:34d2b0d934156b87ee114f599a3ba9b8b9e17b5d79652ba3a13fa50903de965e", size = 842243, upload-time = "2026-06-22T00:46:23.801Z" },
|
|
2529
|
-
{ url = "https://files.pythonhosted.org/packages/4b/4a/526f199626bfcb496bc01a268051b44737962005553b158e985ed7e64865/nh3-0.3.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f2f14b7ae1fca99c4a66c981aac3974e7fbc1ca30a12673d223ae1df76680917", size = 1001468, upload-time = "2026-06-22T00:46:25.481Z" },
|
|
2530
|
-
{ url = "https://files.pythonhosted.org/packages/49/09/0d8e3101636d9ad88cdefb2914e764cb8e876ebdbb4286bfc251277d9c67/nh3-0.3.6-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:889932a97fb4abb6f95fef1914c0d269ebfb60011e67121c1163059b9449dbb4", size = 1082933, upload-time = "2026-06-22T00:46:27.15Z" },
|
|
2531
|
-
{ url = "https://files.pythonhosted.org/packages/09/a1/ea83abe738a3fbaa203dfdb836ca7cbab0e7e9609faaee4fe1d4652599c0/nh3-0.3.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:edb2b4a1a27523e6cc7c417f8d21ce3d005243548b93e56b762b66b0c7f589f9", size = 1043120, upload-time = "2026-06-22T00:46:28.89Z" },
|
|
2532
|
-
{ url = "https://files.pythonhosted.org/packages/66/69/0654482b8635012fbae67826bd6c381abb05d841ac7388b9b4666300fdad/nh3-0.3.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43bc1ed3fa0716295fabee29ba42b2667e4a51d140b0a68e092170a765474fa6", size = 1023824, upload-time = "2026-06-22T00:46:30.453Z" },
|
|
2533
|
-
{ url = "https://files.pythonhosted.org/packages/ed/a6/1f7285ffadc8307c4dbeb08d21b920536d5117785056d1079e998c4dfa44/nh3-0.3.6-cp314-cp314t-win32.whl", hash = "sha256:597a8e843bea00b2eb5520658dc24a9bb032e7fc9e7c2c0c4cd29420220c9796", size = 599253, upload-time = "2026-06-22T00:46:32.072Z" },
|
|
2534
|
-
{ url = "https://files.pythonhosted.org/packages/36/ea/5542f3c45da4c00290d9d67a65e996702e23e613c4b627de3e09cb9fe357/nh3-0.3.6-cp314-cp314t-win_amd64.whl", hash = "sha256:4713502748f564fee0633b37b3403783ce0a3af3a3d148ad91025a5bdadb7bc6", size = 612553, upload-time = "2026-06-22T00:46:33.53Z" },
|
|
2535
|
-
{ url = "https://files.pythonhosted.org/packages/66/35/26bd47e6af5915a628281dccdac354ddf4e32f7397047894270acd8c9870/nh3-0.3.6-cp314-cp314t-win_arm64.whl", hash = "sha256:69bbb92865a693d909db3a700d3c01537533844d0948c1e9323561ce06ecda41", size = 595151, upload-time = "2026-06-22T00:46:34.878Z" },
|
|
2536
|
-
{ url = "https://files.pythonhosted.org/packages/f3/ab/a7653bce9a3b204be6a6931767a9e23595807bb84790ce6685e4d7e5bd08/nh3-0.3.6-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:a43ebd7543555c3ac1bc353023d0794e75cb76f6f18f19c32e95441496c0cc25", size = 1443564, upload-time = "2026-06-22T00:46:36.66Z" },
|
|
2537
|
-
{ url = "https://files.pythonhosted.org/packages/41/21/e1084ab18eb589506335c7c7576f2d4643e9a0c0e33983ef0e549a256b96/nh3-0.3.6-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1b160831c9cdb06a6c79c2f9cdb11386602938f9af260d1c457a85add4f6f69", size = 838002, upload-time = "2026-06-22T00:46:38.101Z" },
|
|
2538
|
-
{ url = "https://files.pythonhosted.org/packages/b0/94/f48d08e6f72a406300fa11d8acd929fea1a80d4bf750fa292cb10785f126/nh3-0.3.6-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d14bf7982e7a77c0c775634c29c07ce08b38a046df73e1c1f139b3e82f18a38e", size = 823045, upload-time = "2026-06-22T00:46:39.495Z" },
|
|
2539
|
-
{ url = "https://files.pythonhosted.org/packages/25/bb/431615ba1d1d3eb63cde0f974f2114edf863a8a3f6049a12fed23fc241d3/nh3-0.3.6-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:44673b27010051ab5a5e438a86ec31bbda61d4a77d7e900af6b7be3037c1abae", size = 1093171, upload-time = "2026-06-22T00:46:41.21Z" },
|
|
2540
|
-
{ url = "https://files.pythonhosted.org/packages/0e/24/a0d80182a18919665fefd19c1c06f1d1df1c9a6455d0252de40c034a0bc3/nh3-0.3.6-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6b7beece07525dc6e6b0fc2f104442de2ba328360ad00e50cbe2e1fd620447d", size = 1049217, upload-time = "2026-06-22T00:46:42.804Z" },
|
|
2541
|
-
{ url = "https://files.pythonhosted.org/packages/0a/13/6f1e302ca674ac74362e150848ad56a1be5145391204f74facdb8e94df12/nh3-0.3.6-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:455469a29951edc92bc48b47ac2281c3f2609e6c4f6a047056449f8c2c23facf", size = 917372, upload-time = "2026-06-22T00:46:44.495Z" },
|
|
2542
|
-
{ url = "https://files.pythonhosted.org/packages/5b/67/314f6151bad77a93d751978a344033e1fc890822f05f0416079338e34231/nh3-0.3.6-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:905f877dc66dd7aea4a76e54bcb26acb5ff8216f720c0017ccf63e0e6035698e", size = 806699, upload-time = "2026-06-22T00:46:45.99Z" },
|
|
2543
|
-
{ url = "https://files.pythonhosted.org/packages/3c/a6/bfaa00046e58603507dcfc266c4778e3ab7adf68a5dedd73b6274b8d9314/nh3-0.3.6-cp38-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:25c733bee928530556b1db0ea46c52cf5aa686146e38e60a6fc7cb801ef91cec", size = 835165, upload-time = "2026-06-22T00:46:47.617Z" },
|
|
2544
|
-
{ url = "https://files.pythonhosted.org/packages/30/a8/fb2c38845efb703a9173bffdfc745fc64d2b0e55cfc73a3647d2f028250c/nh3-0.3.6-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2f90d9a0cfdbee218994fdaaeeb5a0fde62d08f35e4eef0378ec1e2200172fd0", size = 858282, upload-time = "2026-06-22T00:46:49.276Z" },
|
|
2545
|
-
{ url = "https://files.pythonhosted.org/packages/68/17/06e72a18ee9b572914447338237ca7eb164c0df901f141bc10d1282247a2/nh3-0.3.6-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:82ca5bf427ad1b216b65ede1a2e2d87dc49bec417ceba0f297213107d3cd9d78", size = 1014328, upload-time = "2026-06-22T00:46:51.026Z" },
|
|
2546
|
-
{ url = "https://files.pythonhosted.org/packages/11/f9/3966c61455668c08853bf5e33b4bed93c421f3194ce4de896dc248d6f6ce/nh3-0.3.6-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:f5ed5fe84aee7f39db95c214a7421bf0499fbf500fec6d86a4e29bfc37971438", size = 1098207, upload-time = "2026-06-22T00:46:52.674Z" },
|
|
2547
|
-
{ url = "https://files.pythonhosted.org/packages/19/d3/479cb4ae440424825735d60525b53e3c77fd60fd6e6afc0e984f00eb0178/nh3-0.3.6-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:082675ff87b9385ec430ffe6d5847ba7456cc39b73720cd4add472f9f4cffd56", size = 1056961, upload-time = "2026-06-22T00:46:54.335Z" },
|
|
2548
|
-
{ url = "https://files.pythonhosted.org/packages/17/0c/6cdb5ee1e127be50dc8391e54bddc1f64e87bf4bfad0c55633320e2e02db/nh3-0.3.6-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36d06341bd501240d320f5942481ed5e6846136b666e1ba4faf802b78ebc875f", size = 1033829, upload-time = "2026-06-22T00:46:56.258Z" },
|
|
2549
|
-
{ url = "https://files.pythonhosted.org/packages/e9/55/9de666ad975d6ccd77d799ea0add55ee2347aa81286ce21b2a97c070746b/nh3-0.3.6-cp38-abi3-win32.whl", hash = "sha256:5276ef17bdba9ad8040575c74072008b13aae429436e9d0429e718bb5f90f4da", size = 609081, upload-time = "2026-06-22T00:46:57.665Z" },
|
|
2550
|
-
{ url = "https://files.pythonhosted.org/packages/82/fa/2b5d684e3edf1e81bfd02d298c78c3e3da77ca1d8a2be3183a79544a7548/nh3-0.3.6-cp38-abi3-win_amd64.whl", hash = "sha256:f338ac7d594c067679f1e99b4f5ec3906842979560f9d8f15d6bdfa39a353b10", size = 624461, upload-time = "2026-06-22T00:46:59.163Z" },
|
|
2551
|
-
{ url = "https://files.pythonhosted.org/packages/7b/e5/7cafee2f0413ca4cb0ef3bd111e94d408a48810008b283ad8aee00dd1809/nh3-0.3.6-cp38-abi3-win_arm64.whl", hash = "sha256:69f365963f63a1e9bff53bdbb3c542c7c2efed3e163c9d5d83a772a2ac468c21", size = 603060, upload-time = "2026-06-22T00:47:00.596Z" },
|
|
2552
|
-
]
|
|
2553
|
-
|
|
2554
2414
|
[[package]]
|
|
2555
2415
|
name = "numpy"
|
|
2556
2416
|
version = "2.2.6"
|
|
@@ -4099,15 +3959,6 @@ wheels = [
|
|
|
4099
3959
|
{ url = "https://files.pythonhosted.org/packages/23/ed/4532e9388e65fa16b46776ef47ad631a64eda1631884488af707666350ed/pywin32-312-cp315-cp315-win_arm64.whl", hash = "sha256:a8597d28f267b39074aef51fa593530082b39cbe5a074226096857b1fed2dfb9", size = 6840337, upload-time = "2026-06-04T07:49:57.531Z" },
|
|
4100
3960
|
]
|
|
4101
3961
|
|
|
4102
|
-
[[package]]
|
|
4103
|
-
name = "pywin32-ctypes"
|
|
4104
|
-
version = "0.2.3"
|
|
4105
|
-
source = { registry = "https://pypi.org/simple" }
|
|
4106
|
-
sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" }
|
|
4107
|
-
wheels = [
|
|
4108
|
-
{ url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" },
|
|
4109
|
-
]
|
|
4110
|
-
|
|
4111
3962
|
[[package]]
|
|
4112
3963
|
name = "pyyaml"
|
|
4113
3964
|
version = "6.0.3"
|
|
@@ -4172,20 +4023,6 @@ wheels = [
|
|
|
4172
4023
|
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" },
|
|
4173
4024
|
]
|
|
4174
4025
|
|
|
4175
|
-
[[package]]
|
|
4176
|
-
name = "readme-renderer"
|
|
4177
|
-
version = "45.0"
|
|
4178
|
-
source = { registry = "https://pypi.org/simple" }
|
|
4179
|
-
dependencies = [
|
|
4180
|
-
{ name = "docutils" },
|
|
4181
|
-
{ name = "nh3" },
|
|
4182
|
-
{ name = "pygments" },
|
|
4183
|
-
]
|
|
4184
|
-
sdist = { url = "https://files.pythonhosted.org/packages/02/51/d3a6ea424652c60f05600d8c2e01a55c913755e7cdad64afabbd1aa16f44/readme_renderer-45.0.tar.gz", hash = "sha256:030a8fac74904f8fba11ad1bb6964e3f76e896dc7e5e71f16af190c9056696d1", size = 36172, upload-time = "2026-06-09T21:05:17.37Z" }
|
|
4185
|
-
wheels = [
|
|
4186
|
-
{ url = "https://files.pythonhosted.org/packages/97/1b/295bf2fa3e740131778065e5ffa2c481f0e7210182d408e9a2c244ff5b0c/readme_renderer-45.0-py3-none-any.whl", hash = "sha256:3385ed220117104a2bceb4a9dac8c5fdf6d1f96890d7ea2a9c7174fd5c84091f", size = 14134, upload-time = "2026-06-09T21:05:15.85Z" },
|
|
4187
|
-
]
|
|
4188
|
-
|
|
4189
4026
|
[[package]]
|
|
4190
4027
|
name = "referencing"
|
|
4191
4028
|
version = "0.37.0"
|
|
@@ -4374,15 +4211,6 @@ wheels = [
|
|
|
4374
4211
|
{ url = "https://files.pythonhosted.org/packages/1d/4a/221da6ca167db45693d8d26c7dc79ccfc978a440251bf6721c9aaf251ac0/respx-0.23.1-py2.py3-none-any.whl", hash = "sha256:b18004b029935384bccfa6d7d9d74b4ec9af73a081cc28600fffc0447f4b8c1a", size = 25557, upload-time = "2026-04-08T14:37:14.613Z" },
|
|
4375
4212
|
]
|
|
4376
4213
|
|
|
4377
|
-
[[package]]
|
|
4378
|
-
name = "rfc3986"
|
|
4379
|
-
version = "2.0.0"
|
|
4380
|
-
source = { registry = "https://pypi.org/simple" }
|
|
4381
|
-
sdist = { url = "https://files.pythonhosted.org/packages/85/40/1520d68bfa07ab5a6f065a186815fb6610c86fe957bc065754e47f7b0840/rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c", size = 49026, upload-time = "2022-01-10T00:52:30.832Z" }
|
|
4382
|
-
wheels = [
|
|
4383
|
-
{ url = "https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", size = 31326, upload-time = "2022-01-10T00:52:29.594Z" },
|
|
4384
|
-
]
|
|
4385
|
-
|
|
4386
4214
|
[[package]]
|
|
4387
4215
|
name = "rich"
|
|
4388
4216
|
version = "14.3.4"
|
|
@@ -4675,19 +4503,6 @@ wheels = [
|
|
|
4675
4503
|
{ url = "https://files.pythonhosted.org/packages/fe/4e/cd76eca6db6115604b7626668e891c9dd03330384082e33662fb0f113614/ruff-0.15.5-py3-none-win_arm64.whl", hash = "sha256:b498d1c60d2fe5c10c45ec3f698901065772730b411f164ae270bb6bfcc4740b", size = 10965572, upload-time = "2026-03-05T20:06:16.984Z" },
|
|
4676
4504
|
]
|
|
4677
4505
|
|
|
4678
|
-
[[package]]
|
|
4679
|
-
name = "secretstorage"
|
|
4680
|
-
version = "3.5.0"
|
|
4681
|
-
source = { registry = "https://pypi.org/simple" }
|
|
4682
|
-
dependencies = [
|
|
4683
|
-
{ name = "cryptography" },
|
|
4684
|
-
{ name = "jeepney" },
|
|
4685
|
-
]
|
|
4686
|
-
sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" }
|
|
4687
|
-
wheels = [
|
|
4688
|
-
{ url = "https://files.pythonhosted.org/packages/b7/46/f5af3402b579fd5e11573ce652019a67074317e18c1935cc0b4ba9b35552/secretstorage-3.5.0-py3-none-any.whl", hash = "sha256:0ce65888c0725fcb2c5bc0fdb8e5438eece02c523557ea40ce0703c266248137", size = 15554, upload-time = "2025-11-23T19:02:51.545Z" },
|
|
4689
|
-
]
|
|
4690
|
-
|
|
4691
4506
|
[[package]]
|
|
4692
4507
|
name = "shellingham"
|
|
4693
4508
|
version = "1.5.4"
|
|
@@ -4836,26 +4651,6 @@ wheels = [
|
|
|
4836
4651
|
{ url = "https://files.pythonhosted.org/packages/22/2a/5e5e750890ada51017d18d0d4c30da696e5b5bd3180947729927628fc3cb/tqdm-4.68.4-py3-none-any.whl", hash = "sha256:5168118b2368f48c561afda8020fd79195b1bdb0bdf8086b88442c267a315dc2", size = 676612, upload-time = "2026-07-07T09:58:16.256Z" },
|
|
4837
4652
|
]
|
|
4838
4653
|
|
|
4839
|
-
[[package]]
|
|
4840
|
-
name = "twine"
|
|
4841
|
-
version = "6.2.0"
|
|
4842
|
-
source = { registry = "https://pypi.org/simple" }
|
|
4843
|
-
dependencies = [
|
|
4844
|
-
{ name = "id" },
|
|
4845
|
-
{ name = "keyring", marker = "platform_machine != 'ppc64le' and platform_machine != 's390x'" },
|
|
4846
|
-
{ name = "packaging" },
|
|
4847
|
-
{ name = "readme-renderer" },
|
|
4848
|
-
{ name = "requests" },
|
|
4849
|
-
{ name = "requests-toolbelt" },
|
|
4850
|
-
{ name = "rfc3986" },
|
|
4851
|
-
{ name = "rich" },
|
|
4852
|
-
{ name = "urllib3" },
|
|
4853
|
-
]
|
|
4854
|
-
sdist = { url = "https://files.pythonhosted.org/packages/e0/a8/949edebe3a82774c1ec34f637f5dd82d1cf22c25e963b7d63771083bbee5/twine-6.2.0.tar.gz", hash = "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf", size = 172262, upload-time = "2025-09-04T15:43:17.255Z" }
|
|
4855
|
-
wheels = [
|
|
4856
|
-
{ url = "https://files.pythonhosted.org/packages/3a/7a/882d99539b19b1490cac5d77c67338d126e4122c8276bf640e411650c830/twine-6.2.0-py3-none-any.whl", hash = "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", size = 42727, upload-time = "2025-09-04T15:43:15.994Z" },
|
|
4857
|
-
]
|
|
4858
|
-
|
|
4859
4654
|
[[package]]
|
|
4860
4655
|
name = "typer"
|
|
4861
4656
|
version = "0.26.8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|