glitchlings 0.4.1__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.
Potentially problematic release.
This version of glitchlings might be problematic. Click here for more details.
- {glitchlings-0.4.1 → glitchlings-0.4.2}/PKG-INFO +67 -3
- {glitchlings-0.4.1 → glitchlings-0.4.2}/README.md +56 -2
- {glitchlings-0.4.1 → glitchlings-0.4.2}/pyproject.toml +74 -1
- {glitchlings-0.4.1 → glitchlings-0.4.2}/rust/zoo/src/text_buffer.rs +2 -8
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/__init__.py +26 -17
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/__main__.py +0 -1
- glitchlings-0.4.2/src/glitchlings/compat.py +215 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/config.py +136 -19
- glitchlings-0.4.2/src/glitchlings/dlc/_shared.py +68 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/dlc/huggingface.py +26 -41
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/dlc/prime.py +64 -101
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/lexicon/__init__.py +8 -19
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/lexicon/_cache.py +0 -7
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/lexicon/graph.py +4 -12
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/lexicon/metrics.py +1 -8
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/lexicon/vector.py +15 -34
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/lexicon/wordnet.py +31 -32
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/main.py +9 -13
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/util/__init__.py +18 -4
- glitchlings-0.4.2/src/glitchlings/util/adapters.py +27 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/__init__.py +21 -14
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/_ocr_confusions.py +1 -3
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/_rate.py +1 -4
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/_sampling.py +0 -1
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/_text_utils.py +1 -5
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/adjax.py +0 -2
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/core.py +114 -75
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/jargoyle.py +9 -14
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/mim1c.py +11 -10
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/redactyl.py +5 -8
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/reduple.py +3 -1
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/rushmore.py +2 -8
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/scannequin.py +5 -4
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/typogre.py +3 -7
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/zeedub.py +2 -2
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings.egg-info/PKG-INFO +67 -3
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings.egg-info/SOURCES.txt +4 -24
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings.egg-info/requires.txt +10 -0
- glitchlings-0.4.1/tests/test_benchmarks.py +0 -137
- glitchlings-0.4.1/tests/test_cli.py +0 -369
- glitchlings-0.4.1/tests/test_config.py +0 -196
- glitchlings-0.4.1/tests/test_dataset_corruption.py +0 -128
- glitchlings-0.4.1/tests/test_gaggle.py +0 -68
- glitchlings-0.4.1/tests/test_glitchling_core.py +0 -68
- glitchlings-0.4.1/tests/test_glitchlings_determinism.py +0 -103
- glitchlings-0.4.1/tests/test_graph_lexicon.py +0 -81
- glitchlings-0.4.1/tests/test_huggingface_dlc.py +0 -78
- glitchlings-0.4.1/tests/test_jargoyle.py +0 -209
- glitchlings-0.4.1/tests/test_keyboard_layouts.py +0 -42
- glitchlings-0.4.1/tests/test_lexicon_backends.py +0 -85
- glitchlings-0.4.1/tests/test_lexicon_config.py +0 -56
- glitchlings-0.4.1/tests/test_lexicon_metrics.py +0 -120
- glitchlings-0.4.1/tests/test_parameter_effects.py +0 -281
- glitchlings-0.4.1/tests/test_pipeline_operations.py +0 -95
- glitchlings-0.4.1/tests/test_prime_echo_chamber.py +0 -390
- glitchlings-0.4.1/tests/test_property_based.py +0 -150
- glitchlings-0.4.1/tests/test_rate_and_sampling.py +0 -51
- glitchlings-0.4.1/tests/test_rust_backed_glitchlings.py +0 -931
- glitchlings-0.4.1/tests/test_text_utils.py +0 -37
- glitchlings-0.4.1/tests/test_util.py +0 -35
- glitchlings-0.4.1/tests/test_vector_lexicon.py +0 -438
- {glitchlings-0.4.1 → glitchlings-0.4.2}/LICENSE +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/MANIFEST.in +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/rust/Cargo.lock +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/rust/Cargo.toml +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/rust/zoo/Cargo.toml +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/rust/zoo/assets/ocr_confusions.tsv +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/rust/zoo/build.rs +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/rust/zoo/src/glitch_ops.rs +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/rust/zoo/src/lib.rs +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/rust/zoo/src/pipeline.rs +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/rust/zoo/src/resources.rs +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/rust/zoo/src/rng.rs +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/rust/zoo/src/typogre.rs +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/rust/zoo/src/zeedub.rs +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/setup.cfg +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/config.toml +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/dlc/__init__.py +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/lexicon/data/default_vector_cache.json +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings/zoo/ocr_confusions.tsv +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings.egg-info/dependency_links.txt +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings.egg-info/entry_points.txt +0 -0
- {glitchlings-0.4.1 → glitchlings-0.4.2}/src/glitchlings.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: glitchlings
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Monsters for your language games.
|
|
5
5
|
Author: osoleve
|
|
6
6
|
License: Apache License
|
|
@@ -239,6 +239,16 @@ Provides-Extra: dev
|
|
|
239
239
|
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
240
240
|
Requires-Dist: hypothesis>=6.140.0; extra == "dev"
|
|
241
241
|
Requires-Dist: numpy<=2.0,>=1.24; extra == "dev"
|
|
242
|
+
Requires-Dist: mkdocs>=1.6.0; extra == "dev"
|
|
243
|
+
Requires-Dist: mkdocstrings[python]>=0.24.0; extra == "dev"
|
|
244
|
+
Requires-Dist: mkdocs-material>=9.5.0; extra == "dev"
|
|
245
|
+
Requires-Dist: mkdocstrings-python>=1.10.0; extra == "dev"
|
|
246
|
+
Requires-Dist: interrogate>=1.5.0; extra == "dev"
|
|
247
|
+
Requires-Dist: black>=24.4.0; extra == "dev"
|
|
248
|
+
Requires-Dist: isort>=5.13.0; extra == "dev"
|
|
249
|
+
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
250
|
+
Requires-Dist: mypy>=1.8.0; extra == "dev"
|
|
251
|
+
Requires-Dist: pre-commit>=3.8.0; extra == "dev"
|
|
242
252
|
Dynamic: license-file
|
|
243
253
|
|
|
244
254
|
#
|
|
@@ -338,10 +348,66 @@ They're horrible little gremlins, but they're not _unreasonable_.
|
|
|
338
348
|
|
|
339
349
|
Keyboard warriors can challenge them directly via the `glitchlings` command:
|
|
340
350
|
|
|
351
|
+
<!-- BEGIN: CLI_USAGE -->
|
|
341
352
|
```bash
|
|
342
353
|
# Discover which glitchlings are currently on the loose.
|
|
343
354
|
glitchlings --list
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
```text
|
|
358
|
+
Typogre — scope: Character, order: early
|
|
359
|
+
Mim1c — scope: Character, order: last
|
|
360
|
+
Jargoyle — scope: Word, order: normal
|
|
361
|
+
Adjax — scope: Word, order: normal
|
|
362
|
+
Reduple — scope: Word, order: normal
|
|
363
|
+
Rushmore — scope: Word, order: normal
|
|
364
|
+
Redactyl — scope: Word, order: normal
|
|
365
|
+
Scannequin — scope: Character, order: late
|
|
366
|
+
Zeedub — scope: Character, order: last
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
# Review the full CLI contract.
|
|
371
|
+
glitchlings --help
|
|
372
|
+
```
|
|
344
373
|
|
|
374
|
+
```text
|
|
375
|
+
usage: glitchlings [-h] [-g SPEC] [-s SEED] [-f FILE] [--sample] [--diff]
|
|
376
|
+
[--list] [-c CONFIG]
|
|
377
|
+
[text]
|
|
378
|
+
|
|
379
|
+
Summon glitchlings to corrupt text. Provide input text as an argument, via
|
|
380
|
+
--file, or pipe it on stdin.
|
|
381
|
+
|
|
382
|
+
positional arguments:
|
|
383
|
+
text Text to corrupt. If omitted, stdin is used or --sample
|
|
384
|
+
provides fallback text.
|
|
385
|
+
|
|
386
|
+
options:
|
|
387
|
+
-h, --help show this help message and exit
|
|
388
|
+
-g SPEC, --glitchling SPEC
|
|
389
|
+
Glitchling to apply, optionally with parameters like
|
|
390
|
+
Typogre(rate=0.05). Repeat for multiples; defaults to
|
|
391
|
+
all built-ins.
|
|
392
|
+
-s SEED, --seed SEED Seed controlling deterministic corruption order
|
|
393
|
+
(default: 151).
|
|
394
|
+
-f FILE, --file FILE Read input text from a file instead of the command
|
|
395
|
+
line argument.
|
|
396
|
+
--sample Use the included SAMPLE_TEXT when no other input is
|
|
397
|
+
provided.
|
|
398
|
+
--diff Show a unified diff between the original and corrupted
|
|
399
|
+
text.
|
|
400
|
+
--list List available glitchlings and exit.
|
|
401
|
+
-c CONFIG, --config CONFIG
|
|
402
|
+
Load glitchlings from a YAML configuration file.
|
|
403
|
+
```
|
|
404
|
+
<!-- END: CLI_USAGE -->
|
|
405
|
+
|
|
406
|
+
Run `python docs/build_cli_reference.py` whenever you tweak the CLI so the README stays in sync with the actual output. The script executes the commands above and replaces the block between the markers automatically.
|
|
407
|
+
|
|
408
|
+
Prefer inline tweaks? You can still configure glitchlings directly in the shell:
|
|
409
|
+
|
|
410
|
+
```bash
|
|
345
411
|
# Run Typogre against the contents of a file and inspect the diff.
|
|
346
412
|
glitchlings -g typogre --file documents/report.txt --diff
|
|
347
413
|
|
|
@@ -355,8 +421,6 @@ echo "Beware LLM-written flavor-text" | glitchlings -g mim1c
|
|
|
355
421
|
glitchlings --config experiments/chaos.yaml "Let slips the glitchlings of war"
|
|
356
422
|
```
|
|
357
423
|
|
|
358
|
-
Use `--help` for a complete breakdown of available options, including support for parameterised glitchlings via `-g "Name(arg=value, ...)"` to mirror the Python API.
|
|
359
|
-
|
|
360
424
|
Attack configurations live in plain YAML files so you can version-control experiments without touching code:
|
|
361
425
|
|
|
362
426
|
```yaml
|
|
@@ -95,10 +95,66 @@ They're horrible little gremlins, but they're not _unreasonable_.
|
|
|
95
95
|
|
|
96
96
|
Keyboard warriors can challenge them directly via the `glitchlings` command:
|
|
97
97
|
|
|
98
|
+
<!-- BEGIN: CLI_USAGE -->
|
|
98
99
|
```bash
|
|
99
100
|
# Discover which glitchlings are currently on the loose.
|
|
100
101
|
glitchlings --list
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
```text
|
|
105
|
+
Typogre — scope: Character, order: early
|
|
106
|
+
Mim1c — scope: Character, order: last
|
|
107
|
+
Jargoyle — scope: Word, order: normal
|
|
108
|
+
Adjax — scope: Word, order: normal
|
|
109
|
+
Reduple — scope: Word, order: normal
|
|
110
|
+
Rushmore — scope: Word, order: normal
|
|
111
|
+
Redactyl — scope: Word, order: normal
|
|
112
|
+
Scannequin — scope: Character, order: late
|
|
113
|
+
Zeedub — scope: Character, order: last
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Review the full CLI contract.
|
|
118
|
+
glitchlings --help
|
|
119
|
+
```
|
|
101
120
|
|
|
121
|
+
```text
|
|
122
|
+
usage: glitchlings [-h] [-g SPEC] [-s SEED] [-f FILE] [--sample] [--diff]
|
|
123
|
+
[--list] [-c CONFIG]
|
|
124
|
+
[text]
|
|
125
|
+
|
|
126
|
+
Summon glitchlings to corrupt text. Provide input text as an argument, via
|
|
127
|
+
--file, or pipe it on stdin.
|
|
128
|
+
|
|
129
|
+
positional arguments:
|
|
130
|
+
text Text to corrupt. If omitted, stdin is used or --sample
|
|
131
|
+
provides fallback text.
|
|
132
|
+
|
|
133
|
+
options:
|
|
134
|
+
-h, --help show this help message and exit
|
|
135
|
+
-g SPEC, --glitchling SPEC
|
|
136
|
+
Glitchling to apply, optionally with parameters like
|
|
137
|
+
Typogre(rate=0.05). Repeat for multiples; defaults to
|
|
138
|
+
all built-ins.
|
|
139
|
+
-s SEED, --seed SEED Seed controlling deterministic corruption order
|
|
140
|
+
(default: 151).
|
|
141
|
+
-f FILE, --file FILE Read input text from a file instead of the command
|
|
142
|
+
line argument.
|
|
143
|
+
--sample Use the included SAMPLE_TEXT when no other input is
|
|
144
|
+
provided.
|
|
145
|
+
--diff Show a unified diff between the original and corrupted
|
|
146
|
+
text.
|
|
147
|
+
--list List available glitchlings and exit.
|
|
148
|
+
-c CONFIG, --config CONFIG
|
|
149
|
+
Load glitchlings from a YAML configuration file.
|
|
150
|
+
```
|
|
151
|
+
<!-- END: CLI_USAGE -->
|
|
152
|
+
|
|
153
|
+
Run `python docs/build_cli_reference.py` whenever you tweak the CLI so the README stays in sync with the actual output. The script executes the commands above and replaces the block between the markers automatically.
|
|
154
|
+
|
|
155
|
+
Prefer inline tweaks? You can still configure glitchlings directly in the shell:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
102
158
|
# Run Typogre against the contents of a file and inspect the diff.
|
|
103
159
|
glitchlings -g typogre --file documents/report.txt --diff
|
|
104
160
|
|
|
@@ -112,8 +168,6 @@ echo "Beware LLM-written flavor-text" | glitchlings -g mim1c
|
|
|
112
168
|
glitchlings --config experiments/chaos.yaml "Let slips the glitchlings of war"
|
|
113
169
|
```
|
|
114
170
|
|
|
115
|
-
Use `--help` for a complete breakdown of available options, including support for parameterised glitchlings via `-g "Name(arg=value, ...)"` to mirror the Python API.
|
|
116
|
-
|
|
117
171
|
Attack configurations live in plain YAML files so you can version-control experiments without touching code:
|
|
118
172
|
|
|
119
173
|
```yaml
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "glitchlings"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.2"
|
|
4
4
|
description = "Monsters for your language games."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -56,6 +56,16 @@ dev = [
|
|
|
56
56
|
"pytest>=8.0.0",
|
|
57
57
|
"hypothesis>=6.140.0",
|
|
58
58
|
"numpy>=1.24,<=2.0",
|
|
59
|
+
"mkdocs>=1.6.0",
|
|
60
|
+
"mkdocstrings[python]>=0.24.0",
|
|
61
|
+
"mkdocs-material>=9.5.0",
|
|
62
|
+
"mkdocstrings-python>=1.10.0",
|
|
63
|
+
"interrogate>=1.5.0",
|
|
64
|
+
"black>=24.4.0",
|
|
65
|
+
"isort>=5.13.0",
|
|
66
|
+
"ruff>=0.6.0",
|
|
67
|
+
"mypy>=1.8.0",
|
|
68
|
+
"pre-commit>=3.8.0",
|
|
59
69
|
]
|
|
60
70
|
|
|
61
71
|
[build-system]
|
|
@@ -85,3 +95,66 @@ debug = false
|
|
|
85
95
|
pythonpath = [
|
|
86
96
|
"src",
|
|
87
97
|
]
|
|
98
|
+
|
|
99
|
+
[tool.interrogate]
|
|
100
|
+
config = true
|
|
101
|
+
fail-under = 80
|
|
102
|
+
ignore-init-module = true
|
|
103
|
+
ignore-module = true
|
|
104
|
+
ignore-nested-functions = true
|
|
105
|
+
ignore-private = true
|
|
106
|
+
ignore-semiprivate = true
|
|
107
|
+
ignore-magic = true
|
|
108
|
+
ignore-property-decorators = false
|
|
109
|
+
color = true
|
|
110
|
+
quiet = false
|
|
111
|
+
exclude = [
|
|
112
|
+
"tests",
|
|
113
|
+
"docs",
|
|
114
|
+
"rust",
|
|
115
|
+
"benchmarks",
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
[tool.black]
|
|
119
|
+
line-length = 100
|
|
120
|
+
target-version = ["py310"]
|
|
121
|
+
|
|
122
|
+
[tool.isort]
|
|
123
|
+
profile = "black"
|
|
124
|
+
line_length = 100
|
|
125
|
+
|
|
126
|
+
[tool.ruff]
|
|
127
|
+
target-version = "py310"
|
|
128
|
+
line-length = 100
|
|
129
|
+
|
|
130
|
+
[tool.ruff.lint]
|
|
131
|
+
select = ["E", "F", "I"]
|
|
132
|
+
|
|
133
|
+
[tool.mypy]
|
|
134
|
+
python_version = "3.10"
|
|
135
|
+
follow_imports = "skip"
|
|
136
|
+
ignore_missing_imports = true
|
|
137
|
+
enable_error_code = ["ignore-without-code"]
|
|
138
|
+
|
|
139
|
+
[[tool.mypy.overrides]]
|
|
140
|
+
module = [
|
|
141
|
+
"glitchlings.util.adapters",
|
|
142
|
+
"glitchlings.dlc._shared",
|
|
143
|
+
"glitchlings.dlc.huggingface",
|
|
144
|
+
"glitchlings.dlc.prime",
|
|
145
|
+
]
|
|
146
|
+
strict = true
|
|
147
|
+
|
|
148
|
+
[[tool.mypy.overrides]]
|
|
149
|
+
module = [
|
|
150
|
+
"glitchlings.compat",
|
|
151
|
+
"glitchlings.config",
|
|
152
|
+
"glitchlings.lexicon",
|
|
153
|
+
"glitchlings.lexicon.*",
|
|
154
|
+
"glitchlings.zoo",
|
|
155
|
+
"glitchlings.zoo.*",
|
|
156
|
+
"glitchlings.main",
|
|
157
|
+
"glitchlings.__main__",
|
|
158
|
+
"glitchlings.__init__",
|
|
159
|
+
]
|
|
160
|
+
ignore_errors = true
|
|
@@ -172,10 +172,7 @@ impl TextBuffer {
|
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
/// Replace multiple words in a single pass, avoiding repeated reindexing.
|
|
175
|
-
pub fn replace_words_bulk<I>(
|
|
176
|
-
&mut self,
|
|
177
|
-
replacements: I,
|
|
178
|
-
) -> Result<(), TextBufferError>
|
|
175
|
+
pub fn replace_words_bulk<I>(&mut self, replacements: I) -> Result<(), TextBufferError>
|
|
179
176
|
where
|
|
180
177
|
I: IntoIterator<Item = (usize, String)>,
|
|
181
178
|
{
|
|
@@ -435,10 +432,7 @@ mod tests {
|
|
|
435
432
|
fn bulk_replace_words_updates_multiple_entries() {
|
|
436
433
|
let mut buffer = TextBuffer::from_str("alpha beta gamma delta");
|
|
437
434
|
buffer
|
|
438
|
-
.replace_words_bulk(vec![
|
|
439
|
-
(0, "delta".to_string()),
|
|
440
|
-
(3, "alpha".to_string()),
|
|
441
|
-
])
|
|
435
|
+
.replace_words_bulk(vec![(0, "delta".to_string()), (3, "alpha".to_string())])
|
|
442
436
|
.expect("bulk replace succeeds");
|
|
443
437
|
assert_eq!(buffer.to_string(), "delta beta gamma alpha");
|
|
444
438
|
let spans = buffer.spans();
|
|
@@ -1,29 +1,33 @@
|
|
|
1
|
+
from .config import AttackConfig, build_gaggle, load_attack_config
|
|
2
|
+
from .util import SAMPLE_TEXT
|
|
1
3
|
from .zoo import (
|
|
2
|
-
Typogre,
|
|
3
|
-
typogre,
|
|
4
|
-
Mim1c,
|
|
5
|
-
mim1c,
|
|
6
|
-
Jargoyle,
|
|
7
|
-
jargoyle,
|
|
8
4
|
Adjax,
|
|
9
|
-
|
|
5
|
+
Gaggle,
|
|
6
|
+
Glitchling,
|
|
7
|
+
Jargoyle,
|
|
8
|
+
Mim1c,
|
|
10
9
|
Redactyl,
|
|
11
|
-
redactyl,
|
|
12
10
|
Reduple,
|
|
13
|
-
reduple,
|
|
14
11
|
Rushmore,
|
|
15
|
-
rushmore,
|
|
16
12
|
Scannequin,
|
|
17
|
-
|
|
13
|
+
Typogre,
|
|
18
14
|
Zeedub,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
adjax,
|
|
16
|
+
is_rust_pipeline_enabled,
|
|
17
|
+
is_rust_pipeline_supported,
|
|
18
|
+
jargoyle,
|
|
19
|
+
mim1c,
|
|
20
|
+
pipeline_feature_flag_enabled,
|
|
21
|
+
plan_glitchling_specs,
|
|
22
|
+
plan_glitchlings,
|
|
23
|
+
redactyl,
|
|
24
|
+
reduple,
|
|
25
|
+
rushmore,
|
|
26
|
+
scannequin,
|
|
22
27
|
summon,
|
|
28
|
+
typogre,
|
|
29
|
+
zeedub,
|
|
23
30
|
)
|
|
24
|
-
from .config import AttackConfig, build_gaggle, load_attack_config
|
|
25
|
-
from .util import SAMPLE_TEXT
|
|
26
|
-
|
|
27
31
|
|
|
28
32
|
__all__ = [
|
|
29
33
|
"Typogre",
|
|
@@ -47,6 +51,11 @@ __all__ = [
|
|
|
47
51
|
"summon",
|
|
48
52
|
"Glitchling",
|
|
49
53
|
"Gaggle",
|
|
54
|
+
"plan_glitchlings",
|
|
55
|
+
"plan_glitchling_specs",
|
|
56
|
+
"is_rust_pipeline_enabled",
|
|
57
|
+
"is_rust_pipeline_supported",
|
|
58
|
+
"pipeline_feature_flag_enabled",
|
|
50
59
|
"SAMPLE_TEXT",
|
|
51
60
|
"AttackConfig",
|
|
52
61
|
"build_gaggle",
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""Compatibility helpers centralising optional dependency imports and extras."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from importlib import import_module, metadata
|
|
8
|
+
from types import ModuleType
|
|
9
|
+
from typing import Any, Iterable
|
|
10
|
+
|
|
11
|
+
try: # pragma: no cover - packaging is bundled with modern Python environments
|
|
12
|
+
from packaging.markers import default_environment
|
|
13
|
+
from packaging.requirements import Requirement
|
|
14
|
+
except ModuleNotFoundError: # pragma: no cover - fallback when packaging missing
|
|
15
|
+
Requirement = None # type: ignore[assignment]
|
|
16
|
+
default_environment = None # type: ignore[assignment]
|
|
17
|
+
|
|
18
|
+
_MISSING = object()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class OptionalDependency:
|
|
23
|
+
"""Lazily import an optional dependency and retain the import error."""
|
|
24
|
+
|
|
25
|
+
module_name: str
|
|
26
|
+
_cached: ModuleType | object = _MISSING
|
|
27
|
+
_error: ModuleNotFoundError | None = None
|
|
28
|
+
|
|
29
|
+
def _attempt_import(self) -> ModuleType | None:
|
|
30
|
+
try:
|
|
31
|
+
module = import_module(self.module_name)
|
|
32
|
+
except ModuleNotFoundError as exc:
|
|
33
|
+
self._cached = None
|
|
34
|
+
self._error = exc
|
|
35
|
+
return None
|
|
36
|
+
else:
|
|
37
|
+
self._cached = module
|
|
38
|
+
self._error = None
|
|
39
|
+
return module
|
|
40
|
+
|
|
41
|
+
def get(self) -> ModuleType | None:
|
|
42
|
+
"""Return the imported module or ``None`` when unavailable."""
|
|
43
|
+
if self._cached is _MISSING:
|
|
44
|
+
return self._attempt_import()
|
|
45
|
+
if self._cached is None:
|
|
46
|
+
return None
|
|
47
|
+
return self._cached
|
|
48
|
+
|
|
49
|
+
def load(self) -> ModuleType:
|
|
50
|
+
"""Return the dependency, raising the original import error when absent."""
|
|
51
|
+
module = self.get()
|
|
52
|
+
if module is None:
|
|
53
|
+
error = self._error
|
|
54
|
+
if error is not None:
|
|
55
|
+
raise error
|
|
56
|
+
message = f"{self.module_name} is not installed"
|
|
57
|
+
raise ModuleNotFoundError(message)
|
|
58
|
+
return module
|
|
59
|
+
|
|
60
|
+
def require(self, message: str) -> ModuleType:
|
|
61
|
+
"""Return the dependency or raise ``ModuleNotFoundError`` with ``message``."""
|
|
62
|
+
try:
|
|
63
|
+
return self.load()
|
|
64
|
+
except ModuleNotFoundError as exc:
|
|
65
|
+
raise ModuleNotFoundError(message) from exc
|
|
66
|
+
|
|
67
|
+
def available(self) -> bool:
|
|
68
|
+
"""Return ``True`` when the dependency can be imported."""
|
|
69
|
+
return self.get() is not None
|
|
70
|
+
|
|
71
|
+
def reset(self) -> None:
|
|
72
|
+
"""Forget any cached import result."""
|
|
73
|
+
self._cached = _MISSING
|
|
74
|
+
self._error = None
|
|
75
|
+
|
|
76
|
+
def attr(self, attribute: str) -> Any | None:
|
|
77
|
+
"""Return ``attribute`` from the dependency when available."""
|
|
78
|
+
module = self.get()
|
|
79
|
+
if module is None:
|
|
80
|
+
return None
|
|
81
|
+
return getattr(module, attribute, None)
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def error(self) -> ModuleNotFoundError | None:
|
|
85
|
+
"""Return the most recent ``ModuleNotFoundError`` (if any)."""
|
|
86
|
+
self.get()
|
|
87
|
+
return self._error
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
datasets = OptionalDependency("datasets")
|
|
91
|
+
verifiers = OptionalDependency("verifiers")
|
|
92
|
+
jellyfish = OptionalDependency("jellyfish")
|
|
93
|
+
jsonschema = OptionalDependency("jsonschema")
|
|
94
|
+
nltk = OptionalDependency("nltk")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def reset_optional_dependencies() -> None:
|
|
98
|
+
"""Clear cached optional dependency imports (used by tests)."""
|
|
99
|
+
for dependency in (datasets, verifiers, jellyfish, jsonschema, nltk):
|
|
100
|
+
dependency.reset()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def get_datasets_dataset() -> Any | None:
|
|
104
|
+
"""Return Hugging Face ``Dataset`` class when the dependency is installed."""
|
|
105
|
+
return datasets.attr("Dataset")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def require_datasets(message: str = "datasets is not installed") -> ModuleType:
|
|
109
|
+
"""Ensure the Hugging Face datasets dependency is present."""
|
|
110
|
+
return datasets.require(message)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def require_verifiers(message: str = "verifiers is not installed") -> ModuleType:
|
|
114
|
+
"""Ensure the verifiers dependency is present."""
|
|
115
|
+
return verifiers.require(message)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def require_jellyfish(message: str = "jellyfish is not installed") -> ModuleType:
|
|
119
|
+
"""Ensure the jellyfish dependency is present."""
|
|
120
|
+
return jellyfish.require(message)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def get_installed_extras(
|
|
124
|
+
extras: Iterable[str] | None = None,
|
|
125
|
+
*,
|
|
126
|
+
distribution: str = "glitchlings",
|
|
127
|
+
) -> dict[str, bool]:
|
|
128
|
+
"""Return a mapping of optional extras to installation availability."""
|
|
129
|
+
try:
|
|
130
|
+
dist = metadata.distribution(distribution)
|
|
131
|
+
except metadata.PackageNotFoundError:
|
|
132
|
+
return {}
|
|
133
|
+
|
|
134
|
+
provided = {extra.lower() for extra in dist.metadata.get_all("Provides-Extra") or []}
|
|
135
|
+
targets = {extra.lower() for extra in extras} if extras is not None else provided
|
|
136
|
+
requirements = dist.requires or []
|
|
137
|
+
mapping: dict[str, set[str]] = {extra: set() for extra in provided}
|
|
138
|
+
|
|
139
|
+
for requirement in requirements:
|
|
140
|
+
names = _extras_from_requirement(requirement, provided)
|
|
141
|
+
if not names:
|
|
142
|
+
continue
|
|
143
|
+
req_name = _requirement_name(requirement)
|
|
144
|
+
for extra in names:
|
|
145
|
+
mapping.setdefault(extra, set()).add(req_name)
|
|
146
|
+
|
|
147
|
+
status: dict[str, bool] = {}
|
|
148
|
+
for extra in targets:
|
|
149
|
+
deps = mapping.get(extra)
|
|
150
|
+
if not deps:
|
|
151
|
+
status[extra] = False
|
|
152
|
+
continue
|
|
153
|
+
status[extra] = all(_distribution_installed(dep) for dep in deps)
|
|
154
|
+
return status
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _distribution_installed(name: str) -> bool:
|
|
158
|
+
try:
|
|
159
|
+
metadata.distribution(name)
|
|
160
|
+
except metadata.PackageNotFoundError:
|
|
161
|
+
return False
|
|
162
|
+
return True
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
_EXTRA_PATTERN = re.compile(r'extra\\s*==\\s*"(?P<extra>[^"]+)"')
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _extras_from_requirement(requirement: str, candidates: set[str]) -> set[str]:
|
|
169
|
+
if Requirement is not None and default_environment is not None:
|
|
170
|
+
req = Requirement(requirement)
|
|
171
|
+
if req.marker is None:
|
|
172
|
+
return set()
|
|
173
|
+
extras: set[str] = set()
|
|
174
|
+
for extra in candidates:
|
|
175
|
+
environment = default_environment()
|
|
176
|
+
environment["extra"] = extra
|
|
177
|
+
if req.marker.evaluate(environment):
|
|
178
|
+
extras.add(extra)
|
|
179
|
+
return extras
|
|
180
|
+
|
|
181
|
+
matches = set()
|
|
182
|
+
for match in _EXTRA_PATTERN.finditer(requirement):
|
|
183
|
+
extra = match.group("extra").lower()
|
|
184
|
+
if extra in candidates:
|
|
185
|
+
matches.add(extra)
|
|
186
|
+
return matches
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _requirement_name(requirement: str) -> str:
|
|
190
|
+
if Requirement is not None:
|
|
191
|
+
req = Requirement(requirement)
|
|
192
|
+
return req.name
|
|
193
|
+
|
|
194
|
+
candidate = requirement.split(";", 1)[0].strip()
|
|
195
|
+
for delimiter in ("[", "(", " ", "<", ">", "=", "!", "~"):
|
|
196
|
+
index = candidate.find(delimiter)
|
|
197
|
+
if index != -1:
|
|
198
|
+
return candidate[:index]
|
|
199
|
+
return candidate
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
__all__ = [
|
|
203
|
+
"OptionalDependency",
|
|
204
|
+
"datasets",
|
|
205
|
+
"verifiers",
|
|
206
|
+
"jellyfish",
|
|
207
|
+
"jsonschema",
|
|
208
|
+
"nltk",
|
|
209
|
+
"get_datasets_dataset",
|
|
210
|
+
"require_datasets",
|
|
211
|
+
"require_verifiers",
|
|
212
|
+
"require_jellyfish",
|
|
213
|
+
"get_installed_extras",
|
|
214
|
+
"reset_optional_dependencies",
|
|
215
|
+
]
|