glitchlings 0.4.5__cp311-cp311-win_amd64.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.

Potentially problematic release.


This version of glitchlings might be problematic. Click here for more details.

Files changed (53) hide show
  1. glitchlings/__init__.py +71 -0
  2. glitchlings/__main__.py +8 -0
  3. glitchlings/_zoo_rust.cp311-win_amd64.pyd +0 -0
  4. glitchlings/compat.py +282 -0
  5. glitchlings/config.py +386 -0
  6. glitchlings/config.toml +3 -0
  7. glitchlings/data/__init__.py +1 -0
  8. glitchlings/data/hokey_assets.json +193 -0
  9. glitchlings/dlc/__init__.py +7 -0
  10. glitchlings/dlc/_shared.py +153 -0
  11. glitchlings/dlc/huggingface.py +81 -0
  12. glitchlings/dlc/prime.py +254 -0
  13. glitchlings/dlc/pytorch.py +166 -0
  14. glitchlings/dlc/pytorch_lightning.py +209 -0
  15. glitchlings/lexicon/__init__.py +192 -0
  16. glitchlings/lexicon/_cache.py +108 -0
  17. glitchlings/lexicon/data/default_vector_cache.json +82 -0
  18. glitchlings/lexicon/metrics.py +162 -0
  19. glitchlings/lexicon/vector.py +652 -0
  20. glitchlings/lexicon/wordnet.py +228 -0
  21. glitchlings/main.py +364 -0
  22. glitchlings/util/__init__.py +195 -0
  23. glitchlings/util/adapters.py +27 -0
  24. glitchlings/util/hokey_generator.py +144 -0
  25. glitchlings/util/stretch_locator.py +140 -0
  26. glitchlings/util/stretchability.py +375 -0
  27. glitchlings/zoo/__init__.py +172 -0
  28. glitchlings/zoo/_ocr_confusions.py +32 -0
  29. glitchlings/zoo/_rate.py +131 -0
  30. glitchlings/zoo/_rust_extensions.py +143 -0
  31. glitchlings/zoo/_sampling.py +54 -0
  32. glitchlings/zoo/_text_utils.py +100 -0
  33. glitchlings/zoo/adjax.py +128 -0
  34. glitchlings/zoo/apostrofae.py +127 -0
  35. glitchlings/zoo/assets/__init__.py +0 -0
  36. glitchlings/zoo/assets/apostrofae_pairs.json +32 -0
  37. glitchlings/zoo/core.py +582 -0
  38. glitchlings/zoo/hokey.py +173 -0
  39. glitchlings/zoo/jargoyle.py +335 -0
  40. glitchlings/zoo/mim1c.py +109 -0
  41. glitchlings/zoo/ocr_confusions.tsv +30 -0
  42. glitchlings/zoo/redactyl.py +193 -0
  43. glitchlings/zoo/reduple.py +148 -0
  44. glitchlings/zoo/rushmore.py +153 -0
  45. glitchlings/zoo/scannequin.py +171 -0
  46. glitchlings/zoo/typogre.py +231 -0
  47. glitchlings/zoo/zeedub.py +185 -0
  48. glitchlings-0.4.5.dist-info/METADATA +648 -0
  49. glitchlings-0.4.5.dist-info/RECORD +53 -0
  50. glitchlings-0.4.5.dist-info/WHEEL +5 -0
  51. glitchlings-0.4.5.dist-info/entry_points.txt +2 -0
  52. glitchlings-0.4.5.dist-info/licenses/LICENSE +201 -0
  53. glitchlings-0.4.5.dist-info/top_level.txt +1 -0
