python-introspect 0.1.2__tar.gz → 0.1.3__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 (19) hide show
  1. {python_introspect-0.1.2/src/python_introspect.egg-info → python_introspect-0.1.3}/PKG-INFO +4 -3
  2. {python_introspect-0.1.2 → python_introspect-0.1.3}/pyproject.toml +6 -5
  3. {python_introspect-0.1.2 → python_introspect-0.1.3}/src/python_introspect/__init__.py +20 -4
  4. python_introspect-0.1.3/src/python_introspect/enableable.py +79 -0
  5. {python_introspect-0.1.2 → python_introspect-0.1.3}/src/python_introspect/signature_analyzer.py +84 -54
  6. {python_introspect-0.1.2 → python_introspect-0.1.3}/src/python_introspect/unified_parameter_analyzer.py +16 -36
  7. {python_introspect-0.1.2 → python_introspect-0.1.3/src/python_introspect.egg-info}/PKG-INFO +4 -3
  8. {python_introspect-0.1.2 → python_introspect-0.1.3}/src/python_introspect.egg-info/SOURCES.txt +1 -0
  9. {python_introspect-0.1.2 → python_introspect-0.1.3}/src/python_introspect.egg-info/requires.txt +3 -2
  10. {python_introspect-0.1.2 → python_introspect-0.1.3}/LICENSE +0 -0
  11. {python_introspect-0.1.2 → python_introspect-0.1.3}/README.md +0 -0
  12. {python_introspect-0.1.2 → python_introspect-0.1.3}/setup.cfg +0 -0
  13. {python_introspect-0.1.2 → python_introspect-0.1.3}/src/python_introspect/exceptions.py +0 -0
  14. {python_introspect-0.1.2 → python_introspect-0.1.3}/src/python_introspect.egg-info/dependency_links.txt +0 -0
  15. {python_introspect-0.1.2 → python_introspect-0.1.3}/src/python_introspect.egg-info/top_level.txt +0 -0
  16. {python_introspect-0.1.2 → python_introspect-0.1.3}/tests/test_exceptions.py +0 -0
  17. {python_introspect-0.1.2 → python_introspect-0.1.3}/tests/test_init.py +0 -0
  18. {python_introspect-0.1.2 → python_introspect-0.1.3}/tests/test_signature_analyzer.py +0 -0
  19. {python_introspect-0.1.2 → python_introspect-0.1.3}/tests/test_unified_parameter_analyzer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-introspect
3
- Version: 0.1.2
3
+ Version: 0.1.3
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
@@ -28,8 +28,9 @@ Requires-Dist: ruff>=0.1.0; extra == "dev"
28
28
  Requires-Dist: black>=23.0; extra == "dev"
29
29
  Requires-Dist: mypy>=1.0; extra == "dev"
30
30
  Provides-Extra: docs
31
- Requires-Dist: mkdocs>=1.5.0; extra == "docs"
32
- Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
31
+ Requires-Dist: sphinx>=7.0.0; extra == "docs"
32
+ Requires-Dist: sphinx-rtd-theme>=2.0.0; extra == "docs"
33
+ Requires-Dist: sphinx-autodoc-typehints>=1.24.0; extra == "docs"
33
34
  Dynamic: license-file
34
35
 
35
36
  # python-introspect
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-introspect"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Pure Python introspection toolkit for function signatures, dataclasses, and type hints"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -37,8 +37,9 @@ dev = [
37
37
  "mypy>=1.0",
38
38
  ]
39
39
  docs = [
40
- "mkdocs>=1.5.0",
41
- "mkdocs-material>=9.0.0",
40
+ "sphinx>=7.0.0",
41
+ "sphinx-rtd-theme>=2.0.0",
42
+ "sphinx-autodoc-typehints>=1.24.0",
42
43
  ]
43
44
 
44
45
  [project.urls]
@@ -57,14 +58,14 @@ python_functions = ["test_*"]
57
58
 
58
59
  [tool.ruff]
59
60
  line-length = 100
60
- target-version = "py39"
61
+ target-version = "0.1.3"
61
62
 
62
63
  [tool.black]
63
64
  line-length = 100
64
65
  target-version = ["py39"]
65
66
 
66
67
  [tool.mypy]
