stamm-sdk 0.1.2__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.
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1 @@
1
+ 3.12.0
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: stamm-sdk
3
+ Version: 0.1.2
4
+ Summary: Python client SDK for Stamm federated learning registry
5
+ Author-email: Santiago <sam2800ml@gmail.com>
6
+ Requires-Python: >=3.12.0
7
+ Requires-Dist: httpx>=0.28.1
8
+ Requires-Dist: pydantic>=2.11.5
9
+ Description-Content-Type: text/markdown
10
+
11
+ # Stamm SDK
12
+
13
+ Lightweight Python SDK to interact with the Stamm ML registry API (FastAPI backend), including auth and model inference/update endpoints.
14
+
15
+ ## Install Dependencies
16
+
17
+ Use `uv` in this repository:
18
+
19
+ ```bash
20
+ uv sync
21
+ ```
22
+
23
+ ## Quick Start
24
+
25
+ ```python
26
+ from stamm_sdk import Client
27
+
28
+ sdk = Client(base_url="http://188.245.241.118:8080")
29
+ sdk.login(email="you@example.com", password="your-password")
30
+
31
+ projects = sdk.ml.list_projects()
32
+ print(projects)
33
+
34
+ prediction = sdk.ml.predict(
35
+ project_id="your-project-id",
36
+ model_id="your-model-id",
37
+ features={"temperature": 24.7, "ph": 6.8},
38
+ )
39
+ print(prediction)
40
+
41
+ sdk.logout()
42
+ sdk.close()
43
+ ```
44
+
45
+ ## Implemented Modules
46
+
47
+ - `stamm_sdk.core`: config and authenticated HTTP session
48
+ - `stamm_sdk.registry`: ML endpoint wrappers, artifact bundle, storage manager
49
+ - `stamm_sdk.schemas`: pydantic schemas for model/federation metadata
50
+ - `stamm_sdk.tracking`: local experiment logger with optional remote push
51
+ - `stamm_sdk.learning`: centralized/federated stubs for training orchestration
52
+
53
+ ## Environment Variables
54
+
55
+ - `STAMM_API_BASE_URL`: default API base URL
56
+ - `STAMM_API_TIMEOUT`: request timeout in seconds
57
+
@@ -0,0 +1,47 @@
1
+ # Stamm SDK
2
+
3
+ Lightweight Python SDK to interact with the Stamm ML registry API (FastAPI backend), including auth and model inference/update endpoints.
4
+
5
+ ## Install Dependencies
6
+
7
+ Use `uv` in this repository:
8
+
9
+ ```bash
10
+ uv sync
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```python
16
+ from stamm_sdk import Client
17
+
18
+ sdk = Client(base_url="http://188.245.241.118:8080")
19
+ sdk.login(email="you@example.com", password="your-password")
20
+
21
+ projects = sdk.ml.list_projects()
22
+ print(projects)
23
+
24
+ prediction = sdk.ml.predict(
25
+ project_id="your-project-id",
26
+ model_id="your-model-id",
27
+ features={"temperature": 24.7, "ph": 6.8},
28
+ )
29
+ print(prediction)
30
+
31
+ sdk.logout()
32
+ sdk.close()
33
+ ```
34
+
35
+ ## Implemented Modules
36
+
37
+ - `stamm_sdk.core`: config and authenticated HTTP session
38
+ - `stamm_sdk.registry`: ML endpoint wrappers, artifact bundle, storage manager
39
+ - `stamm_sdk.schemas`: pydantic schemas for model/federation metadata
40
+ - `stamm_sdk.tracking`: local experiment logger with optional remote push
41
+ - `stamm_sdk.learning`: centralized/federated stubs for training orchestration
42
+
43
+ ## Environment Variables
44
+
45
+ - `STAMM_API_BASE_URL`: default API base URL
46
+ - `STAMM_API_TIMEOUT`: request timeout in seconds
47
+
@@ -0,0 +1,106 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "670b8e69",
6
+ "metadata": {},
7
+ "source": [
8
+ "# Stamm SDK - Colab Quick Test\n",
9
+ "Use this notebook to validate that your published package installs and can talk to the ML registry API."
10
+ ]
11
+ },
12
+ {
13
+ "cell_type": "code",
14
+ "execution_count": null,
15
+ "id": "84e75cb6",
16
+ "metadata": {},
17
+ "outputs": [],
18
+ "source": [
19
+ "# Install from PyPI after publishing\n",
20
+ "!pip -q install --upgrade stamm-package"
21
+ ]
22
+ },
23
+ {
24
+ "cell_type": "code",
25
+ "execution_count": null,
26
+ "id": "0cadfc39",
27
+ "metadata": {},
28
+ "outputs": [],
29
+ "source": [
30
+ "from stamm_sdk import Client\n",
31
+ "from getpass import getpass\n",
32
+ "\n",
33
+ "BASE_URL = \"http://188.245.241.118:8080\"\n",
34
+ "\n",
35
+ "email = input(\"Email: \")\n",
36
+ "password = getpass(\"Password (hidden): \")\n",
37
+ "\n",
38
+ "sdk = Client(base_url=BASE_URL)\n",
39
+ "login_response = sdk.login(email=email, password=password)\n",
40
+ "print(\"Logged in. Token keys:\", list(login_response.keys()))"
41
+ ]
42
+ },
43
+ {
44
+ "cell_type": "code",
45
+ "execution_count": null,
46
+ "id": "1d41074f",
47
+ "metadata": {},
48
+ "outputs": [],
49
+ "source": [
50
+ "# List projects and pick one\n",
51
+ "projects = sdk.ml.list_projects()\n",
52
+ "projects"
53
+ ]
54
+ },
55
+ {
56
+ "cell_type": "code",
57
+ "execution_count": null,
58
+ "id": "dfbec7b5",
59
+ "metadata": {},
60
+ "outputs": [],
61
+ "source": [
62
+ "# Set these values based on your registry\n",
63
+ "project_id = input(\"Project ID: \")\n",
64
+ "models = sdk.ml.list_models(project_id)\n",
65
+ "models"
66
+ ]
67
+ },
68
+ {
69
+ "cell_type": "code",
70
+ "execution_count": null,
71
+ "id": "72d4eaf8",
72
+ "metadata": {},
73
+ "outputs": [],
74
+ "source": [
75
+ "# Prediction test\n",
76
+ "model_id = input(\"Model ID: \")\n",
77
+ "features = {\n",
78
+ " \"temperature\": 24.7,\n",
79
+ " \"ph\": 6.8\n",
80
+ "}\n",
81
+ "prediction = sdk.ml.predict(project_id=project_id, model_id=model_id, features=features)\n",
82
+ "prediction"
83
+ ]
84
+ },
85
+ {
86
+ "cell_type": "code",
87
+ "execution_count": null,
88
+ "id": "c9e7817e",
89
+ "metadata": {},
90
+ "outputs": [],
91
+ "source": [
92
+ "# Cleanup\n",
93
+ "sdk.logout()\n",
94
+ "sdk.close()\n",
95
+ "print(\"Done\")"
96
+ ]
97
+ }
98
+ ],
99
+ "metadata": {
100
+ "language_info": {
101
+ "name": "python"
102
+ }
103
+ },
104
+ "nbformat": 4,
105
+ "nbformat_minor": 5
106
+ }
@@ -0,0 +1,20 @@
1
+ [project]
2
+ name = "stamm-sdk"
3
+ version = "0.1.2"
4
+ description = "Python client SDK for Stamm federated learning registry"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Santiago", email = "sam2800ml@gmail.com" }
8
+ ]
9
+ requires-python = ">=3.12.0"
10
+ dependencies = [
11
+ "httpx>=0.28.1",
12
+ "pydantic>=2.11.5",
13
+ ]
14
+
15
+ [project.scripts]
16
+ stamm-sdk = "stamm_sdk:main"
17
+
18
+ [build-system]
19
+ requires = ["hatchling"]
20
+ build-backend = "hatchling.build"
@@ -0,0 +1,2 @@
1
+ def main() -> None:
2
+ print("Hello from stamm-package!")
@@ -0,0 +1,5 @@
1
+ """Public interface for the Stamm SDK."""
2
+
3
+ from .client import Client
4
+
5
+ __all__ = ["Client"]
@@ -0,0 +1,42 @@
1
+ """High-level SDK client used by downstream applications."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .core.config import Config
6
+ from .core.session import Session
7
+ from .registry.api_client import APIClient
8
+
9
+
10
+ class Client:
11
+ """Main entry point for interacting with the Stamm ML registry API."""
12
+
13
+ def __init__(
14
+ self,
15
+ base_url: str | None = None,
16
+ timeout: float | None = None,
17
+ ) -> None:
18
+ self.config = Config.from_env(base_url=base_url, timeout=timeout)
19
+ self.session = Session(config=self.config)
20
+ self.ml = APIClient(session=self.session)
21
+
22
+ def register(self, email: str, password: str, full_name: str) -> dict:
23
+ return self.session.register(
24
+ email=email,
25
+ password=password,
26
+ full_name=full_name,
27
+ )
28
+
29
+ def login(self, email: str, password: str) -> dict:
30
+ return self.session.login(email=email, password=password)
31
+
32
+ def logout(self) -> dict:
33
+ return self.session.logout()
34
+
35
+ def close(self) -> None:
36
+ self.session.close()
37
+
38
+ def __enter__(self) -> "Client":
39
+ return self
40
+
41
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
42
+ self.close()
@@ -0,0 +1,4 @@
1
+ from .config import Config
2
+ from .session import Session, Tokens
3
+
4
+ __all__ = ["Config", "Session", "Tokens"]
@@ -0,0 +1,39 @@
1
+ """Configuration management for the SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ import os
7
+
8
+
9
+ DEFAULT_BASE_URL = "http://188.245.241.118:8080"
10
+ DEFAULT_TIMEOUT = 30.0
11
+
12
+
13
+ @dataclass(frozen=True, slots=True)
14
+ class Config:
15
+ base_url: str = DEFAULT_BASE_URL
16
+ timeout: float = DEFAULT_TIMEOUT
17
+
18
+ @classmethod
19
+ def from_env(
20
+ cls,
21
+ base_url: str | None = None,
22
+ timeout: float | None = None,
23
+ ) -> "Config":
24
+ resolved_base_url = (
25
+ base_url
26
+ or os.getenv("STAMM_API_BASE_URL")
27
+ or DEFAULT_BASE_URL
28
+ )
29
+ resolved_timeout = timeout
30
+ if resolved_timeout is None:
31
+ env_timeout = os.getenv("STAMM_API_TIMEOUT")
32
+ resolved_timeout = (
33
+ float(env_timeout) if env_timeout else DEFAULT_TIMEOUT
34
+ )
35
+
36
+ return cls(
37
+ base_url=resolved_base_url.rstrip("/"),
38
+ timeout=float(resolved_timeout),
39
+ )
@@ -0,0 +1,121 @@
1
+ """HTTP session with auth helpers for the Stamm API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ from .config import Config
11
+
12
+
13
+ @dataclass(slots=True)
14
+ class Tokens:
15
+ access_token: str
16
+ refresh_token: str
17
+ token_type: str = "bearer"
18
+
19
+
20
+ class Session:
21
+ def __init__(self, config: Config) -> None:
22
+ self.config = config
23
+ self._client = httpx.Client(
24
+ base_url=config.base_url,
25
+ timeout=config.timeout,
26
+ )
27
+ self.tokens: Tokens | None = None
28
+
29
+ def close(self) -> None:
30
+ self._client.close()
31
+
32
+ def _auth_headers(self) -> dict[str, str]:
33
+ if not self.tokens:
34
+ return {}
35
+ return {"Authorization": f"Bearer {self.tokens.access_token}"}
36
+
37
+ def register(
38
+ self,
39
+ email: str,
40
+ password: str,
41
+ full_name: str,
42
+ ) -> dict[str, Any]:
43
+ payload = {
44
+ "email": email,
45
+ "password": password,
46
+ "full_name": full_name,
47
+ }
48
+ response = self._client.post("/auth/register", json=payload)
49
+ response.raise_for_status()
50
+ return response.json()
51
+
52
+ def login(self, email: str, password: str) -> dict[str, Any]:
53
+ payload = {"email": email, "password": password}
54
+ response = self._client.post("/auth/login-json", json=payload)
55
+ response.raise_for_status()
56
+ data = response.json()
57
+ self.tokens = Tokens(**data)
58
+ return data
59
+
60
+ def refresh(self) -> dict[str, Any]:
61
+ if not self.tokens:
62
+ raise RuntimeError("Not authenticated. Call login first.")
63
+
64
+ response = self._client.post(
65
+ "/auth/refresh",
66
+ params={"refresh_token": self.tokens.refresh_token},
67
+ )
68
+ response.raise_for_status()
69
+ data = response.json()
70
+
71
+ access_token = data.get("access_token") or self.tokens.access_token
72
+ refresh_token = data.get("refresh_token") or self.tokens.refresh_token
73
+ token_type = data.get("token_type") or self.tokens.token_type
74
+ self.tokens = Tokens(
75
+ access_token=access_token,
76
+ refresh_token=refresh_token,
77
+ token_type=token_type,
78
+ )
79
+ return data
80
+
81
+ def me(self) -> dict[str, Any]:
82
+ response = self._client.get("/auth/me", headers=self._auth_headers())
83
+ response.raise_for_status()
84
+ return response.json()
85
+
86
+ def logout(self) -> dict[str, Any]:
87
+ if not self.tokens:
88
+ return {"detail": "No active session"}
89
+
90
+ response = self._client.post(
91
+ "/auth/logout",
92
+ params={"refresh_token": self.tokens.refresh_token},
93
+ headers=self._auth_headers(),
94
+ )
95
+ response.raise_for_status()
96
+ data = response.json()
97
+ self.tokens = None
98
+ return data
99
+
100
+ def request(
101
+ self,
102
+ method: str,
103
+ path: str,
104
+ *,
105
+ params: dict[str, Any] | None = None,
106
+ json: dict[str, Any] | None = None,
107
+ data: dict[str, Any] | None = None,
108
+ ) -> dict[str, Any]:
109
+ response = self._client.request(
110
+ method,
111
+ path,
112
+ params=params,
113
+ json=json,
114
+ data=data,
115
+ headers=self._auth_headers(),
116
+ )
117
+ response.raise_for_status()
118
+
119
+ if response.content:
120
+ return response.json()
121
+ return {}
@@ -0,0 +1,5 @@
1
+ from .centralized_trainer import CentralizedTrainer
2
+ from .federated_aggregator import FederatedAggregator
3
+ from .federated_client import FederatedClient
4
+
5
+ __all__ = ["CentralizedTrainer", "FederatedAggregator", "FederatedClient"]
@@ -0,0 +1,19 @@
1
+ """Stub implementation for centralized training workflows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ class CentralizedTrainer:
9
+ def train(
10
+ self,
11
+ dataset: Any,
12
+ config: dict[str, Any] | None = None,
13
+ ) -> dict[str, Any]:
14
+ return {
15
+ "status": "stub",
16
+ "message": "Implement centralized training logic",
17
+ "dataset_type": type(dataset).__name__,
18
+ "config": config or {},
19
+ }
@@ -0,0 +1,17 @@
1
+ """Stub implementation for federated aggregation logic."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ class FederatedAggregator:
9
+ def aggregate(self, updates: list[dict[str, Any]]) -> dict[str, Any]:
10
+ return {
11
+ "status": "stub",
12
+ "message": (
13
+ "Implement server-side aggregation "
14
+ "(for example FedAvg)"
15
+ ),
16
+ "num_updates": len(updates),
17
+ }
@@ -0,0 +1,27 @@
1
+ """Stub implementation for a federated learning client node."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ class FederatedClient:
9
+ def fit(self, model_state: dict[str, Any], data: Any) -> dict[str, Any]:
10
+ return {
11
+ "status": "stub",
12
+ "message": "Implement local FL client training",
13
+ "model_state": model_state,
14
+ "data_type": type(data).__name__,
15
+ }
16
+
17
+ def evaluate(
18
+ self,
19
+ model_state: dict[str, Any],
20
+ data: Any,
21
+ ) -> dict[str, Any]:
22
+ return {
23
+ "status": "stub",
24
+ "message": "Implement local FL client evaluation",
25
+ "model_state": model_state,
26
+ "data_type": type(data).__name__,
27
+ }
@@ -0,0 +1,5 @@
1
+ from .api_client import APIClient
2
+ from .artifact_bundle import ArtifactBundle
3
+ from .storage_manager import StorageManager
4
+
5
+ __all__ = ["APIClient", "ArtifactBundle", "StorageManager"]
@@ -0,0 +1,46 @@
1
+ """Wrapper around ML registry endpoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from stamm_sdk.core.session import Session
8
+
9
+
10
+ class APIClient:
11
+ def __init__(self, session: Session) -> None:
12
+ self.session = session
13
+
14
+ def list_projects(self) -> dict[str, Any]:
15
+ return self.session.request("GET", "/list_projects/")
16
+
17
+ def get_project_info(self, project_id: str) -> dict[str, Any]:
18
+ return self.session.request("GET", f"/{project_id}/project_info/")
19
+
20
+ def get_db_config(self, project_id: str) -> dict[str, Any]:
21
+ return self.session.request("GET", f"/{project_id}/db_config/")
22
+
23
+ def get_references(self, project_id: str) -> dict[str, Any]:
24
+ return self.session.request("GET", f"/{project_id}/references/")
25
+
26
+ def get_variables(self, project_id: str) -> dict[str, Any]:
27
+ return self.session.request("GET", f"/{project_id}/variables/")
28
+
29
+ def list_models(self, project_id: str) -> dict[str, Any]:
30
+ return self.session.request("GET", f"/{project_id}/list_models/")
31
+
32
+ def get_model_metadata(self, project_id: str, model_id: str) -> dict[str, Any]:
33
+ return self.session.request("GET", f"/{project_id}/metadata/{model_id}")
34
+
35
+ def list_models_full(self, project_id: str) -> dict[str, Any]:
36
+ return self.session.request("GET", f"/{project_id}/models_full/")
37
+
38
+ def update_model(self, project_id: str, model_id: str, payload: dict[str, Any]) -> dict[str, Any]:
39
+ return self.session.request("PUT", f"/{project_id}/update/{model_id}", json=payload)
40
+
41
+ def predict(self, project_id: str, model_id: str, features: dict[str, Any]) -> dict[str, Any]:
42
+ return self.session.request(
43
+ "POST",
44
+ f"/{project_id}/predict/{model_id}",
45
+ json={"req": features},
46
+ )
@@ -0,0 +1,33 @@
1
+ """Artifact bundle definition used for model packaging."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+
10
+ @dataclass(slots=True)
11
+ class ArtifactBundle:
12
+ name: str
13
+ version: str
14
+ metadata: dict[str, Any] = field(default_factory=dict)
15
+ files: list[Path] = field(default_factory=list)
16
+
17
+ @classmethod
18
+ def from_directory(
19
+ cls,
20
+ path: str | Path,
21
+ *,
22
+ name: str,
23
+ version: str,
24
+ metadata: dict[str, Any] | None = None,
25
+ ) -> "ArtifactBundle":
26
+ root = Path(path)
27
+ files = [p for p in root.rglob("*") if p.is_file()]
28
+ return cls(
29
+ name=name,
30
+ version=version,
31
+ metadata=metadata or {},
32
+ files=files,
33
+ )
@@ -0,0 +1,23 @@
1
+ """Simple local storage utilities for model artifacts and metadata."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+
10
+ class StorageManager:
11
+ def __init__(self, root_dir: str | Path = ".stamm") -> None:
12
+ self.root_dir = Path(root_dir)
13
+ self.root_dir.mkdir(parents=True, exist_ok=True)
14
+
15
+ def save_json(self, relative_path: str, payload: dict[str, Any]) -> Path:
16
+ target = self.root_dir / relative_path
17
+ target.parent.mkdir(parents=True, exist_ok=True)
18
+ target.write_text(json.dumps(payload, indent=2), encoding="utf-8")
19
+ return target
20
+
21
+ def load_json(self, relative_path: str) -> dict[str, Any]:
22
+ target = self.root_dir / relative_path
23
+ return json.loads(target.read_text(encoding="utf-8"))
@@ -0,0 +1,10 @@
1
+ from .federation_schema import FederationParticipant, FederationSchema
2
+ from .model_schema import ModelSchema, PredictionRequest, PredictionResponse
3
+
4
+ __all__ = [
5
+ "FederationParticipant",
6
+ "FederationSchema",
7
+ "ModelSchema",
8
+ "PredictionRequest",
9
+ "PredictionResponse",
10
+ ]
@@ -0,0 +1,22 @@
1
+ """Typed schemas for federated learning metadata."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+
10
+ class FederationParticipant(BaseModel):
11
+ node_id: str
12
+ role: str = "client"
13
+ metadata: dict[str, Any] = Field(default_factory=dict)
14
+
15
+
16
+ class FederationSchema(BaseModel):
17
+ model_config = ConfigDict(extra="allow")
18
+
19
+ federation_id: str
20
+ strategy: str = "fedavg"
21
+ round_number: int = 0
22
+ participants: list[FederationParticipant] = Field(default_factory=list)
@@ -0,0 +1,25 @@
1
+ """Typed schemas for model operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from pydantic import BaseModel, ConfigDict, Field
8
+
9
+
10
+ class ModelSchema(BaseModel):
11
+ model_config = ConfigDict(extra="allow")
12
+
13
+ id: str | None = None
14
+ name: str | None = None
15
+ version: str | None = None
16
+ status: str | None = None
17
+ metadata: dict[str, Any] = Field(default_factory=dict)
18
+
19
+
20
+ class PredictionRequest(BaseModel):
21
+ req: dict[str, Any]
22
+
23
+
24
+ class PredictionResponse(BaseModel):
25
+ model_config = ConfigDict(extra="allow")
@@ -0,0 +1,3 @@
1
+ from .experiment_logger import ExperimentLogger
2
+
3
+ __all__ = ["ExperimentLogger"]
@@ -0,0 +1,40 @@
1
+ """Simple experiment logger for local and optional remote tracking."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from datetime import UTC, datetime
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from stamm_sdk.core.session import Session
11
+
12
+
13
+ class ExperimentLogger:
14
+ def __init__(
15
+ self,
16
+ session: Session | None = None,
17
+ log_path: str | Path = ".stamm/experiments.jsonl",
18
+ ) -> None:
19
+ self.session = session
20
+ self.log_path = Path(log_path)
21
+ self.log_path.parent.mkdir(parents=True, exist_ok=True)
22
+
23
+ def log(self, event: str, payload: dict[str, Any]) -> dict[str, Any]:
24
+ record = {
25
+ "event": event,
26
+ "timestamp": datetime.now(UTC).isoformat(),
27
+ "payload": payload,
28
+ }
29
+ with self.log_path.open("a", encoding="utf-8") as handle:
30
+ handle.write(json.dumps(record) + "\n")
31
+ return record
32
+
33
+ def push_remote(self, payload: dict[str, Any]) -> dict[str, Any]:
34
+ if not self.session:
35
+ raise RuntimeError("ExperimentLogger.session is not configured")
36
+ return self.session.request(
37
+ "POST",
38
+ "/api/v1/experiments/",
39
+ json=payload,
40
+ )
@@ -0,0 +1,206 @@
1
+ version = 1
2
+ revision = 2
3
+ requires-python = ">=3.12.0"
4
+
5
+ [[package]]
6
+ name = "annotated-types"
7
+ version = "0.7.0"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "anyio"
16
+ version = "4.13.0"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ dependencies = [
19
+ { name = "idna" },
20
+ { name = "typing-extensions", marker = "python_full_version < '3.13'" },
21
+ ]
22
+ sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
23
+ wheels = [
24
+ { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
25
+ ]
26
+
27
+ [[package]]
28
+ name = "certifi"
29
+ version = "2026.5.20"
30
+ source = { registry = "https://pypi.org/simple" }
31
+ sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" }
32
+ wheels = [
33
+ { url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" },
34
+ ]
35
+
36
+ [[package]]
37
+ name = "h11"
38
+ version = "0.16.0"
39
+ source = { registry = "https://pypi.org/simple" }
40
+ sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
41
+ wheels = [
42
+ { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
43
+ ]
44
+
45
+ [[package]]
46
+ name = "httpcore"
47
+ version = "1.0.9"
48
+ source = { registry = "https://pypi.org/simple" }
49
+ dependencies = [
50
+ { name = "certifi" },
51
+ { name = "h11" },
52
+ ]
53
+ sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
54
+ wheels = [
55
+ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
56
+ ]
57
+
58
+ [[package]]
59
+ name = "httpx"
60
+ version = "0.28.1"
61
+ source = { registry = "https://pypi.org/simple" }
62
+ dependencies = [
63
+ { name = "anyio" },
64
+ { name = "certifi" },
65
+ { name = "httpcore" },
66
+ { name = "idna" },
67
+ ]
68
+ sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
69
+ wheels = [
70
+ { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
71
+ ]
72
+
73
+ [[package]]
74
+ name = "idna"
75
+ version = "3.17"
76
+ source = { registry = "https://pypi.org/simple" }
77
+ sdist = { url = "https://files.pythonhosted.org/packages/b9/28/99c51f664567218d824af024c0251650fb27e4ca066df188dab0769c5b91/idna-3.17.tar.gz", hash = "sha256:5eb0cb53bc467c12eadcf6de83163ad8527cec9416f44b9b61b19caedad2b87f", size = 196048, upload-time = "2026-05-28T14:32:38.55Z" }
78
+ wheels = [
79
+ { url = "https://files.pythonhosted.org/packages/de/a7/f76514cc40ad6234098ecdebda08732d75964776c51a42845b7da10649e2/idna-3.17-py3-none-any.whl", hash = "sha256:466e48829084efe2548012b855df21540b96f2e20e51bd124c851536556a592c", size = 65316, upload-time = "2026-05-28T14:32:37.035Z" },
80
+ ]
81
+
82
+ [[package]]
83
+ name = "pydantic"
84
+ version = "2.13.4"
85
+ source = { registry = "https://pypi.org/simple" }
86
+ dependencies = [
87
+ { name = "annotated-types" },
88
+ { name = "pydantic-core" },
89
+ { name = "typing-extensions" },
90
+ { name = "typing-inspection" },
91
+ ]
92
+ sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" }
93
+ wheels = [
94
+ { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" },
95
+ ]
96
+
97
+ [[package]]
98
+ name = "pydantic-core"
99
+ version = "2.46.4"
100
+ source = { registry = "https://pypi.org/simple" }
101
+ dependencies = [
102
+ { name = "typing-extensions" },
103
+ ]
104
+ sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" }
105
+ wheels = [
106
+ { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" },
107
+ { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" },
108
+ { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" },
109
+ { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" },
110
+ { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" },
111
+ { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" },
112
+ { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" },
113
+ { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" },
114
+ { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" },
115
+ { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" },
116
+ { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" },
117
+ { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" },
118
+ { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" },
119
+ { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" },
120
+ { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" },
121
+ { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" },
122
+ { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" },
123
+ { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" },
124
+ { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" },
125
+ { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" },
126
+ { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" },
127
+ { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" },
128
+ { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" },
129
+ { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" },
130
+ { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" },
131
+ { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" },
132
+ { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" },
133
+ { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" },
134
+ { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" },
135
+ { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" },
136
+ { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" },
137
+ { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" },
138
+ { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" },
139
+ { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" },
140
+ { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" },
141
+ { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" },
142
+ { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" },
143
+ { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" },
144
+ { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" },
145
+ { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" },
146
+ { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" },
147
+ { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" },
148
+ { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" },
149
+ { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" },
150
+ { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" },
151
+ { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" },
152
+ { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" },
153
+ { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" },
154
+ { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" },
155
+ { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" },
156
+ { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" },
157
+ { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" },
158
+ { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" },
159
+ { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" },
160
+ { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" },
161
+ { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" },
162
+ { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" },
163
+ { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" },
164
+ { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" },
165
+ { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" },
166
+ { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" },
167
+ { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" },
168
+ { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" },
169
+ { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" },
170
+ ]
171
+
172
+ [[package]]
173
+ name = "stamm-package"
174
+ version = "0.1.1"
175
+ source = { editable = "." }
176
+ dependencies = [
177
+ { name = "httpx" },
178
+ { name = "pydantic" },
179
+ ]
180
+
181
+ [package.metadata]
182
+ requires-dist = [
183
+ { name = "httpx", specifier = ">=0.28.1" },
184
+ { name = "pydantic", specifier = ">=2.11.5" },
185
+ ]
186
+
187
+ [[package]]
188
+ name = "typing-extensions"
189
+ version = "4.15.0"
190
+ source = { registry = "https://pypi.org/simple" }
191
+ sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
192
+ wheels = [
193
+ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
194
+ ]
195
+
196
+ [[package]]
197
+ name = "typing-inspection"
198
+ version = "0.4.2"
199
+ source = { registry = "https://pypi.org/simple" }
200
+ dependencies = [
201
+ { name = "typing-extensions" },
202
+ ]
203
+ sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
204
+ wheels = [
205
+ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
206
+ ]