shadow-tools 0.4.0__tar.gz → 0.4.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.
Files changed (39) hide show
  1. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/PKG-INFO +14 -1
  2. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/README.md +13 -0
  3. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/pyproject.toml +4 -1
  4. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/__init__.py +3 -2
  5. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/training.py +60 -0
  6. shadow_tools-0.4.0/release-0.2.0/shadow_tools-0.2.0-py3-none-any.whl +0 -0
  7. shadow_tools-0.4.0/release-0.2.0/shadow_tools-0.2.0.tar.gz +0 -0
  8. shadow_tools-0.4.0/release-0.2.1/shadow_tools-0.2.1-py3-none-any.whl +0 -0
  9. shadow_tools-0.4.0/release-0.2.1/shadow_tools-0.2.1.tar.gz +0 -0
  10. shadow_tools-0.4.0/release-0.2.2/shadow_tools-0.2.2-py3-none-any.whl +0 -0
  11. shadow_tools-0.4.0/release-0.2.2/shadow_tools-0.2.2.tar.gz +0 -0
  12. shadow_tools-0.4.0/release-0.2.3/shadow_tools-0.2.3-py3-none-any.whl +0 -0
  13. shadow_tools-0.4.0/release-0.2.3/shadow_tools-0.2.3.tar.gz +0 -0
  14. shadow_tools-0.4.0/release-0.2.4/shadow_tools-0.2.4-py3-none-any.whl +0 -0
  15. shadow_tools-0.4.0/release-0.2.4/shadow_tools-0.2.4.tar.gz +0 -0
  16. shadow_tools-0.4.0/release-0.2.5/shadow_tools-0.2.5-py3-none-any.whl +0 -0
  17. shadow_tools-0.4.0/release-0.2.5/shadow_tools-0.2.5.tar.gz +0 -0
  18. shadow_tools-0.4.0/release-0.3.0/shadow_tools-0.3.0-py3-none-any.whl +0 -0
  19. shadow_tools-0.4.0/release-0.3.0/shadow_tools-0.3.0.tar.gz +0 -0
  20. shadow_tools-0.4.0/release-0.4.0/shadow_tools-0.4.0-py3-none-any.whl +0 -0
  21. shadow_tools-0.4.0/release-0.4.0/shadow_tools-0.4.0.tar.gz +0 -0
  22. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/.gitignore +0 -0
  23. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/LICENSE +0 -0
  24. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/ai.py +0 -0
  25. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/async_tools.py +0 -0
  26. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/cli.py +0 -0
  27. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/config.py +0 -0
  28. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/data.py +0 -0
  29. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/files.py +0 -0
  30. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/hashing.py +0 -0
  31. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/http.py +0 -0
  32. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/ids.py +0 -0
  33. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/network.py +0 -0
  34. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/retry.py +0 -0
  35. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/security.py +0 -0
  36. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/text.py +0 -0
  37. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/validation.py +0 -0
  38. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/src/shadowtools/websocket.py +0 -0
  39. {shadow_tools-0.4.0 → shadow_tools-0.4.2}/tests/test_core.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: shadow-tools
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: A practical all-in-one Python toolkit for HTTP, WebSockets, files, data, validation, async work, and CLIs.
5
5
  Project-URL: Homepage, https://github.com/WilliamLaurento/shadow-tools
6
6
  Project-URL: Issues, https://github.com/WilliamLaurento/shadow-tools/issues
@@ -145,6 +145,19 @@ trainer = Trainer(config)
145
145
  history = trainer.fit(train_loader, validation_loader)
146
146
  ```
147
147
 
148
+ For normal lists, use the short root-level API—no PyTorch imports or loaders:
149
+
150
+ ```python
151
+ from shadowtools import train
152
+
153
+ model, history = train(
154
+ features=[[0, 1], [1, 1], [0, 0], [1, 0]],
155
+ labels=[1, 1, 0, 0],
156
+ epochs=10,
157
+ )
158
+ print(history)
159
+ ```
160
+
148
161
  ```bash
149
162
  shadow get https://api.github.com --json
150
163
  shadow json-format settings.json
@@ -119,6 +119,19 @@ trainer = Trainer(config)
119
119
  history = trainer.fit(train_loader, validation_loader)
120
120
  ```
121
121
 
122
+ For normal lists, use the short root-level API—no PyTorch imports or loaders:
123
+
124
+ ```python
125
+ from shadowtools import train
126
+
127
+ model, history = train(
128
+ features=[[0, 1], [1, 1], [0, 0], [1, 0]],
129
+ labels=[1, 1, 0, 0],
130
+ epochs=10,
131
+ )
132
+ print(history)
133
+ ```
134
+
122
135
  ```bash
123
136
  shadow get https://api.github.com --json
124
137
  shadow json-format settings.json
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "shadow-tools"
7
- version = "0.4.0"
7
+ version = "0.4.2"
8
8
  description = "A practical all-in-one Python toolkit for HTTP, WebSockets, files, data, validation, async work, and CLIs."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -35,5 +35,8 @@ Issues = "https://github.com/WilliamLaurento/shadow-tools/issues"
35
35
  [tool.hatch.build.targets.wheel]
36
36
  packages = ["src/shadowtools"]
37
37
 
38
+ [tool.hatch.build]
39
+ exclude = ["*.zip", "release-*", "dist", "build", ".pytest_cache"]
40
+
38
41
  [tool.pytest.ini_options]
39
42
  testpaths = ["tests"]
@@ -10,12 +10,13 @@ from .network import generate_random_ips, generate_test_ips, is_subdomain_of, ma
10
10
  from .retry import retry
