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.
- yanex/__init__.py +74 -0
- yanex/api.py +507 -0
- yanex/cli/__init__.py +3 -0
- yanex/cli/_utils.py +114 -0
- yanex/cli/commands/__init__.py +3 -0
- yanex/cli/commands/archive.py +177 -0
- yanex/cli/commands/compare.py +320 -0
- yanex/cli/commands/confirm.py +198 -0
- yanex/cli/commands/delete.py +203 -0
- yanex/cli/commands/list.py +243 -0
- yanex/cli/commands/run.py +625 -0
- yanex/cli/commands/show.py +560 -0
- yanex/cli/commands/unarchive.py +177 -0
- yanex/cli/commands/update.py +282 -0
- yanex/cli/filters/__init__.py +8 -0
- yanex/cli/filters/base.py +286 -0
- yanex/cli/filters/time_utils.py +178 -0
- yanex/cli/formatters/__init__.py +7 -0
- yanex/cli/formatters/console.py +325 -0
- yanex/cli/main.py +45 -0
- yanex/core/__init__.py +3 -0
- yanex/core/comparison.py +549 -0
- yanex/core/config.py +587 -0
- yanex/core/constants.py +16 -0
- yanex/core/environment.py +146 -0
- yanex/core/git_utils.py +153 -0
- yanex/core/manager.py +555 -0
- yanex/core/storage.py +682 -0
- yanex/ui/__init__.py +1 -0
- yanex/ui/compare_table.py +524 -0
- yanex/utils/__init__.py +3 -0
- yanex/utils/exceptions.py +70 -0
- yanex/utils/validation.py +165 -0
- yanex-0.1.0.dist-info/METADATA +251 -0
- yanex-0.1.0.dist-info/RECORD +39 -0
- yanex-0.1.0.dist-info/WHEEL +5 -0
- yanex-0.1.0.dist-info/entry_points.txt +2 -0
- yanex-0.1.0.dist-info/licenses/LICENSE +21 -0
- yanex-0.1.0.dist-info/top_level.txt +1 -0
@@ -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,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
|