67
- python_version = "3.9"
68
+ python_version = "0.1.3"
68
69
  warn_return_any = true
69
70
  warn_unused_configs = true
70
71
  disallow_untyped_defs = true
@@ -3,15 +3,20 @@ python-introspect: Pure Python introspection toolkit
3
3
 
4
4
  This package provides utilities for introspecting Python functions, methods,
5
5
  dataclasses, and type hints.
6
+
7
+ Extensibility:
8
+ Use register_namespace_provider() and register_type_resolver() to extend
9
+ type resolution for framework-specific types (lazy configs, proxies, etc.)
6
10
  """
7
11
 
8
- __version__ = "0.1.2"
12
+ __version__ = "0.1.3"
9
13
 
10
14
  from .signature_analyzer import (
11
15
  SignatureAnalyzer,
12
16
  ParameterInfo,
13
17
  DocstringInfo,
14
18
  DocstringExtractor,
19
+ # Plugin registration
15
20
  register_namespace_provider,
16
21
  register_type_resolver,
17
22
  )
@@ -25,6 +30,12 @@ from .exceptions import (
25
30
  DocstringParsingError,
26
31
  TypeResolutionError,
27
32
  )
33
+ from .enableable import (
34
+ Enableable,
35
+ is_enableable,
36
+ mark_enableable,
37
+ ENABLED_FIELD,
38
+ )
28
39
 
29
40
  __all__ = [
30
41
  # Version
@@ -34,15 +45,20 @@ __all__ = [
34
45
  "ParameterInfo",
35
46
  "DocstringInfo",
36
47
  "DocstringExtractor",
48
+ # Plugin registration
49
+ "register_namespace_provider",
50
+ "register_type_resolver",
37
51
  # Unified analysis
38
52
  "UnifiedParameterAnalyzer",
39
53
  "UnifiedParameterInfo",
40
- # Plugin system
41
- "register_namespace_provider",
42
- "register_type_resolver",
43
54
  # Exceptions
44
55
  "IntrospectionError",
45
56
  "SignatureAnalysisError",
46
57
  "DocstringParsingError",
47
58
  "TypeResolutionError",
59
+ # Enableable
60
+ "Enableable",
61
+ "is_enableable",
62
+ "mark_enableable",
63
+ "ENABLED_FIELD",
48
64
  ]
@@ -0,0 +1,79 @@
1
+ """Nominal enable semantics as type-safe metadata.
2
+
3
+ This module provides a single, shared "axis" for objects and callables that
4
+ participate in enabled semantics.
5
+
6
+ Design goals:
7
+ - Nominal (not structural): only explicitly branded callables qualify.
8
+ - Dataclass-friendly: configs can inherit Enableable to get an enabled field.
9
+ - Callable-safe: branded callables must declare an `enabled` parameter.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import inspect
15
+ from abc import ABC, ABCMeta
16
+ from dataclasses import dataclass
17
+ from typing import Any
18
+
19
+
20
+ ENABLED_FIELD = 'enabled'
21
+
22
+ _ENABLEABLE_TAG = object()
23
+
24
+
25
+ class EnableableMeta(ABCMeta):
26
+ """Metaclass enabling nominal isinstance checks for branded callables."""
27
+
28
+ def __instancecheck__(cls, instance: Any) -> bool: # type: ignore[override]
29
+ if getattr(instance, '__enableable_tag__', None) is _ENABLEABLE_TAG:
30
+ return True
31
+ return super().__instancecheck__(instance)
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class Enableable(ABC, metaclass=EnableableMeta):
36
+ """Mixin indicating an object participates in enabled semantics."""
37
+
38
+ enabled: bool = True
39
+
40
+
41
+ def is_enableable(obj: Any) -> bool:
42
+ """Return True iff obj is nominally Enableable.
43
+
44
+ Works for both instances (using isinstance) and classes (using issubclass).
45
+ This is needed because widget creation code needs to check if a type (class)
46
+ is enableable, not just instances.
47
+ """
48
+
49
+ # Check if obj is a type/class
50
+ if isinstance(obj, type):
51
+ # obj is a class - check if it's a subclass of Enableable
52
+ try:
53
+ return issubclass(obj, Enableable)
54
+ except TypeError:
55
+ # obj is not a class or is not class-like (e.g., a generic type)
56
+ return False
57
+ else:
58
+ # obj is an instance - use isinstance
59
+ return isinstance(obj, Enableable)
60
+
61
+
62
+ def mark_enableable(obj: Any, *, enabled_default: bool = True) -> Any:
63
+ """Nominally brand an object/callable as Enableable.
64
+
65
+ This does not wrap and does not change call semantics.
66
+ """
67
+
68
+ _ = enabled_default # reserved for future: default enabled semantics
69
+
70
+ # If we're branding a callable, require the enabled kwarg to exist.
71
+ if callable(obj) and not isinstance(obj, type):
72
+ sig = inspect.signature(obj)
73
+ if ENABLED_FIELD not in sig.parameters:
74
+ raise TypeError(
75
+ f"Enableable callable '{getattr(obj, '__name__', obj)}' must have an '{ENABLED_FIELD}' parameter"
76
+ )
77
+
78
+ setattr(obj, '__enableable_tag__', _ENABLEABLE_TAG)
79
+ return obj
@@ -1,86 +1,82 @@
1
1
  # File: python_introspect/signature_analyzer.py