11
11
  from .security import audit_headers, make_password, redact_secrets, verify_file_hash
12
12
  from .text import chunks, slugify, to_camel, to_snake
13
+ from .training import fine_tune, make_loaders, train
13
14
  from .validation import is_email, is_url, require_keys
14
15
 
15
- __version__ = "0.4.0"
16
+ __version__ = "0.4.2"
16
17
 
17
18
  __all__ = [
18
19
  "append_text", "audit_headers", "chunks", "ensure_dir", "env", "file_hash", "generate_random_ips", "generate_test_ips", "get", "hash_text", "is_email", "is_subdomain_of", "is_url", "make_password", "make_subdomain", "random_token", "read_csv",
19
- "read_json", "read_text", "request", "require_env", "require_keys", "retry", "reverse_dns", "resolve_dns", "slugify", "timestamp_id", "to_bool", "to_camel", "to_snake", "uuid4", "write_csv",
20
+ "read_json", "read_text", "request", "require_env", "require_keys", "retry", "reverse_dns", "resolve_dns", "slugify", "timestamp_id", "to_bool", "to_camel", "to_snake", "train", "fine_tune", "make_loaders", "uuid4", "write_csv",
20
21
  "write_json", "write_text", "redact_secrets", "verify_file_hash",
21
22
  ]
@@ -168,3 +168,63 @@ class Trainer:
168
168
  def run_training(config: TrainingConfig | Mapping[str, Any], train_data: Iterable[Any], validation_data: Iterable[Any] | None = None) -> tuple[Trainer, list[dict[str, float]]]:
169
169
  trainer = Trainer(config)
170
170
  return trainer, trainer.fit(train_data, validation_data)
171
+
172
+
173
+ def make_loaders(features: Any, labels: Any, *, batch_size: int = 32,
174
+ validation_split: float = 0.2, shuffle: bool = True) -> tuple[Any, Any]:
175
+ """Turn ordinary lists or tensors into PyTorch training and validation loaders."""
176
+ torch = Trainer._torch()
177
+ if not 0 < validation_split < 1:
178
+ raise ValueError("validation_split must be between 0 and 1.")
179
+ x = torch.as_tensor(features, dtype=torch.float32)
180
+ y = torch.as_tensor(labels, dtype=torch.long).flatten()
181
+ if x.ndim != 2:
182
+ raise ValueError("features must be a 2D list or tensor: rows by input values.")
183
+ if len(x) != len(y) or len(x) < 2:
184
+ raise ValueError("features and labels must have the same length of at least 2.")
185
+ if int(y.min()) < 0:
186
+ raise ValueError("labels must be zero or positive integer class IDs.")
187
+ order = torch.randperm(len(x)) if shuffle else torch.arange(len(x))
188
+ split = max(1, min(len(x) - 1, round(len(x) * (1 - validation_split))))
189
+ train_set = torch.utils.data.TensorDataset(x[order[:split]], y[order[:split]])
190
+ validation_set = torch.utils.data.TensorDataset(x[order[split:]], y[order[split:]])
191
+ return (
192
+ torch.utils.data.DataLoader(train_set, batch_size=batch_size, shuffle=shuffle),
193
+ torch.utils.data.DataLoader(validation_set, batch_size=batch_size),
194
+ )
195
+
196
+
197
+ def train(features: Any, labels: Any, *, hidden_sizes: list[int] | None = None,
198
+ epochs: int = 5, learning_rate: float = 1e-3, batch_size: int = 32,
199
+ validation_split: float = 0.2, device: str = "auto",
200
+ checkpoint_path: str | Path | None = None) -> tuple[Trainer, list[dict[str, float]]]:
201
+ """Train a classifier from regular Python lists or tensors in one call."""
202
+ torch = Trainer._torch()
203
+ x = torch.as_tensor(features)
204
+ y = torch.as_tensor(labels).flatten()
205
+ if x.ndim != 2 or not len(y):
206
+ raise ValueError("features must be 2D and labels must not be empty.")
207
+ train_loader, validation_loader = make_loaders(features, labels, batch_size=batch_size, validation_split=validation_split)
208
+ config = TrainingConfig(
209
+ mode="from_scratch", epochs=epochs, learning_rate=learning_rate, batch_size=batch_size,
210
+ device=device, checkpoint_path=checkpoint_path,
211
+ architecture={"type": "mlp", "input_size": int(x.shape[1]),
212
+ "hidden_sizes": hidden_sizes or [64, 32],
213
+ "num_classes": int(y.max().item()) + 1},
214
+ )
215
+ return run_training(config, train_loader, validation_loader)
216
+
217
+
218
+ def fine_tune(model: Any, features: Any, labels: Any, *, epochs: int = 3,
219
+ learning_rate: float = 1e-4, batch_size: int = 32,
220
+ validation_split: float = 0.2, freeze_layers: tuple[str, ...] = (),
221
+ head_attr: str | None = None, num_classes: int | None = None,
222
+ device: str = "auto", checkpoint_path: str | Path | None = None) -> tuple[Trainer, list[dict[str, float]]]:
223
+ """Fine-tune a supplied PyTorch classifier from regular lists or tensors."""
224
+ train_loader, validation_loader = make_loaders(features, labels, batch_size=batch_size, validation_split=validation_split)
225
+ config = TrainingConfig(
226
+ mode="fine_tune", model=model, epochs=epochs, learning_rate=learning_rate,
227
+ batch_size=batch_size, device=device, checkpoint_path=checkpoint_path,
228
+ freeze_layers=freeze_layers, head_attr=head_attr, num_classes=num_classes,
229
+ )
230
+ return run_training(config, train_loader, validation_loader)
File without changes
File without changes