python-introspect 0.1.9__tar.gz → 0.1.11__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.
Files changed (31) hide show
  1. {python_introspect-0.1.9/src/python_introspect.egg-info → python_introspect-0.1.11}/PKG-INFO +7 -2
  2. {python_introspect-0.1.9 → python_introspect-0.1.11}/README.md +6 -1
  3. {python_introspect-0.1.9 → python_introspect-0.1.11}/pyproject.toml +1 -1
  4. {python_introspect-0.1.9 → python_introspect-0.1.11}/src/python_introspect/__init__.py +7 -1
  5. python_introspect-0.1.11/src/python_introspect/callable_declaration.py +37 -0
  6. python_introspect-0.1.11/src/python_introspect/runtime_parameter.py +85 -0
  7. {python_introspect-0.1.9 → python_introspect-0.1.11/src/python_introspect.egg-info}/PKG-INFO +7 -2
  8. {python_introspect-0.1.9 → python_introspect-0.1.11}/src/python_introspect.egg-info/SOURCES.txt +4 -0
  9. python_introspect-0.1.11/tests/test_callable_declaration.py +40 -0
  10. {python_introspect-0.1.9 → python_introspect-0.1.11}/tests/test_init.py +9 -1
  11. python_introspect-0.1.11/tests/test_runtime_parameter.py +58 -0
  12. {python_introspect-0.1.9 → python_introspect-0.1.11}/LICENSE +0 -0
  13. {python_introspect-0.1.9 → python_introspect-0.1.11}/setup.cfg +0 -0
  14. {python_introspect-0.1.9 → python_introspect-0.1.11}/src/python_introspect/annotation_types.py +0 -0
  15. {python_introspect-0.1.9 → python_introspect-0.1.11}/src/python_introspect/dataclass_projection.py +0 -0
  16. {python_introspect-0.1.9 → python_introspect-0.1.11}/src/python_introspect/enableable.py +0 -0
  17. {python_introspect-0.1.9 → python_introspect-0.1.11}/src/python_introspect/environment_projection.py +0 -0
  18. {python_introspect-0.1.9 → python_introspect-0.1.11}/src/python_introspect/exceptions.py +0 -0
  19. {python_introspect-0.1.9 → python_introspect-0.1.11}/src/python_introspect/signature_analyzer.py +0 -0
  20. {python_introspect-0.1.9 → python_introspect-0.1.11}/src/python_introspect/unified_parameter_analyzer.py +0 -0
  21. {python_introspect-0.1.9 → python_introspect-0.1.11}/src/python_introspect/validation.py +0 -0
  22. {python_introspect-0.1.9 → python_introspect-0.1.11}/src/python_introspect.egg-info/dependency_links.txt +0 -0
  23. {python_introspect-0.1.9 → python_introspect-0.1.11}/src/python_introspect.egg-info/requires.txt +0 -0
  24. {python_introspect-0.1.9 → python_introspect-0.1.11}/src/python_introspect.egg-info/top_level.txt +0 -0
  25. {python_introspect-0.1.9 → python_introspect-0.1.11}/tests/test_annotation_types.py +0 -0
  26. {python_introspect-0.1.9 → python_introspect-0.1.11}/tests/test_dataclass_projection.py +0 -0
  27. {python_introspect-0.1.9 → python_introspect-0.1.11}/tests/test_environment_projection.py +0 -0
  28. {python_introspect-0.1.9 → python_introspect-0.1.11}/tests/test_exceptions.py +0 -0
  29. {python_introspect-0.1.9 → python_introspect-0.1.11}/tests/test_signature_analyzer.py +0 -0
  30. {python_introspect-0.1.9 → python_introspect-0.1.11}/tests/test_unified_parameter_analyzer.py +0 -0
  31. {python_introspect-0.1.9 → python_introspect-0.1.11}/tests/test_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-introspect
3
- Version: 0.1.9
3
+ Version: 0.1.11
4
4
  Summary: Pure Python introspection toolkit for function signatures, dataclasses, and type hints
5
5
  Author-email: Tristan Simas <tristan.simas@mail.mcgill.ca>
6
6
  License: MIT
@@ -37,7 +37,7 @@ Dynamic: license-file
37
37
  # python-introspect
38
38
 
