pyopenapi-gen 0.14.2__py3-none-any.whl → 0.15.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.
pyopenapi_gen/__init__.py CHANGED
@@ -43,7 +43,7 @@ __all__ = [
43
43
  ]
44
44
 
45
45
  # Semantic version of the generator core – automatically managed by semantic-release.
46
- __version__: str = "0.10.2"
46
+ __version__: str = "0.14.3"
47
47
 
48
48
 
49
49
  # ---------------------------------------------------------------------------
@@ -153,10 +153,25 @@ class RenderContext:
153
153
  self.core_package_name + "."
154
154
  )
155
155
  if is_target_in_core_pkg_namespace:
156
+ # For root-level sibling packages (core and output package both at top level),
157
+ # we MUST use absolute imports because Python doesn't support relative imports
158
+ # that go beyond the top-level package.
159
+ #
160
+ # Example structure that requires absolute imports:
161
+ # output/ # NOT a package (no __init__.py)
162
+ # ├── core/ # top-level package
163
+ # └── businessapi/ # top-level package
164
+ #
165
+ # In this case, businessapi/client.py cannot use "from ..core import X"
166
+ # because that would try to go UP from businessapi to output/, which is not a package.
167
+ #
168
+ # Solution: Always use absolute imports for core when it's a root-level sibling.
169
+
170
+ # Always use absolute imports for core imports
156
171
  if name:
157
172
  self.import_collector.add_import(module=logical_module, name=name)
158
173
  else:
159
- self.import_collector.add_plain_import(module=logical_module) # Core plain import
174
+ self.import_collector.add_plain_import(module=logical_module)
160
175
  return
161
176
 
162
177
  # 3. Stdlib/Builtin?
@@ -628,3 +643,84 @@ class RenderContext:
628
643
 
629
644
  except ValueError: # If current_file is not under overall_project_root
630
645
  return None
646
+
647
+ def get_core_import_path(self, submodule: str) -> str:
648
+ """
649
+ Calculate the correct import path to a core package submodule from the current file.
650
+
651
+ Handles both sibling core packages (core/, custom_core/) and external packages (api_sdks.my_core).
652
+
653
+ Args:
654
+ submodule: The submodule within core to import from (e.g., "schemas", "http_transport", "exceptions")
655
+
656
+ Returns:
657
+ The correct import path string (e.g., "...core.schemas", "..core.http_transport", "api_sdks.my_core.schemas")
658
+
659
+ Examples:
660
+ From businessapi/models/user.py → core/schemas.py returns "...core.schemas"
661
+ From businessapi/client.py → core/http_transport.py returns "..core.http_transport"
662
+ From businessapi/endpoints/auth.py → core/exceptions.py returns "...core.exceptions"
663
+ External core package (api_sdks.my_core) returns "api_sdks.my_core.schemas"
664
+ """
665
+ # 1. Check if core_package_name contains dots or is already a relative import
666
+ if "." in self.core_package_name and not self.core_package_name.startswith("."):
667
+ # External package (e.g., "api_sdks.my_core") - use absolute import
668
+ return f"{self.core_package_name}.{submodule}"
669
+
670
+ if self.core_package_name.startswith(".."):
671
+ # Already a relative import path
672
+ return f"{self.core_package_name}.{submodule}"
673
+
674
+ # 2. Local core package (sibling) - calculate relative path
675
+ return self._calculate_relative_core_path(submodule)
676
+
677
+ def _calculate_relative_core_path(self, submodule: str) -> str:
678
+ """Calculate relative import path to sibling core package."""
679
+
680
+ if not self.current_file or not self.package_root_for_generated_code or not self.overall_project_root:
681
+ # Fallback to absolute import if context not fully set
682
+ logger.warning(
683
+ f"Cannot calculate relative core path: context not fully set. "
684
+ f"current_file={self.current_file}, package_root={self.package_root_for_generated_code}, "
685
+ f"project_root={self.overall_project_root}. Using absolute import."
686
+ )
687
+ return f"{self.core_package_name}.{submodule}"
688
+
689
+ try:
690
+ # 1. Get current file's directory
691
+ current_file_abs = Path(self.current_file).resolve()
692
+ current_dir_abs = current_file_abs.parent
693
+
694
+ # 2. Determine core package location (sibling to output package)
695
+ project_root_abs = Path(self.overall_project_root).resolve()
696
+
697
+ # Core is sibling to the output package
698
+ core_abs = project_root_abs / self.core_package_name
699
+ target_abs = core_abs / submodule.replace(".", os.sep)
700
+
701
+ # 3. Calculate relative path from current directory to target
702
+ relative_path = os.path.relpath(target_abs, start=current_dir_abs)
703
+
704
+ # 4. Convert filesystem path to Python import path
705
+ # e.g., "../../core/schemas" → "...core.schemas"
706
+ path_parts = relative_path.split(os.sep)
707
+
708
+ dots = 0
709
+ module_parts = []
710
+
711
+ for part in path_parts:
712
+ if part == "..":
713
+ dots += 1
714
+ elif part != ".":
715
+ module_parts.append(part)
716
+
717
+ # Prefix with dots (add one more for Python relative imports)
718
+ prefix = "." * (dots + 1)
719
+ module_path = ".".join(module_parts)
720
+
721
+ return f"{prefix}{module_path}"
722
+
723
+ except Exception as e:
724
+ # Fallback to absolute import on any error
725
+ logger.warning(f"Failed to calculate relative core path: {e}. Using absolute import.")
726
+ return f"{self.core_package_name}.{submodule}"
@@ -183,21 +183,8 @@ class PythonConstructRenderer:
183
183
  context.add_import("dataclasses", "dataclass")
