sixtyseven 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,84 @@
1
+ Metadata-Version: 2.2
2
+ Name: sixtyseven
3
+ Version: 0.1.0
4
+ Summary: Python SDK for Sixtyseven ML experiment tracking
5
+ Author: Sixtyseven Team
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/sixtyseven/sixtyseven
8
+ Project-URL: Documentation, https://docs.sixtyseven.ai
9
+ Project-URL: Repository, https://github.com/sixtyseven/sixtyseven
10
+ Keywords: ml,machine-learning,experiment-tracking,metrics,training
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.8
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Programming Language :: Python :: 3 :: Only
24
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
+ Requires-Python: >=3.8
26
+ Description-Content-Type: text/markdown
27
+ Requires-Dist: requests>=2.28.0
28
+ Requires-Dist: websocket-client>=1.4.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
31
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
32
+ Requires-Dist: responses>=0.23.0; extra == "dev"
33
+ Requires-Dist: black>=23.0.0; extra == "dev"
34
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
35
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
36
+
37
+ # Sixtyseven Python SDK
38
+
39
+ Track ML experiments locally. No server setup required.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ pip install sixtyseven
45
+ ```
46
+
47
+ ## Usage
48
+
49
+ ```python
50
+ from sixtyseven import Run
51
+
52
+ with Run(project="my-project", name="experiment-1") as run:
53
+ run.log_config({"learning_rate": 0.001, "epochs": 10})
54
+
55
+ for epoch in range(10):
56
+ loss = train_one_epoch()
57
+ run.log_metrics({"loss": loss}, step=epoch)
58
+ ```
59
+
60
+ ## View Results
61
+
62
+ ```bash
63
+ sixtyseven --logdir ~/.sixtyseven/logs
64
+ ```
65
+
66
+ Opens a dashboard at http://localhost:6767
67
+
68
+ ## API
69
+
70
+ ```python
71
+ run.log_metrics({"loss": 0.5, "accuracy": 0.85}, step=epoch) # Log metrics
72
+ run.log_config({"lr": 0.001}) # Log config
73
+ run.add_tags(["baseline"]) # Add tags
74
+ ```
75
+
76
+ ## Environment Variables
77
+
78
+ | Variable | Description | Default |
79
+ | ------------------- | ------------------ | -------------------- |
80
+ | `SIXTYSEVEN_LOGDIR` | Where to save logs | `~/.sixtyseven/logs` |
81
+
82
+ ## License
83
+
84
+ MIT
@@ -0,0 +1,48 @@
1
+ # Sixtyseven Python SDK
2
+
3
+ Track ML experiments locally. No server setup required.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install sixtyseven
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from sixtyseven import Run
15
+
16
+ with Run(project="my-project", name="experiment-1") as run:
17
+ run.log_config({"learning_rate": 0.001, "epochs": 10})
18
+
19
+ for epoch in range(10):
20
+ loss = train_one_epoch()
21
+ run.log_metrics({"loss": loss}, step=epoch)
22
+ ```
23
+
24
+ ## View Results
25
+
26
+ ```bash
27
+ sixtyseven --logdir ~/.sixtyseven/logs
28
+ ```
29
+
30
+ Opens a dashboard at http://localhost:6767
31
+
32
+ ## API
33
+
34
+ ```python
35
+ run.log_metrics({"loss": 0.5, "accuracy": 0.85}, step=epoch) # Log metrics
36
+ run.log_config({"lr": 0.001}) # Log config
37
+ run.add_tags(["baseline"]) # Add tags
38
+ ```
39
+
40
+ ## Environment Variables
41
+
42
+ | Variable | Description | Default |
43
+ | ------------------- | ------------------ | -------------------- |
44
+ | `SIXTYSEVEN_LOGDIR` | Where to save logs | `~/.sixtyseven/logs` |
45
+
46
+ ## License
47
+
48
+ MIT
@@ -0,0 +1,68 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69,<77", "wheel", "packaging>=23.2"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sixtyseven"
7
+ version = "0.1.0"
8
+ description = "Python SDK for Sixtyseven ML experiment tracking"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.8"
12
+ authors = [
13
+ { name = "Sixtyseven Team" }
14
+ ]
15
+ keywords = ["ml", "machine-learning", "experiment-tracking", "metrics", "training"]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "Intended Audience :: Science/Research",
20
+ "Operating System :: OS Independent",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.8",
23
+ "Programming Language :: Python :: 3.9",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Programming Language :: Python :: 3.13",
28
+ "Programming Language :: Python :: 3.14",
29
+ "Programming Language :: Python :: 3 :: Only",
30
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
31
+ ]
32
+
33
+ dependencies = [
34
+ "requests>=2.28.0",
35
+ "websocket-client>=1.4.0",
36
+ ]
37
+
38
+ [project.optional-dependencies]
39
+ dev = [
40
+ "pytest>=7.0.0",
41
+ "pytest-asyncio>=0.21.0",
42
+ "responses>=0.23.0",
43
+ "black>=23.0.0",
44
+ "ruff>=0.1.0",
45
+ "mypy>=1.0.0",
46
+ ]
47
+
48
+ [project.urls]
49
+ Homepage = "https://github.com/sixtyseven/sixtyseven"
50
+ Documentation = "https://docs.sixtyseven.ai"
51
+ Repository = "https://github.com/sixtyseven/sixtyseven"
52
+
53
+ [project.scripts]
54
+ sixtyseven = "sixtyseven.cli:main"
55
+
56
+ [tool.setuptools]
57
+ package-dir = {"" = "src"}
58
+ packages = ["sixtyseven"]
59
+ include-package-data = true
60
+
61
+ [tool.setuptools.package-data]
62
+ sixtyseven = ["bin/**"]
63
+
64
+ [tool.mypy]
65
+ python_version = "3.8"
66
+ warn_return_any = true
67
+ warn_unused_configs = true
68
+ ignore_missing_imports = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from setuptools import setup
4
+
5
+
6
+ try:
7
+ from wheel.bdist_wheel import bdist_wheel as _bdist_wheel
8
+
9
+ class bdist_wheel(_bdist_wheel):
10
+ def finalize_options(self) -> None:
11
+ super().finalize_options()
12
+ self.root_is_pure = False
13
+ if hasattr(self, "root_is_purelib"):
14
+ self.root_is_purelib = False
15
+
16
+ def get_tag(self) -> tuple[str, str, str]:
17
+ _, _, plat = super().get_tag()
18
+ # py3-none-<platform>: works with any Python 3, no ABI, platform-specific
19
+ return "py3", "none", plat
20
+
21
+ except Exception: # wheel not available
22
+ bdist_wheel = None # type: ignore[assignment]
23
+
24
+
25
+ setup(cmdclass={"bdist_wheel": bdist_wheel} if bdist_wheel else {})
@@ -0,0 +1,36 @@
1
+ """
2
+ Sixtyseven - ML Experiment Tracking SDK
3
+
4
+ A Python SDK for tracking machine learning experiments with Sixtyseven.
5
+
6
+ Example usage:
7
+ from sixtyseven import Run
8
+
9
+ with Run(project="my-team/image-classifier") as run:
10
+ run.log_config({"learning_rate": 0.001})
11
+
12
+ for epoch in range(100):
13
+ loss = train()
14
+ run.log_metrics({"loss": loss}, step=epoch)
15
+ """
16
+
17
+ from sixtyseven.config import configure
18
+ from sixtyseven.exceptions import (
19
+ APIError,
20
+ AuthenticationError,
21
+ ServerError,
22
+ SixtySevenError,
23
+ ValidationError,
24
+ )
25
+ from sixtyseven.run import Run
26
+
27
+ __version__ = "0.1.0"
28
+ __all__ = [
29
+ "Run",
30
+ "configure",
31
+ "SixtySevenError",
32
+ "AuthenticationError",
33
+ "APIError",
34
+ "ValidationError",
35
+ "ServerError",
36
+ ]
@@ -0,0 +1,64 @@
1
+ """Console entrypoint for the bundled sixtyseven CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import platform
7
+ import shutil
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Optional
11
+
12
+
13
+ def _platform_id() -> Optional[str]:
14
+ system = platform.system().lower()
15
+ machine = platform.machine().lower()
16
+
17
+ if machine in {"x86_64", "amd64"}:
18
+ arch = "amd64"
19
+ elif machine in {"aarch64", "arm64"}:
20
+ arch = "arm64"
21
+ else:
22
+ return None
23
+
24
+ if system == "darwin":
25
+ return f"darwin-{arch}"
26
+ if system == "linux":
27
+ return f"linux-{arch}"
28
+ if system == "windows":
29
+ return f"windows-{arch}"
30
+ return None
31
+
32
+
33
+ def _bundled_binary_path() -> Optional[str]:
34
+ platform_id = _platform_id()
35
+ if not platform_id:
36
+ return None
37
+
38
+ binary_name = "sixtyseven.exe" if platform.system() == "Windows" else "sixtyseven"
39
+ base_dir = Path(__file__).resolve().parent
40
+ candidate = base_dir / "bin" / platform_id / binary_name
41
+ if candidate.is_file() and os.access(candidate, os.X_OK):
42
+ return str(candidate)
43
+ return None
44
+
45
+
46
+ def _find_binary() -> Optional[str]:
47
+ bundled = _bundled_binary_path()
48
+ if bundled:
49
+ return bundled
50
+
51
+ binary_name = "sixtyseven.exe" if platform.system() == "Windows" else "sixtyseven"
52
+ return shutil.which(binary_name)
53
+
54
+
55
+ def main() -> None:
56
+ binary = _find_binary()
57
+ if not binary:
58
+ print(
59
+ "Could not find 'sixtyseven' binary. Reinstall the package or set SIXTYSEVEN_BINARY.",
60
+ file=sys.stderr,
61
+ )
62
+ raise SystemExit(1)
63
+
64
+ os.execv(binary, [binary, *sys.argv[1:]])
@@ -0,0 +1,190 @@
1
+ """HTTP client for communicating with the Sixtyseven API."""
2
+
3
+ import time
4
+ from typing import Any, Dict, List, Optional
5
+ from urllib.parse import urljoin
6
+
7
+ import requests
8
+
9
+ from sixtyseven.config import SDKConfig
10
+ from sixtyseven.exceptions import APIError, AuthenticationError
11
+
12
+
13
+ class SixtySevenClient:
14
+ """HTTP client for the Sixtyseven API."""
15
+
16
+ def __init__(self, config: SDKConfig, api_key: Optional[str] = None):
17
+ """
18
+ Initialize the client.
19
+
20
+ Args:
21
+ config: SDK configuration
22
+ api_key: API key (overrides config)
23
+ """
24
+ self.config = config
25
+ self.api_key = api_key or config.api_key
26
+ self.session = requests.Session()
27
+
28
+ if self.api_key:
29
+ self.session.headers["Authorization"] = f"Bearer {self.api_key}"
30
+
31
+ self.session.headers["Content-Type"] = "application/json"
32
+ self.session.headers["User-Agent"] = "sixtyseven-python/0.1.0"
33
+
34
+ def _url(self, path: str) -> str:
35
+ """Build full URL for an API endpoint."""
36
+ return urljoin(self.config.base_url, f"/api/v1{path}")
37
+
38
+ def _request(
39
+ self,
40
+ method: str,
41
+ path: str,
42
+ data: Optional[Dict[str, Any]] = None,
43
+ params: Optional[Dict[str, Any]] = None,
44
+ ) -> Dict[str, Any]:
45
+ """
46
+ Make an HTTP request with retry logic.
47
+
48
+ Args:
49
+ method: HTTP method
50
+ path: API path
51
+ data: Request body data
52
+ params: Query parameters
53
+
54
+ Returns:
55
+ Response JSON data
56
+
57
+ Raises:
58
+ AuthenticationError: If authentication fails
59
+ APIError: If the request fails
60
+ """
61
+ url = self._url(path)
62
+ last_error = None
63
+
64
+ for attempt in range(self.config.retry_count):
65
+ try:
66
+ response = self.session.request(
67
+ method=method,
68
+ url=url,
69
+ json=data,
70
+ params=params,
71
+ timeout=self.config.timeout,
72
+ )
73
+
74
+ if response.status_code == 401:
75
+ raise AuthenticationError("Invalid API key")
76
+
77
+ if response.status_code == 403:
78
+ raise AuthenticationError("Insufficient permissions")
79
+
80
+ if response.status_code >= 400:
81
+ error_data = response.json() if response.text else {}
82
+ raise APIError(
83
+ error_data.get(
84
+ "error",
85
+ f"Request failed with status {response.status_code}",
86
+ ),
87
+ status_code=response.status_code,
88
+ response=error_data,
89
+ )
90
+
91
+ if response.text:
92
+ return response.json()
93
+ return {}
94
+
95
+ except requests.exceptions.RequestException as e:
96
+ last_error = e
97
+ if attempt < self.config.retry_count - 1:
98
+ time.sleep(self.config.retry_delay * (attempt + 1))
99
+ continue
100
+
101
+ raise APIError(
102
+ f"Request failed after {self.config.retry_count} attempts: {last_error}"
103
+ )
104
+
105
+ def create_run(
106
+ self,
107
+ team_slug: str,
108
+ app_slug: str,
109
+ name: Optional[str] = None,
110
+ tags: Optional[List[str]] = None,
111
+ config: Optional[Dict[str, Any]] = None,
112
+ git_info: Optional[Dict[str, Any]] = None,
113
+ system_info: Optional[Dict[str, Any]] = None,
114
+ ) -> str:
115
+ """
116
+ Create a new run.
117
+
118
+ Returns:
119
+ The run ID
120
+ """
121
+ data = {
122
+ "name": name,
123
+ "tags": tags or [],
124
+ "config": config or {},
125
+ }
126
+
127
+ if git_info:
128
+ data["git_info"] = git_info
129
+ if system_info:
130
+ data["system_info"] = system_info
131
+
132
+ response = self._request(
133
+ "POST",
134
+ f"/teams/{team_slug}/apps/{app_slug}/runs",
135
+ data=data,
136
+ )
137
+
138
+ return response["id"]
139
+
140
+ def update_run_status(
141
+ self,
142
+ run_id: str,
143
+ status: str,
144
+ error: Optional[str] = None,
145
+ ) -> None:
146
+ """Update run status (completed, failed, aborted)."""
147
+ data = {"status": status}
148
+ if error:
149
+ data["error_message"] = error
150
+
151
+ self._request("PUT", f"/runs/{run_id}/status", data=data)
152
+
153
+ def update_run_config(self, run_id: str, config: Dict[str, Any]) -> None:
154
+ """Merge new config with existing run config."""
155
+ self._request("PUT", f"/runs/{run_id}/config", data=config)
156
+
157
+ def batch_log_metrics(
158
+ self,
159
+ run_id: str,
160
+ metrics: List[Dict[str, Any]],
161
+ ) -> None:
162
+ """
163
+ Log a batch of metrics.
164
+
165
+ Args:
166
+ run_id: The run ID
167
+ metrics: List of metric dictionaries with name, value, step, timestamp
168
+ """
169
+ self._request(
170
+ "POST",
171
+ f"/runs/{run_id}/metrics",
172
+ data={"metrics": metrics},
173
+ )
174
+
175
+ def add_run_tags(self, run_id: str, tags: List[str]) -> None:
176
+ """Add tags to a run."""
177
+ # This would need to be implemented via the update endpoint
178
+ pass
179
+
180
+ def upload_artifact(
181
+ self,
182
+ run_id: str,
183
+ path: str,
184
+ name: Optional[str] = None,
185
+ metadata: Optional[Dict[str, Any]] = None,
186
+ ) -> None:
187
+ """Upload an artifact file."""
188
+ # Artifact upload would need multipart form handling
189
+ # For now, this is a placeholder
190
+ pass
@@ -0,0 +1,161 @@
1
+ """Configuration management for the Sixtyseven SDK."""
2
+
3
+ import os
4
+ from dataclasses import dataclass, field
5
+ from pathlib import Path
6
+ from typing import Literal, Optional
7
+
8
+
9
+ def _default_logdir() -> str:
10
+ """Get the default log directory based on platform."""
11
+ import platform
12
+
13
+ home = Path.home()
14
+ system = platform.system()
15
+
16
+ if system == "Darwin":
17
+ return str(home / "Library" / "Application Support" / "sixtyseven" / "logs")
18
+ elif system == "Windows":
19
+ appdata = os.environ.get("LOCALAPPDATA", str(home / "AppData" / "Local"))
20
+ return str(Path(appdata) / "sixtyseven" / "logs")
21
+ else: # Linux and others
22
+ xdg_data = os.environ.get("XDG_DATA_HOME", str(home / ".local" / "share"))
23
+ return str(Path(xdg_data) / "sixtyseven" / "logs")
24
+
25
+
26
+ @dataclass
27
+ class SDKConfig:
28
+ """SDK configuration settings."""
29
+
30
+ # Mode: "local" or "remote"
31
+ mode: Literal["local", "remote"] = "local"
32
+
33
+ # Remote mode settings
34
+ base_url: str = "http://localhost:8080"
35
+ api_key: Optional[str] = None
36
+
37
+ # Local mode settings
38
+ logdir: str = field(default_factory=_default_logdir)
39
+
40
+ # Common settings
41
+ batch_size: int = 100
42
+ flush_interval: float = 5.0
43
+ capture_git: bool = True
44
+ capture_system: bool = True
45
+
46
+ # Remote-only settings
47
+ timeout: int = 30
48
+ retry_count: int = 3
49
+ retry_delay: float = 1.0
50
+
51
+
52
+ # Global configuration instance
53
+ _config = SDKConfig()
54
+
55
+
56
+ def configure(
57
+ mode: Optional[Literal["local", "remote"]] = None,
58
+ base_url: Optional[str] = None,
59
+ api_key: Optional[str] = None,
60
+ logdir: Optional[str] = None,
61
+ batch_size: Optional[int] = None,
62
+ flush_interval: Optional[float] = None,
63
+ capture_git: Optional[bool] = None,
64
+ capture_system: Optional[bool] = None,
65
+ timeout: Optional[int] = None,
66
+ retry_count: Optional[int] = None,
67
+ retry_delay: Optional[float] = None,
68
+ ) -> SDKConfig:
69
+ """
70
+ Configure the SDK globally.
71
+
72
+ Args:
73
+ mode: Operating mode ("local" for file-based, "remote" for API server)
74
+ base_url: Base URL for the Sixtyseven API server (remote mode)
75
+ api_key: API key for authentication (remote mode)
76
+ logdir: Directory for storing logs (local mode)
77
+ batch_size: Number of metrics to batch before sending/writing
78
+ flush_interval: Seconds between automatic flushes
79
+ capture_git: Whether to capture git information
80
+ capture_system: Whether to capture system information
81
+ timeout: Request timeout in seconds (remote mode)
82
+ retry_count: Number of retries for failed requests (remote mode)
83
+ retry_delay: Delay between retries in seconds (remote mode)
84
+
85
+ Returns:
86
+ The updated configuration
87
+
88
+ Example (local mode - default):
89
+ from sixtyseven import configure
90
+
91
+ configure(logdir="./logs") # Use local file storage
92
+
93
+ Example (remote mode):
94
+ from sixtyseven import configure
95
+
96
+ configure(
97
+ mode="remote",
98
+ base_url="https://api.sixtyseven.ai",
99
+ api_key="ss67_xxxx",
100
+ )
101
+ """
102
+ global _config
103
+
104
+ if mode is not None:
105
+ _config.mode = mode
106
+ if base_url is not None:
107
+ _config.base_url = base_url
108
+ if api_key is not None:
109
+ _config.api_key = api_key
110
+ if logdir is not None:
111
+ _config.logdir = logdir
112
+ if batch_size is not None:
113
+ _config.batch_size = batch_size
114
+ if flush_interval is not None:
115
+ _config.flush_interval = flush_interval
116
+ if capture_git is not None:
117
+ _config.capture_git = capture_git
118
+ if capture_system is not None:
119
+ _config.capture_system = capture_system
120
+ if timeout is not None:
121
+ _config.timeout = timeout
122
+ if retry_count is not None:
123
+ _config.retry_count = retry_count
124
+ if retry_delay is not None:
125
+ _config.retry_delay = retry_delay
126
+
127
+ return _config
128
+
129
+
130
+ def _detect_mode() -> Literal["local", "remote"]:
131
+ """
132
+ Auto-detect the operating mode based on environment variables.
133
+
134
+ Priority:
135
+ 1. SIXTYSEVEN_LOGDIR set -> local mode
136
+ 2. SIXTYSEVEN_URL or SIXTYSEVEN_API_KEY set -> remote mode
137
+ 3. Default -> local mode (zero config experience)
138
+ """
139
+ if os.environ.get("SIXTYSEVEN_LOGDIR"):
140
+ return "local"
141
+ if os.environ.get("SIXTYSEVEN_URL") or os.environ.get("SIXTYSEVEN_API_KEY"):
142
+ return "remote"
143
+ return "local"
144
+
145
+
146
+ def get_config() -> SDKConfig:
147
+ """Get the current SDK configuration with environment variable overrides."""
148
+ global _config
149
+
150
+ # Auto-detect mode from environment
151
+ _config.mode = _detect_mode()
152
+
153
+ # Override with environment variables
154
+ if os.environ.get("SIXTYSEVEN_LOGDIR"):
155
+ _config.logdir = os.environ["SIXTYSEVEN_LOGDIR"]
156
+ if os.environ.get("SIXTYSEVEN_URL"):
157
+ _config.base_url = os.environ["SIXTYSEVEN_URL"]
158
+ if os.environ.get("SIXTYSEVEN_API_KEY"):
159
+ _config.api_key = os.environ["SIXTYSEVEN_API_KEY"]
160
+
161
+ return _config