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 +1 -1
- pyopenapi_gen/context/render_context.py +97 -1
- pyopenapi_gen/core/writers/python_construct_renderer.py +2 -15
- pyopenapi_gen/types/resolvers/schema_resolver.py +20 -2
- pyopenapi_gen/types/services/type_service.py +4 -0
- pyopenapi_gen/visit/model/dataclass_generator.py +2 -1
- {pyopenapi_gen-0.14.2.dist-info → pyopenapi_gen-0.15.0.dist-info}/METADATA +1 -1
- {pyopenapi_gen-0.14.2.dist-info → pyopenapi_gen-0.15.0.dist-info}/RECORD +11 -11
- {pyopenapi_gen-0.14.2.dist-info → pyopenapi_gen-0.15.0.dist-info}/WHEEL +0 -0
- {pyopenapi_gen-0.14.2.dist-info → pyopenapi_gen-0.15.0.dist-info}/entry_points.txt +0 -0
- {pyopenapi_gen-0.14.2.dist-info → pyopenapi_gen-0.15.0.dist-info}/licenses/LICENSE +0 -0
pyopenapi_gen/__init__.py
CHANGED
@@ -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)
|
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
|
187
|
-
|
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
|
205
|
-
|
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
|
-
|
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.
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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.
|
129
|
-
pyopenapi_gen-0.
|
130
|
-
pyopenapi_gen-0.
|
131
|
-
pyopenapi_gen-0.
|
132
|
-
pyopenapi_gen-0.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|