2
+ """
3
+ Signature analysis with extensible type resolution.
4
+
5
+ This module provides pure Python introspection with a plugin architecture
6
+ for framework-specific extensions. Register namespace providers and type
7
+ resolvers to extend functionality without modifying this code.
8
+ """
2
9
 
3
10
  import ast
4
11
  import inspect
5
12
  import dataclasses
6
13
  import re
7
14
  from typing import Any, Dict, Callable, get_type_hints, NamedTuple, Union, Optional, Type, List
15
+
8
16
  from dataclasses import dataclass
9
17
 
10
- # Plugin system for namespace and type resolution
11
- # External packages can register their own providers/resolvers
18
+ # =============================================================================
19
+ # PLUGIN REGISTRY - Allows frameworks to extend type resolution
20
+ # =============================================================================
21
+
22
+ # Namespace providers: functions that return Dict[str, Any] for get_type_hints()
23
+ # Used to resolve forward references like "GlobalPipelineConfig" -> actual class
12
24
  _namespace_providers: List[Callable[[], Dict[str, Any]]] = []
25
+
26
+ # Type resolvers: functions that map types to their "real" types
27
+ # e.g., LazyWellFilterConfig -> WellFilterConfig
13
28
  _type_resolvers: List[Callable[[type], Optional[type]]] = []
14
29
 
15
30
 
16
31
  def register_namespace_provider(provider: Callable[[], Dict[str, Any]]) -> None:
17
32
  """Register a namespace provider for forward reference resolution.
18
33
 
19
- Args:
20
- provider: A callable that returns a dict of names to types/values
21
- to be used when resolving forward references in type hints.
34
+ The provider function should return a dict of names to types/values
35
+ that will be available during get_type_hints() resolution.
22
36
 
23
37
  Example:
24
- def my_namespace_provider():
25
- import mypackage
26
- return vars(mypackage)
27
-
28
- register_namespace_provider(my_namespace_provider)
38
+ register_namespace_provider(lambda: {'MyClass': MyClass, 'MyEnum': MyEnum})
29
39
  """
30
40
  _namespace_providers.append(provider)
31
41
 
32
42
 
33
43
  def register_type_resolver(resolver: Callable[[type], Optional[type]]) -> None:
34
- """Register a type resolver for custom type transformations.
44
+ """Register a type resolver for lazy/proxy type unwrapping.
35
45
 
36
- Args:
37
- resolver: A callable that takes a type and returns either:
38
- - A transformed type (e.g., LazyConfig -> Config)
39
- - None to defer to other resolvers
46
+ The resolver function should return the resolved type if it can handle
47
+ the input type, or None to defer to other resolvers.
40
48
 
41
49
  Example:
42
- def my_type_resolver(t):
50
+ def resolve_lazy(t):
43
51
  if t.__name__.startswith('Lazy'):
44
52
  return get_base_type(t)
45
53
  return None
