glitchlings 0.4.5__cp313-cp313-win_amd64.whl → 0.5.0__cp313-cp313-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.

Binary file
glitchlings/config.py CHANGED
@@ -4,7 +4,6 @@ from __future__ import annotations
4
4
 
5
5
  import importlib
6
6
  import os
7
- import warnings
8
7
  from dataclasses import dataclass, field
9
8
  from io import TextIOBase
10
9
  from pathlib import Path
@@ -57,17 +56,6 @@ ATTACK_CONFIG_SCHEMA: dict[str, Any] = {
57
56
  "required": ["name"],
58
57
  "properties": {
59
58
  "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
59
  "parameters": {"type": "object"},
72
60
  },
73
61
  "additionalProperties": True,
@@ -263,7 +251,12 @@ def _validate_attack_config_schema(data: Any, *, source: str) -> Mapping[str, An
263
251
 
264
252
  for index, entry in enumerate(raw_glitchlings, start=1):
265
253
  if isinstance(entry, Mapping):
266
- name_candidate = entry.get("name") or entry.get("type")
254
+ if "type" in entry:
255
+ raise ValueError(
256
+ f"{source}: glitchling #{index} uses unsupported 'type'; use 'name'."
257
+ )
258
+
259
+ name_candidate = entry.get("name")
267
260
  if not isinstance(name_candidate, str) or not name_candidate.strip():
268
261
  raise ValueError(f"{source}: glitchling #{index} is missing a 'name'.")
269
262
  parameters = entry.get("parameters")
@@ -326,17 +319,12 @@ def _build_glitchling(entry: Any, source: str, index: int) -> "Glitchling":
326
319
  raise ValueError(f"{source}: glitchling #{index}: {exc}") from exc
327
320
 
328
321
  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,
322
+ if "type" in entry:
323
+ raise ValueError(
324
+ f"{source}: glitchling #{index} uses unsupported 'type'; use 'name'."
336
325
  )
337
- name_value = legacy_type
338
- elif name_value is None:
339
- name_value = legacy_type
326
+
327
+ name_value = entry.get("name")
340
328
 
341
329
  if not isinstance(name_value, str) or not name_value.strip():
342
330
  raise ValueError(f"{source}: glitchling #{index} is missing a 'name'.")
@@ -352,7 +340,7 @@ def _build_glitchling(entry: Any, source: str, index: int) -> "Glitchling":
352
340
  kwargs = {
353
341
  key: value
354
342
  for key, value in entry.items()
355
- if key not in {"name", "type", "parameters"}
343
+ if key not in {"name", "parameters"}
356
344
  }
357
345
 
358
346
  try:
@@ -0,0 +1,5 @@
1
+ """Developer-facing utilities for maintaining the Glitchlings repository."""
2
+
3
+ from .sync_assets import sync_assets
4
+
5
+ __all__ = ["sync_assets"]
@@ -0,0 +1,153 @@
1
+ """Synchronise canonical glitchling assets with the vendored Rust copies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import shutil
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Iterator, Sequence
10
+
11
+ RUST_VENDORED_ASSETS: frozenset[str] = frozenset({
12
+ "hokey_assets.json",
13
+ "ocr_confusions.tsv",
14
+ })
15
+
16
+
17
+ def _project_root(default: Path | None = None) -> Path:
18
+ if default is not None:
19
+ return default
20
+ return Path(__file__).resolve().parents[3]
21
+
22
+
23
+ def _canonical_asset_dir(project_root: Path) -> Path:
24
+ canonical = project_root / "src" / "glitchlings" / "zoo" / "assets"
25
+ if not canonical.is_dir():
26
+ raise RuntimeError(
27
+ "expected canonical assets under 'src/glitchlings/zoo/assets'; "
28
+ "run this command from the repository root"
29
+ )
30
+ return canonical
31
+
32
+
33
+ def _rust_asset_dir(project_root: Path) -> Path:
34
+ return project_root / "rust" / "zoo" / "assets"
35
+
36
+
37
+ def _iter_extraneous_assets(rust_dir: Path) -> Iterator[Path]:
38
+ if not rust_dir.exists():
39
+ return
40
+ for path in rust_dir.iterdir():
41
+ if path.is_file() and path.name not in RUST_VENDORED_ASSETS:
42
+ yield path
43
+
44
+
45
+ def sync_assets(
46
+ project_root: Path | None = None,
47
+ *,
48
+ check: bool = False,
49
+ quiet: bool = False,
50
+ ) -> bool:
51
+ """Synchronise the vendored Rust asset copies with the canonical sources."""
52
+
53
+ root = _project_root(project_root)
54
+ canonical_dir = _canonical_asset_dir(root)
55
+ rust_dir = _rust_asset_dir(root)
56
+
57
+ missing_sources = [
58
+ name
59
+ for name in RUST_VENDORED_ASSETS
60
+ if not (canonical_dir / name).is_file()
61
+ ]
62
+ if missing_sources:
63
+ missing_list = ", ".join(sorted(missing_sources))
64
+ raise RuntimeError(f"missing canonical assets: {missing_list}")
65
+
66
+ extraneous = list(_iter_extraneous_assets(rust_dir))
67
+
68
+ mismatched: list[tuple[str, str]] = []
69
+ for name in sorted(RUST_VENDORED_ASSETS):
70
+ source = canonical_dir / name
71
+ target = rust_dir / name
72
+ if not target.exists():
73
+ mismatched.append((name, "missing"))
74
+ continue
75
+ if source.read_bytes() != target.read_bytes():
76
+ mismatched.append((name, "outdated"))
77
+
78
+ if check:
79
+ if mismatched or extraneous:
80
+ if not quiet:
81
+ for name, reason in mismatched:
82
+ target = rust_dir / name
83
+ print(
84
+ f"{target.relative_to(root)} is {reason}; run sync_assets to refresh it",
85
+ file=sys.stderr,
86
+ )
87
+ for extra in extraneous:
88
+ print(
89
+ (
90
+ "unexpected vendored asset "
91
+ f"{extra.relative_to(root)}; run sync_assets to prune it"
92
+ ),
93
+ file=sys.stderr,
94
+ )
95
+ return False
96
+ if not quiet:
97
+ print("Rust asset bundle is up to date.")
98
+ return True
99
+
100
+ rust_dir.mkdir(parents=True, exist_ok=True)
101
+
102
+ for name, reason in mismatched:
103
+ source = canonical_dir / name
104
+ target = rust_dir / name
105
+ shutil.copy2(source, target)
106
+ if not quiet:
107
+ verb = "Copied" if reason == "missing" else "Updated"
108
+ print(
109
+ f"{verb} {source.relative_to(root)} -> {target.relative_to(root)}",
110
+ )
111
+
112
+ for extra in extraneous:
113
+ extra.unlink()
114
+ if not quiet:
115
+ print(f"Removed extraneous vendored asset {extra.relative_to(root)}")
116
+
117
+ if not mismatched and not extraneous and not quiet:
118
+ print("Rust asset bundle already aligned with canonical copies.")
119
+
120
+ return True
121
+
122
+
123
+ def build_parser() -> argparse.ArgumentParser:
124
+ parser = argparse.ArgumentParser(
125
+ description="Synchronise canonical glitchling assets with the vendored Rust copies.",
126
+ )
127
+ parser.add_argument(
128
+ "--check",
129
+ action="store_true",
130
+ help="exit with a non-zero status when vendored assets diverge",
131
+ )
132
+ parser.add_argument(
133
+ "--quiet",
134
+ action="store_true",
135
+ help="suppress status output",
136
+ )
137
+ parser.add_argument(
138
+ "--project-root",
139
+ type=Path,
140
+ help="override the detected project root (useful for testing)",
141
+ )
142
+ return parser
143
+
144
+
145
+ def main(argv: Sequence[str] | None = None) -> int:
146
+ parser = build_parser()
147
+ args = parser.parse_args(argv)
148
+ ok = sync_assets(project_root=args.project_root, check=args.check, quiet=args.quiet)
149
+ return 0 if ok else 1
150
+
151
+
152
+ if __name__ == "__main__": # pragma: no cover - CLI entry point
153
+ raise SystemExit(main())
@@ -2,11 +2,11 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import json
6
5
  import re
7
6
  from dataclasses import dataclass
8
- from importlib import resources
9
- from typing import Any, Protocol, Sequence, TypedDict, cast
7
+ from typing import Protocol, Sequence, TypedDict, cast
8
+
9
+ from glitchlings.zoo import assets
10
10
 
11
11
  # Regexes reused across the module
12
12
  TOKEN_REGEX = re.compile(r"\w+|\W+")
@@ -32,12 +32,7 @@ class RandomLike(Protocol):
32
32
 
33
33
  # Lexical prior probabilities and pragmatic lexica shared with the Rust fast path.
34
34
  def _load_assets() -> HokeyAssets:
35
- with (
36
- resources.files("glitchlings.data")
37
- .joinpath("hokey_assets.json")
38
- .open("r", encoding="utf-8") as payload
39
- ):
40
- data: Any = json.load(payload)
35
+ data = assets.load_json("hokey_assets.json")
41
36
  return cast(HokeyAssets, data)
42
37
 
43
38
 
@@ -1,18 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
- from importlib import resources
3
+ from .assets import read_text
4
4
 
5
5
  _CONFUSION_TABLE: list[tuple[str, list[str]]] | None = None
6
6
 
7
7
 
8
8
  def load_confusion_table() -> list[tuple[str, list[str]]]:
9
9
  """Load the OCR confusion table shared by Python and Rust implementations."""
10
+
10
11
  global _CONFUSION_TABLE
11
12
  if _CONFUSION_TABLE is not None:
12
13
  return _CONFUSION_TABLE
13
14
 
14
- data = resources.files(__package__) / "ocr_confusions.tsv"
15
- text = data.read_text(encoding="utf-8")
15
+ text = read_text("ocr_confusions.tsv")
16
16
  indexed_entries: list[tuple[int, tuple[str, list[str]]]] = []
17
17
  for line_number, line in enumerate(text.splitlines()):
18
18
  stripped = line.strip()
@@ -21,9 +21,9 @@ def split_token_edges(token: str) -> tuple[str, str, str]:
21
21
  return match.group(1), match.group(2), match.group(3)
22
22
 
23
23
 
24
- def token_core_length(token: str) -> int:
25
- """Return the length of the main word characters for weighting heuristics."""
26
- _, core, _ = split_token_edges(token)
24
+ def _resolve_core_length(core: str, token: str) -> int:
25
+ """Return a stable core-length measurement used by weighting heuristics."""
26
+
27
27
  candidate = core if core else token
28
28
  length = len(candidate)
29
29
  if length <= 0:
@@ -34,6 +34,12 @@ def token_core_length(token: str) -> int:
34
34
  return length
35
35
 
36
36
 
37
+ def token_core_length(token: str) -> int:
38
+ """Return the length of the main word characters for weighting heuristics."""
39
+ _, core, _ = split_token_edges(token)
40
+ return _resolve_core_length(core, token)
41
+
42
+
37
43
  @dataclass(frozen=True)
38
44
  class WordToken:
39
45
  """Metadata describing a non-whitespace token yielded by word splitters."""
@@ -71,12 +77,7 @@ def collect_word_tokens(
71
77
  continue
72
78
 
73
79
  prefix, core, suffix = split_token_edges(token)
74
- core_length = len(core)
75
- if core_length <= 0:
76
- stripped = token.strip()
77
- core_length = len(stripped) if stripped else len(token)
78
- if core_length <= 0:
79
- core_length = 1
80
+ core_length = _resolve_core_length(core, token)
80
81
 
81
82
  collected.append(
82
83
  WordToken(
glitchlings/zoo/adjax.py CHANGED
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  import random
4
4
  from typing import Any, cast
5
5
 
6
- from ._rate import resolve_rate
7
6
  from ._rust_extensions import get_rust_operation
8
7
  from ._text_utils import split_preserving_whitespace, split_token_edges
9
8
  from .core import AttackWave, Glitchling
@@ -66,16 +65,9 @@ def swap_adjacent_words(
66
65
  rate: float | None = None,
67
66
  seed: int | None = None,
68
67
  rng: random.Random | None = None,
69
- *,
70
- swap_rate: float | None = None,
71
68
  ) -> str:
72
69
  """Swap adjacent word cores while preserving spacing and punctuation."""
73
- effective_rate = resolve_rate(
74
- rate=rate,
75
- legacy_value=swap_rate,
76
- default=0.5,
77
- legacy_name="swap_rate",
78
- )
70
+ effective_rate = 0.5 if rate is None else rate
79
71
  clamped_rate = max(0.0, min(effective_rate, 1.0))
80
72
 
81
73
  if rng is None:
@@ -94,16 +86,9 @@ class Adjax(Glitchling):
94
86
  self,
95
87
  *,
96
88
  rate: float | None = None,
97
- swap_rate: float | None = None,
98
89
  seed: int | None = None,
99
90
  ) -> None:
100
- self._param_aliases = {"swap_rate": "rate"}
101
- effective_rate = resolve_rate(
102
- rate=rate,
103
- legacy_value=swap_rate,
104
- default=0.5,
105
- legacy_name="swap_rate",
106
- )
91
+ effective_rate = 0.5 if rate is None else rate
107
92
  super().__init__(
108
93
  name="Adjax",
109
94
  corruption_function=swap_adjacent_words,
@@ -118,7 +103,7 @@ class Adjax(Glitchling):
118
103
  return None
119
104
  return {
120
105
  "type": "swap_adjacent",
121
- "swap_rate": float(rate),
106
+ "rate": float(rate),
122
107
  }
123
108
 
124
109
 
@@ -2,13 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import json
6
5
  import random
7
6
  from functools import cache
8
- from importlib import resources
9
7
  from typing import Any, Sequence, cast
10
8
 
11
9
  from ._rust_extensions import get_rust_operation
10
+ from .assets import load_json
12
11
  from .core import AttackOrder, AttackWave, Gaggle, Glitchling
13
12
 
14
13
  # Load Rust-accelerated operation if available
@@ -19,9 +18,7 @@ _apostrofae_rust = get_rust_operation("apostrofae")
19
18
  def _load_replacement_pairs() -> dict[str, list[tuple[str, str]]]:
20
19
  """Load the curated mapping of straight quotes to fancy pairs."""
21
20
 
22
- resource = resources.files(f"{__package__}.assets").joinpath("apostrofae_pairs.json")
23
- with resource.open("r", encoding="utf-8") as handle:
24
- data: dict[str, list[Sequence[str]]] = json.load(handle)
21
+ data: dict[str, list[Sequence[str]]] = load_json("apostrofae_pairs.json")
25
22
 
26
23
  parsed: dict[str, list[tuple[str, str]]] = {}
27
24
  for straight, replacements in data.items():
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from hashlib import blake2b
5
+ from importlib import resources
6
+ from importlib.resources.abc import Traversable
7
+ from typing import Any, BinaryIO, TextIO, cast
8
+
9
+ _DEFAULT_DIGEST_SIZE = 32
10
+
11
+
12
+ def _asset(name: str) -> Traversable:
13
+ asset = resources.files(__name__).joinpath(name)
14
+ if not asset.is_file(): # pragma: no cover - defensive guard for packaging issues
15
+ raise FileNotFoundError(f"Asset '{name}' not found at {asset}")
16
+ return asset
17
+
18
+
19
+ def read_text(name: str, *, encoding: str = "utf-8") -> str:
20
+ """Return the decoded contents of a bundled text asset."""
21
+
22
+ return cast(str, _asset(name).read_text(encoding=encoding))
23
+
24
+
25
+ def open_text(name: str, *, encoding: str = "utf-8") -> TextIO:
26
+ """Open a bundled text asset for reading."""
27
+
28
+ return cast(TextIO, _asset(name).open("r", encoding=encoding))
29
+
30
+
31
+ def open_binary(name: str) -> BinaryIO:
32
+ """Open a bundled binary asset for reading."""
33
+
34
+ return cast(BinaryIO, _asset(name).open("rb"))
35
+
36
+
37
+ def load_json(name: str, *, encoding: str = "utf-8") -> Any:
38
+ """Deserialize a JSON asset using the shared loader helpers."""
39
+
40
+ with open_text(name, encoding=encoding) as handle:
41
+ return json.load(handle)
42
+
43
+
44
+ def hash_asset(name: str) -> str:
45
+ """Return a BLAKE2b digest for the bundled asset ``name``."""
46
+
47
+ digest = blake2b(digest_size=_DEFAULT_DIGEST_SIZE)
48
+ with open_binary(name) as handle:
49
+ for chunk in iter(lambda: handle.read(8192), b""):
50
+ digest.update(chunk)
51
+ return digest.hexdigest()
52
+
53
+
54
+ __all__ = ["read_text", "open_text", "open_binary", "load_json", "hash_asset"]
@@ -7,7 +7,6 @@ from typing import Any, Literal, cast
7
7
 
8
8
  from glitchlings.lexicon import Lexicon, get_default_lexicon
9
9
 
10
- from ._rate import resolve_rate
11
10
  from .core import AttackWave, Glitchling
12
11
 
13
12
  _wordnet_module: ModuleType | None
@@ -119,7 +118,6 @@ def substitute_random_synonyms(
119
118
  seed: int | None = None,
120
119
  rng: random.Random | None = None,
121
120
  *,
122
- replacement_rate: float | None = None,
123
121
  lexicon: Lexicon | None = None,
124
122
  ) -> str:
125
123
  """Replace words with random lexicon-driven synonyms.
@@ -144,12 +142,7 @@ def substitute_random_synonyms(
144
142
  deterministic subsets per word and part-of-speech using the active seed.
145
143
 
146
144
  """
147
- effective_rate = resolve_rate(
148
- rate=rate,
149
- legacy_value=replacement_rate,
150
- default=0.1,
151
- legacy_name="replacement_rate",
152
- )
145
+ effective_rate = 0.1 if rate is None else rate
153
146
 
154
147
  active_rng: random.Random
155
148
  if rng is not None:
@@ -258,23 +251,16 @@ class Jargoyle(Glitchling):
258
251
  self,
259
252
  *,
260
253
  rate: float | None = None,
261
- replacement_rate: float | None = None,
262
254
  part_of_speech: PartOfSpeechInput = "n",
263
255
  seed: int | None = None,
264
256
  lexicon: Lexicon | None = None,
265
257
  ) -> None:
266
- self._param_aliases = {"replacement_rate": "rate"}
267
258
  self._owns_lexicon = lexicon is None
268
259
  self._external_lexicon_original_seed = (
269
260
  lexicon.seed if isinstance(lexicon, Lexicon) else None
270
261
  )
271
262
  self._initializing = True
272
- effective_rate = resolve_rate(
273
- rate=rate,
274
- legacy_value=replacement_rate,
275
- default=0.01,
276
- legacy_name="replacement_rate",
277
- )
263
+ effective_rate = 0.01 if rate is None else rate
278
264
  prepared_lexicon = lexicon or get_default_lexicon(seed=seed)
279
265
  if lexicon is not None and seed is not None:
280
266
  prepared_lexicon.reseed(seed)
glitchlings/zoo/mim1c.py CHANGED
@@ -4,7 +4,6 @@ from typing import Literal
4
4
 
5
5
  from confusable_homoglyphs import confusables
6
6
 
7
- from ._rate import resolve_rate
8
7
  from .core import AttackOrder, AttackWave, Glitchling
9
8
 
10
9
 
@@ -15,8 +14,6 @@ def swap_homoglyphs(
15
14
  banned_characters: Collection[str] | None = None,
16
15
  seed: int | None = None,
17
16
  rng: random.Random | None = None,
18
- *,
19
- replacement_rate: float | None = None,
20
17
  ) -> str:
21
18
  """Replace characters with visually confusable homoglyphs.
22
19
 
@@ -37,12 +34,7 @@ def swap_homoglyphs(
37
34
  - Maintains determinism by shuffling candidates and sampling via the provided RNG.
38
35
 
39
36
  """
40
- effective_rate = resolve_rate(
41
- rate=rate,
42
- legacy_value=replacement_rate,
43
- default=0.02,
44
- legacy_name="replacement_rate",
45
- )
37
+ effective_rate = 0.02 if rate is None else rate
46
38
 
47
39
  if rng is None:
48
40
  rng = random.Random(seed)
@@ -79,18 +71,11 @@ class Mim1c(Glitchling):
79
71
  self,
80
72
  *,
81
73
  rate: float | None = None,
82
- replacement_rate: float | None = None,
83
74
  classes: list[str] | Literal["all"] | None = None,
84
75
  banned_characters: Collection[str] | None = None,
85
76
  seed: int | None = None,
86
77
  ) -> None:
87
- self._param_aliases = {"replacement_rate": "rate"}
88
- effective_rate = resolve_rate(
89
- rate=rate,
90
- legacy_value=replacement_rate,
91
- default=0.02,
92
- legacy_name="replacement_rate",
93
- )
78
+ effective_rate = 0.02 if rate is None else rate
94
79
  super().__init__(
95
80
  name="Mim1c",
96
81
  corruption_function=swap_homoglyphs,
@@ -2,7 +2,6 @@ import random
2
2
  import re
3
3
  from typing import Any, cast
4
4
 
5
- from ._rate import resolve_rate
6
5
  from ._rust_extensions import get_rust_operation
7
6
  from ._sampling import weighted_sample_without_replacement
8
7
  from ._text_utils import (
@@ -97,16 +96,10 @@ def redact_words(
97
96
  seed: int = 151,
98
97
  rng: random.Random | None = None,
99
98
  *,
100
- redaction_rate: float | None = None,
101
99
  unweighted: bool = False,
102
100
  ) -> str:
103
101
  """Redact random words by replacing their characters."""
104
- effective_rate = resolve_rate(
105
- rate=rate,
106
- legacy_value=redaction_rate,
107
- default=0.025,
108
- legacy_name="redaction_rate",
109
- )
102
+ effective_rate = 0.025 if rate is None else rate
110
103
 
111
104
  if rng is None:
112
105
  rng = random.Random(seed)
@@ -148,18 +141,11 @@ class Redactyl(Glitchling):
148
141
  *,
149
142
  replacement_char: str = FULL_BLOCK,
150
143
  rate: float | None = None,
151
- redaction_rate: float | None = None,
152
144
  merge_adjacent: bool = False,
153
145
  seed: int = 151,
154
146
  unweighted: bool = False,
155
147
  ) -> None:
156
- self._param_aliases = {"redaction_rate": "rate"}
157
- effective_rate = resolve_rate(
158
- rate=rate,
159
- legacy_value=redaction_rate,
160
- default=0.025,
161
- legacy_name="redaction_rate",
162
- )
148
+ effective_rate = 0.025 if rate is None else rate
163
149
  super().__init__(
164
150
  name="Redactyl",
165
151
  corruption_function=redact_words,
@@ -181,7 +167,7 @@ class Redactyl(Glitchling):
181
167
  return {
182
168
  "type": "redact",
183
169
  "replacement_char": str(replacement_char),
184
- "redaction_rate": float(rate),
170
+ "rate": float(rate),
185
171
  "merge_adjacent": bool(merge_adjacent),
186
172
  "unweighted": unweighted,
187
173
  }
@@ -1,7 +1,6 @@
1
1
  import random
2
2
  from typing import Any, cast
3
3
 
4
- from ._rate import resolve_rate
5
4
  from ._rust_extensions import get_rust_operation
6
5
  from ._text_utils import WordToken, collect_word_tokens, split_preserving_whitespace
7
6
  from .core import AttackWave, Glitchling
@@ -71,7 +70,6 @@ def reduplicate_words(
71
70
  seed: int | None = None,
72
71
  rng: random.Random | None = None,
73
72
  *,
74
- reduplication_rate: float | None = None,
75
73
  unweighted: bool = False,
76
74
  ) -> str:
77
75
  """Randomly reduplicate words in the text.
@@ -79,12 +77,7 @@ def reduplicate_words(
79
77
  Falls back to the Python implementation when the optional Rust
80
78
  extension is unavailable.
81
79
  """
82
- effective_rate = resolve_rate(
83
- rate=rate,
84
- legacy_value=reduplication_rate,
85
- default=0.01,
86
- legacy_name="reduplication_rate",
87
- )
80
+ effective_rate = 0.01 if rate is None else rate
88
81
 
89
82
  if rng is None:
90
83
  rng = random.Random(seed)
@@ -110,17 +103,10 @@ class Reduple(Glitchling):
110
103
  self,
111
104
  *,
112
105
  rate: float | None = None,
113
- reduplication_rate: float | None = None,
114
106
  seed: int | None = None,
115
107
  unweighted: bool = False,
116
108
  ) -> None:
117
- self._param_aliases = {"reduplication_rate": "rate"}
118
- effective_rate = resolve_rate(
119
- rate=rate,
120
- legacy_value=reduplication_rate,
121
- default=0.01,
122
- legacy_name="reduplication_rate",
123
- )
109
+ effective_rate = 0.01 if rate is None else rate
124
110
  super().__init__(
125
111
  name="Reduple",
126
112
  corruption_function=reduplicate_words,
@@ -137,7 +123,7 @@ class Reduple(Glitchling):
137
123
  unweighted = bool(self.kwargs.get("unweighted", False))
138
124
  return {
139
125
  "type": "reduplicate",
140
- "reduplication_rate": float(rate),
126
+ "rate": float(rate),
141
127
  "unweighted": unweighted,
142
128
  }
143
129
 
@@ -3,7 +3,6 @@ import random
3
3
  import re
4
4
  from typing import Any, cast
5
5
 
6
- from ._rate import resolve_rate
7
6
  from ._rust_extensions import get_rust_operation
8
7
  from ._text_utils import WordToken, collect_word_tokens, split_preserving_whitespace
9
8
  from .core import AttackWave, Glitchling
@@ -74,20 +73,13 @@ def delete_random_words(
74
73
  rate: float | None = None,
75
74
  seed: int | None = None,
76
75
  rng: random.Random | None = None,
77
- *,
78
- max_deletion_rate: float | None = None,
79
76
  unweighted: bool = False,
80
77
  ) -> str:
81
78
  """Delete random words from the input text.
82
79
 
83
80
  Uses the optional Rust implementation when available.
84
81
  """
85
- effective_rate = resolve_rate(
86
- rate=rate,
87
- legacy_value=max_deletion_rate,
88
- default=0.01,
89
- legacy_name="max_deletion_rate",
90
- )
82
+ effective_rate = 0.01 if rate is None else rate
91
83
 
92
84
  if rng is None:
93
85
  rng = random.Random(seed)
@@ -113,17 +105,10 @@ class Rushmore(Glitchling):
113
105
  self,
114
106
  *,
115
107
  rate: float | None = None,
116
- max_deletion_rate: float | None = None,
117
108
  seed: int | None = None,
118
109
  unweighted: bool = False,
119
110
  ) -> None:
120
- self._param_aliases = {"max_deletion_rate": "rate"}
121
- effective_rate = resolve_rate(
122
- rate=rate,
123
- legacy_value=max_deletion_rate,
124
- default=0.01,
125
- legacy_name="max_deletion_rate",
126
- )
111
+ effective_rate = 0.01 if rate is None else rate
127
112
  super().__init__(
128
113
  name="Rushmore",
129
114
  corruption_function=delete_random_words,
@@ -135,14 +120,12 @@ class Rushmore(Glitchling):
135
120
 
136
121
  def pipeline_operation(self) -> dict[str, Any] | None:
137
122
  rate = self.kwargs.get("rate")
138
- if rate is None:
139
- rate = self.kwargs.get("max_deletion_rate")
140
123
  if rate is None:
141
124
  return None
142
125
  unweighted = bool(self.kwargs.get("unweighted", False))
143
126
  return {
144
127
  "type": "delete",
145
- "max_deletion_rate": float(rate),
128
+ "rate": float(rate),
146
129
  "unweighted": unweighted,
147
130
  }
148
131
 
@@ -3,7 +3,6 @@ import re
3
3
  from typing import Any, cast
4
4
 
5
5
  from ._ocr_confusions import load_confusion_table
6
- from ._rate import resolve_rate
7
6
  from ._rust_extensions import get_rust_operation
8
7
  from .core import AttackOrder, AttackWave, Glitchling
9
8
 
@@ -102,8 +101,6 @@ def ocr_artifacts(
102
101
  rate: float | None = None,
103
102
  seed: int | None = None,
104
103
  rng: random.Random | None = None,
105
- *,
106
- error_rate: float | None = None,
107
104
  ) -> str:
108
105
  """Introduce OCR-like artifacts into text.
109
106
 
@@ -112,12 +109,7 @@ def ocr_artifacts(
112
109
  if not text:
113
110
  return text
114
111
 
115
- effective_rate = resolve_rate(
116
- rate=rate,
117
- legacy_value=error_rate,
118
- default=0.02,
119
- legacy_name="error_rate",
120
- )
112
+ effective_rate = 0.02 if rate is None else rate
121
113
 
122
114
  if rng is None:
123
115
  rng = random.Random(seed)
@@ -137,16 +129,9 @@ class Scannequin(Glitchling):
137
129
  self,
138
130
  *,
139
131
  rate: float | None = None,
140
- error_rate: float | None = None,
141
132
  seed: int | None = None,
142
133
  ) -> None:
143
- self._param_aliases = {"error_rate": "rate"}
144
- effective_rate = resolve_rate(
145
- rate=rate,
146
- legacy_value=error_rate,
147
- default=0.02,
148
- legacy_name="error_rate",
149
- )
134
+ effective_rate = 0.02 if rate is None else rate
150
135
  super().__init__(
151
136
  name="Scannequin",
152
137
  corruption_function=ocr_artifacts,
@@ -158,11 +143,9 @@ class Scannequin(Glitchling):
158
143
 
159
144
  def pipeline_operation(self) -> dict[str, Any] | None:
160
145
  rate = self.kwargs.get("rate")
161
- if rate is None:
162
- rate = self.kwargs.get("error_rate")
163
146
  if rate is None:
164
147
  return None
165
- return {"type": "ocr", "error_rate": float(rate)}
148
+ return {"type": "ocr", "rate": float(rate)}
166
149
 
167
150
 
168
151
  scannequin = Scannequin()
@@ -5,7 +5,6 @@ import random
5
5
  from typing import Any, Optional, cast
6
6
 
7
7
  from ..util import KEYNEIGHBORS
8
- from ._rate import resolve_rate
9
8
  from ._rust_extensions import get_rust_operation
10
9
  from .core import AttackOrder, AttackWave, Glitchling
11
10
 
@@ -144,16 +143,9 @@ def fatfinger(
144
143
  keyboard: str = "CURATOR_QWERTY",
145
144
  seed: int | None = None,
146
145
  rng: random.Random | None = None,
147
- *,
148
- max_change_rate: float | None = None,
149
146
  ) -> str:
150
147
  """Introduce character-level "fat finger" edits with a Rust fast path."""
151
- effective_rate = resolve_rate(
152
- rate=rate,
153
- legacy_value=max_change_rate,
154
- default=0.02,
155
- legacy_name="max_change_rate",
156
- )
148
+ effective_rate = 0.02 if rate is None else rate
157
149
 
158
150
  if rng is None:
159
151
  rng = random.Random(seed)
@@ -182,17 +174,10 @@ class Typogre(Glitchling):
182
174
  self,
183
175
  *,
184
176
  rate: float | None = None,
185
- max_change_rate: float | None = None,
186
177
  keyboard: str = "CURATOR_QWERTY",
187
178
  seed: int | None = None,
188
179
  ) -> None:
189
- self._param_aliases = {"max_change_rate": "rate"}
190
- effective_rate = resolve_rate(
191
- rate=rate,
192
- legacy_value=max_change_rate,
193
- default=0.02,
194
- legacy_name="max_change_rate",
195
- )
180
+ effective_rate = 0.02 if rate is None else rate
196
181
  super().__init__(
197
182
  name="Typogre",
198
183
  corruption_function=fatfinger,
@@ -205,8 +190,6 @@ class Typogre(Glitchling):
205
190
 
206
191
  def pipeline_operation(self) -> dict[str, Any] | None:
207
192
  rate = self.kwargs.get("rate")
208
- if rate is None:
209
- rate = self.kwargs.get("max_change_rate")
210
193
  if rate is None:
211
194
  return None
212
195
 
glitchlings/zoo/zeedub.py CHANGED
@@ -5,7 +5,6 @@ import random
5
5
  from collections.abc import Sequence
6
6
  from typing import Any, cast
7
7
 
8
- from ._rate import resolve_rate
9
8
  from ._rust_extensions import get_rust_operation
10
9
  from .core import AttackOrder, AttackWave, Glitchling
11
10
 
@@ -77,12 +76,7 @@ def insert_zero_widths(
77
76
  characters: Sequence[str] | None = None,
78
77
  ) -> str:
79
78
  """Inject zero-width characters between non-space character pairs."""
80
- effective_rate = resolve_rate(
81
- rate=rate,
82
- legacy_value=None,
83
- default=0.02,
84
- legacy_name="rate",
85
- )
79
+ effective_rate = 0.02 if rate is None else rate
86
80
 
87
81
  if rng is None:
88
82
  rng = random.Random(seed)
@@ -142,12 +136,7 @@ class Zeedub(Glitchling):
142
136
  seed: int | None = None,
143
137
  characters: Sequence[str] | None = None,
144
138
  ) -> None:
145
- effective_rate = resolve_rate(
146
- rate=rate,
147
- legacy_value=None,
148
- default=0.02,
149
- legacy_name="rate",
150
- )
139
+ effective_rate = 0.02 if rate is None else rate
151
140
  super().__init__(
152
141
  name="Zeedub",
153
142
  corruption_function=insert_zero_widths,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: glitchlings
3
- Version: 0.4.5
3
+ Version: 0.5.0
4
4
  Summary: Monsters for your language games.
5
5
  Author: osoleve
6
6
  License: Apache License
@@ -294,11 +294,14 @@ Dynamic: license-file
294
294
  Every language game breeds monsters.
295
295
  ```
296
296
 
297
+ ![Python Versions](https://img.shields.io/pypi/pyversions/glitchlings.svg)
297
298
  [![PyPI version](https://img.shields.io/pypi/v/glitchlings.svg)](https://pypi.org/project/glitchlings/)
298
- [![PyPI Status](https://github.com/osoleve/glitchlings/actions/workflows/publish.yml/badge.svg)](https://github.com/osoleve/glitchlings/actions/workflows/publish.yml)
299
- [![Lint and Type](https://github.com/osoleve/glitchlings/actions/workflows/ci.yml/badge.svg)](https://github.com/osoleve/glitchlings/actions/workflows/ci.yml)
300
- [![Website status](https://img.shields.io/website-up-down-green-red/https/osoleve.github.io/glitchlings)](https://osoleve.github.io/glitchlings/)
301
- [![License](https://img.shields.io/github/license/osoleve/glitchlings.svg)](https://github.com/osoleve/glitchlings/blob/main/LICENSE)
299
+ ![Wheel](https://img.shields.io/pypi/wheel/glitchlings.svg)
300
+ ![Linting and Typing](https://github.com/osoleve/glitchlings/actions/workflows/ci.yml/badge.svg)
301
+ ![Entropy Budget](https://img.shields.io/badge/entropy-lifegiving-magenta.svg)
302
+ ![Chaos](https://img.shields.io/badge/chaos-friend--shaped-chartreuse.svg)
303
+ ![Charm](https://img.shields.io/badge/jouissance-indefatigable-cyan.svg)
304
+ ![Lore Compliance](https://img.shields.io/badge/ISO--474--▓▓-Z--Compliant-blue.svg)
302
305
 
303
306
  `Glitchlings` are **utilities for corrupting the text inputs to your language models in deterministic, _linguistically principled_** ways.
304
307
  Each embodies a different way that documents can be compromised in the wild.
@@ -596,7 +599,6 @@ _Keep your hands and punctuation where I can see them._
596
599
  > Args
597
600
  >
598
601
  > - `rate (float)`: Probability that each adjacent pair swaps cores (default: 0.5, 50%).
599
- > - `swap_rate (float)`: Alias for `rate`, retained for backward compatibility.
600
602
  > - `seed (int)`: The random seed for reproducibility (default: 151).
601
603
 
602
604
  ### Redactyl
@@ -1,12 +1,12 @@
1
1
  glitchlings/__init__.py,sha256=pNL6Vx6ggpRq-vW0CXJjnWOe_LktP6Zz9xNhqDHj3Dw,1301
2
2
  glitchlings/__main__.py,sha256=nB7btO_T4wBFOcyawfWpjEindVrUfTqqV5hdeeS1HT8,128
3
- glitchlings/_zoo_rust.cp313-win_amd64.pyd,sha256=6hdbgPFxuHKkzuoajgzrX67X9--NKf5BX2UehNGO1AY,2349568
3
+ glitchlings/_zoo_rust.cp313-win_amd64.pyd,sha256=OFN9vx0K5-NueJ-sOSQKwhWudU2mRqGcHK4AkWxZ8-c,2349056
4
4
  glitchlings/compat.py,sha256=j4lkWNtyox5sen5j7u0SHfnk8QUn-yicaqvuLlZp1-s,9174
5
- glitchlings/config.py,sha256=rBBHRmkIyoWRPcUFrsxmSm08qxZ07wDAuO5AAF6sjAk,13323
5
+ glitchlings/config.py,sha256=00BAHPbfOFnGHvC7FCcz1YpJjdsfpn1ET5SJJdsVQog,12677
6
6
  glitchlings/config.toml,sha256=OywXmEpuOPtyJRbcRt4cwQkHiZ__5axEHoCaX9ye-uA,102
7
7
  glitchlings/main.py,sha256=eCUEFsu8-NLDz1xyKNDIucVm975HNQZJYm6YCv8RIyg,10987
8
- glitchlings/data/__init__.py,sha256=kIT4a2EDNo_R-iL8kVisJ8PxR_BrygNYegfG_Ua6DcE,69
9
- glitchlings/data/hokey_assets.json,sha256=1GaSEzXwtT1nvf0B9mFyLzHOcqzKbPreibsC6iBWAHA,3083
8
+ glitchlings/dev/__init__.py,sha256=Pr2tXDwfa6SHW1wPseLHRXPINi7lEs6QNClreU0rAiw,147
9
+ glitchlings/dev/sync_assets.py,sha256=QgUNrNpNloxqVGTCX0kVYwQMkJ58zGiZd37svteeS5M,4830
10
10
  glitchlings/dlc/__init__.py,sha256=iFDTwkaWl2C0_QUYykIXfmOUzy__oURX_BiJhexf-8o,312
11
11
  glitchlings/dlc/_shared.py,sha256=q1xMclEsbR0KIEn9chwZXRubMnIvU-uIc_0PxCFpmqE,4560
12
12
  glitchlings/dlc/huggingface.py,sha256=6wD4Vu2jp1d8GYSUiAvOAXqCO9w4HBxCOSfbJM8Ylzw,2657
@@ -23,31 +23,31 @@ glitchlings/util/__init__.py,sha256=Q5lkncOaM6f2eJK3HAtZyxpCjGnekCpwPloqasS3JDo,
23
23
  glitchlings/util/adapters.py,sha256=mFhPlE8JaFuO_C-3_aqhgwkqa6isV8Y2ifqVh3Iv9JM,720
24
24
  glitchlings/util/hokey_generator.py,sha256=hNWVbscVeKvcqwFtJ1oaWYf2Z0qHSxy0-iMLjht6zuM,4627
25
25
  glitchlings/util/stretch_locator.py,sha256=tM4XsQ_asNXQq2Yee8STybFMCC2HbSKCu9I49G0yJ3c,4334
26
- glitchlings/util/stretchability.py,sha256=ja1PJKVAKySUP5G2T0Q_THwIvydJn49xYFaQn58cQYw,13194
26
+ glitchlings/util/stretchability.py,sha256=WWGMN9MTbHhgQIBB_p_cz-JKb51rCHZqyDRjPGkUOjY,13037
27
27
  glitchlings/zoo/__init__.py,sha256=Ab69SD_RUXfiWUEzqU3wteGWobpm2kkbmwndYLEbv_0,5511
28
- glitchlings/zoo/_ocr_confusions.py,sha256=pPlvJOoan3ouwwGt8hATcO-9luIrGJl0vwUqssUMXD8,1236
29
- glitchlings/zoo/_rate.py,sha256=KxFDFJEGWsv76v1JcoHXIETj9kGdbbAiUqCPwAcWcDw,3710
28
+ glitchlings/zoo/_ocr_confusions.py,sha256=Kyo3W6w-FGs4C7jXp4bLFHFCaa0RUJWvUrWKqfBOdBk,1171
30
29
  glitchlings/zoo/_rust_extensions.py,sha256=SdU06m-qjf-rRHnqM5OMophx1IacoI4KbyRu2HXrTUc,4354
31
30
  glitchlings/zoo/_sampling.py,sha256=AAPLObjqKrmX882TX8hdvPHReBOcv0Z4pUuW6AxuGgU,1640
32
- glitchlings/zoo/_text_utils.py,sha256=LqCa33E-Qxbk6N5AVfxEmAz6C2u7_mCF0xPT9-404A8,2854
33
- glitchlings/zoo/adjax.py,sha256=8AqTfcJe50vPVxER5wREuyr4qqsmyKqi-klDW77HVYM,3646
34
- glitchlings/zoo/apostrofae.py,sha256=WB1zvCc1_YvTD9XdTNtFrK8H9FleaF-UNMCai_E7lqE,3949
31
+ glitchlings/zoo/_text_utils.py,sha256=DRjPuC-nFoxFeA7KIbpuIB5k8DXbvvvcHNJniL_8KoQ,2872
32
+ glitchlings/zoo/adjax.py,sha256=Oz3WGSsa4qpH7HLgBQC0r4JktJYGl3A0vkVknqeSG5I,3249
33
+ glitchlings/zoo/apostrofae.py,sha256=C9x9_jj9CidYEWL9OEoNJU_Jzr3Vk4UQ8tITThVbwvY,3798
35
34
  glitchlings/zoo/core.py,sha256=tkJFqGwpVa7qQxkUr9lsHOB8zS3lDCo987R6Nvz375U,21257
36
35
  glitchlings/zoo/hokey.py,sha256=h-yjCLqYAZFksIYw4fMniuTWUywFw9GU9yUFs_uECHo,5471
37
- glitchlings/zoo/jargoyle.py,sha256=kgKw_hsaJaKQjrxzt_CMlgtZApedM6I2-U3d3aeipXs,12014
38
- glitchlings/zoo/mim1c.py,sha256=GqUMErVAVcqMAZjx4hhJ0Af25CxA0Aorv3U_fTqLZek,3546
39
- glitchlings/zoo/ocr_confusions.tsv,sha256=S-IJEYCIXYKT1Uu7Id8Lnvg5pw528yNigTtWUdnMv9k,213
40
- glitchlings/zoo/redactyl.py,sha256=xSdt-Az3HI8XLC-uV8hK6JIoNa1nL-cTCFCoShFvKt0,5752
41
- glitchlings/zoo/reduple.py,sha256=YfdKZPr2TO2ZK7h8K5xlsnoubEZ3kQLRpDX65GzrKls,4372
42
- glitchlings/zoo/rushmore.py,sha256=JjLKTKQoXlvp0uvCzMRlJSXE_N2VFZPHMF_nkolKm3g,4439
43
- glitchlings/zoo/scannequin.py,sha256=-YG6tRTE72MGw4CpWAPo42gFaCTpyDvOJnk0gynitCg,5041
44
- glitchlings/zoo/typogre.py,sha256=X6jcnCYifNtoSwe2ig7YS3QrODyrACirMZ9pjN5LLBA,6917
45
- glitchlings/zoo/zeedub.py,sha256=KApSCSiTr3b412vsA8UHWFhYQDxe_jg0308wWK-GNtM,5025
46
- glitchlings/zoo/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
+ glitchlings/zoo/jargoyle.py,sha256=2FwL4A_DpI2Lz6pnZo5iSJZzgd8wDNM9bMeOsMVFSmw,11581
37
+ glitchlings/zoo/mim1c.py,sha256=86lCawTFWRWsO_vH6GMRJfM09TMTUy5MNhO9gsBEkk0,3105
38
+ glitchlings/zoo/redactyl.py,sha256=2BYQfcKwp1WFPwXomQCmh-z0UPN9sNx6Px84WilFs3k,5323
39
+ glitchlings/zoo/reduple.py,sha256=YukTZvLMBoarx96T4LHLGHr_KJVmFkAb0OBsO13ws8A,3911
40
+ glitchlings/zoo/rushmore.py,sha256=CiVkLlskOmfKtzFanmYe6wxKJkCSMbLeKTWDrwZZaXw,3895
41
+ glitchlings/zoo/scannequin.py,sha256=s2wno1_7aGebXvG3o9WvDQ5RRwp5Upl2dejbDUGIXkY,4560
42
+ glitchlings/zoo/typogre.py,sha256=B1-kMfOygKtiKXDBQOHJxvheielBWGbbdWk-cgoONgs,6402
43
+ glitchlings/zoo/zeedub.py,sha256=4x9bNQEq4VSsfol77mYx8jyBRyTugjRQPyk-ANy6gpY,4792
44
+ glitchlings/zoo/assets/__init__.py,sha256=6dOYQnMw-Hk9Tb3lChgZ4aoRr9qacZtbXR8ROnDfBq8,1691
47
45
  glitchlings/zoo/assets/apostrofae_pairs.json,sha256=lPLFLndzn_f7_5wZizxsLMnwBY4O63zsCvDjyJ56MLA,553
48
- glitchlings-0.4.5.dist-info/licenses/LICENSE,sha256=EFEP1evBfHaxsMTBjxm0sZVRp2wct8QLvHE1saII5FI,11538
49
- glitchlings-0.4.5.dist-info/METADATA,sha256=sULt2kvkK1lfhdqBbSvawNoRNyevwLsFrITBPMBrE9M,34146
50
- glitchlings-0.4.5.dist-info/WHEEL,sha256=qV0EIPljj1XC_vuSatRWjn02nZIz3N1t8jsZz7HBr2U,101
51
- glitchlings-0.4.5.dist-info/entry_points.txt,sha256=kGOwuAsjFDLtztLisaXtOouq9wFVMOJg5FzaAkg-Hto,54
52
- glitchlings-0.4.5.dist-info/top_level.txt,sha256=VHFNBrLjtDwPCYXbGKi6o17Eueedi81eNbR3hBOoST0,12
53
- glitchlings-0.4.5.dist-info/RECORD,,
46
+ glitchlings/zoo/assets/hokey_assets.json,sha256=1GaSEzXwtT1nvf0B9mFyLzHOcqzKbPreibsC6iBWAHA,3083
47
+ glitchlings/zoo/assets/ocr_confusions.tsv,sha256=S-IJEYCIXYKT1Uu7Id8Lnvg5pw528yNigTtWUdnMv9k,213
48
+ glitchlings-0.5.0.dist-info/licenses/LICENSE,sha256=EFEP1evBfHaxsMTBjxm0sZVRp2wct8QLvHE1saII5FI,11538
49
+ glitchlings-0.5.0.dist-info/METADATA,sha256=Nu4n4Oy-LoMko5-2ncvshT_OKMWxr-k9YOm5uMLIHBk,34013
50
+ glitchlings-0.5.0.dist-info/WHEEL,sha256=qV0EIPljj1XC_vuSatRWjn02nZIz3N1t8jsZz7HBr2U,101
51
+ glitchlings-0.5.0.dist-info/entry_points.txt,sha256=kGOwuAsjFDLtztLisaXtOouq9wFVMOJg5FzaAkg-Hto,54
52
+ glitchlings-0.5.0.dist-info/top_level.txt,sha256=VHFNBrLjtDwPCYXbGKi6o17Eueedi81eNbR3hBOoST0,12
53
+ glitchlings-0.5.0.dist-info/RECORD,,
@@ -1 +0,0 @@
1
- """Static data assets shared across Glitchlings implementations."""
glitchlings/zoo/_rate.py DELETED
@@ -1,131 +0,0 @@
1
- """Utilities for handling legacy parameter names across glitchling classes."""
2
-
3
- from __future__ import annotations
4
-
5
- import warnings
6
-
7
-
8
- def resolve_rate(
9
- *,
10
- rate: float | None,
11
- legacy_value: float | None,
12
- default: float,
13
- legacy_name: str,
14
- ) -> float:
15
- """Return the effective rate while enforcing mutual exclusivity.
16
-
17
- This function centralizes the handling of legacy parameter names, allowing
18
- glitchlings to maintain backwards compatibility while encouraging migration
19
- to the standardized 'rate' parameter.
20
-
21
- Parameters
22
- ----------
23
- rate : float | None
24
- The preferred parameter value.
25
- legacy_value : float | None
26
- The deprecated legacy parameter value.
27
- default : float
28
- Default value if neither parameter is specified.
29
- legacy_name : str
30
- Name of the legacy parameter for error/warning messages.
31
-
32
- Returns
33
- -------
34
- float
35
- The resolved rate value.
36
-
37
- Raises
38
- ------
39
- ValueError
40
- If both rate and legacy_value are specified simultaneously.
41
-
42
- Warnings
43
- --------
44
- DeprecationWarning
45
- If the legacy parameter is used, a deprecation warning is issued.
46
-
47
- Examples
48
- --------
49
- >>> resolve_rate(rate=0.5, legacy_value=None, default=0.1, legacy_name="old_rate")
50
- 0.5
51
- >>> resolve_rate(rate=None, legacy_value=0.3, default=0.1, legacy_name="old_rate")
52
- 0.3 # Issues deprecation warning
53
- >>> resolve_rate(rate=None, legacy_value=None, default=0.1, legacy_name="old_rate")
54
- 0.1
55
-
56
- """
57
- if rate is not None and legacy_value is not None:
58
- raise ValueError(f"Specify either 'rate' or '{legacy_name}', not both.")
59
-
60
- if rate is not None:
61
- return rate
62
-
63
- if legacy_value is not None:
64
- warnings.warn(
65
- f"The '{legacy_name}' parameter is deprecated and will be removed in version 0.6.0. "
66
- f"Use 'rate' instead.",
67
- DeprecationWarning,
68
- stacklevel=3,
69
- )
70
- return legacy_value
71
-
72
- return default
73
-
74
-
75
- def resolve_legacy_param(
76
- *,
77
- preferred_value: object,
78
- legacy_value: object,
79
- default: object,
80
- preferred_name: str,
81
- legacy_name: str,
82
- ) -> object:
83
- """Resolve a parameter that has both preferred and legacy names.
84
-
85
- This is a generalized version of resolve_rate() that works with any type.
86
-
87
- Parameters
88
- ----------
89
- preferred_value : object
90
- The value from the preferred parameter name.
91
- legacy_value : object
92
- The value from the legacy parameter name.
93
- default : object
94
- Default value if neither parameter is specified.
95
- preferred_name : str
96
- Name of the preferred parameter.
97
- legacy_name : str
98
- Name of the legacy parameter for warning messages.
99
-
100
- Returns
101
- -------
102
- object
103
- The resolved parameter value.
104
-
105
- Raises
106
- ------
107
- ValueError
108
- If both preferred and legacy values are specified simultaneously.
109
-
110
- Warnings
111
- --------
112
- DeprecationWarning
113
- If the legacy parameter is used.
114
-
115
- """
116
- if preferred_value is not None and legacy_value is not None:
117
- raise ValueError(f"Specify either '{preferred_name}' or '{legacy_name}', not both.")
118
-
119
- if preferred_value is not None:
120
- return preferred_value
121
-
122
- if legacy_value is not None:
123
- warnings.warn(
124
- f"The '{legacy_name}' parameter is deprecated and will be removed in version 0.6.0. "
125
- f"Use '{preferred_name}' instead.",
126
- DeprecationWarning,
127
- stacklevel=3,
128
- )
129
- return legacy_value
130
-
131
- return default
File without changes