worldcut 0.1.1__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.
- worldcut-0.1.1/.gitignore +12 -0
- worldcut-0.1.1/PKG-INFO +91 -0
- worldcut-0.1.1/README.md +61 -0
- worldcut-0.1.1/pyproject.toml +70 -0
- worldcut-0.1.1/src/worldcut/__init__.py +30 -0
- worldcut-0.1.1/src/worldcut/canonicalization.py +82 -0
- worldcut-0.1.1/src/worldcut/cli.py +77 -0
- worldcut-0.1.1/src/worldcut/errors.py +27 -0
- worldcut-0.1.1/src/worldcut/models.py +259 -0
- worldcut-0.1.1/src/worldcut/planning.py +181 -0
- worldcut-0.1.1/src/worldcut/py.typed +0 -0
- worldcut-0.1.1/src/worldcut/requirements.py +600 -0
- worldcut-0.1.1/src/worldcut/validation.py +524 -0
- worldcut-0.1.1/src/worldcut/verifier.py +116 -0
- worldcut-0.1.1/tests/data/conformance/0.1/canonicalization-vectors.json +60 -0
- worldcut-0.1.1/tests/data/conformance/0.1/invalid-vectors.json +1833 -0
- worldcut-0.1.1/tests/data/conformance/0.1/manifest.json +27 -0
- worldcut-0.1.1/tests/data/conformance/0.1/raw/unpaired-high-surrogate.json +145 -0
- worldcut-0.1.1/tests/data/conformance/0.1/raw-vectors.json +14 -0
- worldcut-0.1.1/tests/data/conformance/0.1/verification-vectors.json +6422 -0
- worldcut-0.1.1/tests/test_cli.py +93 -0
- worldcut-0.1.1/tests/test_conformance.py +107 -0
- worldcut-0.1.1/tests/test_planning.py +71 -0
- worldcut-0.1.1/tests/test_properties.py +89 -0
- worldcut-0.1.1/tests/test_public_api.py +30 -0
worldcut-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: worldcut
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Independent Python verifier for the WorldCut decision-coherence protocol
|
|
5
|
+
Project-URL: Homepage, https://github.com/Jason-Doyle/WorldCut
|
|
6
|
+
Project-URL: Issues, https://github.com/Jason-Doyle/WorldCut/issues
|
|
7
|
+
Project-URL: Repository, https://github.com/Jason-Doyle/WorldCut
|
|
8
|
+
Author: Jason Doyle
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
Keywords: consistency,distributed-systems,provenance,verification
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: rfc8785==0.1.4
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: build>=1.2.2; extra == 'dev'
|
|
24
|
+
Requires-Dist: hypothesis>=6.130; extra == 'dev'
|
|
25
|
+
Requires-Dist: mypy>=1.15; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=8.3; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.11; extra == 'dev'
|
|
28
|
+
Requires-Dist: twine>=6.1; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# WorldCut Python
|
|
32
|
+
|
|
33
|
+
Independent Python implementation of WorldCut protocol **0.1**, engine
|
|
34
|
+
**0.1.2**, and canonicalization **worldcut-json-v1**. Python 3.11 or newer is
|
|
35
|
+
required.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
Install from PyPI after the `0.1.1` registry release is live:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
python -m pip install worldcut==0.1.1
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Until then, or when a source install is preferred, use the protected tag:
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
python -m pip install "worldcut @ git+https://github.com/Jason-Doyle/WorldCut.git@ports/python/v0.1.1#subdirectory=ports/python"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Library
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from pathlib import Path
|
|
55
|
+
|
|
56
|
+
from worldcut import parse_input, verify
|
|
57
|
+
|
|
58
|
+
parsed = parse_input(Path("verification.json").read_bytes())
|
|
59
|
+
result = verify(parsed)
|
|
60
|
+
print(result["verdict"])
|
|
61
|
+
print(result["verificationRecordDigest"])
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`ParsedInput` is an immutable validated snapshot. Every verification returns a
|
|
65
|
+
fresh result, so mutating one result cannot affect later verification.
|
|
66
|
+
|
|
67
|
+
## CLI
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
worldcut-py verification.json
|
|
71
|
+
worldcut-py --require-satisfied verification.json
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The second form exits with status 2 unless the verdict is
|
|
75
|
+
`CONTRACT_SATISFIED`.
|
|
76
|
+
|
|
77
|
+
## Validate
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
python -m pip install -e ".[dev]"
|
|
81
|
+
ruff format --check .
|
|
82
|
+
ruff check .
|
|
83
|
+
mypy src
|
|
84
|
+
pytest
|
|
85
|
+
python -m build
|
|
86
|
+
twine check dist/*
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The source distribution includes the complete mirrored conformance corpus and
|
|
90
|
+
can run its tests without files from the parent repository. This port includes
|
|
91
|
+
the verifier and CLI only; integrations are intentionally not included yet.
|
worldcut-0.1.1/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# WorldCut Python
|
|
2
|
+
|
|
3
|
+
Independent Python implementation of WorldCut protocol **0.1**, engine
|
|
4
|
+
**0.1.2**, and canonicalization **worldcut-json-v1**. Python 3.11 or newer is
|
|
5
|
+
required.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Install from PyPI after the `0.1.1` registry release is live:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
python -m pip install worldcut==0.1.1
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Until then, or when a source install is preferred, use the protected tag:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
python -m pip install "worldcut @ git+https://github.com/Jason-Doyle/WorldCut.git@ports/python/v0.1.1#subdirectory=ports/python"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Library
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
from worldcut import parse_input, verify
|
|
27
|
+
|
|
28
|
+
parsed = parse_input(Path("verification.json").read_bytes())
|
|
29
|
+
result = verify(parsed)
|
|
30
|
+
print(result["verdict"])
|
|
31
|
+
print(result["verificationRecordDigest"])
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`ParsedInput` is an immutable validated snapshot. Every verification returns a
|
|
35
|
+
fresh result, so mutating one result cannot affect later verification.
|
|
36
|
+
|
|
37
|
+
## CLI
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
worldcut-py verification.json
|
|
41
|
+
worldcut-py --require-satisfied verification.json
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The second form exits with status 2 unless the verdict is
|
|
45
|
+
`CONTRACT_SATISFIED`.
|
|
46
|
+
|
|
47
|
+
## Validate
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
python -m pip install -e ".[dev]"
|
|
51
|
+
ruff format --check .
|
|
52
|
+
ruff check .
|
|
53
|
+
mypy src
|
|
54
|
+
pytest
|
|
55
|
+
python -m build
|
|
56
|
+
twine check dist/*
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The source distribution includes the complete mirrored conformance corpus and
|
|
60
|
+
can run its tests without files from the parent repository. This port includes
|
|
61
|
+
the verifier and CLI only; integrations are intentionally not included yet.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling==1.32.0"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "worldcut"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Independent Python verifier for the WorldCut decision-coherence protocol"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
authors = [{ name = "Jason Doyle" }]
|
|
13
|
+
keywords = ["consistency", "distributed-systems", "provenance", "verification"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: Apache Software License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Programming Language :: Python :: 3.14",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
dependencies = ["rfc8785==0.1.4"]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = [
|
|
29
|
+
"build>=1.2.2",
|
|
30
|
+
"hypothesis>=6.130",
|
|
31
|
+
"mypy>=1.15",
|
|
32
|
+
"pytest>=8.3",
|
|
33
|
+
"ruff>=0.11",
|
|
34
|
+
"twine>=6.1",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
worldcut-py = "worldcut.cli:main"
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/Jason-Doyle/WorldCut"
|
|
42
|
+
Issues = "https://github.com/Jason-Doyle/WorldCut/issues"
|
|
43
|
+
Repository = "https://github.com/Jason-Doyle/WorldCut"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["src/worldcut"]
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.sdist]
|
|
49
|
+
include = [
|
|
50
|
+
"/src/worldcut",
|
|
51
|
+
"/tests",
|
|
52
|
+
"/README.md",
|
|
53
|
+
"/pyproject.toml",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[tool.pytest.ini_options]
|
|
57
|
+
addopts = "-ra --strict-config --strict-markers"
|
|
58
|
+
testpaths = ["tests"]
|
|
59
|
+
|
|
60
|
+
[tool.mypy]
|
|
61
|
+
python_version = "3.11"
|
|
62
|
+
strict = true
|
|
63
|
+
files = ["src"]
|
|
64
|
+
|
|
65
|
+
[tool.ruff]
|
|
66
|
+
target-version = "py311"
|
|
67
|
+
line-length = 88
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint]
|
|
70
|
+
select = ["B", "E", "F", "I", "RUF", "SIM", "UP"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Independent Python implementation of the WorldCut 0.1 verifier."""
|
|
2
|
+
|
|
3
|
+
from .canonicalization import canonical_json, sha256_digest
|
|
4
|
+
from .errors import WorldCutError, WorldCutErrorCode, WorldCutInputError
|
|
5
|
+
from .models import ParsedInput, VerificationResult
|
|
6
|
+
from .validation import PROTOCOL_VERSION, parse_input
|
|
7
|
+
from .verifier import (
|
|
8
|
+
CANONICALIZATION,
|
|
9
|
+
ENGINE_VERSION,
|
|
10
|
+
verify,
|
|
11
|
+
verify_json,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"CANONICALIZATION",
|
|
16
|
+
"ENGINE_VERSION",
|
|
17
|
+
"PROTOCOL_VERSION",
|
|
18
|
+
"ParsedInput",
|
|
19
|
+
"VerificationResult",
|
|
20
|
+
"WorldCutError",
|
|
21
|
+
"WorldCutErrorCode",
|
|
22
|
+
"WorldCutInputError",
|
|
23
|
+
"canonical_json",
|
|
24
|
+
"parse_input",
|
|
25
|
+
"sha256_digest",
|
|
26
|
+
"verify",
|
|
27
|
+
"verify_json",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
__version__ = "0.1.1"
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import math
|
|
5
|
+
|
|
6
|
+
import rfc8785
|
|
7
|
+
|
|
8
|
+
from .models import JsonValue
|
|
9
|
+
|
|
10
|
+
_MAX_SAFE_INTEGER = 9_007_199_254_740_991
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _assert_valid_unicode(value: str, field: str) -> None:
|
|
14
|
+
for character in value:
|
|
15
|
+
code_point = ord(character)
|
|
16
|
+
if 0xD800 <= code_point <= 0xDFFF:
|
|
17
|
+
raise TypeError(f"{field} contains an unpaired surrogate")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _snapshot_json(value: object, field: str, ancestors: set[int]) -> JsonValue:
|
|
21
|
+
if value is None or isinstance(value, (str, bool)):
|
|
22
|
+
if isinstance(value, str):
|
|
23
|
+
_assert_valid_unicode(value, field)
|
|
24
|
+
return value
|
|
25
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
26
|
+
if abs(value) > _MAX_SAFE_INTEGER:
|
|
27
|
+
raise TypeError(f"{field} contains an integer outside the safe domain")
|
|
28
|
+
return value
|
|
29
|
+
if isinstance(value, float):
|
|
30
|
+
if not math.isfinite(value):
|
|
31
|
+
raise TypeError(f"{field} contains a non-finite number")
|
|
32
|
+
return value
|
|
33
|
+
if type(value) is list:
|
|
34
|
+
identity = id(value)
|
|
35
|
+
if identity in ancestors:
|
|
36
|
+
raise TypeError(f"{field} must not contain cycles")
|
|
37
|
+
ancestors.add(identity)
|
|
38
|
+
array_result = [
|
|
39
|
+
_snapshot_json(item, f"{field}[{index}]", ancestors)
|
|
40
|
+
for index, item in enumerate(value)
|
|
41
|
+
]
|
|
42
|
+
ancestors.remove(identity)
|
|
43
|
+
return array_result
|
|
44
|
+
if type(value) is dict:
|
|
45
|
+
identity = id(value)
|
|
46
|
+
if identity in ancestors:
|
|
47
|
+
raise TypeError(f"{field} must not contain cycles")
|
|
48
|
+
ancestors.add(identity)
|
|
49
|
+
object_result: dict[str, JsonValue] = {}
|
|
50
|
+
for raw_key, item in value.items():
|
|
51
|
+
if not isinstance(raw_key, str):
|
|
52
|
+
raise TypeError(f"{field} must use string object keys")
|
|
53
|
+
_assert_valid_unicode(raw_key, f"{field} key")
|
|
54
|
+
object_result[raw_key] = _snapshot_json(
|
|
55
|
+
item, f"{field}.{raw_key}", ancestors
|
|
56
|
+
)
|
|
57
|
+
ancestors.remove(identity)
|
|
58
|
+
return object_result
|
|
59
|
+
raise TypeError(f"{field} contains unsupported {type(value).__name__} data")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def canonical_json(value: JsonValue) -> str:
|
|
63
|
+
"""Return the worldcut-json-v1 canonical representation of JSON data."""
|
|
64
|
+
|
|
65
|
+
snapshot = _snapshot_json(value, "value", set())
|
|
66
|
+
try:
|
|
67
|
+
return rfc8785.dumps(snapshot).decode("utf-8")
|
|
68
|
+
except (rfc8785.CanonicalizationError, UnicodeError) as error:
|
|
69
|
+
raise TypeError(f"cannot canonicalize JSON data: {error}") from error
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def sha256_digest(value: JsonValue) -> str:
|
|
73
|
+
"""Return the lowercase SHA-256 digest of canonical JSON data."""
|
|
74
|
+
|
|
75
|
+
return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def utf16_sort_key(value: str) -> bytes:
|
|
79
|
+
"""Produce the protocol's raw UTF-16 code-unit ordering key."""
|
|
80
|
+
|
|
81
|
+
_assert_valid_unicode(value, "text")
|
|
82
|
+
return value.encode("utf-16-be")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .errors import WorldCutError
|
|
8
|
+
from .verifier import verify_json
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _usage() -> str:
|
|
12
|
+
return "\n".join(
|
|
13
|
+
[
|
|
14
|
+
"Usage: worldcut-py [--require-satisfied] <verification.json>",
|
|
15
|
+
"",
|
|
16
|
+
"Options:",
|
|
17
|
+
" --require-satisfied Exit with code 2 unless the contract is satisfied",
|
|
18
|
+
" --help Show this help",
|
|
19
|
+
]
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _write_error(code: str, message: str) -> None:
|
|
24
|
+
print(
|
|
25
|
+
json.dumps({"error": {"code": code, "message": message}}),
|
|
26
|
+
file=sys.stderr,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def run(arguments: list[str] | None = None) -> int:
|
|
31
|
+
"""Run the WorldCut CLI and return its process exit status."""
|
|
32
|
+
|
|
33
|
+
arguments = list(sys.argv[1:] if arguments is None else arguments)
|
|
34
|
+
if "--help" in arguments:
|
|
35
|
+
print(_usage())
|
|
36
|
+
return 0
|
|
37
|
+
require_satisfied = False
|
|
38
|
+
positional: list[str] = []
|
|
39
|
+
for argument in arguments:
|
|
40
|
+
if argument == "--require-satisfied":
|
|
41
|
+
require_satisfied = True
|
|
42
|
+
elif argument.startswith("-"):
|
|
43
|
+
_write_error("WORLDCUT_INVALID_ARGUMENT", f"Unknown option: {argument}")
|
|
44
|
+
return 1
|
|
45
|
+
else:
|
|
46
|
+
positional.append(argument)
|
|
47
|
+
if len(positional) != 1:
|
|
48
|
+
_write_error(
|
|
49
|
+
"WORLDCUT_INVALID_ARGUMENT",
|
|
50
|
+
"Exactly one verification JSON file is required",
|
|
51
|
+
)
|
|
52
|
+
return 1
|
|
53
|
+
path = Path(positional[0]).resolve()
|
|
54
|
+
try:
|
|
55
|
+
source = path.read_bytes()
|
|
56
|
+
except OSError:
|
|
57
|
+
_write_error("WORLDCUT_FILE_READ_FAILED", f"Unable to read {path}")
|
|
58
|
+
return 1
|
|
59
|
+
try:
|
|
60
|
+
result = verify_json(source)
|
|
61
|
+
except WorldCutError as error:
|
|
62
|
+
_write_error(error.code, str(error))
|
|
63
|
+
return 1
|
|
64
|
+
print(json.dumps(result, indent=2))
|
|
65
|
+
if require_satisfied and result["verdict"] != "CONTRACT_SATISFIED":
|
|
66
|
+
return 2
|
|
67
|
+
return 0
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def main() -> None:
|
|
71
|
+
"""Console-script entry point."""
|
|
72
|
+
|
|
73
|
+
raise SystemExit(run())
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
main()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Literal, TypeAlias
|
|
4
|
+
|
|
5
|
+
WorldCutErrorCode: TypeAlias = Literal[
|
|
6
|
+
"WORLDCUT_INVALID_INPUT",
|
|
7
|
+
"WORLDCUT_INVALID_ARGUMENT",
|
|
8
|
+
"WORLDCUT_FILE_READ_FAILED",
|
|
9
|
+
"WORLDCUT_RUNTIME_ERROR",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class WorldCutError(Exception):
|
|
14
|
+
"""Base exception carrying a stable WorldCut error code."""
|
|
15
|
+
|
|
16
|
+
code: WorldCutErrorCode
|
|
17
|
+
|
|
18
|
+
def __init__(self, code: WorldCutErrorCode, message: str) -> None:
|
|
19
|
+
super().__init__(message)
|
|
20
|
+
self.code = code
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class WorldCutInputError(WorldCutError):
|
|
24
|
+
"""Raised when transport data violates the WorldCut protocol."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, message: str) -> None:
|
|
27
|
+
super().__init__("WORLDCUT_INVALID_INPUT", message)
|