46
-
47
- register_type_resolver(my_type_resolver)
54
+ register_type_resolver(resolve_lazy)
48
55
  """
49
56
  _type_resolvers.append(resolver)
50
57
 
51
58
 
52
- def _get_registered_namespaces() -> Dict[str, Any]:
53
- """Get all registered namespaces merged together."""
54
- result = {}
59
+ def _get_extended_namespace() -> Dict[str, Any]:
60
+ """Get combined namespace from all registered providers."""
61
+ result: Dict[str, Any] = {}
55
62
  for provider in _namespace_providers:
56
63
  try:
57
- namespace = provider()
58
- if namespace:
59
- result.update(namespace)
64
+ result.update(provider())
60
65
  except Exception:
61
- # Silently skip providers that fail
62
- pass
66
+ pass # Ignore providers that fail
63
67
  return result
64
68
 
65
69
 
66
70
  def _resolve_type(t: type) -> type:
67
- """Resolve a type using registered type resolvers.
68
-
69
- Args:
70
- t: The type to resolve
71
-
72
- Returns:
73
- The resolved type, or the original type if no resolver handled it
74
- """
71
+ """Resolve a type through registered resolvers, returning the unwrapped type."""
75
72
  for resolver in _type_resolvers:
76
73
  try:
77
74
  resolved = resolver(t)
78
75
  if resolved is not None:
79
76
  return resolved
80
77
  except Exception:
81
- # Silently skip resolvers that fail
82
- pass
83
- return t
78
+ pass # Ignore resolvers that fail
79
+ return t # No resolver handled it, return as-is
84
80
 
85
81
 
86
82
  @dataclass(frozen=True)
@@ -440,13 +436,14 @@ class SignatureAnalyzer:
440
436
  """
441
437
  sig = inspect.signature(callable_obj)
442
438
  # Build comprehensive namespace for forward reference resolution
443
- # Start with registered namespaces, then add function's globals
439
+ # Start with registered namespace providers, then add function's globals
440
+ extended_ns = _get_extended_namespace()
444
441
  globalns = {
445
- **_get_registered_namespaces(),
442
+ **extended_ns,
446
443
  **getattr(callable_obj, '__globals__', {})
447
444
  }
448
445
 
449
- # For functions with a module, prioritize the function's actual module globals
446
+ # Prioritize the function's actual module globals for type resolution
450
447
  if hasattr(callable_obj, '__module__') and callable_obj.__module__:
451
448
  try:
452
449
  import sys
@@ -454,7 +451,7 @@ class SignatureAnalyzer:
454
451
  if actual_module:
455
452
  # Function's module globals should take precedence for type resolution
456
453
  globalns = {
457
- **_get_registered_namespaces(),
454
+ **extended_ns,
458
455
  **vars(actual_module) # This overwrites with the actual module types
459
456
  }
460
457
  except Exception:
@@ -640,10 +637,10 @@ class SignatureAnalyzer:
640
637
  # Use the class object itself as the key (classes are hashable and have stable identity)
641
638
  cache_key = dataclass_type
642
639
  if cache_key in SignatureAnalyzer._dataclass_analysis_cache:
643
- logger.info(f"✅ CACHE HIT for {dataclass_type.__name__} (id={id(dataclass_type)})")
640
+ logger.debug(f"✅ CACHE HIT for {dataclass_type.__name__} (id={id(dataclass_type)})")
644
641
  return SignatureAnalyzer._dataclass_analysis_cache[cache_key]
645
642
 
646
- logger.info(f"❌ CACHE MISS for {dataclass_type.__name__} (id={id(dataclass_type)}), cache has {len(SignatureAnalyzer._dataclass_analysis_cache)} entries")
643
+ logger.debug(f"❌ CACHE MISS for {dataclass_type.__name__} (id={id(dataclass_type)}), cache has {len(SignatureAnalyzer._dataclass_analysis_cache)} entries")
647
644
 
648
645
  try:
649
646
  # Try to get type hints, fall back to __annotations__ if resolution fails
@@ -1013,7 +1010,8 @@ class SignatureAnalyzer:
1013
1010
  def _resolve_lazy_dataclass_for_docs(dataclass_type: type) -> type:
1014
1011
  """Resolve lazy dataclasses to their base classes for documentation extraction.
1015
1012
 
1016
- Uses registered type resolvers to handle custom type transformations.
1013
+ Uses registered type resolvers to unwrap lazy/proxy types.
1014
+ Falls back to heuristics if no resolver handles the type.
1017
1015
 
1018
1016
  Args:
1019
1017
  dataclass_type: The dataclass type (potentially lazy)
@@ -1021,7 +1019,40 @@ class SignatureAnalyzer:
1021
1019
  Returns:
1022
1020
  The resolved dataclass type for documentation extraction
1023
1021
  """
