syvain-metrics-api-client 0.0.57__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,12 @@
1
+ Metadata-Version: 2.3
2
+ Name: syvain-metrics-api-client
3
+ Version: 0.0.57
4
+ Summary: Typed Python client for the Syvain Metrics REST API
5
+ Requires-Dist: niquests>=3.16.0
6
+ Requires-Dist: pydantic>=2.13.3
7
+ Requires-Dist: typing-extensions>=4.15.0
8
+ Requires-Dist: pytest>=8.0.0 ; extra == 'dev'
9
+ Requires-Dist: ruff>=0.15.12 ; extra == 'dev'
10
+ Requires-Dist: ty>=0.0.34 ; extra == 'dev'
11
+ Requires-Python: >=3.10
12
+ Provides-Extra: dev
@@ -0,0 +1,16 @@
1
+ [project]
2
+ name = "syvain-metrics-api-client"
3
+ version = "0.0.57"
4
+ description = "Typed Python client for the Syvain Metrics REST API"
5
+ requires-python = ">=3.10"
6
+ dependencies = ["niquests>=3.16.0", "pydantic>=2.13.3", "typing-extensions>=4.15.0"]
7
+
8
+ [project.optional-dependencies]
9
+ dev = ["pytest>=8.0.0", "ruff>=0.15.12", "ty>=0.0.34"]
10
+
11
+ [tool.pytest.ini_options]
12
+ testpaths = ["tests"]
13
+
14
+ [build-system]
15
+ requires = ["uv_build>=0.11.9,<0.12"]
16
+ build-backend = "uv_build"
@@ -0,0 +1,99 @@
1
+ from syvain_metrics_api_client.auth import (
2
+ ClientCredentials,
3
+ MetricsApiAuthError,
4
+ cli_auth_path,
5
+ load_cli_auth,
6
+ normalize_metrics_host,
7
+ )
8
+ from syvain_metrics_api_client.client import (
9
+ MetricsApiError,
10
+ MetricsApiRequestError,
11
+ MetricsApiResponseError,
12
+ SyvainMetricsApiClient,
13
+ )
14
+ from syvain_metrics_api_client.types import (
15
+ Annotation,
16
+ AnnotationCreateInput,
17
+ AnnotationsResponse,
18
+ AuthStatusResponse,
19
+ ComparisonMetricSeries,
20
+ Experiment,
21
+ ExperimentAnnotationResponse,
22
+ ExperimentCreateInput,
23
+ ExperimentResponse,
24
+ ExperimentsResponse,
25
+ ExperimentStatusError,
26
+ ExperimentStatusUpdateInput,
27
+ Folder,
28
+ FolderContentsResponse,
29
+ FolderCreateInput,
30
+ FolderPatchInput,
31
+ FolderResponse,
32
+ FoldersResponse,
33
+ IngestCreateInput,
34
+ IngestResponse,
35
+ IngestionKey,
36
+ IngestionKeyCreateInput,
37
+ IngestionKeyCreateResponse,
38
+ IngestionKeyResponse,
39
+ IngestionKeysResponse,
40
+ JsonObject,
41
+ JsonPayload,
42
+ JsonValue,
43
+ MetricCatalogItem,
44
+ MetricCatalogMetadataValue,
45
+ MetricCatalogResponse,
46
+ MetricCatalogValues,
47
+ MetricCatalogValuesResponse,
48
+ MetricCreateInput,
49
+ MetricEventRow,
50
+ RowsResponse,
51
+ )
52
+
53
+ __all__ = [
54
+ "Annotation",
55
+ "AnnotationCreateInput",
56
+ "AnnotationsResponse",
57
+ "AuthStatusResponse",
58
+ "ClientCredentials",
59
+ "ComparisonMetricSeries",
60
+ "Experiment",
61
+ "ExperimentAnnotationResponse",
62
+ "ExperimentCreateInput",
63
+ "ExperimentResponse",
64
+ "ExperimentsResponse",
65
+ "ExperimentStatusError",
66
+ "ExperimentStatusUpdateInput",
67
+ "Folder",
68
+ "FolderContentsResponse",
69
+ "FolderCreateInput",
70
+ "FolderPatchInput",
71
+ "FolderResponse",
72
+ "FoldersResponse",
73
+ "IngestCreateInput",
74
+ "IngestResponse",
75
+ "IngestionKey",
76
+ "IngestionKeyCreateInput",
77
+ "IngestionKeyCreateResponse",
78
+ "IngestionKeyResponse",
79
+ "IngestionKeysResponse",
80
+ "JsonObject",
81
+ "JsonPayload",
82
+ "JsonValue",
83
+ "MetricCatalogItem",
84
+ "MetricCatalogMetadataValue",
85
+ "MetricCatalogResponse",
86
+ "MetricCatalogValues",
87
+ "MetricCatalogValuesResponse",
88
+ "MetricCreateInput",
89
+ "MetricEventRow",
90
+ "MetricsApiAuthError",
91
+ "MetricsApiError",
92
+ "MetricsApiRequestError",
93
+ "MetricsApiResponseError",
94
+ "RowsResponse",
95
+ "SyvainMetricsApiClient",
96
+ "cli_auth_path",
97
+ "load_cli_auth",
98
+ "normalize_metrics_host",
99
+ ]
@@ -0,0 +1,140 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Literal, cast
8
+ from urllib.parse import urlparse
9
+
10
+ AuthMode = Literal["local", "local-dev"]
11
+
12
+ DEFAULT_DEV_HOST = "https://syvain-metrics-app-dev.syvaintech.workers.dev"
13
+ DEFAULT_PROD_HOST = "https://metrics.syvain.com"
14
+
15
+
16
+ class MetricsApiAuthError(RuntimeError):
17
+ """Raised when client authentication cannot be resolved."""
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class ClientCredentials:
22
+ api_key: str
23
+ host: str
24
+ org_id: str | None = None
25
+ login_at: int | None = None
26
+ source: str = "api_key"
27
+
28
+
29
+ def default_host_for_mode(mode: AuthMode | None = None) -> str:
30
+ return DEFAULT_DEV_HOST if mode == "local-dev" else DEFAULT_PROD_HOST
31
+
32
+
33
+ def normalize_metrics_host(value: str | None, *, default_host: str) -> str:
34
+ raw_host = (value or "").strip() or default_host
35
+ parsed = urlparse(raw_host)
36
+ if parsed.scheme not in {"http", "https"}:
37
+ raise MetricsApiAuthError("Metrics API host must use http or https.")
38
+ if (
39
+ parsed.netloc == ""
40
+ or parsed.username is not None
41
+ or parsed.password is not None
42
+ ):
43
+ raise MetricsApiAuthError("Metrics API host must be a valid HTTP(S) origin.")
44
+ if parsed.path not in {"", "/"} or parsed.params or parsed.query or parsed.fragment:
45
+ raise MetricsApiAuthError(
46
+ "Metrics API host must not include a path, query, or fragment."
47
+ )
48
+ return f"{parsed.scheme}://{parsed.netloc}"
49
+
50
+
51
+ def cli_auth_path(mode: AuthMode) -> Path:
52
+ config_home = os.environ.get("XDG_CONFIG_HOME")
53
+ root = Path(config_home).expanduser() if config_home else Path.home() / ".config"
54
+ app_name = "syvain-metrics-dev" if mode == "local-dev" else "syvain-metrics"
55
+ return root / app_name / "auth.json"
56
+
57
+
58
+ def load_cli_auth(mode: AuthMode) -> ClientCredentials:
59
+ path = cli_auth_path(mode)
60
+ try:
61
+ raw = json.loads(path.read_text(encoding="utf-8"))
62
+ except FileNotFoundError as error:
63
+ raise MetricsApiAuthError(
64
+ f"Metrics CLI auth file was not found at {path}."
65
+ ) from error
66
+ except json.JSONDecodeError as error:
67
+ raise MetricsApiAuthError(
68
+ f"Metrics CLI auth file is not valid JSON: {path}."
69
+ ) from error
70
+
71
+ if not isinstance(raw, dict):
72
+ raise MetricsApiAuthError(
73
+ f"Metrics CLI auth file must contain a JSON object: {path}."
74
+ )
75
+
76
+ api_key = raw.get("apiKey")
77
+ if not isinstance(api_key, str) or len(api_key) == 0:
78
+ raise MetricsApiAuthError(
79
+ f"Metrics CLI auth file does not contain apiKey: {path}."
80
+ )
81
+
82
+ raw_host = raw.get("host")
83
+ host = normalize_metrics_host(
84
+ raw_host if isinstance(raw_host, str) else None,
85
+ default_host=default_host_for_mode(mode),
86
+ )
87
+
88
+ raw_org_id = raw.get("orgId")
89
+ org_id = raw_org_id if isinstance(raw_org_id, str) and raw_org_id else None
90
+
91
+ raw_login_at = raw.get("loginAt")
92
+ login_at = raw_login_at if isinstance(raw_login_at, int) else None
93
+
94
+ return ClientCredentials(
95
+ api_key=api_key,
96
+ host=host,
97
+ org_id=org_id,
98
+ login_at=login_at,
99
+ source=mode,
100
+ )
101
+
102
+
103
+ def resolve_client_credentials(
104
+ auth: str | ClientCredentials,
105
+ *,
106
+ host: str | None = None,
107
+ ) -> ClientCredentials:
108
+ if isinstance(auth, ClientCredentials):
109
+ resolved_host = normalize_metrics_host(
110
+ host or auth.host, default_host=auth.host
111
+ )
112
+ return ClientCredentials(
113
+ api_key=auth.api_key,
114
+ host=resolved_host,
115
+ org_id=auth.org_id,
116
+ login_at=auth.login_at,
117
+ source=auth.source,
118
+ )
119
+
120
+ if auth in {"local", "local-dev"}:
121
+ mode = cast(AuthMode, auth)
122
+ credentials = load_cli_auth(mode)
123
+ if host is None:
124
+ return credentials
125
+ return ClientCredentials(
126
+ api_key=credentials.api_key,
127
+ host=normalize_metrics_host(host, default_host=credentials.host),
128
+ org_id=credentials.org_id,
129
+ login_at=credentials.login_at,
130
+ source=credentials.source,
131
+ )
132
+
133
+ api_key = auth.strip()
134
+ if len(api_key) == 0:
135
+ raise MetricsApiAuthError("Metrics API key must not be empty.")
136
+
137
+ return ClientCredentials(
138
+ api_key=api_key,
139
+ host=normalize_metrics_host(host, default_host=DEFAULT_PROD_HOST),
140
+ )