184
184
 
185
185
  # Always use self-contained BaseSchema for client independence with automatic field mapping
186
- # Use the core package from context - could be relative (..core) or absolute (api_sdks.my_core)
187
- if context.core_package_name.startswith(".."):
188
- # Already a relative import
189
- core_import_path = f"{context.core_package_name}.schemas"
190
- elif "." in context.core_package_name:
191
- # External core package with dots (e.g., api_sdks.my_core) - use absolute import
192
- core_import_path = f"{context.core_package_name}.schemas"
193
- elif context.core_package_name == "core":
194
- # Default relative core package
195
- core_import_path = "..core.schemas"
196
- else:
197
- # Simple external core package name (e.g., shared_core_pkg) - use absolute import
198
- core_import_path = f"{context.core_package_name}.schemas"
199
-
200
- context.add_import(core_import_path, "BaseSchema")
186
+ # Use absolute core import path - add_import will handle it correctly
187
+ context.add_import(f"{context.core_package_name}.schemas", "BaseSchema")
201
188
 
202
189
  # Add __all__ export
203
190
  writer.write_line(f'__all__ = ["{class_name}"]')
@@ -201,10 +201,24 @@ class OpenAPISchemaResolver(SchemaTypeResolver):
201
201
  # If anything goes wrong with attribute access, assume not a self-reference
202
202
  current_file = None
203
203
 
204
- # Only treat as self-reference if we have a real file path that matches
205
- if current_file and isinstance(current_file, str) and current_file.endswith(f"{module_stem}.py"):
204
+ # Only treat as self-reference if the EXACT filename matches (not just a suffix)
205
+ # Bug fix: Use basename comparison to avoid false positives with similar names
206
+ # e.g., "vector_index_with_embedding_response_data.py" should NOT match "embedding_response_data.py"
207
+ is_self_import = False
208
+ if current_file and isinstance(current_file, str):
209
+ import os
210
+
211
+ current_filename = os.path.basename(current_file)
212
+ expected_filename = f"{module_stem}.py"
213
+ is_self_import = current_filename == expected_filename
214
+
215
+ if is_self_import:
206
216
  # This is a self-import (importing from the same file), so skip the import
207
217
  # and mark as forward reference to require quoting