1024
- return _resolve_type(dataclass_type)
1022
+ try:
1023
+ # First, try registered type resolvers (framework-specific)
1024
+ resolved = _resolve_type(dataclass_type)
1025
+ if resolved is not dataclass_type:
1026
+ return resolved
1027
+
1028
+ # Fallback heuristics for common patterns (framework-agnostic)
1029
+ class_name = dataclass_type.__name__
1030
+
1031
+ # Handle LazyXxxConfig -> XxxConfig by looking in same module
1032
+ if class_name.startswith('Lazy') and class_name.endswith('Config'):
1033
+ try:
1034
+ base_class_name = class_name[4:] # Remove 'Lazy' prefix
1035
+ module = __import__(dataclass_type.__module__, fromlist=[base_class_name])
1036
+ if hasattr(module, base_class_name):
1037
+ return getattr(module, base_class_name)
1038
+ except (ImportError, AttributeError):
1039
+ pass
1040
+
1041
+ # Try to find GlobalXxxConfig version in same module
1042
+ if not class_name.startswith('Global') and class_name.endswith('Config'):
1043
+ try:
1044
+ global_class_name = f'Global{class_name}'
1045
+ module = __import__(dataclass_type.__module__, fromlist=[global_class_name])
1046
+ if hasattr(module, global_class_name):
1047
+ return getattr(module, global_class_name)
1048
+ except (ImportError, AttributeError):
1049
+ pass
1050
+
1051
+ # If no resolution found, return the original type
1052
+ return dataclass_type
1053
+
1054
+ except Exception:
1055
+ return dataclass_type
1025
1056
 
1026
1057
  @staticmethod
1027
1058
  def _extract_all_field_docs(dataclass_type: type) -> Dict[str, str]:
@@ -1120,16 +1151,12 @@ class SignatureAnalyzer:
1120
1151
  parameters = SignatureAnalyzer._analyze_dataclass(dataclass_type)
1121
1152
 
1122
1153
  # Update default values with current instance values
1123
- # For lazy dataclasses, use object.__getattribute__ to preserve None values for placeholders
1154
+ # CRITICAL: Always use object.__getattribute__ to bypass __getattribute__ overrides
1155
+ # This ensures we get the raw stored value, not a resolved/computed value
1124
1156
  for name, param_info in parameters.items():
1125
- if hasattr(instance, name):
1126
- # Check if this is a lazy dataclass that should preserve None values
1127
- if hasattr(instance, '_resolve_field_value'):
1128
- # This is a lazy dataclass - use object.__getattribute__ to get stored value
1129
- current_value = object.__getattribute__(instance, name)
1130
- else:
1131
- # Regular dataclass - use normal getattr
1132
- current_value = getattr(instance, name)
1157
+ try:
1158
+ # Bypass __getattribute__ to get raw stored value (not resolved)
1159
+ current_value = object.__getattribute__(instance, name)
1133
1160
 
1134
1161
  # Create new ParameterInfo with current value as default
1135
1162
  parameters[name] = ParameterInfo(
@@ -1139,6 +1166,9 @@ class SignatureAnalyzer:
1139
1166
  is_required=param_info.is_required,
1140
1167
  description=param_info.description
1141
1168
  )
1169
+ except AttributeError:
1170
+ # Field doesn't exist on instance, keep signature default
1171
+ pass
1142
1172
 
1143
1173
  return parameters
1144
1174
 
@@ -82,12 +82,11 @@ class UnifiedParameterAnalyzer:
82
82
  if dataclasses.is_dataclass(target):
83
83
  result = UnifiedParameterAnalyzer._analyze_dataclass_type(target)
84
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
85
+ # For classes, use _analyze_object_instance to traverse MRO
87
86
  # Create a dummy instance just to get the class hierarchy analyzed
88
87
  try:
89
88
  dummy_instance = target.__new__(target)
90
- result = UnifiedParameterAnalyzer._analyze_object_instance(dummy_instance, use_signature_defaults=True)
89
+ result = UnifiedParameterAnalyzer._analyze_object_instance(dummy_instance)
91
90
  except:
