yanex 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,165 @@
1
+ """
2
+ Input validation utilities.
3
+ """
4
+
5
+ import re
6
+ from pathlib import Path
7
+ from typing import Any, Dict
8
+
9
+ from .exceptions import ValidationError
10
+
11
+
12
+ def validate_experiment_name(name: str) -> str:
13
+ """
14
+ Validate experiment name.
15
+
16
+ Args:
17
+ name: Experiment name to validate
18
+
19
+ Returns:
20
+ Validated name
21
+
22
+ Raises:
23
+ ValidationError: If name is invalid
24
+ """
25
+ if not name or not name.strip():
26
+ raise ValidationError("Experiment name cannot be empty")
27
+
28
+ name = name.strip()
29
+
30
+ # Check length
31
+ if len(name) > 100:
32
+ raise ValidationError("Experiment name cannot exceed 100 characters")
33
+
34
+ # Check for invalid characters (basic validation)
35
+ if not re.match(r"^[a-zA-Z0-9_\-\s]+$", name):
36
+ raise ValidationError(
37
+ "Experiment name can only contain letters, numbers, spaces, hyphens, and underscores"
38
+ )
39
+
40
+ return name
41
+
42
+
43
+ def validate_experiment_id(experiment_id: str) -> str:
44
+ """
45
+ Validate experiment ID format.
46
+
47
+ Args:
48
+ experiment_id: ID to validate
49
+
50
+ Returns:
51
+ Validated ID
52
+
53
+ Raises:
54
+ ValidationError: If ID format is invalid
55
+ """
56
+ if not re.match(r"^[a-f0-9]{8}$", experiment_id):
57
+ raise ValidationError(
58
+ f"Invalid experiment ID format: {experiment_id}. "
59
+ "Expected 8-character hex string (e.g., 'a1b2c3d4')"
60
+ )
61
+
62
+ return experiment_id
63
+
64
+
65
+ def validate_tags(tags: list[str]) -> list[str]:
66
+ """
67
+ Validate experiment tags.
68
+
69
+ Args:
70
+ tags: List of tags to validate
71
+
72
+ Returns:
73
+ Validated and cleaned tags
74
+
75
+ Raises:
76
+ ValidationError: If any tag is invalid
77
+ """
78
+ if not isinstance(tags, list):
79
+ raise ValidationError("Tags must be a list")
80
+
81
+ validated_tags = []
82
+ for tag in tags:
83
+ if not isinstance(tag, str):
84
+ raise ValidationError(f"Tag must be a string: {tag}")
85
+
86
+ tag = tag.strip()
87
+ if not tag:
88
+ continue # Skip empty tags
89
+
90
+ if len(tag) > 50:
91
+ raise ValidationError(f"Tag too long (max 50 chars): {tag}")
92
+
93
+ if not re.match(r"^[a-zA-Z0-9_\-]+$", tag):
94
+ raise ValidationError(
95
+ f"Tag contains invalid characters: {tag}. "
96
+ "Only letters, numbers, hyphens, and underscores allowed"
97
+ )
98
+
99
+ validated_tags.append(tag)
100
+
101
+ return validated_tags
102
+
103
+
104
+ def validate_script_path(script_path: Path) -> Path:
105
+ """
106
+ Validate experiment script path.
107
+
108
+ Args:
109
+ script_path: Path to script file
110
+
111
+ Returns:
112
+ Validated path
113
+
114
+ Raises:
115
+ ValidationError: If script path is invalid
116
+ """
117
+ if not script_path.exists():
118
+ raise ValidationError(f"Script file does not exist: {script_path}")
119
+
120
+ if not script_path.is_file():
121
+ raise ValidationError(f"Script path is not a file: {script_path}")
122
+
123
+ if script_path.suffix != ".py":
124
+ raise ValidationError(f"Script must be a Python file (.py): {script_path}")
125
+
126
+ return script_path
127
+
128
+
129
+ def validate_config_data(config_data: Dict[str, Any]) -> Dict[str, Any]:
130
+ """
131
+ Validate configuration data structure.
132
+
133
+ Args:
134
+ config_data: Configuration dictionary
135
+
136
+ Returns:
137
+ Validated configuration
138
+
139
+ Raises:
140
+ ValidationError: If configuration is invalid
141
+ """
142
+ if not isinstance(config_data, dict):
143
+ raise ValidationError("Configuration must be a dictionary")
144
+
145
+ # Basic validation - ensure values are JSON serializable types
146
+ allowed_types = (str, int, float, bool, list, dict, type(None))
147
+
148
+ def validate_value(key: str, value: Any) -> None:
149
+ if not isinstance(value, allowed_types):
150
+ raise ValidationError(
151
+ f"Configuration value for '{key}' must be JSON serializable, "
152
+ f"got {type(value).__name__}"
153
+ )
154
+
155
+ if isinstance(value, dict):
156
+ for subkey, subvalue in value.items():
157
+ validate_value(f"{key}.{subkey}", subvalue)
158
+ elif isinstance(value, list):
159
+ for i, item in enumerate(value):
160
+ validate_value(f"{key}[{i}]", item)
161
+
162
+ for key, value in config_data.items():
163
+ validate_value(key, value)
164
+
165
+ return config_data
@@ -0,0 +1,251 @@
1
+ Metadata-Version: 2.4
2
+ Name: yanex
3
+ Version: 0.1.0
4
+ Summary: Yet Another Experiment Tracker - A lightweight experiment tracking harness
5
+ Author: Thomas
6
+ Author-email: Thomas <from+gitgub@tomr.au>
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/rueckstiess/yanex
9
+ Project-URL: Repository, https://github.com/rueckstiess/yanex
10
+ Project-URL: Documentation, https://github.com/rueckstiess/yanex/blob/main/docs/README.md
11
+ Project-URL: Issues, https://github.com/rueckstiess/yanex/issues
12
+ Keywords: experiment,tracking,machine-learning,research,reproducibility
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Topic :: Scientific/Engineering
25
+ Classifier: Topic :: Software Development :: Libraries
26
+ Requires-Python: >=3.8
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: click>=8.0.0
30
+ Requires-Dist: pyyaml>=6.0
31
+ Requires-Dist: rich>=12.0.0
32
+ Requires-Dist: gitpython>=3.1.0
33
+ Requires-Dist: dateparser>=1.1.0
34
+ Requires-Dist: textual>=0.45.0
35
+ Provides-Extra: dev
36
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
37
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
38
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
39
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
40
+ Provides-Extra: matplotlib
41
+ Requires-Dist: matplotlib>=3.5.0; extra == "matplotlib"
42
+ Dynamic: author
43
+ Dynamic: license-file
44
+ Dynamic: requires-python
45
+
46
+ # Yanex - Yet Another Experiment Tracker
47
+
48
+ A lightweight, Git-aware experiment tracking system for Python that makes reproducible research effortless.
49
+
50
+ ## Why Yanex?
51
+
52
+ **Stop losing track of your experiments.** Yanex automatically tracks parameters, results, and code state so you can focus on what matters - your research.
53
+
54
+ ```python
55
+ import yanex
56
+
57
+ # read parameters from config file or CLI arguments
58
+ lr = yanex.get_param('lr', default=0.001)
59
+ epochs = yanex.get_param('epochs', default=10)
60
+
61
+ # access nested parameters with dot notation
62
+ model_lr = yanex.get_param('model.learning_rate', default=0.001)
63
+ optimizer_type = yanex.get_param('model.optimizer.type', default='adam')
64
+
65
+ # your experiment code
66
+ # ...
67
+
68
+ # log results, artifacts and figures
69
+ yanex.log_results({"step": epoch, "loss", loss, "accuracy": accuracy})
70
+ yanex.log_artifact("model.pth", model_path)
71
+ yanex.log_matplotlib_figure(fig, "loss_curve.png")
72
+ ```
73
+
74
+ Run from the command line:
75
+
76
+ ```bash
77
+ # Run with yanex CLI for automatic tracking
78
+ yanex run train.py --name "my-experiment" --tag testing --param lr=0.001 --param epochs=10
79
+ ```
80
+
81
+ That's it. Yanex tracks the experiment, saves the logged results and files, stdout and stderr outptus, Python environment
82
+ information, and even the Git state of your code repository. You can then compare results, search experiments, and reproduce them with ease.
83
+
84
+ ## Key Features
85
+
86
+ - 🔒 **Reproducible**: Automatic Git state tracking ensures every experiment is reproducible
87
+ - 📊 **Interactive Comparison**: Compare experiments side-by-side with an interactive table
88
+ - ⚙️ **Flexible Parameters**: YAML configs with CLI overrides for easy experimentation and syntax for parameter sweeps
89
+ - 📈 **Rich Logging**: Track metrics, artifacts, and figures
90
+ - 🔍 **Powerful Search**: Find experiments by status, parameters, tags, or time ranges
91
+ - 📦 **Zero Dependencies**: No external services required - works offline
92
+
93
+ ## Quick Start
94
+
95
+ ### Install
96
+ ```bash
97
+ pip install yanex
98
+ ```
99
+
100
+ ### 1. Run Your First Experiment
101
+
102
+ ```python
103
+ # experiment.py
104
+ import yanex
105
+
106
+ params = yanex.get_params()
107
+ print(f"Learning rate: {params.get('learning_rate', 0.001)}")
108
+
109
+ # Simulate training
110
+ accuracy = 0.85 + (params.get('learning_rate', 0.001) * 10)
111
+
112
+ yanex.log_results({
113
+ "accuracy": accuracy,
114
+ "loss": 1 - accuracy
115
+ })
116
+ ```
117
+
118
+ ```bash
119
+ # Run with default parameters
120
+ yanex run experiment.py
121
+
122
+ # Override parameters
123
+ yanex run experiment.py --param learning_rate=0.01 --param epochs=50
124
+
125
+ # Add tags for organization
126
+ yanex run experiment.py --tag baseline --tag "quick-test"
127
+ ```
128
+
129
+ ### 2. Compare Results
130
+
131
+ ```bash
132
+ # Interactive comparison table
133
+ yanex compare
134
+
135
+ # Compare specific experiments
136
+ yanex compare exp1 exp2 exp3
137
+
138
+ # Filter and compare
139
+ yanex compare --status completed --tag baseline
140
+ ```
141
+
142
+ ### 3. Track Everything
143
+
144
+ List, search, and manage your experiments:
145
+
146
+ ```bash
147
+ # List recent experiments
148
+ yanex list
149
+
150
+ # Find experiments by criteria
151
+ yanex list --status completed --tag production
152
+ yanex list --started-after "1 week ago"
153
+
154
+ # Show detailed experiment info
155
+ yanex show exp_id
156
+
157
+ # Archive old experiments
158
+ yanex archive --started-before "1 month ago"
159
+ ```
160
+
161
+ ## Two Ways to Use Yanex
162
+
163
+ Yanex supports two usage patterns:
164
+
165
+ ### 1. CLI-First (Recommended)
166
+ Write scripts that work both standalone and with yanex tracking:
167
+
168
+ ```python
169
+ # train.py - Works both ways!
170
+ import yanex
171
+
172
+ params = yanex.get_params() # Gets parameters or defaults
173
+ lr = params.get('learning_rate', 0.001)
174
+
175
+ # Your training code
176
+ accuracy = train_model(lr=lr)
177
+
178
+ # Logging works in both contexts
179
+ yanex.log_results({"accuracy": accuracy})
180
+ ```
181
+
182
+ ```bash
183
+ # Run standalone (no tracking)
184
+ python train.py
185
+
186
+ # Run with yanex (full tracking)
187
+ yanex run train.py --param learning_rate=0.01
188
+ ```
189
+
190
+ ### 2. Explicit Experiment Creation (Advanced)
191
+ For Jupyter notebook usage, or when you need fine control:
192
+
193
+ ```python
194
+ import yanex
195
+ from pathlib import Path
196
+
197
+ with yanex.create_experiment(
198
+ script_path=Path(__file__),
199
+ name="my-experiment",
200
+ config={"learning_rate": 0.01}
201
+ ) as exp:
202
+ # Your code here
203
+ exp.log_results({"accuracy": 0.95})
204
+ ```
205
+
206
+ > **Note:** Don't mix both patterns! Use CLI-first for most cases, explicit creation for advanced scenarios.
207
+
208
+
209
+ ## Configuration Files
210
+
211
+ Create `config.yaml` for default parameters:
212
+
213
+ ```yaml
214
+ # config.yaml
215
+ model:
216
+ learning_rate: 0.001
217
+ batch_size: 32
218
+ epochs: 100
219
+
220
+ data:
221
+ dataset: "cifar10"
222
+ augmentation: true
223
+
224
+ training:
225
+ optimizer: "adam"
226
+ scheduler: "cosine"
227
+ ```
228
+
229
+
230
+ ## Documentation
231
+
232
+ 📚 **[Complete Documentation](docs/README.md)** - Detailed guides and API reference
233
+
234
+ **Quick Links:**
235
+ - [CLI Commands](docs/cli-commands.md) - All yanex commands with examples
236
+ - [Python API](docs/python-api.md) - Complete Python API reference
237
+ - [Configuration](docs/configuration.md) - Parameter management and config files
238
+ - [Comparison Tool](docs/compare.md) - Interactive experiment comparison
239
+ - [Best Practices](docs/best-practices.md) - Tips for effective experiment tracking
240
+
241
+
242
+ ## Contributing
243
+
244
+ Yanex is open source and welcomes contributions! See our [contributing guidelines](CONTRIBUTING.md) for details.
245
+
246
+ **Built with assistance from [Claude](https://claude.ai).**
247
+
248
+ ## License
249
+
250
+ MIT License - see [LICENSE](LICENSE) for details.
251
+
@@ -0,0 +1,39 @@
1
+ yanex/__init__.py,sha256=cLkL_PqB9yEfN6IezrwBhUSZMO27GfYF0Lj-ZEplOr8,1656
2
+ yanex/api.py,sha256=NCn62gBayaDqGpDMx3YHbsaaYVdDUdPNQ8qBse5kLMA,15872
3
+ yanex/cli/__init__.py,sha256=cSwcfp10naAJ7h_0cMP69rcKjkNf8skMUm1o-ilpRoM,42
4
+ yanex/cli/_utils.py,sha256=21IaSR5Q4w3fuHRFLAs4n01ot9YjDiZEbVB_f3P7bk0,3581
5
+ yanex/cli/main.py,sha256=9OpGsMdMbb2N02vYXTXGCoE6wSEHdlPWutuRUcM8kII,1406
6
+ yanex/cli/commands/__init__.py,sha256=oXtVFCrTNmapQnpHRhK-EZPv5ZVLXhdVzW0giuk4II8,37
7
+ yanex/cli/commands/archive.py,sha256=klbgZl_iDz12qTd87WJirp0QVIp7sxdoAg_lbc9yOCY,6178
8
+ yanex/cli/commands/compare.py,sha256=x2uv0q_z40eufkMj1xLb60dwPruzfBnausMZeml0sB4,10650
9
+ yanex/cli/commands/confirm.py,sha256=tFvUFAZXPQIBU68pi_LLi_LL5B4MLb8FrScdNKbwZHo,6633
10
+ yanex/cli/commands/delete.py,sha256=a0j5dkG6s-Qvm4pIl5Y4C46_CDwTfg_A-OD_jb8x_0U,7145
11
+ yanex/cli/commands/list.py,sha256=QTPBzd6Twc9l3SPFdDXtqXnn-N1INhJKc5qmE-I5mGo,7826
12
+ yanex/cli/commands/run.py,sha256=X2Q2UP9ayQ62kP33rosMQfbsj9y8GxEeFS9mQIrKomM,20728
13
+ yanex/cli/commands/show.py,sha256=D9GxhmzWg56iEN2AwRIUX1SbStnwEYpMT7vYgLJ1ctU,21380
14
+ yanex/cli/commands/unarchive.py,sha256=HSqYuyMkhWv8N0-UxBDXfZfCwBJc1PB2MXhImQTmlck,6178
15
+ yanex/cli/commands/update.py,sha256=pR8v2cF4YKYVY71qhhquw-F6SO6Tq3-ZFnnOS5t06M8,9412
16
+ yanex/cli/filters/__init__.py,sha256=ljd8rJJTW-jq6CRmXiF7WtRBwH76kesjbCQH3OMZZjo,180
17
+ yanex/cli/filters/base.py,sha256=jteG3xTOtfsn5HydOwGlaG_lyDrnP9mLYW5xOzTO1QM,10523
18
+ yanex/cli/filters/time_utils.py,sha256=ow8BmwObVASt5fP2lbLOrUVydxXiBYt448DHrNic6dQ,5395
19
+ yanex/cli/formatters/__init__.py,sha256=ont-oEcZPuSFDy6xYjQCbUOpt-oSDtjBlfkXslNiFIk,149
20
+ yanex/cli/formatters/console.py,sha256=WGhSr5Od2PGZRxNE_DoSfSVh2aPNbuUrcDRNPt_fdug,10829
21
+ yanex/core/__init__.py,sha256=5TDyT1B2UzH0pKUEXv0qq-owBURx85PNmtPR-T-Qgow,82
22
+ yanex/core/comparison.py,sha256=1RsDULYDapFCgrtqKn3D0Al_Ve6VW-CR_iNIKn3hbBM,18607
23
+ yanex/core/config.py,sha256=P5e6RP9a366r5gMEzmmsMsMyEGiCqFd2acQphPZ1CSs,17633
24
+ yanex/core/constants.py,sha256=5dJDeH9kowrtsl_4koZmUsneDalPENaiiKpKg8Bf9x8,287
25
+ yanex/core/environment.py,sha256=hEd67ci1N267JZk9V1NMfgbC-iD--lkVPWXS9CJx6jo,4009
26
+ yanex/core/git_utils.py,sha256=I1ODxpImjbuiuXYqALixYJzyUwJ4lVDLRO7NnaF1jEM,3873
27
+ yanex/core/manager.py,sha256=FW61YUv5yxxdQQdHV6im8Tgdo_S6s8ngPKzhfpiaP30,19227
28
+ yanex/core/storage.py,sha256=bfjt2WoJeEj4tupY7pBA-cRhGiavTzxQherIPYNszw4,21255
29
+ yanex/ui/__init__.py,sha256=kSrnufTm8RgL3zFSmm8z0UG4uHYJ8ukLJapKGAFXR1s,26
30
+ yanex/ui/compare_table.py,sha256=hnnyMTIYGS_uUDLzaO9xVfU3-LCqw7MG6FzzJN5NOZk,17453
31
+ yanex/utils/__init__.py,sha256=VKx8CJQrl6fOsufYs2w_0OUrFUfzNFNOyvztrBskHiU,49
32
+ yanex/utils/exceptions.py,sha256=-DN-0PEQRC2Gm-Jbix2LIRdfDmbZF0eETcunMQrKzYs,1632
33
+ yanex/utils/validation.py,sha256=H4uMSni16KX1gXgKKlTXqzTdGmwKZZLJi7qm8MX-ips,4216
34
+ yanex-0.1.0.dist-info/licenses/LICENSE,sha256=rZEeAJISICOoGdJ3WfS4JVLrbzkNKKM0h4p521HTD0s,1074
35
+ yanex-0.1.0.dist-info/METADATA,sha256=zVbt5yvJZnFfqDBX47be-zI2PVGB2UuEsxdqEg8as3s,6985
36
+ yanex-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
37
+ yanex-0.1.0.dist-info/entry_points.txt,sha256=XcqUiw6toVUyeDuZ1_TYr33ghcaAjjndaanhIaRT9qE,45
38
+ yanex-0.1.0.dist-info/top_level.txt,sha256=j6GpTPgyijbV2tyrZQxT7ZHEdZkvTBxjYY2VKcJMJ7Y,6
39
+ yanex-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ yanex = yanex.cli.main:cli
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Thomas Rueckstiess
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ yanex