39
39
  Extensible analysis of callable signatures, dataclass fields, type hints, and
40
- docstrings.
40
+ docstrings, plus signature-derived callable declaration projection.
41
41
 
42
42
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
43
43
  [![PyPI version](https://badge.fury.io/py/python-introspect.svg)](https://badge.fury.io/py/python-introspect)
@@ -66,6 +66,11 @@ for name, info in parameters.items():
66
66
  dataclass types, and instances. It returns a mapping of names to
67
67
  ``ParameterInfo`` records.
68
68
 
69
+ Use ``callable_declaration_kwargs`` when declaration identity should omit
70
+ keyword arguments equal to their signature defaults. The caller supplies value
71
+ equality so array, lazy, or other framework-specific values keep their owning
72
+ semantics.
73
+
69
74
  ## Extension points
70
75
 
71
76
  Use ``register_namespace_provider`` to contribute names used while resolving
@@ -1,7 +1,7 @@
1
1
  # python-introspect
2
2
 
3
3
  Extensible analysis of callable signatures, dataclass fields, type hints, and
4
- docstrings.
4
+ docstrings, plus signature-derived callable declaration projection.
5
5
 
6
6
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
7
7
  [![PyPI version](https://badge.fury.io/py/python-introspect.svg)](https://badge.fury.io/py/python-introspect)
@@ -30,6 +30,11 @@ for name, info in parameters.items():
30
30
  dataclass types, and instances. It returns a mapping of names to
31
31
  ``ParameterInfo`` records.
32
32
 
33
+ Use ``callable_declaration_kwargs`` when declaration identity should omit
34
+ keyword arguments equal to their signature defaults. The caller supplies value
35
+ equality so array, lazy, or other framework-specific values keep their owning
36
+ semantics.
37
+
33
38
  ## Extension points
34
39
 
35
40
  Use ``register_namespace_provider`` to contribute names used while resolving
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-introspect"
7
- version = "0.1.9"
7
+ version = "0.1.11"
8
8
  description = "Pure Python introspection toolkit for function signatures, dataclasses, and type hints"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -9,7 +9,7 @@ Extensibility:
9
9
  type resolution for framework-specific types (lazy configs, proxies, etc.)
10
10
  """
11
11
 
12
- __version__ = "0.1.9"
12
+ __version__ = "0.1.11"
13
13
 
14
14
  from .signature_analyzer import (
15
15
  SignatureAnalyzer,
@@ -71,6 +71,8 @@ from .annotation_types import (
71
71
  resolve_annotated,
72
72
  resolve_optional,
73
73
  )
74
+ from .callable_declaration import callable_declaration_kwargs
75
+ from .runtime_parameter import RuntimeParameterDeclarationABC
74
76
 
75
77
  __all__ = [
76
78
  # Version
@@ -127,4 +129,8 @@ __all__ = [
127
129
  "optional_member_type",
128
130
  "resolve_annotated",
129
131
  "resolve_optional",
132
+ # Callable declarations
133
+ "callable_declaration_kwargs",
134
+ # Runtime-supplied callable parameters
135
+ "RuntimeParameterDeclarationABC",
130
136
  ]
@@ -0,0 +1,37 @@
1
+ """Declaration projections derived from callable signatures."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from collections.abc import Callable, Mapping
7
+ from typing import TypeVar
8
+
9
+
10
+ ParameterValue = TypeVar("ParameterValue")
11
+
12
+
13
+ def callable_declaration_kwargs(
14
+ func: Callable,
15
+ kwargs: Mapping[str, ParameterValue],
16
+ *,
17
+ values_equal: Callable[[object, object], bool],
18
+ ) -> dict[str, ParameterValue]:
19
+ """Return declared kwargs whose values differ from signature defaults.
20
+
21
+ Equality remains caller-owned so frameworks can supply semantics for
22
+ arrays, lazy values, and other values whose ``==`` result is not boolean.
23
+ """
24
+
25
+ try:
26
+ defaults = {
27
+ name: parameter.default
28
+ for name, parameter in inspect.signature(func).parameters.items()
29
+ if parameter.default is not inspect.Parameter.empty
30
+ }
31
+ except (TypeError, ValueError):
32
+ defaults = {}
33
+ return {
34
+ name: value
35
+ for name, value in kwargs.items()
36
+ if name not in defaults or not values_equal(value, defaults[name])
37
+ }
@@ -0,0 +1,85 @@
1
+ """Nominal declarations for runtime-supplied callable parameters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from abc import ABC, abstractmethod
7
+ from collections.abc import Iterable
8
+ from typing import ClassVar
9
+
10
+
11
+ class RuntimeParameterDeclarationABC(ABC):
12
+ """Semantic owner for one parameter supplied by runtime infrastructure."""
13
+
14
+ preserve_for_execution: ClassVar[bool] = False
15
+ is_semantic_control: ClassVar[bool] = False
16
+
17
+ @classmethod
18
+ @abstractmethod
19
+ def require_parameter_name(cls) -> str:
20
+ """Return the public callable parameter name."""
21
+
22
+ @classmethod
23
+ @abstractmethod
24
+ def parameter(cls) -> inspect.Parameter:
25
+ """Return the complete callable signature parameter declaration."""
26
+
27
+ @classmethod
28
+ def validated_parameter(cls) -> inspect.Parameter:
29
+ """Return the parameter after proving its nominal declaration is coherent."""
30
+
31
+ parameter = cls.parameter()
32
+ if not isinstance(parameter, inspect.Parameter):
33
+ raise TypeError(f"{cls.__name__}.parameter() must return inspect.Parameter.")
34
+ parameter_name = cls.require_parameter_name()
35
+ if not isinstance(parameter_name, str) or not parameter_name.strip():
36
+ raise TypeError(
37
+ f"{cls.__name__}.require_parameter_name() must return a non-empty string."
38
+ )
39
+ if parameter.name != parameter_name:
40
+ raise TypeError(
41
+ f"{cls.__name__}.parameter() name {parameter.name!r} does not "
42
+ f"match require_parameter_name() {parameter_name!r}."
43
+ )
44
+ return parameter
45
+
46
+ @classmethod
47
+ def require_declaration_type(
48
+ cls,
49
+ candidate: object,
50
+ *,
51
+ boundary: str,
52
+ ) -> type[RuntimeParameterDeclarationABC]:
53
+ """Require one nominal runtime-parameter declaration type."""
54
+
55
+ if not isinstance(candidate, type) or not issubclass(candidate, cls):
56
+ raise TypeError(
57
+ f"{boundary} must contain {cls.__name__} subclasses, got {candidate!r}."
58
+ )
59
+ candidate.validated_parameter()
60
+ return candidate
61
+
62
+ @classmethod
63
+ def require_declaration_types(
64
+ cls,
65
+ candidates: Iterable[object],
66
+ *,
67
+ boundary: str,
68
+ ) -> tuple[type[RuntimeParameterDeclarationABC], ...]:
69
+ """Require coherent declarations with unique public parameter names."""
70
+
71
+ declarations: list[type[RuntimeParameterDeclarationABC]] = []
72
+ seen_names: set[str] = set()
73
+ for candidate in candidates:
74
+ declaration = cls.require_declaration_type(candidate, boundary=boundary)
75
+ parameter_name = declaration.require_parameter_name()
76
+ if parameter_name in seen_names:
77
+ raise ValueError(
78
+ f"{boundary} declares duplicate runtime parameter {parameter_name!r}."
79
+ )
80
+ declarations.append(declaration)
81
+ seen_names.add(parameter_name)
82
+ return tuple(declarations)
83
+
84
+
85
+ __all__ = ("RuntimeParameterDeclarationABC",)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-introspect
3
- Version: 0.1.9
3
+ Version: 0.1.11
4
4
  Summary: Pure Python introspection toolkit for function signatures, dataclasses, and type hints
5
5
  Author-email: Tristan Simas <tristan.simas@mail.mcgill.ca>
6
6
  License: MIT
@@ -37,7 +37,7 @@ Dynamic: license-file
37
37
  # python-introspect
38
38
 
39
39
  Extensible analysis of callable signatures, dataclass fields, type hints, and
40
- docstrings.
40
+ docstrings, plus signature-derived callable declaration projection.
41
41
 
42
42
  [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
43
43
  [![PyPI version](https://badge.fury.io/py/python-introspect.svg)](https://badge.fury.io/py/python-introspect)
@@ -66,6 +66,11 @@ for name, info in parameters.items():
66
66
  dataclass types, and instances. It returns a mapping of names to
67
67
  ``ParameterInfo`` records.
68
68
 
69
+ Use ``callable_declaration_kwargs`` when declaration identity should omit
70
+ keyword arguments equal to their signature defaults. The caller supplies value
71
+ equality so array, lazy, or other framework-specific values keep their owning
72
+ semantics.
73
+
69
74
  ## Extension points
70
75
 
71
76
  Use ``register_namespace_provider`` to contribute names used while resolving
@@ -3,10 +3,12 @@ README.md
3
3
  pyproject.toml
4
4
  src/python_introspect/__init__.py
5
5
  src/python_introspect/annotation_types.py
6
+ src/python_introspect/callable_declaration.py
6
7
  src/python_introspect/dataclass_projection.py
7
8
  src/python_introspect/enableable.py
8
9
  src/python_introspect/environment_projection.py
9
10
  src/python_introspect/exceptions.py
11
+ src/python_introspect/runtime_parameter.py
10
12
  src/python_introspect/signature_analyzer.py
11
13
  src/python_introspect/unified_parameter_analyzer.py
12
14
  src/python_introspect/validation.py
@@ -16,10 +18,12 @@ src/python_introspect.egg-info/dependency_links.txt
16
18
  src/python_introspect.egg-info/requires.txt
17
19
  src/python_introspect.egg-info/top_level.txt
18
20
  tests/test_annotation_types.py
21
+ tests/test_callable_declaration.py
19
22
  tests/test_dataclass_projection.py
20
23
  tests/test_environment_projection.py
21
24
  tests/test_exceptions.py
22
25
  tests/test_init.py
26
+ tests/test_runtime_parameter.py
23
27
  tests/test_signature_analyzer.py
24
28
  tests/test_unified_parameter_analyzer.py
25
29
  tests/test_validation.py
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ from operator import attrgetter
4
+
5
+ from python_introspect import callable_declaration_kwargs
6
+
7
+
8
+ def sample_function(value, threshold: float = 1.0, enabled: bool = True):
9
+ return value
10
+
11
+
12
+ def test_callable_declaration_kwargs_projects_signature_defaults() -> None:
13
+ kwargs = {
14
+ "threshold": 1,
15
+ "enabled": False,
16
+ "extension_parameter": "preserved",
17
+ }
18
+
19
+ declaration = callable_declaration_kwargs(
20
+ sample_function,
21
+ kwargs,
22
+ values_equal=lambda left, right: left == right,
23
+ )
24
+
25
+ assert declaration == {
26
+ "enabled": False,
27
+ "extension_parameter": "preserved",
28
+ }
29
+
30
+
31
+ def test_callable_declaration_kwargs_preserves_values_for_opaque_callable() -> None:
32
+ kwargs = {"extension_parameter": "preserved"}
33
+
34
+ declaration = callable_declaration_kwargs(
35
+ attrgetter("value"),
36
+ kwargs,
37
+ values_equal=lambda left, right: left == right,
38
+ )
39
+
40
+ assert declaration == kwargs
@@ -11,42 +11,48 @@ class TestPackageImports:
11
11
  """Test that __version__ is available."""
12
12
  assert hasattr(python_introspect, "__version__")
13
13
  assert isinstance(python_introspect.__version__, str)
14
- assert python_introspect.__version__ == "0.1.9"
14
+ assert python_introspect.__version__ == "0.1.11"
15
15
 
16
16
  def test_signature_analyzer_import(self):
17
17
  """Test SignatureAnalyzer is importable."""
18
18
  assert hasattr(python_introspect, "SignatureAnalyzer")
19
19
  from python_introspect import SignatureAnalyzer
20
+
20
21
  assert SignatureAnalyzer is not None
21
22
 
22
23
  def test_parameter_info_import(self):
23
24
  """Test ParameterInfo is importable."""
24
25
  assert hasattr(python_introspect, "ParameterInfo")
25
26
  from python_introspect import ParameterInfo
27
+
26
28
  assert ParameterInfo is not None
27
29
 
28
30
  def test_docstring_info_import(self):
29
31
  """Test DocstringInfo is importable."""
30
32
  assert hasattr(python_introspect, "DocstringInfo")
31
33
  from python_introspect import DocstringInfo
34
+
32
35
  assert DocstringInfo is not None
33
36
 
34
37
  def test_docstring_extractor_import(self):
35
38
  """Test DocstringExtractor is importable."""
36
39
  assert hasattr(python_introspect, "DocstringExtractor")
37
40
  from python_introspect import DocstringExtractor
41
+
38
42
  assert DocstringExtractor is not None
39
43
 
40
44
  def test_unified_parameter_analyzer_import(self):
41
45
  """Test UnifiedParameterAnalyzer is importable."""
42
46
  assert hasattr(python_introspect, "UnifiedParameterAnalyzer")
43
47
  from python_introspect import UnifiedParameterAnalyzer
48
+
44
49
  assert UnifiedParameterAnalyzer is not None
45
50
 
46
51
  def test_unified_parameter_info_import(self):
47
52
  """Test UnifiedParameterInfo is importable."""
48
53
  assert hasattr(python_introspect, "UnifiedParameterInfo")
49
54
  from python_introspect import UnifiedParameterInfo
55
+
50
56
  assert UnifiedParameterInfo is not None
51
57
 
52
58
  def test_exceptions_import(self):
@@ -57,6 +63,7 @@ class TestPackageImports:
57
63
  DocstringParsingError,
58
64
  TypeResolutionError,
59
65
  )
66
+
60
67
  assert IntrospectionError is not None
61
68
  assert SignatureAnalysisError is not None
62
69
  assert DocstringParsingError is not None
@@ -98,6 +105,7 @@ class TestPublicAPI:
98
105
  """Test that 'from python_introspect import *' works."""
99
106
  # This is a sanity check that __all__ is properly defined
100
107
  import python_introspect
108
+
101
109
  all_names = python_introspect.__all__
102
110
 
103
111
  for name in all_names:
@@ -0,0 +1,58 @@
1
+ """Tests for nominal runtime-parameter declarations."""
2
+
3
+ import inspect
4
+
5
+ import pytest
6
+
7
+ from python_introspect import RuntimeParameterDeclarationABC
8
+
9
+
10
+ class ExampleRuntimeParameter(RuntimeParameterDeclarationABC):
11
+ @classmethod
12
+ def require_parameter_name(cls) -> str:
13
+ return "runtime_value"
14
+
15
+ @classmethod
16
+ def parameter(cls) -> inspect.Parameter:
17
+ return inspect.Parameter(
18
+ cls.require_parameter_name(),
19
+ inspect.Parameter.KEYWORD_ONLY,
20
+ annotation=int,
21
+ )
22
+
23
+
24
+ class StructuralImpostor:
25
+ @classmethod
26
+ def require_parameter_name(cls) -> str:
27
+ return "runtime_value"
28
+
29
+ @classmethod
30
+ def parameter(cls) -> inspect.Parameter:
31
+ return inspect.Parameter("runtime_value", inspect.Parameter.KEYWORD_ONLY)
32
+
33
+
34
+ class MismatchedRuntimeParameter(ExampleRuntimeParameter):
35
+ @classmethod
36
+ def parameter(cls) -> inspect.Parameter:
37
+ return inspect.Parameter("different_name", inspect.Parameter.KEYWORD_ONLY)
38
+
39
+
40
+ def test_runtime_parameter_requires_nominal_subclass() -> None:
41
+ with pytest.raises(TypeError, match="RuntimeParameterDeclarationABC subclasses"):
42
+ RuntimeParameterDeclarationABC.require_declaration_type(
43
+ StructuralImpostor,
44
+ boundary="test declarations",
45
+ )
46
+
47
+
48
+ def test_runtime_parameter_requires_matching_declared_name() -> None:
49
+ with pytest.raises(TypeError, match="does not match require_parameter_name"):
50
+ MismatchedRuntimeParameter.validated_parameter()
51
+
52
+
53
+ def test_runtime_parameter_collection_requires_unique_names() -> None:
54
+ with pytest.raises(ValueError, match="duplicate runtime parameter 'runtime_value'"):
55
+ RuntimeParameterDeclarationABC.require_declaration_types(
56
+ (ExampleRuntimeParameter, ExampleRuntimeParameter),
57
+ boundary="test declarations",
58
+ )