92
91
  # If we can't create a dummy instance, fall back to just analyzing __init__
93
92
  result = UnifiedParameterAnalyzer._analyze_callable(target.__init__)
@@ -147,12 +146,14 @@ class UnifiedParameterAnalyzer:
147
146
  return unified_params
148
147
 
149
148
  @staticmethod
150
- def _analyze_object_instance(instance: object, use_signature_defaults: bool = False) -> Dict[str, UnifiedParameterInfo]:
149
+ def _analyze_object_instance(instance: object) -> Dict[str, UnifiedParameterInfo]:
151
150
  """Analyze a regular object instance by examining its full inheritance hierarchy.
152
151
 
152
+ Always returns CLASS signature defaults (not instance values).
153
+ ObjectState extracts instance values separately via object.__getattribute__.
154
+
153
155
  Args:
154
156
  instance: Object instance to analyze
155
- use_signature_defaults: If True, use signature defaults instead of instance values
156
157
  """
157
158
  # Use MRO to get all constructor parameters from the inheritance chain
158
159
  instance_class = type(instance)
@@ -184,20 +185,13 @@ class UnifiedParameterAnalyzer:
184
185
  # Add parameters that haven't been seen yet (most specific wins)
185
186
  for param_name, param_info in class_params.items():
186
187
  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
188
+ # Always use signature defaults - ObjectState extracts instance values separately
195
189
  all_params[param_name] = UnifiedParameterInfo(
196
190
  name=param_name,
197
191
  param_type=param_info.param_type,
198
- default_value=default_value,
192
+ default_value=param_info.default_value,
199
193
  is_required=param_info.is_required,
200
- description=param_info.description, # CRITICAL FIX: Include description
194
+ description=param_info.description,
201
195
  source_type="object_instance"
202
196
  )
203
197
 
@@ -210,28 +204,14 @@ class UnifiedParameterAnalyzer:
210
204
 
211
205
  @staticmethod
212
206
  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
- )
207
+ """Analyze a dataclass instance.
233
208
 
234
- return unified_params
209
+ Always returns CLASS signature defaults (not instance values).
210
+ ObjectState extracts instance values separately via object.__getattribute__.
211
+ """
212
+ # Get the type and analyze it - returns CLASS signature defaults
213
+ dataclass_type = type(instance)
214
+ return UnifiedParameterAnalyzer._analyze_dataclass_type(dataclass_type)
235
215
 
236
216
  @staticmethod
237
217
  def analyze_nested(target: Union[Callable, Type, object], parent_info: Dict[str, UnifiedParameterInfo] = None) -> Dict[str, UnifiedParameterInfo]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-introspect
3
- Version: 0.1.2
3
+ Version: 0.1.3
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
@@ -28,8 +28,9 @@ Requires-Dist: ruff>=0.1.0; extra == "dev"
28
28
  Requires-Dist: black>=23.0; extra == "dev"
29
29
  Requires-Dist: mypy>=1.0; extra == "dev"
30
30
  Provides-Extra: docs
31
- Requires-Dist: mkdocs>=1.5.0; extra == "docs"
32
- Requires-Dist: mkdocs-material>=9.0.0; extra == "docs"
31
+ Requires-Dist: sphinx>=7.0.0; extra == "docs"
32
+ Requires-Dist: sphinx-rtd-theme>=2.0.0; extra == "docs"
33
+ Requires-Dist: sphinx-autodoc-typehints>=1.24.0; extra == "docs"
33
34
  Dynamic: license-file
34
35
 
35
36
  # python-introspect
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  src/python_introspect/__init__.py
5
+ src/python_introspect/enableable.py
5
6
  src/python_introspect/exceptions.py
6
7
  src/python_introspect/signature_analyzer.py
7
8
  src/python_introspect/unified_parameter_analyzer.py
@@ -7,5 +7,6 @@ black>=23.0
7
7
  mypy>=1.0
8
8
 
9
9
  [docs]
10
- mkdocs>=1.5.0
11
- mkdocs-material>=9.0.0
10
+ sphinx>=7.0.0
11
+ sphinx-rtd-theme>=2.0.0
12
+ sphinx-autodoc-typehints>=1.24.0