diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 9559ef7..138d820 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -10,7 +10,7 @@ on: jobs: build: - runs-on: windows-latest + runs-on: ubuntu-latest strategy: fail-fast: false matrix: @@ -41,3 +41,5 @@ jobs: - name: Test with pytest run: | uv run pytest -v + env: + FORCE_COLOR: "1" diff --git a/.vscode/settings.json b/.vscode/settings.json index e59d60b..5330bb0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,7 @@ "tests" ], "python.testing.unittestEnabled": false, - "python.testing.pytestEnabled": true + "python.testing.pytestEnabled": true, + "python-envs.defaultEnvManager": "ms-python.python:venv", + "python-envs.pythonProjects": [] } \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 549726c..660126b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ dynamic = ["version"] [dependency-groups] dev = [ "pytest", - "mypy==1.15.0", + "mypy==1.19.1", ] [tool.setuptools_scm] diff --git a/tests/conftest.py b/tests/conftest.py index 458a86d..d5a6db9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,25 +1,69 @@ # Note that this conftest file exists at the package level as it is needed to configure # the JVM for docstrings at the package level. -import os -import pathlib +import logging +import tempfile +import textwrap +from collections.abc import Generator +from pathlib import Path import pytest +import chaquopy_stubgen -pytest_plugins = [ - "mypy.test.data", -] -os.environ["MYPY_TEST_PREFIX"] = str(pathlib.Path(__file__).parent / "stubtest") +logger = logging.getLogger(__name__) -ANDROID_JAR = pathlib.Path(__file__).parent / "android-35.jar" +ANDROID_JAR = Path(__file__).parent / "android-35.jar" -@pytest.fixture(autouse=True, scope="session") + +@pytest.fixture(scope="session") def jvm(): import jpype # type: ignore - if not jpype.isJVMStarted(): - jpype.startJVM(None, classpath=[ANDROID_JAR], convertStrings=True) # type: ignore + jpype.startJVM(None, classpath=[ANDROID_JAR], convertStrings=True) # type: ignore import jpype.imports # type: ignore - yield jpype + try: + yield jpype + finally: + jpype.shutdownJVM() + + +@pytest.fixture(scope="session") +def stub_dir(jvm) -> Generator[Path, None, None]: + # logging.basicConfig(level="DEBUG") + with tempfile.TemporaryDirectory() as tmpdir: + tmpdir = Path(tmpdir) + import java # type: ignore + + logger.debug(f"Generating stubs in {tmpdir}...") + + chaquopy_stubgen.generate_java_stubs( + [java], # type: ignore + output_dir=tmpdir, + ) + + # Rename "java-stubs" to "java" + (tmpdir / "java-stubs").rename(tmpdir / "java") + + yield tmpdir + + +@pytest.fixture(scope="session") +def mypy_project_dir(stub_dir: Path) -> Generator[Path, None, None]: + with tempfile.TemporaryDirectory() as tmp: + project_dir = Path(tmp) + with project_dir.joinpath("pyproject.toml").open("w") as f: + f.write( + textwrap.dedent( + f"""\ + [tool.mypy] + mypy_path = "{stub_dir.absolute()}" + + [[tool.mypy.overrides]] + module = "java.*" + ignore_errors = true + """ + ) + ) + yield project_dir diff --git a/tests/mypy_helper.py b/tests/mypy_helper.py new file mode 100644 index 0000000..1db2456 --- /dev/null +++ b/tests/mypy_helper.py @@ -0,0 +1,159 @@ +import io +import json +import logging +import os +import re +import tokenize +from collections.abc import Generator +from contextlib import contextmanager +from pathlib import Path +from typing import Literal, Optional, TypedDict + +import mypy.api + +logger = logging.getLogger(__name__) + + +class MypyMessage(TypedDict): + file: str + line: int + column: int + message: str + hint: Optional[str] + code: Optional[str] + severity: Literal["error", "note", "warning"] + + +@contextmanager +def change_dir(path: Path) -> Generator[None, None, None]: + """Temporary change the current working directory.""" + old = Path.cwd() + try: + os.chdir(path) + yield + finally: + os.chdir(old) + + +def _parse_mypy_jsonl_output(mypy_stdout: str) -> list[MypyMessage]: + """Parse mypy's JSONL output into a list of MypyMessage objects.""" + messages = [] + for line in mypy_stdout.strip().split("\n"): + if line: # Skip empty lines + messages.append(json.loads(line)) + return messages + + +def run_mypy( + project_dir: Path, + test_code: str, +) -> str: + """Run mypy on the given test code and return the mypy stdout.""" + testfile_name = "testfile.py" + with project_dir.joinpath(testfile_name).open("w") as f: + f.write(test_code) + + with change_dir(project_dir): + result = mypy.api.run([testfile_name, "--output", "json"]) + + mypy_stdout, mypy_stderr, mypy_returncode = result + logger.debug(f"mypy stdout: {mypy_stdout}") + logger.debug(f"mypy stderr: {mypy_stderr}") + logger.debug(f"mypy returncode: {mypy_returncode}") + + return mypy_stdout + + +def run_and_assert_mypy( + project_dir: Path, + test_code: str, + expected_output: dict[str, str] | str, +): + """ + Run mypy on the given test code and assert that the output matches the expected output. + + The expected_output can be either: + - A dictionary mapping markers like "*1" to expected mypy messages. In this case, + the function will parse the mypy JSON output and compare the messages for each marked line. + - A raw string containing the expected mypy output. In this case, the function will compare + the raw mypy stdout with the expected string. + """ + mypy_stdout = run_mypy(project_dir, test_code) + if isinstance(expected_output, dict): + mypy_output = _parse_mypy_jsonl_output(mypy_stdout) + assert_mypy_json_output(test_code, mypy_output, expected_output) + else: + # some errors like syntax errors are not returned as json message. In + # this case, we just check the raw stdout for the expected error message. + assert expected_output.strip() == mypy_stdout.strip() + + +def _format_mypy_msg(msg: MypyMessage) -> str: + """Format a mypy message into a human-readable string, including hints if present.""" + formatted_msg = f"{msg['severity']}: {msg['message']}" + if hints := msg.get("hint"): + for hint in hints.splitlines(): + formatted_msg += f"\nhint: {hint}" + return formatted_msg + + +def _parse_marked_lines_with_tokenize(code: str) -> dict[str, int]: + """Parse the code to find all lines with markers like *1, *2, etc. in comments.""" + marked_lines = {} + + # Tokenize the code + tokens = tokenize.generate_tokens(io.StringIO(code).readline) + + for token in tokens: + if token.type == tokenize.COMMENT: + # token.string contains the comment including the # + match = re.search(r"#\s*\*(\d+)", token.string) + if match: + marker = f"*{match.group(1)}" + marked_lines[marker] = token.start[0] # Line number + + return marked_lines + + +def assert_mypy_json_output( + code: str, + mypy_output: list[MypyMessage], + expected_output: dict[str, str], +): + """Assert that the mypy output matches the expected output for the given code.""" + + # Parse the code to find the marked lines + marked_lines: dict[str, int] = _parse_marked_lines_with_tokenize(code) + + # Validate that all expected markers are present in the code + missing_markers = set(expected_output.keys()) - set(marked_lines.keys()) + if missing_markers: + assert False, f"Expected markers not found in code: {', '.join(sorted(missing_markers))}" + + # Group messages by line number + result_by_line: dict[int, list[MypyMessage]] = {} + for msg in mypy_output: + line = msg["line"] + if line not in result_by_line: + result_by_line[line] = [] + result_by_line[line].append(msg) + + # Compare the expected output with the actual mypy messages for each marked line + for marker, line in marked_lines.items(): + expected_msgs = expected_output[marker] + mypy_msgs: list[MypyMessage] | None = result_by_line.pop(line) + assert mypy_msgs is not None and len(mypy_msgs) > 0 + + if isinstance(expected_msgs, str): + expected_msgs = [expected_msgs] + + for expected_msg, msg in zip(expected_msgs, mypy_msgs, strict=True): + actual_msg = _format_mypy_msg(msg) + assert expected_msg.strip() == actual_msg.strip() + + if len(result_by_line) > 0: + unexpected_msgs = [] + for line, msgs in result_by_line.items(): + for msg in msgs: + unexpected_msgs.append(f"line {line}: {_format_mypy_msg(msg)}") + assert False, "Unexpected mypy messages:\n" + "\n".join(unexpected_msgs) diff --git a/tests/stubtest/README.md b/tests/stubtest/README.md deleted file mode 100644 index d901194..0000000 --- a/tests/stubtest/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Stub tests -These data-driven tests are collected executed by the `mypy.test.data` pytest plugin. -The test suite is declared in `test_stubtest.py`. - -Note that the structure of this directory must be compatible with what `mypy.test.data` expects. \ No newline at end of file diff --git a/tests/stubtest/test-data/unit/arraylist.test b/tests/stubtest/test-data/unit/arraylist.test deleted file mode 100644 index 5088f11..0000000 --- a/tests/stubtest/test-data/unit/arraylist.test +++ /dev/null @@ -1,42 +0,0 @@ -[case testArrayListValid] -from java.util import ArrayList - -java_array_list: ArrayList[str] = ArrayList() -java_array_list.add('42') - -reveal_type(java_array_list) # N: Revealed type is "java.util.ArrayList[builtins.str]" -reveal_type(java_array_list.get(0)) # N: Revealed type is "builtins.str" - - -[case testArrayListInvalid] -from java.util import ArrayList - -java_array_list: ArrayList[str] = ArrayList() -java_array_list.add(42) # E: No overload variant of "add" of "ArrayList" matches argument type "int" \ - # N: Possible overload variants: \ - # N: def add(self, e: str) -> bool \ - # N: def add(self, int: Union[int, jint, Integer], e: str) -> None - - -[case testArrayListNoImplicitConversion] -from java.util import ArrayList - -pylist = ['test', '1', '2'] -java_array_list = ArrayList(pylist) # E: No overload variant of "ArrayList" matches argument type "List[str]" \ - # N: Possible overload variants: \ - # N: def [_ArrayList__E] __init__(self) -> ArrayList[_ArrayList__E] \ - # N: def [_ArrayList__E] __init__(self, int: Union[int, jint, Integer]) -> ArrayList[_ArrayList__E] \ - # N: def [_ArrayList__E] __init__(self, collection: Collection[_ArrayList__E]) -> ArrayList[_ArrayList__E] - - -[case testArrayListMissingTypeAnnotation] -from java.util import ArrayList - -java_array_list = ArrayList(2) # E: Need type annotation for "java_array_list" - - -[case testArrayListNoGetitem] -from java.util import ArrayList - -java_array_list: ArrayList[str] = ArrayList() -java_array_list[0] # E: Value of type "ArrayList[str]" is not indexable \ No newline at end of file diff --git a/tests/stubtest/test-data/unit/enummap.test b/tests/stubtest/test-data/unit/enummap.test deleted file mode 100644 index e7e970b..0000000 --- a/tests/stubtest/test-data/unit/enummap.test +++ /dev/null @@ -1,21 +0,0 @@ -[case testEnumMapValid] -import typing -from java.util import EnumMap -from java.util.concurrent import TimeUnit - -timeunit_enummap: "EnumMap[TimeUnit, typing.Any]" = EnumMap(TimeUnit) -reveal_type(timeunit_enummap) # N: Revealed type is "java.util.EnumMap[java.util.concurrent.TimeUnit, Any]" - -timeunit_enummap.put(TimeUnit.SECONDS, 'test') - - -[case testEnumMapInvalid] -import typing -from java.util import EnumMap -from java.util.concurrent import TimeUnit - -timeunit_enummap: "EnumMap[TimeUnit, typing.Any]" = EnumMap(TimeUnit) - -timeunit_enummap.put(42, 'test') # E: Argument 1 to "put" of "EnumMap" has incompatible type "int"; expected "TimeUnit" - -timeunit_enummap.put('fail', 'test') # E: Argument 1 to "put" of "EnumMap" has incompatible type "str"; expected "TimeUnit" diff --git a/tests/stubtest/test-data/unit/exception.test b/tests/stubtest/test-data/unit/exception.test deleted file mode 100644 index 21e3c76..0000000 --- a/tests/stubtest/test-data/unit/exception.test +++ /dev/null @@ -1,7 +0,0 @@ -[case testJavaException] -from java.lang import Exception -java_exception = Exception("Testing") - -reveal_type(java_exception) # N: Revealed type is "java.lang.Exception" - -raise RuntimeError("42") from java_exception \ No newline at end of file diff --git a/tests/stubtest/test-data/unit/forward_declaration.test b/tests/stubtest/test-data/unit/forward_declaration.test deleted file mode 100644 index 33094dc..0000000 --- a/tests/stubtest/test-data/unit/forward_declaration.test +++ /dev/null @@ -1,9 +0,0 @@ - -[case testArgumentTypeDeclaration] -import typing - -if typing.TYPE_CHECKING: - import java.util - -def foo(arg: "java.util.Formatter"): - reveal_type(arg) # N: Revealed type is "java.util.Formatter" diff --git a/tests/stubtest/test-data/unit/hashmap.test b/tests/stubtest/test-data/unit/hashmap.test deleted file mode 100644 index 690cd26..0000000 --- a/tests/stubtest/test-data/unit/hashmap.test +++ /dev/null @@ -1,26 +0,0 @@ -[case testHashMapValid] -from java.util import HashMap - -java_map: "HashMap[str, float]" = HashMap() -java_map.put("hello", 1.0) -java_map.put("world", 42.0) - -reveal_type(java_map) # N: Revealed type is "java.util.HashMap[builtins.str, builtins.float]" -reveal_type(java_map.values()) # N: Revealed type is "java.util.Collection[builtins.float]" -reveal_type(java_map.keySet()) # N: Revealed type is "java.util.Set[builtins.str]" - -reveal_type(java_map.get('hello')) # N: Revealed type is "builtins.float" - -java_map.put('test1', 42) - - -[case testHashMapInvalid] -from java.util import HashMap - -java_map: "HashMap[str, float]" = HashMap() -java_map.put("hello", 1.0) -java_map.put("world", 42.0) - -java_map.put('test1', 'foo') # E: Argument 2 to "put" of "HashMap" has incompatible type "str"; expected "float" - - diff --git a/tests/stubtest/test-data/unit/jarray.test b/tests/stubtest/test-data/unit/jarray.test deleted file mode 100644 index 6dec3e9..0000000 --- a/tests/stubtest/test-data/unit/jarray.test +++ /dev/null @@ -1,37 +0,0 @@ -[case testJavaArrayAsReturnType] -from java.lang import String - -s = String("asdf") -reveal_type(s) # N: Revealed type is "java.lang.String" -reveal_type(s.split("s")) # N: Revealed type is "java.chaquopy.JavaArray[java.lang.String]" -reveal_type(s.toCharArray()) # N: Revealed type is "java.chaquopy.JavaArrayJChar" - -[case testJavaArrayAsParameter] -from java.lang import String -from java import jarray, jchar, jint, jbyte - -char_array = jarray(jchar)(["a", "b", "c"]) -byte_array = jarray(jbyte)([55, 66, 77]) -int_array = jarray(jint)([55555, 66, 77]) -reveal_type(char_array) # N: Revealed type is "java.chaquopy.JavaArrayJChar" -reveal_type(byte_array) # N: Revealed type is "java.chaquopy.JavaArrayJByte" -reveal_type(int_array) # N: Revealed type is "java.chaquopy.JavaArrayJInt" -String(char_array) -String(byte_array) -String(int_array) # E: No overload variant of "String" matches argument type "JavaArrayJInt" \ - # N: Possible overload variants: \ - # N: def __init__(self) -> String \ - # N: def __init__(self, byteArray: JavaArrayJByte) -> String \ - # N: def __init__(self, byteArray: JavaArrayJByte, int: Union[int, jint, Integer]) -> String \ - # N: def __init__(self, byteArray: JavaArrayJByte, int: Union[int, jint, Integer], int2: Union[int, jint, Integer]) -> String \ - # N: def __init__(self, byteArray: JavaArrayJByte, int: Union[int, jint, Integer], int2: Union[int, jint, Integer], int3: Union[int, jint, Integer]) -> String \ - # N: def __init__(self, byteArray: JavaArrayJByte, int: Union[int, jint, Integer], int2: Union[int, jint, Integer], string: Union[str, String]) -> String \ - # N: def __init__(self, byteArray: JavaArrayJByte, int: Union[int, jint, Integer], int2: Union[int, jint, Integer], charset: Charset) -> String \ - # N: def __init__(self, byteArray: JavaArrayJByte, string: Union[str, String]) -> String \ - # N: def __init__(self, byteArray: JavaArrayJByte, charset: Charset) -> String \ - # N: def __init__(self, charArray: JavaArrayJChar) -> String \ - # N: def __init__(self, charArray: JavaArrayJChar, int: Union[int, jint, Integer], int2: Union[int, jint, Integer]) -> String \ - # N: def __init__(self, intArray: JavaArrayJInt, int2: Union[int, jint, Integer], int3: Union[int, jint, Integer]) -> String \ - # N: def __init__(self, string: Union[str, String]) -> String \ - # N: def __init__(self, stringBuffer: StringBuffer) -> String \ - # N: def __init__(self, stringBuilder: StringBuilder) -> String \ No newline at end of file diff --git a/tests/stubtest/test-data/unit/mangled_python_keywords.test b/tests/stubtest/test-data/unit/mangled_python_keywords.test deleted file mode 100644 index 9157dd9..0000000 --- a/tests/stubtest/test-data/unit/mangled_python_keywords.test +++ /dev/null @@ -1,16 +0,0 @@ -[case testBitArrayStubIsValid] -from java.util import BitSet - -[case testMangledMethodsAreGenerated] -from java.util import BitSet -BitSet(1).and_(BitSet(2)) -BitSet(1).or_(BitSet(2)) - -[case testNoUnmangledMethodsAreGenerated_and] -from java.util import BitSet -BitSet(1).and(BitSet(2)) # E: invalid syntax - -[case testNoUnmangledMethodsAreGenerated_or] -from java.util import BitSet -BitSet(1).or(BitSet(2)) # E: invalid syntax - diff --git a/tests/stubtest/test-data/unit/type_conversions.test b/tests/stubtest/test-data/unit/type_conversions.test deleted file mode 100644 index a44b9c4..0000000 --- a/tests/stubtest/test-data/unit/type_conversions.test +++ /dev/null @@ -1,8 +0,0 @@ -[case testTypeConversionsByteFromInt] -from java.lang import Byte -Byte(5) - -[case testTypeConversionsByteFromString] -from java.lang import Byte -Byte("Testing") - diff --git a/tests/stubtest/test-data/unit/varargs.test b/tests/stubtest/test-data/unit/varargs.test deleted file mode 100644 index e8fa63d..0000000 --- a/tests/stubtest/test-data/unit/varargs.test +++ /dev/null @@ -1,18 +0,0 @@ -[case testVarArgsInts] -from java.util import Arrays - -java_list = Arrays.asList(5, 23, 42) # <- varargs invocation! -reveal_type(java_list) # N: Revealed type is "java.util.List[builtins.int]" - -[case testVarArgsStrings] -from java.util import Arrays - -java_list = Arrays.asList('test', 'foo') # <- varargs invocation! -reveal_type(java_list) # N: Revealed type is "java.util.List[builtins.str]" - - -[case testVarArgsMixed] -from java.util import Arrays - -java_list = Arrays.asList('life', 'universe', 'everything', 42) # <- varargs invocation! -reveal_type(java_list) # N: Revealed type is "java.util.List[builtins.object]" diff --git a/tests/test_array_list.py b/tests/test_array_list.py new file mode 100644 index 0000000..7fc9bac --- /dev/null +++ b/tests/test_array_list.py @@ -0,0 +1,92 @@ +from pathlib import Path + +from .mypy_helper import run_and_assert_mypy + + +def test_array_list_valid(mypy_project_dir: Path): + code = """\ +from java.util import ArrayList + +java_array_list: ArrayList[str] = ArrayList() +java_array_list.add('42') + +reveal_type(java_array_list) # *1 +reveal_type(java_array_list.get(0)) # *2 +""" + + expected_mypy_output = { + "*1": '''\ +note: Revealed type is "java.util.ArrayList[builtins.str]"''', + "*2": '''\ +note: Revealed type is "builtins.str"''', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) + + +def test_array_list_invalid(mypy_project_dir: Path): + code = """\ +from java.util import ArrayList + +java_array_list: ArrayList[str] = ArrayList() +java_array_list.add(42) # *1 +""" + + expected_mypy_output = { + "*1": """\ +error: No overload variant of "add" of "ArrayList" matches argument type "int" +hint: Possible overload variants: +hint: def add(self, e: str) -> bool +hint: def add(self, int: int | jint | Integer, e: str) -> None""", + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) + + +def test_array_list_no_implicit_conversion(mypy_project_dir: Path): + code = """\ +from java.util import ArrayList + +pylist = ['test', '1', '2'] +java_array_list = ArrayList(pylist) # *1 +""" + + expected_mypy_output = { + "*1": """\ +error: No overload variant of "ArrayList" matches argument type "list[str]" +hint: Possible overload variants: +hint: def [_ArrayList__E] ArrayList(self) -> ArrayList[_ArrayList__E] +hint: def [_ArrayList__E] ArrayList(self, int: int | jint | Integer) -> ArrayList[_ArrayList__E] +hint: def [_ArrayList__E] ArrayList(self, collection: Collection[_ArrayList__E]) -> ArrayList[_ArrayList__E]""", + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) + + +def test_array_list_missing_type_annotation(mypy_project_dir: Path): + code = """\ +from java.util import ArrayList + +java_array_list = ArrayList(2) # *1 +""" + + expected_mypy_output = { + "*1": 'error: Need type annotation for "java_array_list"', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) + + +def test_array_list_no_getitem(mypy_project_dir: Path): + code = """\ +from java.util import ArrayList + +java_array_list: ArrayList[str] = ArrayList() +java_array_list[0] # *1 +""" + + expected_mypy_output = { + "*1": 'error: Value of type "ArrayList[str]" is not indexable', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) diff --git a/tests/stubtest/test-data/unit/callbacks.test b/tests/test_callbacks.py similarity index 63% rename from tests/stubtest/test-data/unit/callbacks.test rename to tests/test_callbacks.py index b362bb4..4892db8 100644 --- a/tests/stubtest/test-data/unit/callbacks.test +++ b/tests/test_callbacks.py @@ -1,4 +1,10 @@ -[case testBiFunctionCallback] +from pathlib import Path + +from .mypy_helper import run_and_assert_mypy + + +def test_bifunction_callback(mypy_project_dir: Path): + code = """\ import typing from java import dynamic_proxy @@ -42,17 +48,26 @@ def invalid_for_each_cb_2(k: str) -> None: java_map.forEach(PyBiConsumer(valid_for_each_cb)) java_map.forEach( PyBiConsumer( - invalid_for_each_cb_1 # E: Argument 1 to "PyBiConsumer" has incompatible type "Callable[[str, str], None]"; expected "Callable[[str, float], None]" + invalid_for_each_cb_1 # *1 ) ) java_map.forEach( PyBiConsumer( - invalid_for_each_cb_2 # E: Argument 1 to "PyBiConsumer" has incompatible type "Callable[[str], None]"; expected "Callable[[str, float], None]" + invalid_for_each_cb_2 # *2 ) ) +""" + + expected_mypy_output = { + "*1": 'error: Argument 1 to "PyBiConsumer" has incompatible type "Callable[[str, str], None]"; expected "Callable[[str, float], None]"', + "*2": 'error: Argument 1 to "PyBiConsumer" has incompatible type "Callable[[str], None]"; expected "Callable[[str, float], None]"', + } + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) -[case testPredicateCallback] + +def test_predicate_callback(mypy_project_dir: Path): + code = """\ import typing from java import dynamic_proxy @@ -99,22 +114,32 @@ def invalid_predicate_3(k: str) -> int: java_list.removeIf(PyPredicate(valid_predicate)) java_list.removeIf( PyPredicate( - invalid_predicate_1 # E: Argument 1 to "PyPredicate" has incompatible type "Callable[[], bool]"; expected "Callable[[str], bool]" + invalid_predicate_1 # *1 ) ) java_list.removeIf( PyPredicate( - invalid_predicate_2 # E: Argument 1 to "PyPredicate" has incompatible type "Callable[[int], bool]"; expected "Callable[[str], bool]" + invalid_predicate_2 # *2 ) ) java_list.removeIf( PyPredicate( - invalid_predicate_3 # E: Argument 1 to "PyPredicate" has incompatible type "Callable[[str], int]"; expected "Callable[[str], bool]" + invalid_predicate_3 # *3 ) ) +""" + + expected_mypy_output = { + "*1": 'error: Argument 1 to "PyPredicate" has incompatible type "Callable[[], bool]"; expected "Callable[[str], bool]"', + "*2": 'error: Argument 1 to "PyPredicate" has incompatible type "Callable[[int], bool]"; expected "Callable[[str], bool]"', + "*3": 'error: Argument 1 to "PyPredicate" has incompatible type "Callable[[str], int]"; expected "Callable[[str], bool]"', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) -[case testStreamChainingAndTypeInference] +def test_stream_chaining_and_type_inference(mypy_project_dir: Path): + code = """\ import typing from java import dynamic_proxy @@ -177,28 +202,40 @@ def map_atomicint_to_bool(v: AtomicInteger) -> bool: java_list.add("2") str_stream = java_list.stream() -reveal_type(str_stream) # N: Revealed type is "java.util.stream.Stream[builtins.str]" +reveal_type(str_stream) # *1 int_stream = str_stream.map(PyFunction(map_str_to_int)) -reveal_type(int_stream) # N: Revealed type is "java.util.stream.Stream[builtins.int]" +reveal_type(int_stream) # *2 int_stream.filter( PyPredicate( - str_predicate # E: Argument 1 to "PyPredicate" has incompatible type "Callable[[str], bool]"; expected "Callable[[int], bool]" + str_predicate # *3 ) ) int_stream_2 = int_stream.filter(PyPredicate(int_predicate)) -reveal_type(int_stream_2) # N: Revealed type is "java.util.stream.Stream[builtins.int]" +reveal_type(int_stream_2) # *4 int_stream_2.map( PyFunction( - map_atomicint_to_bool # E: Argument 1 to "PyFunction" has incompatible type "Callable[[AtomicInteger], bool]"; expected "Callable[[int], bool]" + map_atomicint_to_bool # *5 ) ) atomicint_stream = int_stream_2.map(PyFunction(map_int_to_atomicint)) java_set = atomicint_stream.collect(Collectors.toSet()) reveal_type( - java_set # N: Revealed type is "java.util.Set[java.util.concurrent.atomic.AtomicInteger]" + java_set # *6 ) +""" + + expected_mypy_output = { + "*1": 'note: Revealed type is "java.util.stream.Stream[builtins.str]"', + "*2": 'note: Revealed type is "java.util.stream.Stream[builtins.int]"', + "*3": 'error: Argument 1 to "PyPredicate" has incompatible type "Callable[[str], bool]"; expected "Callable[[int], bool]"', + "*4": 'note: Revealed type is "java.util.stream.Stream[builtins.int]"', + "*5": 'error: Argument 1 to "PyFunction" has incompatible type "Callable[[AtomicInteger], bool]"; expected "Callable[[int], bool]"', + "*6": 'note: Revealed type is "java.util.Set[java.util.concurrent.atomic.AtomicInteger]"', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) diff --git a/tests/test_enummap.py b/tests/test_enummap.py new file mode 100644 index 0000000..d584950 --- /dev/null +++ b/tests/test_enummap.py @@ -0,0 +1,43 @@ +from pathlib import Path + +from .mypy_helper import run_and_assert_mypy + + +def test_enum_map_valid(mypy_project_dir: Path): + code = """\ +import typing +from java.util import EnumMap +from java.util.concurrent import TimeUnit + +timeunit_enummap: "EnumMap[TimeUnit, typing.Any]" = EnumMap(TimeUnit) +reveal_type(timeunit_enummap) # *1 + +timeunit_enummap.put(TimeUnit.SECONDS, 'test') +""" + + expected_mypy_output = { + "*1": 'note: Revealed type is "java.util.EnumMap[java.util.concurrent.TimeUnit, Any]"', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) + + +def test_enum_map_invalid(mypy_project_dir: Path): + code = """\ +import typing +from java.util import EnumMap +from java.util.concurrent import TimeUnit + +timeunit_enummap: "EnumMap[TimeUnit, typing.Any]" = EnumMap(TimeUnit) + +timeunit_enummap.put(42, 'test') # *1 + +timeunit_enummap.put('fail', 'test') # *2 +""" + + expected_mypy_output = { + "*1": 'error: Argument 1 to "put" of "EnumMap" has incompatible type "int"; expected "TimeUnit"', + "*2": 'error: Argument 1 to "put" of "EnumMap" has incompatible type "str"; expected "TimeUnit"', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) diff --git a/tests/test_exception.py b/tests/test_exception.py new file mode 100644 index 0000000..fc8f1b3 --- /dev/null +++ b/tests/test_exception.py @@ -0,0 +1,20 @@ +from pathlib import Path + +from .mypy_helper import run_and_assert_mypy + + +def test_java_exception(mypy_project_dir: Path): + code = """\ +from java.lang import Exception +java_exception = Exception("Testing") + +reveal_type(java_exception) # *1 + +raise RuntimeError("42") from java_exception +""" + + expected_mypy_output = { + "*1": 'note: Revealed type is "java.lang.Exception"', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) diff --git a/tests/test_forward_declaration.py b/tests/test_forward_declaration.py new file mode 100644 index 0000000..a3cd71e --- /dev/null +++ b/tests/test_forward_declaration.py @@ -0,0 +1,21 @@ +from pathlib import Path + +from .mypy_helper import run_and_assert_mypy + + +def test_argument_type_declaration(mypy_project_dir: Path): + code = """\ +import typing + +if typing.TYPE_CHECKING: + import java.util + +def foo(arg: "java.util.Formatter"): + reveal_type(arg) # *1 +""" + + expected_mypy_output = { + "*1": 'note: Revealed type is "java.util.Formatter"', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) diff --git a/tests/test_hashmap.py b/tests/test_hashmap.py new file mode 100644 index 0000000..5cdd833 --- /dev/null +++ b/tests/test_hashmap.py @@ -0,0 +1,48 @@ +from pathlib import Path + +from .mypy_helper import run_and_assert_mypy + + +def test_hash_map_valid(mypy_project_dir: Path): + code = """\ +from java.util import HashMap + +java_map: "HashMap[str, float]" = HashMap() +java_map.put("hello", 1.0) +java_map.put("world", 42.0) + +reveal_type(java_map) # *1 +reveal_type(java_map.values()) # *2 +reveal_type(java_map.keySet()) # *3 + +reveal_type(java_map.get('hello')) # *4 + +java_map.put('test1', 42) +""" + + expected_mypy_output = { + "*1": 'note: Revealed type is "java.util.HashMap[builtins.str, builtins.float]"', + "*2": 'note: Revealed type is "java.util.Collection[builtins.float]"', + "*3": 'note: Revealed type is "java.util.Set[builtins.str]"', + "*4": 'note: Revealed type is "builtins.float"', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) + + +def test_hash_map_invalid(mypy_project_dir: Path): + code = """\ +from java.util import HashMap + +java_map: "HashMap[str, float]" = HashMap() +java_map.put("hello", 1.0) +java_map.put("world", 42.0) + +java_map.put('test1', 'foo') # *1 +""" + + expected_mypy_output = { + "*1": 'error: Argument 2 to "put" of "HashMap" has incompatible type "str"; expected "float"', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) diff --git a/tests/test_jarray.py b/tests/test_jarray.py new file mode 100644 index 0000000..8471eae --- /dev/null +++ b/tests/test_jarray.py @@ -0,0 +1,65 @@ +from pathlib import Path + +from .mypy_helper import run_and_assert_mypy + + +def test_java_array_as_return_type(mypy_project_dir: Path): + code = """\ +from java.lang import String + +s = String("asdf") +reveal_type(s) # *1 +reveal_type(s.split("s")) # *2 +reveal_type(s.toCharArray()) # *3 +""" + + expected_mypy_output = { + "*1": 'note: Revealed type is "java.lang.String"', + "*2": 'note: Revealed type is "java.chaquopy.JavaArray[java.lang.String]"', + "*3": 'note: Revealed type is "java.chaquopy.JavaArrayJChar"', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) + + +def test_java_array_as_parameter(mypy_project_dir: Path): + code = """\ +from java.lang import String +from java import jarray, jchar, jint, jbyte + +char_array = jarray(jchar)(["a", "b", "c"]) +byte_array = jarray(jbyte)([55, 66, 77]) +int_array = jarray(jint)([55555, 66, 77]) +reveal_type(char_array) # *1 +reveal_type(byte_array) # *2 +reveal_type(int_array) # *3 +String(char_array) +String(byte_array) +String(int_array) # *4 +""" + + expected_mypy_output = { + "*1": 'note: Revealed type is "java.chaquopy.JavaArrayJChar"', + "*2": 'note: Revealed type is "java.chaquopy.JavaArrayJByte"', + "*3": 'note: Revealed type is "java.chaquopy.JavaArrayJInt"', + "*4": """\ +error: No overload variant of "String" matches argument type "JavaArrayJInt" +hint: Possible overload variants: +hint: def String(self) -> String +hint: def String(self, byteArray: JavaArrayJByte) -> String +hint: def String(self, byteArray: JavaArrayJByte, int: int | jint | Integer) -> String +hint: def String(self, byteArray: JavaArrayJByte, int: int | jint | Integer, int2: int | jint | Integer) -> String +hint: def String(self, byteArray: JavaArrayJByte, int: int | jint | Integer, int2: int | jint | Integer, int3: int | jint | Integer) -> String +hint: def String(self, byteArray: JavaArrayJByte, int: int | jint | Integer, int2: int | jint | Integer, string: str | String) -> String +hint: def String(self, byteArray: JavaArrayJByte, int: int | jint | Integer, int2: int | jint | Integer, charset: Charset) -> String +hint: def String(self, byteArray: JavaArrayJByte, string: str | String) -> String +hint: def String(self, byteArray: JavaArrayJByte, charset: Charset) -> String +hint: def String(self, charArray: JavaArrayJChar) -> String +hint: def String(self, charArray: JavaArrayJChar, int: int | jint | Integer, int2: int | jint | Integer) -> String +hint: def String(self, intArray: JavaArrayJInt, int2: int | jint | Integer, int3: int | jint | Integer) -> String +hint: def String(self, string: str | String) -> String +hint: def String(self, stringBuffer: StringBuffer) -> String +hint: def String(self, stringBuilder: StringBuilder) -> String""", + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) diff --git a/tests/test_mangled_python_keywords.py b/tests/test_mangled_python_keywords.py new file mode 100644 index 0000000..7d9f172 --- /dev/null +++ b/tests/test_mangled_python_keywords.py @@ -0,0 +1,53 @@ +from pathlib import Path + +from .mypy_helper import run_and_assert_mypy + + +def test_bitarray_stub_is_valid(mypy_project_dir: Path): + code = """\ +from java.util import BitSet +""" + + expected_mypy_output = {} + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) + + +def test_mangled_methods_are_generated(mypy_project_dir: Path): + code = """\ +from java.util import BitSet +BitSet(1).and_(BitSet(2)) +BitSet(1).or_(BitSet(2)) +""" + + expected_mypy_output = {} + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) + + +def test_no_unmangled_methods_are_generated_and(mypy_project_dir: Path): + code = """\ +from java.util import BitSet +BitSet(1).and(BitSet(2)) +""" + + expected_mypy_output = """\ +testfile.py:2: error: Invalid syntax [syntax] +Found 1 error in 1 file (errors prevented further checking) +""" + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) + + +def test_no_unmangled_methods_are_generated_or(mypy_project_dir: Path): + code = """\ +from java.util import BitSet +BitSet(1).or(BitSet(2)) +""" + + expected_mypy_output = """\ +testfile.py:2: error: Invalid syntax [syntax] +Found 1 error in 1 file (errors prevented further checking) +""" + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) diff --git a/tests/test_stubtest.py b/tests/test_stubtest.py deleted file mode 100644 index 55974c0..0000000 --- a/tests/test_stubtest.py +++ /dev/null @@ -1,60 +0,0 @@ -import logging -import tempfile - -import mypy.build -import mypy.modulefinder -import mypy.test.testcheck -import pytest - -import chaquopy_stubgen -from pathlib import Path -from typing import Generator - - -@pytest.fixture(scope="session", autouse=True) -def stub_tmpdir() -> Generator[str, None, None]: - logging.basicConfig(level="INFO") - with tempfile.TemporaryDirectory() as tmpdir: - yield tmpdir - - -@pytest.fixture(scope="session", autouse=True) -def setup_mypy_for_data_driven_tests(stub_tmpdir: str): - _real_build = mypy.build.build - - def _patched_build(sources, options, *args, **kwargs): - options.use_builtins_fixtures = False - return _real_build(sources, options, *args, **kwargs) - - mypy.build.build = _patched_build - - mypy.modulefinder.get_search_dirs = lambda _: ([stub_tmpdir], [stub_tmpdir]) # type: ignore - - -def test_generate_stubs(stub_tmpdir: Path): - import java # type: ignore - - chaquopy_stubgen.generate_java_stubs( - [java], # type: ignore - output_dir=stub_tmpdir, - ) - - -@pytest.mark.trylast -class StubTestSuite(mypy.test.testcheck.TypeCheckSuite): - def setup(self) -> None: - pass - - - files = [ - "arraylist.test", - "callbacks.test", - "enummap.test", - "forward_declaration.test", - "hashmap.test", - "mangled_python_keywords.test", - "varargs.test", - "exception.test", - "type_conversions.test", - "jarray.test" - ] diff --git a/tests/test_type_conversions.py b/tests/test_type_conversions.py new file mode 100644 index 0000000..ab7a632 --- /dev/null +++ b/tests/test_type_conversions.py @@ -0,0 +1,25 @@ +from pathlib import Path + +from .mypy_helper import run_and_assert_mypy + + +def test_type_conversions_byte_from_int(mypy_project_dir: Path): + code = """\ +from java.lang import Byte +Byte(5) +""" + + expected_mypy_output = {} + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) + + +def test_type_conversions_byte_from_string(mypy_project_dir: Path): + code = """\ +from java.lang import Byte +Byte("Testing") +""" + + expected_mypy_output = {} + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) diff --git a/tests/test_varargs.py b/tests/test_varargs.py new file mode 100644 index 0000000..6f7c3b2 --- /dev/null +++ b/tests/test_varargs.py @@ -0,0 +1,48 @@ +from pathlib import Path + +from .mypy_helper import run_and_assert_mypy + + +def test_varargs_ints(mypy_project_dir: Path): + code = """\ +from java.util import Arrays + +java_list = Arrays.asList(5, 23, 42) +reveal_type(java_list) # *1 +""" + + expected_mypy_output = { + "*1": 'note: Revealed type is "java.util.List[builtins.int]"', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) + + +def test_varargs_strings(mypy_project_dir: Path): + code = """\ +from java.util import Arrays + +java_list = Arrays.asList('test', 'foo') +reveal_type(java_list) # *1 +""" + + expected_mypy_output = { + "*1": 'note: Revealed type is "java.util.List[builtins.str]"', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) + + +def test_varargs_mixed(mypy_project_dir: Path): + code = """\ +from java.util import Arrays + +java_list = Arrays.asList('life', 'universe', 'everything', 42) +reveal_type(java_list) # *1 +""" + + expected_mypy_output = { + "*1": 'note: Revealed type is "java.util.List[builtins.object]"', + } + + run_and_assert_mypy(mypy_project_dir, code, expected_mypy_output) diff --git a/uv.lock b/uv.lock index 61efca4..c60450c 100644 --- a/uv.lock +++ b/uv.lock @@ -1,4 +1,5 @@ version = 1 +revision = 3 requires-python = ">=3.13" [[package]] @@ -19,7 +20,7 @@ requires-dist = [{ name = "jpype1", specifier = ">=1.6.0,<2" }] [package.metadata.requires-dev] dev = [ - { name = "mypy", specifier = "==1.15.0" }, + { name = "mypy", specifier = "==1.19.1" }, { name = "pytest" }, ] @@ -27,18 +28,18 @@ dev = [ name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] name = "iniconfig" version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] [[package]] @@ -48,71 +49,135 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/49/6090a131d84b22c6aae13b1853092028b060fd17da1af87c0e42ad69d50f/jpype1-1.6.0.tar.gz", hash = "sha256:2d46b2a14f8f0e6f17d8aa22b4fc3a64b2790851ebf1409ad79a37c698fd6e9a", size = 1057888 } +sdist = { url = "https://files.pythonhosted.org/packages/34/49/6090a131d84b22c6aae13b1853092028b060fd17da1af87c0e42ad69d50f/jpype1-1.6.0.tar.gz", hash = "sha256:2d46b2a14f8f0e6f17d8aa22b4fc3a64b2790851ebf1409ad79a37c698fd6e9a", size = 1057888, upload-time = "2025-07-07T14:04:11.244Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/1b/7167cfb702cfbcb17b0973150608f9cb92c3f409ebaaff655f7389617766/jpype1-1.6.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ce1da9ff01010a4529de194aecc053bfc8ab25440ea6b63579ef8c8d381da21d", size = 582384 }, - { url = "https://files.pythonhosted.org/packages/c0/48/8ae425d8842bc76eb51052bbea5941dac230c8f6b58614ef2b7cb2da1a74/jpype1-1.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4a387430cd4f3f68761013dca7f71b79d8d64b47dfd56eedcbd44c894d98d7f2", size = 468237 }, - { url = "https://files.pythonhosted.org/packages/ef/2f/dbdc15aa99404dda0315a9883add7422c9ac7acce28ce24b66c89052c2fb/jpype1-1.6.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:6c9d99f41a5d05a8a2b6aeda796f646904c1c2b68aed0cc9a3bbc165483d9e1e", size = 511905 }, - { url = "https://files.pythonhosted.org/packages/00/0d/50fcdd6d3bb42a87cbda8b04fc72283a9c6d955ada0d011190ce31e0e027/jpype1-1.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:55ba7257988a69bee84f7cd1444131725d5447999149fdafdba25bf46e4b1a3f", size = 496001 }, - { url = "https://files.pythonhosted.org/packages/4f/b8/25214a9ebecbd31447b685500dca5fe2ac070df38358d6823f591a8ce52e/jpype1-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:ef8dd72442d91cde7e5eefef24ab71323cdd4137283a59f79d265a6620f7d0ab", size = 355727 }, - { url = "https://files.pythonhosted.org/packages/29/a0/b2347572998020de8ce3ee0a285a1b84b052b522e8d452ee9ad330570918/jpype1-1.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:dc0c1c8b989b9857f87648980a746084b323858b0450225eab3642bb674645d6", size = 471150 }, - { url = "https://files.pythonhosted.org/packages/7c/62/5e0f64a185f37303d0dd07e7b7a927cd5237fdc3c8176add949ed8cafaa2/jpype1-1.6.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:cbbba16381ccd23530ad4ef09f7b2b1b12bd7fd0f6ca9fe4c7b4e3da8fe4cf63", size = 514015 }, - { url = "https://files.pythonhosted.org/packages/72/68/84050ed2679e2d2ee2030a60d3e5a59a29d0533cd03fd8d9891e36592b0f/jpype1-1.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1a3814e4f65d67e36bdb03b8851d5ece8d7a408aa3a24251ea0609bb8fba77dd", size = 497229 }, + { url = "https://files.pythonhosted.org/packages/3f/1b/7167cfb702cfbcb17b0973150608f9cb92c3f409ebaaff655f7389617766/jpype1-1.6.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ce1da9ff01010a4529de194aecc053bfc8ab25440ea6b63579ef8c8d381da21d", size = 582384, upload-time = "2025-07-07T13:50:47.418Z" }, + { url = "https://files.pythonhosted.org/packages/c0/48/8ae425d8842bc76eb51052bbea5941dac230c8f6b58614ef2b7cb2da1a74/jpype1-1.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4a387430cd4f3f68761013dca7f71b79d8d64b47dfd56eedcbd44c894d98d7f2", size = 468237, upload-time = "2025-07-07T13:50:48.925Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2f/dbdc15aa99404dda0315a9883add7422c9ac7acce28ce24b66c89052c2fb/jpype1-1.6.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:6c9d99f41a5d05a8a2b6aeda796f646904c1c2b68aed0cc9a3bbc165483d9e1e", size = 511905, upload-time = "2025-07-07T13:50:50.87Z" }, + { url = "https://files.pythonhosted.org/packages/00/0d/50fcdd6d3bb42a87cbda8b04fc72283a9c6d955ada0d011190ce31e0e027/jpype1-1.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:55ba7257988a69bee84f7cd1444131725d5447999149fdafdba25bf46e4b1a3f", size = 496001, upload-time = "2025-07-07T13:50:52.711Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b8/25214a9ebecbd31447b685500dca5fe2ac070df38358d6823f591a8ce52e/jpype1-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:ef8dd72442d91cde7e5eefef24ab71323cdd4137283a59f79d265a6620f7d0ab", size = 355727, upload-time = "2025-07-07T13:51:00.402Z" }, + { url = "https://files.pythonhosted.org/packages/29/a0/b2347572998020de8ce3ee0a285a1b84b052b522e8d452ee9ad330570918/jpype1-1.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:dc0c1c8b989b9857f87648980a746084b323858b0450225eab3642bb674645d6", size = 471150, upload-time = "2025-07-07T13:50:54.254Z" }, + { url = "https://files.pythonhosted.org/packages/7c/62/5e0f64a185f37303d0dd07e7b7a927cd5237fdc3c8176add949ed8cafaa2/jpype1-1.6.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:cbbba16381ccd23530ad4ef09f7b2b1b12bd7fd0f6ca9fe4c7b4e3da8fe4cf63", size = 514015, upload-time = "2025-07-07T13:50:56.299Z" }, + { url = "https://files.pythonhosted.org/packages/72/68/84050ed2679e2d2ee2030a60d3e5a59a29d0533cd03fd8d9891e36592b0f/jpype1-1.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1a3814e4f65d67e36bdb03b8851d5ece8d7a408aa3a24251ea0609bb8fba77dd", size = 497229, upload-time = "2025-07-07T13:50:57.842Z" }, +] + +[[package]] +name = "librt" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/3f/4ca7dd7819bf8ff303aca39c3c60e5320e46e766ab7f7dd627d3b9c11bdf/librt-0.8.0.tar.gz", hash = "sha256:cb74cdcbc0103fc988e04e5c58b0b31e8e5dd2babb9182b6f9490488eb36324b", size = 177306, upload-time = "2026-02-12T14:53:54.743Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/32/3edb0bcb4113a9c8bdcd1750663a54565d255027657a5df9d90f13ee07fa/librt-0.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7f820210e21e3a8bf8fde2ae3c3d10106d4de9ead28cbfdf6d0f0f41f5b12fa1", size = 66522, upload-time = "2026-02-12T14:52:48.219Z" }, + { url = "https://files.pythonhosted.org/packages/30/ab/e8c3d05e281f5d405ebdcc5bc8ab36df23e1a4b40ac9da8c3eb9928b72b9/librt-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4831c44b8919e75ca0dfb52052897c1ef59fdae19d3589893fbd068f1e41afbf", size = 68658, upload-time = "2026-02-12T14:52:50.351Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d3/74a206c47b7748bbc8c43942de3ed67de4c231156e148b4f9250869593df/librt-0.8.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:88c6e75540f1f10f5e0fc5e87b4b6c290f0e90d1db8c6734f670840494764af8", size = 199287, upload-time = "2026-02-12T14:52:51.938Z" }, + { url = "https://files.pythonhosted.org/packages/fa/29/ef98a9131cf12cb95771d24e4c411fda96c89dc78b09c2de4704877ebee4/librt-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9646178cd794704d722306c2c920c221abbf080fede3ba539d5afdec16c46dad", size = 210293, upload-time = "2026-02-12T14:52:53.128Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3e/89b4968cb08c53d4c2d8b02517081dfe4b9e07a959ec143d333d76899f6c/librt-0.8.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e1af31a710e17891d9adf0dbd9a5fcd94901a3922a96499abdbf7ce658f4e01", size = 224801, upload-time = "2026-02-12T14:52:54.367Z" }, + { url = "https://files.pythonhosted.org/packages/6d/28/f38526d501f9513f8b48d78e6be4a241e15dd4b000056dc8b3f06ee9ce5d/librt-0.8.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:507e94f4bec00b2f590fbe55f48cd518a208e2474a3b90a60aa8f29136ddbada", size = 218090, upload-time = "2026-02-12T14:52:55.758Z" }, + { url = "https://files.pythonhosted.org/packages/02/ec/64e29887c5009c24dc9c397116c680caffc50286f62bd99c39e3875a2854/librt-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f1178e0de0c271231a660fbef9be6acdfa1d596803464706862bef6644cc1cae", size = 225483, upload-time = "2026-02-12T14:52:57.375Z" }, + { url = "https://files.pythonhosted.org/packages/ee/16/7850bdbc9f1a32d3feff2708d90c56fc0490b13f1012e438532781aa598c/librt-0.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:71fc517efc14f75c2f74b1f0a5d5eb4a8e06aa135c34d18eaf3522f4a53cd62d", size = 218226, upload-time = "2026-02-12T14:52:58.534Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4a/166bffc992d65ddefa7c47052010a87c059b44a458ebaf8f5eba384b0533/librt-0.8.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0583aef7e9a720dd40f26a2ad5a1bf2ccbb90059dac2b32ac516df232c701db3", size = 218755, upload-time = "2026-02-12T14:52:59.701Z" }, + { url = "https://files.pythonhosted.org/packages/da/5d/9aeee038bcc72a9cfaaee934463fe9280a73c5440d36bd3175069d2cb97b/librt-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d0f76fc73480d42285c609c0ea74d79856c160fa828ff9aceab574ea4ecfd7b", size = 241617, upload-time = "2026-02-12T14:53:00.966Z" }, + { url = "https://files.pythonhosted.org/packages/64/ff/2bec6b0296b9d0402aa6ec8540aa19ebcb875d669c37800cb43d10d9c3a3/librt-0.8.0-cp313-cp313-win32.whl", hash = "sha256:e79dbc8f57de360f0ed987dc7de7be814b4803ef0e8fc6d3ff86e16798c99935", size = 54966, upload-time = "2026-02-12T14:53:02.042Z" }, + { url = "https://files.pythonhosted.org/packages/08/8d/bf44633b0182996b2c7ea69a03a5c529683fa1f6b8e45c03fe874ff40d56/librt-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:25b3e667cbfc9000c4740b282df599ebd91dbdcc1aa6785050e4c1d6be5329ab", size = 62000, upload-time = "2026-02-12T14:53:03.822Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fd/c6472b8e0eac0925001f75e366cf5500bcb975357a65ef1f6b5749389d3a/librt-0.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:e9a3a38eb4134ad33122a6d575e6324831f930a771d951a15ce232e0237412c2", size = 52496, upload-time = "2026-02-12T14:53:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/13/79ebfe30cd273d7c0ce37a5f14dc489c5fb8b722a008983db2cfd57270bb/librt-0.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:421765e8c6b18e64d21c8ead315708a56fc24f44075059702e421d164575fdda", size = 66078, upload-time = "2026-02-12T14:53:06.085Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8f/d11eca40b62a8d5e759239a80636386ef88adecb10d1a050b38cc0da9f9e/librt-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:48f84830a8f8ad7918afd743fd7c4eb558728bceab7b0e38fd5a5cf78206a556", size = 68309, upload-time = "2026-02-12T14:53:07.121Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b4/f12ee70a3596db40ff3c88ec9eaa4e323f3b92f77505b4d900746706ec6a/librt-0.8.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9f09d4884f882baa39a7e36bbf3eae124c4ca2a223efb91e567381d1c55c6b06", size = 196804, upload-time = "2026-02-12T14:53:08.164Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7e/70dbbdc0271fd626abe1671ad117bcd61a9a88cdc6a10ccfbfc703db1873/librt-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:693697133c3b32aa9b27f040e3691be210e9ac4d905061859a9ed519b1d5a376", size = 206915, upload-time = "2026-02-12T14:53:09.333Z" }, + { url = "https://files.pythonhosted.org/packages/79/13/6b9e05a635d4327608d06b3c1702166e3b3e78315846373446cf90d7b0bf/librt-0.8.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5512aae4648152abaf4d48b59890503fcbe86e85abc12fb9b096fe948bdd816", size = 221200, upload-time = "2026-02-12T14:53:10.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/6c/e19a3ac53e9414de43a73d7507d2d766cd22d8ca763d29a4e072d628db42/librt-0.8.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:995d24caa6bbb34bcdd4a41df98ac6d1af637cfa8975cb0790e47d6623e70e3e", size = 214640, upload-time = "2026-02-12T14:53:12.342Z" }, + { url = "https://files.pythonhosted.org/packages/30/f0/23a78464788619e8c70f090cfd099cce4973eed142c4dccb99fc322283fd/librt-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b9aef96d7593584e31ef6ac1eb9775355b0099fee7651fae3a15bc8657b67b52", size = 221980, upload-time = "2026-02-12T14:53:13.603Z" }, + { url = "https://files.pythonhosted.org/packages/03/32/38e21420c5d7aa8a8bd2c7a7d5252ab174a5a8aaec8b5551968979b747bf/librt-0.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4f6e975377fbc4c9567cb33ea9ab826031b6c7ec0515bfae66a4fb110d40d6da", size = 215146, upload-time = "2026-02-12T14:53:14.8Z" }, + { url = "https://files.pythonhosted.org/packages/bb/00/bd9ecf38b1824c25240b3ad982fb62c80f0a969e6679091ba2b3afb2b510/librt-0.8.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:daae5e955764be8fd70a93e9e5133c75297f8bce1e802e1d3683b98f77e1c5ab", size = 215203, upload-time = "2026-02-12T14:53:16.087Z" }, + { url = "https://files.pythonhosted.org/packages/b9/60/7559bcc5279d37810b98d4a52616febd7b8eef04391714fd6bdf629598b1/librt-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7bd68cebf3131bb920d5984f75fe302d758db33264e44b45ad139385662d7bc3", size = 237937, upload-time = "2026-02-12T14:53:17.236Z" }, + { url = "https://files.pythonhosted.org/packages/41/cc/be3e7da88f1abbe2642672af1dc00a0bccece11ca60241b1883f3018d8d5/librt-0.8.0-cp314-cp314-win32.whl", hash = "sha256:1e6811cac1dcb27ca4c74e0ca4a5917a8e06db0d8408d30daee3a41724bfde7a", size = 50685, upload-time = "2026-02-12T14:53:18.888Z" }, + { url = "https://files.pythonhosted.org/packages/38/27/e381d0df182a8f61ef1f6025d8b138b3318cc9d18ad4d5f47c3bf7492523/librt-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:178707cda89d910c3b28bf5aa5f69d3d4734e0f6ae102f753ad79edef83a83c7", size = 57872, upload-time = "2026-02-12T14:53:19.942Z" }, + { url = "https://files.pythonhosted.org/packages/c5/0c/ca9dfdf00554a44dea7d555001248269a4bab569e1590a91391feb863fa4/librt-0.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3e8b77b5f54d0937b26512774916041756c9eb3e66f1031971e626eea49d0bf4", size = 48056, upload-time = "2026-02-12T14:53:21.473Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ed/6cc9c4ad24f90c8e782193c7b4a857408fd49540800613d1356c63567d7b/librt-0.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:789911e8fa40a2e82f41120c936b1965f3213c67f5a483fc5a41f5839a05dcbb", size = 68307, upload-time = "2026-02-12T14:53:22.498Z" }, + { url = "https://files.pythonhosted.org/packages/84/d8/0e94292c6b3e00b6eeea39dd44d5703d1ec29b6dafce7eea19dc8f1aedbd/librt-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2b37437e7e4ef5e15a297b36ba9e577f73e29564131d86dd75875705e97402b5", size = 70999, upload-time = "2026-02-12T14:53:23.603Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f4/6be1afcbdeedbdbbf54a7c9d73ad43e1bf36897cebf3978308cd64922e02/librt-0.8.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:671a6152edf3b924d98a5ed5e6982ec9cb30894085482acadce0975f031d4c5c", size = 220782, upload-time = "2026-02-12T14:53:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f0/8d/f306e8caa93cfaf5c6c9e0d940908d75dc6af4fd856baa5535c922ee02b1/librt-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8992ca186a1678107b0af3d0c9303d8c7305981b9914989b9788319ed4d89546", size = 235420, upload-time = "2026-02-12T14:53:27.047Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f2/65d86bd462e9c351326564ca805e8457442149f348496e25ccd94583ffa2/librt-0.8.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:001e5330093d887b8b9165823eca6c5c4db183fe4edea4fdc0680bbac5f46944", size = 246452, upload-time = "2026-02-12T14:53:28.341Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/39c88b503b4cb3fcbdeb3caa29672b6b44ebee8dcc8a54d49839ac280f3f/librt-0.8.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d920789eca7ef71df7f31fd547ec0d3002e04d77f30ba6881e08a630e7b2c30e", size = 238891, upload-time = "2026-02-12T14:53:29.625Z" }, + { url = "https://files.pythonhosted.org/packages/e3/c6/6c0d68190893d01b71b9569b07a1c811e280c0065a791249921c83dc0290/librt-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:82fb4602d1b3e303a58bfe6165992b5a78d823ec646445356c332cd5f5bbaa61", size = 250249, upload-time = "2026-02-12T14:53:30.93Z" }, + { url = "https://files.pythonhosted.org/packages/52/7a/f715ed9e039035d0ea637579c3c0155ab3709a7046bc408c0fb05d337121/librt-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4d3e38797eb482485b486898f89415a6ab163bc291476bd95712e42cf4383c05", size = 240642, upload-time = "2026-02-12T14:53:32.174Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3c/609000a333debf5992efe087edc6467c1fdbdddca5b610355569bbea9589/librt-0.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a905091a13e0884701226860836d0386b88c72ce5c2fdfba6618e14c72be9f25", size = 239621, upload-time = "2026-02-12T14:53:33.39Z" }, + { url = "https://files.pythonhosted.org/packages/b9/df/87b0673d5c395a8f34f38569c116c93142d4dc7e04af2510620772d6bd4f/librt-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:375eda7acfce1f15f5ed56cfc960669eefa1ec8732e3e9087c3c4c3f2066759c", size = 262986, upload-time = "2026-02-12T14:53:34.617Z" }, + { url = "https://files.pythonhosted.org/packages/09/7f/6bbbe9dcda649684773aaea78b87fff4d7e59550fbc2877faa83612087a3/librt-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:2ccdd20d9a72c562ffb73098ac411de351b53a6fbb3390903b2d33078ef90447", size = 51328, upload-time = "2026-02-12T14:53:36.15Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f3/e1981ab6fa9b41be0396648b5850267888a752d025313a9e929c4856208e/librt-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:25e82d920d4d62ad741592fcf8d0f3bda0e3fc388a184cb7d2f566c681c5f7b9", size = 58719, upload-time = "2026-02-12T14:53:37.183Z" }, + { url = "https://files.pythonhosted.org/packages/94/d1/433b3c06e78f23486fe4fdd19bc134657eb30997d2054b0dbf52bbf3382e/librt-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:92249938ab744a5890580d3cb2b22042f0dce71cdaa7c1369823df62bedf7cbc", size = 48753, upload-time = "2026-02-12T14:53:38.539Z" }, ] [[package]] name = "mypy" -version = "1.15.0" +version = "1.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, + { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717 } +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592 }, - { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611 }, - { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443 }, - { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541 }, - { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348 }, - { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648 }, - { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777 }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, ] [[package]] name = "mypy-extensions" version = "1.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, ] [[package]] name = "packaging" version = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pathspec" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] [[package]] name = "pygments" version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] [[package]] @@ -126,16 +191,16 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714 } +sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474 }, + { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] [[package]] name = "typing-extensions" version = "4.14.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673 } +sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906 }, + { url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" }, ]