218
+ logger.debug(
219
+ f"Self-import detected: current_file={current_file}, module_stem={module_stem}, "
220
+ f"class_name={class_name}, returning forward ref WITHOUT import"
221
+ )
208
222
  return ResolvedType(python_type=class_name or "Any", is_optional=not required, is_forward_ref=True)
209
223
 
210
224
  # Use the render context's relative path calculation to determine proper import path
@@ -226,6 +240,10 @@ class OpenAPISchemaResolver(SchemaTypeResolver):
226
240
  # If relative path calculation fails, fall back to the default
227
241
  logger.debug(f"Failed to calculate relative path for {module_stem}, using default: {e}")
228
242
 
243
+ logger.debug(
244
+ f"Adding import for named schema: module={import_module}, name={class_name}, "
245
+ f"current_file={current_file}, module_stem={module_stem}"
246
+ )
229
247
  context.add_import(import_module, class_name or "Any")
230
248
 
231
249
  return ResolvedType(
@@ -144,6 +144,10 @@ class UnifiedTypeService:
144
144
 
145
145
  # Quote forward references BEFORE adding | None so we get: "DataSource" | None not "DataSource | None"
146
146
  if resolved.is_forward_ref and not python_type.startswith('"'):
147
+ logger.debug(
148
+ f'Quoting forward ref: {python_type} -> "{python_type}" '
149
+ f"(is_forward_ref={resolved.is_forward_ref}, needs_import={resolved.needs_import})"
150
+ )
147
151
  python_type = f'"{python_type}"'
148
152
 
149
153
  # Add modern | None syntax if needed
@@ -80,7 +80,8 @@ class DataclassGenerator:
80
80
  context.add_import("dataclasses", "dataclass")
81
81
  context.add_import("dataclasses", "field")
82
82
  context.add_import("typing", "Any")
83
- context.add_import("..core.schemas", "BaseSchema")
83
+ # Use absolute core import path - add_import will handle it correctly
84
+ context.add_import(f"{context.core_package_name}.schemas", "BaseSchema")
84
85
 
85
86
  description = schema.description or "Generic JSON value object that preserves arbitrary data."
86
87
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyopenapi-gen
3
- Version: 0.14.2
3
+ Version: 0.15.0
4
4
  Summary: Modern, async-first Python client generator for OpenAPI specifications with advanced cycle detection and unified type resolution
5
5
  Project-URL: Homepage, https://github.com/your-org/pyopenapi-gen
6
6
  Project-URL: Documentation, https://github.com/your-org/pyopenapi-gen/blob/main/README.md
@@ -1,4 +1,4 @@
1
- pyopenapi_gen/__init__.py,sha256=jU5GYbWa4MBDeDzOzwUHl_8eLGtotxRGLj42d2yskUo,3017
1
+ pyopenapi_gen/__init__.py,sha256=AUYdbyW8dbrYxo2DvttZkFO79T5QHsS96P8z4TU8y6U,3017
2
2
  pyopenapi_gen/__main__.py,sha256=4-SCaCNhBd7rtyRK58uoDbdl93J0KhUeajP_b0CPpLE,110
3
3
  pyopenapi_gen/cli.py,sha256=9T_XF3-ih_JlM_BOkmHft-HoMCGOqL5UrnAHBJ0fb5w,2320
4
4
  pyopenapi_gen/http_types.py,sha256=EMMYZBt8PNVZKPFu77TQija-JI-nOKyXvpiQP9-VSWE,467
@@ -7,7 +7,7 @@ pyopenapi_gen/py.typed,sha256=Nqnn8clbgv-5l0PgxcTOldg8mkMKrFn4TvPL-rYUUGg,1
7
7
  pyopenapi_gen/context/CLAUDE.md,sha256=eUPvSY2ADQK21i52bWfzyBcDPVvvepErMiQrq6ndwlU,9004
8
8
  pyopenapi_gen/context/file_manager.py,sha256=vpbRByO5SH6COdjb6C-pXkdSIRu7QFqrXxi69VLKBnM,1691
9
9
  pyopenapi_gen/context/import_collector.py,sha256=5WzQ5fXxARHJFZmYZ_GjPG5YjjzRLZTdN7jO2cbbk88,15225
10
- pyopenapi_gen/context/render_context.py,sha256=07oINhHWi78qhcf-FNSOtojUrshmw0ZOTEgPpzBptbU,30271
10
+ pyopenapi_gen/context/render_context.py,sha256=fAq0u3rDMZ0_OEHegrRUd98ySxs390XJbdypqQX8bwI,34894
11
11
  pyopenapi_gen/core/CLAUDE.md,sha256=bz48K-PSrhxCq5ScmiLiU9kfpVVzSWRKOA9RdKk_pbg,6482
12
12
  pyopenapi_gen/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  pyopenapi_gen/core/exceptions.py,sha256=HYFiYdmzsZUl46vB8M3B6Vpp6m8iqjUcKDWdL4yEKHo,498
@@ -64,7 +64,7 @@ pyopenapi_gen/core/parsing/transformers/inline_object_promoter.py,sha256=d9DZ1CV
64
64
  pyopenapi_gen/core/writers/code_writer.py,sha256=5rWMPRdj5dWu3NRER6do4NEJEIa9qL68Hc7zhJ8jSdg,4763
65
65
  pyopenapi_gen/core/writers/documentation_writer.py,sha256=ZnXJsRc9y9bUMsQR7g0qoBJZbBo2MCxHH_5KgxrPtyA,8645
66
66
  pyopenapi_gen/core/writers/line_writer.py,sha256=-K2FaMtQa6hdzGZLcjQrT2ItEmfE-kquTCrn6R3I_QA,7743
67
- pyopenapi_gen/core/writers/python_construct_renderer.py,sha256=aRLSiqUkf5Cp72BbJP7nVTmwPPrYlk8W66175joKAg0,12766
67
+ pyopenapi_gen/core/writers/python_construct_renderer.py,sha256=mJ8iCDQAJWg32ZaDI4U7eWRzym3bwzOrJ2VKAXOZds0,12066
68
68
  pyopenapi_gen/core_package_template/README.md,sha256=8YP-MS0KxphRbCGBf7kV3dYIFLU9piOJ3IMm3K_0hcI,1488
69
69
  pyopenapi_gen/emit/models_emitter.py,sha256=MBRq71UjtlZHrpf9QhDN0wXk_X-oGeGd6TAq3RKG7ko,7180
70
70
  pyopenapi_gen/emitters/CLAUDE.md,sha256=iZYEZq1a1h033rxuh97cMpsKUElv72ysvTm3-QQUvrs,9323
@@ -98,9 +98,9 @@ pyopenapi_gen/types/contracts/types.py,sha256=k3tCjMu6M1zcVXF6jzr-Q9S6LXaMEMoKoO
98
98
  pyopenapi_gen/types/resolvers/__init__.py,sha256=_5kA49RvyOTyXgt0GbbOfHJcdQw2zHxvU9af8GGyNWc,295
99
99
  pyopenapi_gen/types/resolvers/reference_resolver.py,sha256=ue6gw665Wo07POuAg5820A6AXiCVyQY-unfIzGjxrSY,2034
100
100
  pyopenapi_gen/types/resolvers/response_resolver.py,sha256=_W6-Z_SBQ8tDMd_VqveO5AiFs7Od7eAuqevpUIDCT5o,6481
101
- pyopenapi_gen/types/resolvers/schema_resolver.py,sha256=tWNWvCYkXyZ40bzy4ShQJPX2vb_FVgDcrc7tKnWTTwo,22784
101
+ pyopenapi_gen/types/resolvers/schema_resolver.py,sha256=PWw77fPh8SG4i1YD45-cOmjmrA1HQkIz1u75AILlvM0,23617
102
102
  pyopenapi_gen/types/services/__init__.py,sha256=inSUKmY_Vnuym6tC-AhvjCTj16GbkfxCGLESRr_uQPE,123
103
- pyopenapi_gen/types/services/type_service.py,sha256=N_UCyTdt8DRO7FdM7tQT_0G8UJ3OrzqSxTEqEkyrOLg,6784
103
+ pyopenapi_gen/types/services/type_service.py,sha256=3IH0GBLeC-ruokBmhvC8iR5GT5tmFg1n6tcLZYa5TSk,6998
104
104
  pyopenapi_gen/types/strategies/__init__.py,sha256=bju8_KEPNIow1-woMO-zJCgK_E0M6JnFq0NFsK1R4Ss,171
105
105
  pyopenapi_gen/types/strategies/response_strategy.py,sha256=TmXX2YDQB0cQQ7BvRCGSJM6Iu1p0MMJioCi-_8HPCAg,7341
106
106
  pyopenapi_gen/visit/CLAUDE.md,sha256=Rq2e4S74TXv0ua2ZcCrO6cwCCccf3Yph44oVdj1yFPY,8297
@@ -122,11 +122,11 @@ pyopenapi_gen/visit/endpoint/processors/import_analyzer.py,sha256=ou5pl3S6PXvHrh
122
122
  pyopenapi_gen/visit/endpoint/processors/parameter_processor.py,sha256=E0VoygkhU8iCsvH0U-tA6ZZg2Nm3rfr4e17vxqQLH7c,7666
123
123
  pyopenapi_gen/visit/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
124
124
  pyopenapi_gen/visit/model/alias_generator.py,sha256=wEMHipPA1_CFxvQ6CS9j4qgXK93seI1bI_tFJvIrb70,3563
125
- pyopenapi_gen/visit/model/dataclass_generator.py,sha256=z0kKH_V2PJyiI9snmryDpAzRiOsQJtk1jrijCnEQQwg,14326
125
+ pyopenapi_gen/visit/model/dataclass_generator.py,sha256=L794s2yyYUO__akGd120NJMV7g2xqiPfQyZo8CduIVM,14426
126
126
  pyopenapi_gen/visit/model/enum_generator.py,sha256=AXqKUFuWUUjUF_6_HqBKY8vB5GYu35Pb2C2WPFrOw1k,10061
127
127
  pyopenapi_gen/visit/model/model_visitor.py,sha256=TC6pbxpQiy5FWhmQpfllLuXA3ImTYNMcrazkOFZCIyo,9470
128
- pyopenapi_gen-0.14.2.dist-info/METADATA,sha256=ERW_JKw-KdBCYXTQsG1sSXDYV9ILr9GgnKEvzfgHHNY,14025
129
- pyopenapi_gen-0.14.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
130
- pyopenapi_gen-0.14.2.dist-info/entry_points.txt,sha256=gxSlNiwom50T3OEZnlocA6qRjGdV0bn6hN_Xr-Ub5wA,56
131
- pyopenapi_gen-0.14.2.dist-info/licenses/LICENSE,sha256=UFAyTWKa4w10-QerlJaHJeep7G2gcwpf-JmvI2dS2Gc,1088
132
- pyopenapi_gen-0.14.2.dist-info/RECORD,,
128
+ pyopenapi_gen-0.15.0.dist-info/METADATA,sha256=iRFEkYUsm_-6q06MRBl03b1y3J7Utw1xqiZ7NF6aFSQ,14025
129
+ pyopenapi_gen-0.15.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
130
+ pyopenapi_gen-0.15.0.dist-info/entry_points.txt,sha256=gxSlNiwom50T3OEZnlocA6qRjGdV0bn6hN_Xr-Ub5wA,56
131
+ pyopenapi_gen-0.15.0.dist-info/licenses/LICENSE,sha256=UFAyTWKa4w10-QerlJaHJeep7G2gcwpf-JmvI2dS2Gc,1088
132
+ pyopenapi_gen-0.15.0.dist-info/RECORD,,