python-introspect 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,275 @@
1
+ """Unified parameter analysis interface for all parameter sources in OpenHCS TUI.
2
+
3
+ This module provides a single, consistent interface for analyzing parameters from:
4
+ - Functions and methods
5
+ - Dataclasses and their fields
6
+ - Nested dataclass structures
7
+ - Any callable or type with parameters
8
+
9
+ Replaces the fragmented approach of SignatureAnalyzer vs FieldIntrospector.
10
+ """
11
+
12
+ import inspect
13
+ import dataclasses
14
+ from typing import Dict, Union, Callable, Type, Any, Optional
15
+ from dataclasses import dataclass
16
+
17
+ from .signature_analyzer import SignatureAnalyzer, ParameterInfo
18
+
19
+
20
+ @dataclass
21
+ class UnifiedParameterInfo:
22
+ """Unified parameter information that works for all parameter sources."""
23
+ name: str
24
+ param_type: Type
25
+ default_value: Any
26
+ is_required: bool
27
+ description: Optional[str] = None
28
+ source_type: str = "unknown" # "function", "dataclass", "nested"
29
+
30
+ @classmethod
31
+ def from_parameter_info(cls, param_info: ParameterInfo, source_type: str = "function") -> "UnifiedParameterInfo":
32
+ """Convert from existing ParameterInfo to unified format."""
33
+ return cls(
34
+ name=param_info.name,
35
+ param_type=param_info.param_type,
36
+ default_value=param_info.default_value,
37
+ is_required=param_info.is_required,
38
+ description=param_info.description,
39
+ source_type=source_type
40
+ )
41
+
42
+
43
+ class UnifiedParameterAnalyzer:
44
+ """Single interface for analyzing parameters from any source.
45
+
46
+ This class provides a unified way to extract parameter information
47
+ from functions, dataclasses, and other parameter sources, ensuring
48
+ consistent behavior across the entire application.
49
+ """
50
+
51
+ @staticmethod
52
+ def analyze(target: Union[Callable, Type, object], exclude_params: Optional[list] = None) -> Dict[str, UnifiedParameterInfo]:
53
+ """Analyze parameters from any source.
54
+
55
+ Args:
56
+ target: Function, method, dataclass type, or instance to analyze
57
+ exclude_params: Optional list of parameter names to exclude from analysis
58
+
59
+ Returns:
60
+ Dictionary mapping parameter names to UnifiedParameterInfo objects
61
+
62
+ Examples:
63
+ # Function analysis
64
+ param_info = UnifiedParameterAnalyzer.analyze(my_function)
65
+
66
+ # Dataclass analysis
67
+ param_info = UnifiedParameterAnalyzer.analyze(MyDataclass)
68
+
69
+ # Instance analysis
70
+ param_info = UnifiedParameterAnalyzer.analyze(my_instance)
71
+
72
+ # Instance analysis with exclusions (e.g., exclude 'func' from FunctionStep)
73
+ param_info = UnifiedParameterAnalyzer.analyze(step_instance, exclude_params=['func'])
74
+ """
75
+ if target is None:
76
+ return {}
77
+
78
+ # Determine the type of target and route to appropriate analyzer
79
+ if inspect.isfunction(target) or inspect.ismethod(target):
80
+ result = UnifiedParameterAnalyzer._analyze_callable(target)
81
+ elif inspect.isclass(target):
82
+ if dataclasses.is_dataclass(target):
83
+ result = UnifiedParameterAnalyzer._analyze_dataclass_type(target)
84
+ else:
85
+ # CRITICAL FIX: For classes, use _analyze_object_instance with use_signature_defaults=True
86
+ # This traverses MRO to get all inherited parameters with signature defaults
87
+ # Create a dummy instance just to get the class hierarchy analyzed
88
+ try:
89
+ dummy_instance = target.__new__(target)
90
+ result = UnifiedParameterAnalyzer._analyze_object_instance(dummy_instance, use_signature_defaults=True)
91
+ except:
92
+ # If we can't create a dummy instance, fall back to just analyzing __init__
93
+ result = UnifiedParameterAnalyzer._analyze_callable(target.__init__)
94
+ elif dataclasses.is_dataclass(target):
95
+ # Instance of dataclass
96
+ result = UnifiedParameterAnalyzer._analyze_dataclass_instance(target)
97
+ else:
98
+ # Try to analyze as callable
99
+ if callable(target):
100
+ # Check if it has a __call__ method (callable object)
101
+ if hasattr(target, '__call__') and not inspect.isfunction(target):
102
+ # It's a callable object, analyze its __call__ method
103
+ result = UnifiedParameterAnalyzer._analyze_callable(target.__call__)
104
+ else:
105
+ result = UnifiedParameterAnalyzer._analyze_callable(target)
106
+ else:
107
+ # For regular object instances (like step instances), analyze their class constructor
108
+ result = UnifiedParameterAnalyzer._analyze_object_instance(target)
109
+
110
+ # Apply exclusions if specified
111
+ if exclude_params:
112
+ result = {name: info for name, info in result.items() if name not in exclude_params}
113
+
114
+ return result
115
+
116
+ @staticmethod
117
+ def _analyze_callable(callable_obj: Callable) -> Dict[str, UnifiedParameterInfo]:
118
+ """Analyze a callable (function, method, etc.)."""
119
+ # Use existing SignatureAnalyzer for callables
120
+ param_info_dict = SignatureAnalyzer.analyze(callable_obj)
121
+
122
+ # Convert to unified format
123
+ unified_params = {}
124
+ for name, param_info in param_info_dict.items():
125
+ unified_params[name] = UnifiedParameterInfo.from_parameter_info(
126
+ param_info,
127
+ source_type="function"
128
+ )
129
+
130
+ return unified_params
131
+
132
+ @staticmethod
133
+ def _analyze_dataclass_type(dataclass_type: Type) -> Dict[str, UnifiedParameterInfo]:
134
+ """Analyze a dataclass type using existing SignatureAnalyzer infrastructure."""
135
+ # CRITICAL FIX: Use existing SignatureAnalyzer._analyze_dataclass method
136
+ # which already handles all the docstring extraction properly
137
+ param_info_dict = SignatureAnalyzer._analyze_dataclass(dataclass_type)
138
+
139
+ # Convert to unified format
140
+ unified_params = {}
141
+ for name, param_info in param_info_dict.items():
142
+ unified_params[name] = UnifiedParameterInfo.from_parameter_info(
143
+ param_info,
144
+ source_type="dataclass"
145
+ )
146
+
147
+ return unified_params
148
+
149
+ @staticmethod
150
+ def _analyze_object_instance(instance: object, use_signature_defaults: bool = False) -> Dict[str, UnifiedParameterInfo]:
151
+ """Analyze a regular object instance by examining its full inheritance hierarchy.
152
+
153
+ Args:
154
+ instance: Object instance to analyze
155
+ use_signature_defaults: If True, use signature defaults instead of instance values
156
+ """
157
+ # Use MRO to get all constructor parameters from the inheritance chain
158
+ instance_class = type(instance)
159
+ all_params = {}
160
+
161
+ # Traverse MRO from most specific to most general (like dual-axis resolver)
162
+ for cls in instance_class.__mro__:
163
+ if cls == object:
164
+ continue
165
+
166
+ # Skip classes without custom __init__
167
+ if not hasattr(cls, '__init__') or cls.__init__ == object.__init__:
168
+ continue
169
+
170
+ try:
171
+ # Analyze this class's constructor
172
+ class_params = UnifiedParameterAnalyzer._analyze_callable(cls.__init__)
173
+
174
+ # Remove 'self' parameter
175
+ if 'self' in class_params:
176
+ del class_params['self']
177
+
178
+ # Special handling for **kwargs - if we see 'kwargs', skip this class
179
+ # and let parent classes provide the actual parameters
180
+ if 'kwargs' in class_params and len(class_params) <= 2:
181
+ # This class uses **kwargs, skip it and let parent classes define parameters
182
+ continue
183
+
184
+ # Add parameters that haven't been seen yet (most specific wins)
185
+ for param_name, param_info in class_params.items():
186
+ if param_name not in all_params and param_name != 'kwargs':
187
+ # CRITICAL FIX: For reset functionality, use signature defaults instead of instance values
188
+ if use_signature_defaults:
189
+ default_value = param_info.default_value
190
+ else:
191
+ # Get current value from instance if it exists
192
+ default_value = getattr(instance, param_name, param_info.default_value)
193
+
194
+ # Create parameter info with appropriate default value
195
+ all_params[param_name] = UnifiedParameterInfo(
196
+ name=param_name,
197
+ param_type=param_info.param_type,
198
+ default_value=default_value,
199
+ is_required=param_info.is_required,
200
+ description=param_info.description, # CRITICAL FIX: Include description
201
+ source_type="object_instance"
202
+ )
203
+
204
+ except Exception:
205
+ # Skip classes that can't be analyzed - this is legitimate since some classes
206
+ # in MRO might not have analyzable constructors (e.g., ABC, object)
207
+ continue
208
+
209
+ return all_params
210
+
211
+ @staticmethod
212
+ def _analyze_dataclass_instance(instance: object) -> Dict[str, UnifiedParameterInfo]:
213
+ """Analyze a dataclass instance."""
214
+ # Get the type and analyze it
215
+ dataclass_type = type(instance)
216
+ unified_params = UnifiedParameterAnalyzer._analyze_dataclass_type(dataclass_type)
217
+
218
+ # Update default values with current instance values
219
+ for name, param_info in unified_params.items():
220
+ if hasattr(instance, name):
221
+ # For regular dataclasses, use normal getattr
222
+ current_value = getattr(instance, name)
223
+
224
+ # Create new UnifiedParameterInfo with current value as default
225
+ unified_params[name] = UnifiedParameterInfo(
226
+ name=param_info.name,
227
+ param_type=param_info.param_type,
228
+ default_value=current_value,
229
+ is_required=param_info.is_required,
230
+ description=param_info.description,
231
+ source_type="dataclass_instance"
232
+ )
233
+
234
+ return unified_params
235
+
236
+ @staticmethod
237
+ def analyze_nested(target: Union[Callable, Type, object], parent_info: Dict[str, UnifiedParameterInfo] = None) -> Dict[str, UnifiedParameterInfo]:
238
+ """Analyze parameters with nested dataclass support.
239
+
240
+ This method provides enhanced analysis that can handle nested dataclasses
241
+ and maintain parent context information.
242
+
243
+ Args:
244
+ target: The target to analyze
245
+ parent_info: Optional parent parameter information for context
246
+
247
+ Returns:
248
+ Dictionary of unified parameter information with nested support
249
+ """
250
+ base_params = UnifiedParameterAnalyzer.analyze(target)
251
+
252
+ # For each parameter, check if it's a nested dataclass
253
+ enhanced_params = {}
254
+ for name, param_info in base_params.items():
255
+ enhanced_params[name] = param_info
256
+
257
+ # If this parameter is a dataclass, mark it as having nested structure
258
+ if dataclasses.is_dataclass(param_info.param_type):
259
+ # Update source type to indicate nesting capability
260
+ enhanced_params[name] = UnifiedParameterInfo(
261
+ name=param_info.name,
262
+ param_type=param_info.param_type,
263
+ default_value=param_info.default_value,
264
+ is_required=param_info.is_required,
265
+ description=param_info.description,
266
+ source_type=f"{param_info.source_type}_nested"
267
+ )
268
+
269
+ return enhanced_params
270
+
271
+
272
+ # Backward compatibility aliases
273
+ # These allow existing code to continue working while migration happens
274
+ ParameterAnalyzer = UnifiedParameterAnalyzer
275
+ analyze_parameters = UnifiedParameterAnalyzer.analyze
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-introspect
3
+ Version: 0.1.0
4
+ Summary: Pure Python introspection toolkit for function signatures, dataclasses, and type hints
5
+ Author-email: Tristan Simas <tristan.simas@mail.mcgill.ca>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/OpenHCSDev/python-introspect
8
+ Project-URL: Repository, https://github.com/OpenHCSDev/python-introspect
9
+ Project-URL: Issues, https://github.com/OpenHCSDev/python-introspect/issues
10
+ Keywords: introspection,reflection,signature,dataclass,type-hints,docstring
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Utilities
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.0; extra == "dev"
26
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
27
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
28
+ Requires-Dist: black>=23.0; extra == "dev"
29
+ Requires-Dist: mypy>=1.0; extra == "dev"
30
+ Provides-Extra: docs
31
+ Requires-Dist: mkdocs>=1.5.0; extra == "docs"
32
+ Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
33
+ Dynamic: license-file
34
+
35
+ # python-introspect
36
+
37
+ **Pure Python introspection toolkit for function signatures, dataclasses, and type hints**
38
+
39
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
40
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
41
+ [![CI](https://github.com/trissim/python-introspect/actions/workflows/ci.yml/badge.svg)](https://github.com/trissim/python-introspect/actions/workflows/ci.yml)
42
+ [![PyPI version](https://badge.fury.io/py/python-introspect.svg)](https://badge.fury.io/py/python-introspect)
43
+
44
+ ## Features
45
+
46
+ - 🔍 **Function/Method Signature Analysis** - Extract parameter info from any callable
47
+ - 📦 **Dataclass Field Extraction** - Analyze dataclass fields and types
48
+ - 📝 **Docstring Parsing** - Extract and parse docstrings (Google, NumPy, Sphinx styles)
49
+ - 🏷️ **Type Hint Resolution** - Resolve complex type hints and annotations
50
+ - 🎯 **Unified API** - Single interface for all parameter sources
51
+ - 🚀 **Pure Python** - No external dependencies, pure stdlib
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ pip install python-introspect
57
+ ```
58
+
59
+ ## Quick Start
60
+
61
+ ```python
62
+ from python_introspect import SignatureAnalyzer
63
+
64
+ def example_function(name: str, age: int = 25, *, active: bool = True):
65
+ """
66
+ Example function with parameters.
67
+
68
+ Args:
69
+ name: The person's name
70
+ age: The person's age
71
+ active: Whether the person is active
72
+ """
73
+ pass
74
+
75
+ # Analyze the function
76
+ analyzer = SignatureAnalyzer()
77
+ params = analyzer.analyze_function(example_function)
78
+
79
+ for param in params:
80
+ print(f"{param.name}: {param.annotation} = {param.default}")
81
+ ```
82
+
83
+ ## Use Cases
84
+
85
+ - **Form Generation** - Generate UI forms from function signatures
86
+ - **API Documentation** - Auto-generate API docs from code
87
+ - **Configuration Validation** - Validate config against function parameters
88
+ - **Dynamic UI** - Build dynamic UIs based on function signatures
89
+ - **Parameter Analysis** - Analyze and validate function parameters
90
+
91
+ ## Documentation
92
+
93
+ Full documentation available at: https://github.com/trissim/python-introspect
94
+
95
+ ## Development
96
+
97
+ ### Setup
98
+
99
+ ```bash
100
+ # Clone the repository
101
+ git clone https://github.com/trissim/python-introspect.git
102
+ cd python-introspect
103
+
104
+ # Create virtual environment
105
+ python -m venv venv
106
+ source venv/bin/activate # On Windows: venv\Scripts\activate
107
+
108
+ # Install in development mode with dev dependencies
109
+ pip install -e ".[dev]"
110
+ ```
111
+
112
+ ### Running Tests
113
+
114
+ ```bash
115
+ # Run all tests
116
+ pytest tests/
117
+
118
+ # Run with coverage
119
+ pytest tests/ --cov=python_introspect --cov-report=term --cov-report=html
120
+
121
+ # Run linting and formatting checks
122
+ ruff check src/ tests/
123
+ black --check src/ tests/
124
+ mypy src/python_introspect/
125
+ ```
126
+
127
+ ## Contributing
128
+
129
+ Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
130
+
131
+ ## Credits
132
+
133
+ Developed by Tristan Simas as part of the OpenHCS project.
@@ -0,0 +1,9 @@
1
+ python_introspect/__init__.py,sha256=4b7Hho24-R0BwWnllBcn_oIi1Im2wlJfWyd_56kYRIc,1069
2
+ python_introspect/exceptions.py,sha256=TG8Vo2JdArBbL8hlHmfmt31LtEKtGAuFEh2Rh07DQHs,488
3
+ python_introspect/signature_analyzer.py,sha256=bjEnkVpsBm8wdEzpuVfki95azLf_ylbLAlRigXzZT08,51072
4
+ python_introspect/unified_parameter_analyzer.py,sha256=npsl7FMyUSg4JjV56PMOHQzqo2z0ZldvR7ZTSgAvQjE,12156
5
+ python_introspect-0.1.0.dist-info/licenses/LICENSE,sha256=xagEoeTAj1WT64RmyR3E6HH-eTGdgXN6gqPMUUt7L_Y,1070
6
+ python_introspect-0.1.0.dist-info/METADATA,sha256=2ahrEP_hkwqkjePXy3--4ZUIcQPhCUUtporTZXGCPas,4372
7
+ python_introspect-0.1.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
8
+ python_introspect-0.1.0.dist-info/top_level.txt,sha256=TZq9Yj1LeXI7A96PDqf7ZbWGSB1zLlQDww0esC91bwY,18
9
+ python_introspect-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.10.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Tristan Simas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ python_introspect