glitchlings/config.py ADDED
@@ -0,0 +1,386 @@
1
+ """Configuration utilities for runtime behaviour and declarative attack setups."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ import os
7
+ import warnings
8
+ from dataclasses import dataclass, field
9
+ from io import TextIOBase
10
+ from pathlib import Path
11
+ from typing import IO, TYPE_CHECKING, Any, Mapping, Protocol, Sequence, cast
12
+
13
+ from glitchlings.compat import jsonschema
14
+
15
+ try: # Python 3.11+
16
+ import tomllib as _tomllib
17
+ except ModuleNotFoundError: # pragma: no cover - Python < 3.11
18
+ _tomllib = importlib.import_module("tomli")
19
+
20
+
21
+ class _TomllibModule(Protocol):
22
+ def load(self, fp: IO[bytes]) -> Any: ...
23
+
24
+
25
+ tomllib = cast(_TomllibModule, _tomllib)
26
+
27
+
28
+ class _YamlModule(Protocol):
29
+ YAMLError: type[Exception]
30
+
31
+ def safe_load(self, stream: str) -> Any: ...
32
+
33
+
34
+ yaml = cast(_YamlModule, importlib.import_module("yaml"))
35
+
36
+ if TYPE_CHECKING: # pragma: no cover - typing only
37
+ from .zoo import Gaggle, Glitchling
38
+
39
+
40
+ CONFIG_ENV_VAR = "GLITCHLINGS_CONFIG"
41
+ DEFAULT_CONFIG_PATH = Path(__file__).with_name("config.toml")
42
+ DEFAULT_LEXICON_PRIORITY = ["vector", "wordnet"]
43
+ DEFAULT_ATTACK_SEED = 151
44
+
45
+ ATTACK_CONFIG_SCHEMA: dict[str, Any] = {
46
+ "type": "object",
47
+ "required": ["glitchlings"],
48
+ "properties": {
49
+ "glitchlings": {
50
+ "type": "array",
51
+ "minItems": 1,
52
+ "items": {
53
+ "anyOf": [
54
+ {"type": "string", "minLength": 1},
55
+ {
56
+ "type": "object",
57
+ "required": ["name"],
58
+ "properties": {
59
+ "name": {"type": "string", "minLength": 1},
60
+ "type": {"type": "string", "minLength": 1},
61
+ "parameters": {"type": "object"},
62
+ },
63
+ "additionalProperties": True,
64
+ },
65
+ {
66
+ "type": "object",
67
+ "required": ["type"],
68
+ "properties": {
69
+ "name": {"type": "string", "minLength": 1},
70
+ "type": {"type": "string", "minLength": 1},
71
+ "parameters": {"type": "object"},
72
+ },
73
+ "additionalProperties": True,
74
+ },
75
+ ]
76
+ },
77
+ },
78
+ "seed": {"type": "integer"},
79
+ },
80
+ "additionalProperties": False,
81
+ }
82
+
83
+
84
+ @dataclass(slots=True)
85
+ class LexiconConfig:
86
+ """Lexicon-specific configuration section."""
87
+
88
+ priority: list[str] = field(default_factory=lambda: list(DEFAULT_LEXICON_PRIORITY))
89
+ vector_cache: Path | None = None
90
+
91
+
92
+ @dataclass(slots=True)
93
+ class RuntimeConfig:
94
+ """Top-level runtime configuration loaded from ``config.toml``."""
95
+
96
+ lexicon: LexiconConfig
97
+ path: Path
98
+
99
+
100
+ _CONFIG: RuntimeConfig | None = None
101
+
102
+
103
+ def reset_config() -> None:
104
+ """Forget any cached runtime configuration."""
105
+ global _CONFIG
106
+ _CONFIG = None
107
+
108
+
109
+ def reload_config() -> RuntimeConfig:
110
+ """Reload the runtime configuration from disk."""
111
+ reset_config()
112
+ return get_config()
113
+
114
+
115
+ def get_config() -> RuntimeConfig:
116
+ """Return the cached runtime configuration, loading it if necessary."""
117
+ global _CONFIG
118
+ if _CONFIG is None:
119
+ _CONFIG = _load_runtime_config()
120
+ return _CONFIG
121
+
122
+
123
+ def _load_runtime_config() -> RuntimeConfig:
124
+ path = _resolve_config_path()
125
+ data = _read_toml(path)
126
+ mapping = _validate_runtime_config_data(data, source=path)
127
+
128
+ lexicon_section = mapping.get("lexicon", {})
129
+
130
+ priority = lexicon_section.get("priority", DEFAULT_LEXICON_PRIORITY)
131
+ if not isinstance(priority, Sequence) or isinstance(priority, (str, bytes)):
132
+ raise ValueError("lexicon.priority must be a sequence of strings.")
133
+ normalized_priority = []
134
+ for item in priority:
135
+ string_value = str(item)
136
+ if not string_value:
137
+ raise ValueError("lexicon.priority entries must be non-empty strings.")
138
+ normalized_priority.append(string_value)
139
+
140
+ vector_cache = _resolve_optional_path(
141
+ lexicon_section.get("vector_cache"),
142
+ base=path.parent,
143
+ )
144
+ lexicon_config = LexiconConfig(
145
+ priority=normalized_priority,
146
+ vector_cache=vector_cache,
147
+ )
148
+
149
+ return RuntimeConfig(lexicon=lexicon_config, path=path)
150
+
151
+
152
+ def _resolve_config_path() -> Path:
153
+ override = os.environ.get(CONFIG_ENV_VAR)
154
+ if override:
155
+ return Path(override)
156
+ return DEFAULT_CONFIG_PATH
157
+
158
+
159
+ def _read_toml(path: Path) -> dict[str, Any]:
160
+ if not path.exists():
161
+ if path == DEFAULT_CONFIG_PATH:
162
+ return {}
163
+ raise FileNotFoundError(f"Configuration file '{path}' not found.")
164
+ with path.open("rb") as handle:
165
+ loaded = tomllib.load(handle)
166
+ if isinstance(loaded, Mapping):
167
+ return dict(loaded)
168
+ raise ValueError(f"Configuration file '{path}' must contain a top-level mapping.")
169
+
170
+
171
+ def _validate_runtime_config_data(data: Any, *, source: Path) -> Mapping[str, Any]:
172
+ if data is None:
173
+ return {}
174
+ if not isinstance(data, Mapping):
175
+ raise ValueError(f"Configuration file '{source}' must contain a top-level mapping.")
176
+
177
+ allowed_sections = {"lexicon"}
178
+ unexpected_sections = [str(key) for key in data if key not in allowed_sections]
179
+ if unexpected_sections:
180
+ extras = ", ".join(sorted(unexpected_sections))
181
+ raise ValueError(f"Configuration file '{source}' has unsupported sections: {extras}.")
182
+
183
+ lexicon_section = data.get("lexicon", {})
184
+ if not isinstance(lexicon_section, Mapping):
185
+ raise ValueError("Configuration 'lexicon' section must be a table.")
186
+
187
+ allowed_lexicon_keys = {"priority", "vector_cache"}
188
+ unexpected_keys = [str(key) for key in lexicon_section if key not in allowed_lexicon_keys]
189
+ if unexpected_keys:
190
+ extras = ", ".join(sorted(unexpected_keys))
191
+ raise ValueError(f"Unknown lexicon settings: {extras}.")
192
+
193
+ for key in ("vector_cache",):
194
+ value = lexicon_section.get(key)
195
+ if value is not None and not isinstance(value, (str, os.PathLike)):
196
+ raise ValueError(f"lexicon.{key} must be a path or string when provided.")
197
+
198
+ return data
199
+
200
+
201
+ def _resolve_optional_path(value: Any, *, base: Path) -> Path | None:
202
+ if value in (None, ""):
203
+ return None
204
+
205
+ candidate = Path(str(value))
206
+ if not candidate.is_absolute():
207
+ candidate = (base / candidate).resolve()
208
+ return candidate
209
+
210
+
211
+ @dataclass(slots=True)
212
+ class AttackConfig:
213
+ """Structured representation of a glitchling roster loaded from YAML."""
214
+
215
+ glitchlings: list["Glitchling"]
216
+ seed: int | None = None
217
+
218
+
219
+ def load_attack_config(
220
+ source: str | Path | TextIOBase,
221
+ *,
222
+ encoding: str = "utf-8",
223
+ ) -> AttackConfig:
224
+ """Load and parse an attack configuration from YAML."""
225
+ if isinstance(source, (str, Path)):
226
+ path = Path(source)
227
+ label = str(path)
228
+ try:
229
+ text = path.read_text(encoding=encoding)
230
+ except FileNotFoundError as exc:
231
+ raise ValueError(f"Attack configuration '{label}' was not found.") from exc
232
+ elif isinstance(source, TextIOBase):
233
+ label = getattr(source, "name", "<stream>")
234
+ text = source.read()
235
+ else:
236
+ raise TypeError("Attack configuration source must be a path or text stream.")
237
+
238
+ data = _load_yaml(text, label)
239
+ return parse_attack_config(data, source=label)
240
+
241
+
242
+ def _validate_attack_config_schema(data: Any, *, source: str) -> Mapping[str, Any]:
243
+ if data is None:
244
+ raise ValueError(f"Attack configuration '{source}' is empty.")
245
+ if not isinstance(data, Mapping):
246
+ raise ValueError(f"Attack configuration '{source}' must be a mapping.")
247
+
248
+ unexpected = [key for key in data if key not in {"glitchlings", "seed"}]
249
+ if unexpected:
250
+ extras = ", ".join(sorted(unexpected))
251
+ raise ValueError(f"Attack configuration '{source}' has unsupported fields: {extras}.")
252
+
253
+ if "glitchlings" not in data:
254
+ raise ValueError(f"Attack configuration '{source}' must define 'glitchlings'.")
255
+
256
+ raw_glitchlings = data["glitchlings"]
257
+ if not isinstance(raw_glitchlings, Sequence) or isinstance(raw_glitchlings, (str, bytes)):
258
+ raise ValueError(f"'glitchlings' in '{source}' must be a sequence.")
259
+
260
+ seed = data.get("seed")
261
+ if seed is not None and not isinstance(seed, int):
262
+ raise ValueError(f"Seed in '{source}' must be an integer if provided.")
263
+
264
+ for index, entry in enumerate(raw_glitchlings, start=1):
265
+ if isinstance(entry, Mapping):
266
+ name_candidate = entry.get("name") or entry.get("type")
267
+ if not isinstance(name_candidate, str) or not name_candidate.strip():
268
+ raise ValueError(f"{source}: glitchling #{index} is missing a 'name'.")
269
+ parameters = entry.get("parameters")
270
+ if parameters is not None and not isinstance(parameters, Mapping):
271
+ raise ValueError(
272
+ f"{source}: glitchling '{name_candidate}' parameters must be a mapping."
273
+ )
274
+
275
+ schema_module = jsonschema.get()
276
+ if schema_module is not None:
277
+ try:
278
+ schema_module.validate(instance=data, schema=ATTACK_CONFIG_SCHEMA)
279
+ except schema_module.exceptions.ValidationError as exc: # pragma: no cover - optional dep
280
+ message = exc.message
281
+ raise ValueError(f"Attack configuration '{source}' is invalid: {message}") from exc
282
+
283
+ return data
284
+
285
+
286
+ def parse_attack_config(data: Any, *, source: str = "<config>") -> AttackConfig:
287
+ """Convert arbitrary YAML data into a validated ``AttackConfig``."""
288
+ mapping = _validate_attack_config_schema(data, source=source)
289
+
290
+ raw_glitchlings = mapping["glitchlings"]
291
+
292
+ glitchlings: list["Glitchling"] = []
293
+ for index, entry in enumerate(raw_glitchlings, start=1):
294
+ glitchlings.append(_build_glitchling(entry, source, index))
295
+
296
+ seed = mapping.get("seed")
297
+
298
+ return AttackConfig(glitchlings=glitchlings, seed=seed)
299
+
300
+
301
+ def build_gaggle(config: AttackConfig, *, seed_override: int | None = None) -> "Gaggle":
302
+ """Instantiate a ``Gaggle`` according to ``config``."""
303
+ from .zoo import Gaggle # Imported lazily to avoid circular dependencies
304
+
305
+ seed = seed_override if seed_override is not None else config.seed
306
+ if seed is None:
307
+ seed = DEFAULT_ATTACK_SEED
308
+
309
+ return Gaggle(config.glitchlings, seed=seed)
310
+
311
+
312
+ def _load_yaml(text: str, label: str) -> Any:
313
+ try:
314
+ return yaml.safe_load(text)
315
+ except yaml.YAMLError as exc:
316
+ raise ValueError(f"Failed to parse attack configuration '{label}': {exc}") from exc
317
+
318
+
319
+ def _build_glitchling(entry: Any, source: str, index: int) -> "Glitchling":
320
+ from .zoo import get_glitchling_class, parse_glitchling_spec
321
+
322
+ if isinstance(entry, str):
323
+ try:
324
+ return parse_glitchling_spec(entry)
325
+ except ValueError as exc:
326
+ raise ValueError(f"{source}: glitchling #{index}: {exc}") from exc
327
+
328
+ if isinstance(entry, Mapping):
329
+ name_value = entry.get("name")
330
+ legacy_type = entry.get("type")
331
+ if name_value is None and legacy_type is not None:
332
+ warnings.warn(
333
+ f"{source}: glitchling #{index} uses 'type'; prefer 'name'.",
334
+ DeprecationWarning,
335
+ stacklevel=2,
336
+ )
337
+ name_value = legacy_type
338
+ elif name_value is None:
339
+ name_value = legacy_type
340
+
341
+ if not isinstance(name_value, str) or not name_value.strip():
342
+ raise ValueError(f"{source}: glitchling #{index} is missing a 'name'.")
343
+
344
+ parameters = entry.get("parameters")
345
+ if parameters is not None:
346
+ if not isinstance(parameters, Mapping):
347
+ raise ValueError(
348
+ f"{source}: glitchling '{name_value}' parameters must be a mapping."
349
+ )
350
+ kwargs = dict(parameters)
351
+ else:
352
+ kwargs = {
353
+ key: value
354
+ for key, value in entry.items()
355
+ if key not in {"name", "type", "parameters"}
356
+ }
357
+
358
+ try:
359
+ glitchling_type = get_glitchling_class(name_value)
360
+ except ValueError as exc:
361
+ raise ValueError(f"{source}: glitchling #{index}: {exc}") from exc
362
+
363
+ try:
364
+ return glitchling_type(**kwargs)
365
+ except TypeError as exc:
366
+ raise ValueError(
367
+ f"{source}: glitchling #{index}: failed to instantiate '{name_value}': {exc}"
368
+ ) from exc
369
+
370
+ raise ValueError(f"{source}: glitchling #{index} must be a string or mapping.")
371
+
372
+
373
+ __all__ = [
374
+ "AttackConfig",
375
+ "DEFAULT_ATTACK_SEED",
376
+ "DEFAULT_CONFIG_PATH",
377
+ "DEFAULT_LEXICON_PRIORITY",
378
+ "RuntimeConfig",
379
+ "LexiconConfig",
380
+ "build_gaggle",
381
+ "get_config",
382
+ "load_attack_config",
383
+ "parse_attack_config",
384
+ "reload_config",
385
+ "reset_config",
386
+ ]
@@ -0,0 +1,3 @@
1
+ [lexicon]
2
+ priority = ["vector", "wordnet"]
3
+ vector_cache = "lexicon/data/default_vector_cache.json"
@@ -0,0 +1 @@
1
+ """Static data assets shared across Glitchlings implementations."""
@@ -0,0 +1,193 @@
1
+ {
2
+ "lexical_prior": {
3
+ "so": 0.92,
4
+ "no": 0.89,
5
+ "go": 0.72,
6
+ "yeah": 0.86,
7
+ "yay": 0.81,
8
+ "ya": 0.7,
9
+ "hey": 0.66,
10
+ "okay": 0.68,
11
+ "ok": 0.64,
12
+ "cool": 0.78,
13
+ "omg": 0.74,
14
+ "wow": 0.88,
15
+ "wee": 0.62,
16
+ "woo": 0.69,
17
+ "woohoo": 0.74,
18
+ "whoa": 0.71,
19
+ "woah": 0.7,
20
+ "yayyy": 0.75,
21
+ "yayyyy": 0.76,
22
+ "yas": 0.79,
23
+ "yass": 0.8,
24
+ "yaaas": 0.82,
25
+ "please": 0.53,
26
+ "pleaseee": 0.57,
27
+ "pleaseeee": 0.6,
28
+ "pleaseeeee": 0.63,
29
+ "lol": 0.83,
30
+ "lmao": 0.65,
31
+ "omggg": 0.75,
32
+ "omgggg": 0.76,
33
+ "squee": 0.64,
34
+ "hahaha": 0.6,
35
+ "haha": 0.56,
36
+ "really": 0.58,
37
+ "very": 0.49,
38
+ "love": 0.55,
39
+ "cute": 0.52,
40
+ "nice": 0.47,
41
+ "sweet": 0.45,
42
+ "yayness": 0.44,
43
+ "ugh": 0.5,
44
+ "aww": 0.61,
45
+ "yess": 0.81,
46
+ "yes": 0.9,
47
+ "pls": 0.48,
48
+ "pleeeease": 0.62,
49
+ "nooo": 0.88,
50
+ "noooo": 0.89,
51
+ "dang": 0.41,
52
+ "geez": 0.39,
53
+ "danggg": 0.44,
54
+ "dangit": 0.38,
55
+ "sick": 0.35,
56
+ "epic": 0.37,
57
+ "rad": 0.5,
58
+ "goal": 0.56,
59
+ "great": 0.46,
60
+ "awesome": 0.51,
61
+ "amazing": 0.52,
62
+ "perfect": 0.49,
63
+ "fantastic": 0.5,
64
+ "stellar": 0.48,
65
+ "yippee": 0.67,
66
+ "stoked": 0.48,
67
+ "yikes": 0.43,
68
+ "gosh": 0.41,
69
+ "heck": 0.36
70
+ },
71
+ "interjections": [
72
+ "wow",
73
+ "omg",
74
+ "hey",
75
+ "ugh",
76
+ "yay",
77
+ "yayyy",
78
+ "yayyyy",
79
+ "woo",
80
+ "woohoo",
81
+ "whoa",
82
+ "woah",
83
+ "whooo",
84
+ "ah",
85
+ "aw",
86
+ "aww",
87
+ "hmm",
88
+ "huh",
89
+ "yo",
90
+ "yikes",
91
+ "gah",
92
+ "phew",
93
+ "sheesh"
94
+ ],
95
+ "intensifiers": [
96
+ "so",
97
+ "very",
98
+ "really",
99
+ "super",
100
+ "mega",
101
+ "ultra",
102
+ "too",
103
+ "way",
104
+ "crazy",
105
+ "insanely",
106
+ "totally",
107
+ "extremely",
108
+ "seriously",
109
+ "absolutely",
110
+ "completely",
111
+ "entirely",
112
+ "utterly",
113
+ "hella",
114
+ "wicked",
115
+ "truly"
116
+ ],
117
+ "evaluatives": [
118
+ "cool",
119
+ "great",
120
+ "awesome",
121
+ "amazing",
122
+ "perfect",
123
+ "nice",
124
+ "sweet",
125
+ "lovely",
126
+ "loving",
127
+ "silly",
128
+ "wild",
129
+ "fun",
130
+ "funny",
131
+ "adorable",
132
+ "cute",
133
+ "fantastic",
134
+ "fabulous",
135
+ "brilliant",
136
+ "stellar",
137
+ "rad",
138
+ "epic",
139
+ "delightful",
140
+ "gorgeous"
141
+ ],
142
+ "positive_lexicon": [
143
+ "love",
144
+ "loved",
145
+ "loving",
146
+ "like",
147
+ "liked",
148
+ "awesome",
149
+ "amazing",
150
+ "yay",
151
+ "great",
152
+ "good",
153
+ "fun",
154
+ "funny",
155
+ "blessed",
156
+ "excited",
157
+ "cool",
158
+ "best",
159
+ "beautiful",
160
+ "happy",
161
+ "happiest",
162
+ "joy",
163
+ "joyful",
164
+ "thrilled",
165
+ "ecstatic",
166
+ "stoked",
167
+ "pumped",
168
+ "glad"
169
+ ],
170
+ "negative_lexicon": [
171
+ "bad",
172
+ "sad",
173
+ "angry",
174
+ "annoyed",
175
+ "mad",
176
+ "terrible",
177
+ "awful",
178
+ "hate",
179
+ "hated",
180
+ "crying",
181
+ "hurt",
182
+ "tired",
183
+ "worst",
184
+ "ugh",
185
+ "nope",
186
+ "upset",
187
+ "frustrated",
188
+ "drained",
189
+ "exhausted",
190
+ "bummed",
191
+ "grumpy"
192
+ ]
193
+ }
@@ -0,0 +1,7 @@
1
+ """Optional DLC integrations for Glitchlings."""
2
+
3
+ from .huggingface import install as install_huggingface
4
+ from .pytorch import install as install_pytorch
5
+ from .pytorch_lightning import install as install_pytorch_lightning
6
+
7
+ __all__ = ["install_huggingface", "install_pytorch", "install_pytorch_lightning"]