satvu 0.2.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.
- builder/__init__.py +8 -0
- builder/__main__.py +12 -0
- builder/ast_generator.py +555 -0
- builder/build.py +457 -0
- builder/config.py +32 -0
- builder/jinja_filters/__init__.py +3 -0
- builder/jinja_filters/to_pydantic_model_field.py +35 -0
- builder/load.py +154 -0
- builder/openapi_preprocessor.py +86 -0
- builder/patches.py +769 -0
- builder/schema_utils.py +249 -0
- builder/streaming_detector.py +214 -0
- builder/streaming_post_processor.py +121 -0
- builder/templates/endpoint_module.py.jinja +325 -0
- builder/templates/macros/return_annotation.jinja +46 -0
- builder/templates/macros/test_error.jinja +80 -0
- builder/templates/macros/test_helpers.jinja +93 -0
- builder/templates/macros/test_no_content.jinja +31 -0
- builder/templates/macros/test_success.jinja +50 -0
- builder/templates/model.py.jinja +54 -0
- builder/templates/models_init.py.jinja +23 -0
- builder/templates/property_templates/list_property.py.jinja +55 -0
- builder/templates/property_templates/model_property.py.jinja +38 -0
- builder/templates/property_templates/union_property.py.jinja +97 -0
- builder/templates/test_module.py.jinja +114 -0
- builder/templates/test_schemas.py.jinja +80 -0
- builder/test_generator.py +465 -0
- satvu/__init__.py +11 -0
- satvu/auth.py +248 -0
- satvu/auth_test.py +415 -0
- satvu/core.py +306 -0
- satvu/core_retry_test.py +242 -0
- satvu/core_streaming_test.py +268 -0
- satvu/core_test.py +588 -0
- satvu/http/__init__.py +130 -0
- satvu/http/errors.py +499 -0
- satvu/http/httpx_adapter.py +300 -0
- satvu/http/httpx_adapter_test.py +236 -0
- satvu/http/protocol.py +132 -0
- satvu/http/requests_adapter.py +298 -0
- satvu/http/requests_adapter_test.py +284 -0
- satvu/http/stdlib_adapter.py +356 -0
- satvu/http/stdlib_adapter_test.py +208 -0
- satvu/http/urllib3_adapter.py +351 -0
- satvu/http/urllib3_adapter_test.py +282 -0
- satvu/py.typed +0 -0
- satvu/result.py +351 -0
- satvu/sdk.py +161 -0
- satvu/services/catalog/__init__.py +1 -0
- satvu/services/catalog/api.py +576 -0
- satvu/services/catalog/api_test.py +1165 -0
- satvu/services/catalog/models/__init__.py +159 -0
- satvu/services/catalog/models/and_or_expression.py +40 -0
- satvu/services/catalog/models/and_or_expression_op.py +9 -0
- satvu/services/catalog/models/api_error.py +21 -0
- satvu/services/catalog/models/arithmetic_expression.py +25 -0
- satvu/services/catalog/models/arithmetic_expression_op.py +14 -0
- satvu/services/catalog/models/asset.py +43 -0
- satvu/services/catalog/models/asset_bands.py +62 -0
- satvu/services/catalog/models/asset_bands_nodata_type_1.py +10 -0
- satvu/services/catalog/models/asset_bands_statistics.py +45 -0
- satvu/services/catalog/models/bbox_literal.py +16 -0
- satvu/services/catalog/models/binary_comparison_predicate.py +36 -0
- satvu/services/catalog/models/binary_comparison_predicate_op.py +13 -0
- satvu/services/catalog/models/catalog.py +51 -0
- satvu/services/catalog/models/collection.py +64 -0
- satvu/services/catalog/models/collections.py +28 -0
- satvu/services/catalog/models/conformance.py +18 -0
- satvu/services/catalog/models/conformance_response_429.py +18 -0
- satvu/services/catalog/models/cql_2_queryables_schema.py +28 -0
- satvu/services/catalog/models/data_type_of_the_band.py +23 -0
- satvu/services/catalog/models/date_instant.py +14 -0
- satvu/services/catalog/models/error.py +30 -0
- satvu/services/catalog/models/extent.py +31 -0
- satvu/services/catalog/models/feature.py +89 -0
- satvu/services/catalog/models/feature_collection.py +40 -0
- satvu/services/catalog/models/feature_properties.py +109 -0
- satvu/services/catalog/models/geo_json_geometry_collection.py +38 -0
- satvu/services/catalog/models/geo_json_geometry_collection_type.py +8 -0
- satvu/services/catalog/models/geo_json_line_string.py +22 -0
- satvu/services/catalog/models/geo_json_line_string_type.py +8 -0
- satvu/services/catalog/models/geo_json_multi_line_string.py +24 -0
- satvu/services/catalog/models/geo_json_multi_line_string_type.py +8 -0
- satvu/services/catalog/models/geo_json_multi_point.py +22 -0
- satvu/services/catalog/models/geo_json_multi_point_type.py +8 -0
- satvu/services/catalog/models/geo_json_multi_polygon.py +24 -0
- satvu/services/catalog/models/geo_json_multi_polygon_type.py +8 -0
- satvu/services/catalog/models/geo_json_point.py +22 -0
- satvu/services/catalog/models/geo_json_point_type.py +8 -0
- satvu/services/catalog/models/geo_json_polygon.py +24 -0
- satvu/services/catalog/models/geo_json_polygon_type.py +8 -0
- satvu/services/catalog/models/geojson_crs.py +20 -0
- satvu/services/catalog/models/geojson_geometry.py +32 -0
- satvu/services/catalog/models/get_collection_response_429.py +18 -0
- satvu/services/catalog/models/get_collections_response_429.py +18 -0
- satvu/services/catalog/models/get_item_collection_response_429.py +18 -0
- satvu/services/catalog/models/get_item_response_429.py +18 -0
- satvu/services/catalog/models/get_search_response_429.py +18 -0
- satvu/services/catalog/models/http_error.py +22 -0
- satvu/services/catalog/models/is_between_predicate.py +26 -0
- satvu/services/catalog/models/is_between_predicate_op.py +8 -0
- satvu/services/catalog/models/is_in_list_predicate.py +48 -0
- satvu/services/catalog/models/is_in_list_predicate_op.py +8 -0
- satvu/services/catalog/models/is_like_predicate.py +23 -0
- satvu/services/catalog/models/is_like_predicate_op.py +8 -0
- satvu/services/catalog/models/is_null_predicate.py +68 -0
- satvu/services/catalog/models/is_null_predicate_op.py +8 -0
- satvu/services/catalog/models/landing_page_response_429.py +18 -0
- satvu/services/catalog/models/link.py +54 -0
- satvu/services/catalog/models/not_expression.py +40 -0
- satvu/services/catalog/models/not_expression_op.py +8 -0
- satvu/services/catalog/models/pixel_sampling_in_the_band.py +9 -0
- satvu/services/catalog/models/post_search_input.py +111 -0
- satvu/services/catalog/models/post_search_response_429.py +18 -0
- satvu/services/catalog/models/property_ref.py +14 -0
- satvu/services/catalog/models/queryable_property.py +22 -0
- satvu/services/catalog/models/queryables_response_429.py +18 -0
- satvu/services/catalog/models/search_response.py +52 -0
- satvu/services/catalog/models/sort_by_element.py +22 -0
- satvu/services/catalog/models/spatial_extent.py +19 -0
- satvu/services/catalog/models/stac_geometry.py +35 -0
- satvu/services/catalog/models/temporal_extent.py +21 -0
- satvu/services/catalog/models/timestamp_instant.py +14 -0
- satvu/services/catalog/test_schemas.py +48120 -0
- satvu/services/conftest.py +57 -0
- satvu/services/cos/__init__.py +1 -0
- satvu/services/cos/api.py +572 -0
- satvu/services/cos/api_test.py +1298 -0
- satvu/services/cos/models/__init__.py +89 -0
- satvu/services/cos/models/collection.py +9 -0
- satvu/services/cos/models/feature_collection_order.py +64 -0
- satvu/services/cos/models/feature_order.py +39 -0
- satvu/services/cos/models/geojson_polygon.py +20 -0
- satvu/services/cos/models/http_exception_response.py +14 -0
- satvu/services/cos/models/http_validation_error.py +21 -0
- satvu/services/cos/models/link.py +59 -0
- satvu/services/cos/models/method.py +9 -0
- satvu/services/cos/models/order.py +36 -0
- satvu/services/cos/models/order_download_url.py +23 -0
- satvu/services/cos/models/order_edit_payload.py +17 -0
- satvu/services/cos/models/order_item_download_url.py +23 -0
- satvu/services/cos/models/order_item_price.py +22 -0
- satvu/services/cos/models/order_page.py +40 -0
- satvu/services/cos/models/order_price.py +41 -0
- satvu/services/cos/models/order_pricing.py +44 -0
- satvu/services/cos/models/order_submission_payload.py +27 -0
- satvu/services/cos/models/point_geometry.py +22 -0
- satvu/services/cos/models/polygon.py +20 -0
- satvu/services/cos/models/polygon_1.py +20 -0
- satvu/services/cos/models/polygon_geometry.py +24 -0
- satvu/services/cos/models/price_information.py +50 -0
- satvu/services/cos/models/price_request.py +28 -0
- satvu/services/cos/models/primary_format.py +9 -0
- satvu/services/cos/models/reseller_feature_collection_order.py +70 -0
- satvu/services/cos/models/reseller_order_price.py +49 -0
- satvu/services/cos/models/reseller_price_request.py +36 -0
- satvu/services/cos/models/reseller_submission_order_payload.py +35 -0
- satvu/services/cos/models/response_context.py +25 -0
- satvu/services/cos/models/satvu_filter.py +9 -0
- satvu/services/cos/models/search_request.py +26 -0
- satvu/services/cos/models/stac_metadata.py +42 -0
- satvu/services/cos/models/stac_properties_v4.py +83 -0
- satvu/services/cos/models/stac_properties_v7.py +155 -0
- satvu/services/cos/models/stac_properties_v9.py +161 -0
- satvu/services/cos/models/validation_error.py +18 -0
- satvu/services/cos/test_schemas.py +73873 -0
- satvu/services/id/__init__.py +1 -0
- satvu/services/id/api.py +554 -0
- satvu/services/id/api_test.py +1523 -0
- satvu/services/id/models/__init__.py +81 -0
- satvu/services/id/models/client_credentials.py +16 -0
- satvu/services/id/models/client_id.py +14 -0
- satvu/services/id/models/core_webhook.py +29 -0
- satvu/services/id/models/create_webhook_response.py +56 -0
- satvu/services/id/models/edit_webhook_payload.py +34 -0
- satvu/services/id/models/error_response.py +14 -0
- satvu/services/id/models/http_validation_error.py +21 -0
- satvu/services/id/models/link.py +22 -0
- satvu/services/id/models/list_response_context.py +24 -0
- satvu/services/id/models/list_webhook_response.py +29 -0
- satvu/services/id/models/notification_category.py +8 -0
- satvu/services/id/models/notification_config.py +24 -0
- satvu/services/id/models/notification_description.py +20 -0
- satvu/services/id/models/notification_settings.py +26 -0
- satvu/services/id/models/notification_update.py +31 -0
- satvu/services/id/models/post_webhook_response.py +50 -0
- satvu/services/id/models/reseller_notification_category.py +9 -0
- satvu/services/id/models/reseller_notification_config.py +22 -0
- satvu/services/id/models/reseller_notification_description.py +22 -0
- satvu/services/id/models/reseller_notification_update.py +32 -0
- satvu/services/id/models/reseller_webhook_event.py +8 -0
- satvu/services/id/models/test_webhook_response.py +47 -0
- satvu/services/id/models/topic.py +9 -0
- satvu/services/id/models/user_info.py +33 -0
- satvu/services/id/models/user_metadata.py +27 -0
- satvu/services/id/models/user_settings.py +28 -0
- satvu/services/id/models/validation_error.py +18 -0
- satvu/services/id/models/verbose_notification.py +33 -0
- satvu/services/id/models/webhook_event.py +8 -0
- satvu/services/id/models/webhook_failure_title.py +10 -0
- satvu/services/id/models/webhook_response.py +44 -0
- satvu/services/id/models/webhook_result.py +40 -0
- satvu/services/id/test_schemas.py +38179 -0
- satvu/services/otm/__init__.py +1 -0
- satvu/services/otm/api.py +866 -0
- satvu/services/otm/api_test.py +2822 -0
- satvu/services/otm/models/__init__.py +217 -0
- satvu/services/otm/models/asset.py +22 -0
- satvu/services/otm/models/assured_feasibility_fields.py +24 -0
- satvu/services/otm/models/assured_feasibility_fields_with_addons.py +38 -0
- satvu/services/otm/models/assured_feasibility_response_feature.py +38 -0
- satvu/services/otm/models/assured_feasibility_response_properties.py +87 -0
- satvu/services/otm/models/assured_order_request.py +21 -0
- satvu/services/otm/models/assured_order_request_properties.py +40 -0
- satvu/services/otm/models/assured_stored_feasibility_request_properties.py +44 -0
- satvu/services/otm/models/collection.py +9 -0
- satvu/services/otm/models/collections.py +10 -0
- satvu/services/otm/models/day_night_mode.py +10 -0
- satvu/services/otm/models/direction.py +9 -0
- satvu/services/otm/models/edit_order_payload.py +35 -0
- satvu/services/otm/models/edit_order_properties.py +84 -0
- satvu/services/otm/models/error_response.py +17 -0
- satvu/services/otm/models/extra_ignore_assured_feasibility_response_properties.py +86 -0
- satvu/services/otm/models/feasibility_request.py +31 -0
- satvu/services/otm/models/feasibility_request_status.py +12 -0
- satvu/services/otm/models/feasibility_response.py +52 -0
- satvu/services/otm/models/filter_.py +9 -0
- satvu/services/otm/models/filter_fields.py +24 -0
- satvu/services/otm/models/full_well_capacitance.py +9 -0
- satvu/services/otm/models/geometry_collection.py +42 -0
- satvu/services/otm/models/get_assured_order_properties.py +111 -0
- satvu/services/otm/models/get_order_response.py +44 -0
- satvu/services/otm/models/get_standard_order_properties.py +109 -0
- satvu/services/otm/models/http_validation_error.py +21 -0
- satvu/services/otm/models/line_string.py +23 -0
- satvu/services/otm/models/link.py +54 -0
- satvu/services/otm/models/list_stored_orders_response.py +36 -0
- satvu/services/otm/models/modify_feasibility_request.py +42 -0
- satvu/services/otm/models/modify_feasibility_request_properties.py +52 -0
- satvu/services/otm/models/multi_line_string.py +25 -0
- satvu/services/otm/models/multi_point.py +23 -0
- satvu/services/otm/models/multi_polygon.py +25 -0
- satvu/services/otm/models/order_item_download_url.py +23 -0
- satvu/services/otm/models/order_modification_price.py +36 -0
- satvu/services/otm/models/order_price.py +44 -0
- satvu/services/otm/models/order_status.py +15 -0
- satvu/services/otm/models/outage.py +29 -0
- satvu/services/otm/models/point.py +21 -0
- satvu/services/otm/models/point_geometry.py +22 -0
- satvu/services/otm/models/polygon.py +23 -0
- satvu/services/otm/models/polygon_geometry.py +24 -0
- satvu/services/otm/models/price.py +56 -0
- satvu/services/otm/models/price_information.py +65 -0
- satvu/services/otm/models/price_request.py +37 -0
- satvu/services/otm/models/primary_format.py +9 -0
- satvu/services/otm/models/readout_mode.py +9 -0
- satvu/services/otm/models/request_method.py +9 -0
- satvu/services/otm/models/reseller_assured_order_request.py +27 -0
- satvu/services/otm/models/reseller_get_order_response.py +48 -0
- satvu/services/otm/models/reseller_search_response_feature_assured_order_request.py +59 -0
- satvu/services/otm/models/reseller_search_response_feature_standard_order_request.py +59 -0
- satvu/services/otm/models/reseller_standard_order_request.py +34 -0
- satvu/services/otm/models/reseller_stored_order_response.py +54 -0
- satvu/services/otm/models/response_context.py +25 -0
- satvu/services/otm/models/search_assured_order_properties.py +111 -0
- satvu/services/otm/models/search_request.py +92 -0
- satvu/services/otm/models/search_response.py +77 -0
- satvu/services/otm/models/search_response_feature_assured_feasibility_request.py +54 -0
- satvu/services/otm/models/search_response_feature_assured_feasibility_response.py +54 -0
- satvu/services/otm/models/search_response_feature_assured_order_request.py +55 -0
- satvu/services/otm/models/search_response_feature_standard_feasibility_request.py +54 -0
- satvu/services/otm/models/search_response_feature_standard_feasibility_response.py +54 -0
- satvu/services/otm/models/search_response_feature_standard_order_request.py +55 -0
- satvu/services/otm/models/search_standard_order_properties.py +109 -0
- satvu/services/otm/models/sort_entities.py +21 -0
- satvu/services/otm/models/sortable_field.py +8 -0
- satvu/services/otm/models/stac_feature.py +66 -0
- satvu/services/otm/models/standard_feasibility_response_feature.py +36 -0
- satvu/services/otm/models/standard_feasibility_response_properties.py +100 -0
- satvu/services/otm/models/standard_order_fields_with_addons.py +65 -0
- satvu/services/otm/models/standard_order_request.py +29 -0
- satvu/services/otm/models/standard_order_request_properties.py +71 -0
- satvu/services/otm/models/standard_price_request_properties.py +65 -0
- satvu/services/otm/models/standard_request_properties.py +51 -0
- satvu/services/otm/models/standard_stored_feasibility_request_properties.py +70 -0
- satvu/services/otm/models/stored_assured_order_request_properties.py +90 -0
- satvu/services/otm/models/stored_feasibility_feature_collection.py +36 -0
- satvu/services/otm/models/stored_feasibility_request.py +50 -0
- satvu/services/otm/models/stored_order_response.py +50 -0
- satvu/services/otm/models/stored_standard_order_request_properties.py +88 -0
- satvu/services/otm/models/validation_error.py +18 -0
- satvu/services/otm/models/validation_error_detail.py +27 -0
- satvu/services/otm/models/validation_error_response.py +23 -0
- satvu/services/otm/test_schemas.py +444886 -0
- satvu/services/policy/__init__.py +1 -0
- satvu/services/policy/api.py +117 -0
- satvu/services/policy/api_test.py +264 -0
- satvu/services/policy/models/__init__.py +39 -0
- satvu/services/policy/models/civil_date.py +21 -0
- satvu/services/policy/models/contracts_addon.py +25 -0
- satvu/services/policy/models/contracts_addon_option.py +34 -0
- satvu/services/policy/models/contracts_contract_with_products.py +74 -0
- satvu/services/policy/models/contracts_geometry.py +23 -0
- satvu/services/policy/models/contracts_product.py +18 -0
- satvu/services/policy/models/list_active_contracts_input.py +14 -0
- satvu/services/policy/models/router_active_contracts_response.py +27 -0
- satvu/services/policy/models/router_http_error.py +22 -0
- satvu/services/policy/models/terms_user_terms_accepted.py +20 -0
- satvu/services/policy/models/user_acceptance_terms_input.py +18 -0
- satvu/services/policy/test_schemas.py +2061 -0
- satvu/services/reseller/__init__.py +1 -0
- satvu/services/reseller/api.py +418 -0
- satvu/services/reseller/api_test.py +532 -0
- satvu/services/reseller/models/__init__.py +59 -0
- satvu/services/reseller/models/company_address.py +28 -0
- satvu/services/reseller/models/company_search.py +28 -0
- satvu/services/reseller/models/company_search_fields.py +9 -0
- satvu/services/reseller/models/country_code.py +242 -0
- satvu/services/reseller/models/create_user.py +34 -0
- satvu/services/reseller/models/create_user_response.py +68 -0
- satvu/services/reseller/models/get_companies.py +36 -0
- satvu/services/reseller/models/get_company.py +46 -0
- satvu/services/reseller/models/get_user.py +72 -0
- satvu/services/reseller/models/get_users.py +36 -0
- satvu/services/reseller/models/http_validation_error.py +21 -0
- satvu/services/reseller/models/kyc_status.py +10 -0
- satvu/services/reseller/models/link.py +52 -0
- satvu/services/reseller/models/match_type.py +9 -0
- satvu/services/reseller/models/request_method.py +8 -0
- satvu/services/reseller/models/response_context.py +25 -0
- satvu/services/reseller/models/search_companies.py +39 -0
- satvu/services/reseller/models/search_users.py +37 -0
- satvu/services/reseller/models/user_search.py +28 -0
- satvu/services/reseller/models/user_search_fields.py +11 -0
- satvu/services/reseller/models/validation_error.py +18 -0
- satvu/services/reseller/test_schemas.py +15639 -0
- satvu/services/wallet/__init__.py +1 -0
- satvu/services/wallet/api.py +103 -0
- satvu/services/wallet/api_test.py +196 -0
- satvu/services/wallet/models/__init__.py +25 -0
- satvu/services/wallet/models/batch_balance_response.py +18 -0
- satvu/services/wallet/models/credit_balance_response.py +29 -0
- satvu/services/wallet/models/http_validation_error.py +21 -0
- satvu/services/wallet/models/validation_error.py +18 -0
- satvu/services/wallet/test_schemas.py +687 -0
- satvu/shared/__init__.py +0 -0
- satvu/shared/parsing.py +136 -0
- satvu/shared/parsing_test.py +387 -0
- satvu-0.2.0.dist-info/METADATA +107 -0
- satvu-0.2.0.dist-info/RECORD +352 -0
- satvu-0.2.0.dist-info/WHEEL +4 -0
- satvu-0.2.0.dist-info/licenses/LICENSE +21 -0
builder/__init__.py
ADDED
builder/__main__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from argparse import ArgumentParser
|
|
2
|
+
|
|
3
|
+
from builder.build import build
|
|
4
|
+
from builder.config import CMD_ARGS
|
|
5
|
+
|
|
6
|
+
parser = ArgumentParser(__package__, description="OpenAPI SDK builder")
|
|
7
|
+
parser.add_argument("api", choices=CMD_ARGS.keys())
|
|
8
|
+
parser.add_argument("--cached", action="store_true", default=False)
|
|
9
|
+
|
|
10
|
+
args = parser.parse_args()
|
|
11
|
+
|
|
12
|
+
build(api_id=args.api, use_cached=args.cached)
|
builder/ast_generator.py
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
"""AST-based code generation for streaming download methods.
|
|
2
|
+
|
|
3
|
+
This module provides robust, type-safe code generation using Python's AST module
|
|
4
|
+
instead of fragile string templates. Guarantees syntactically correct Python code.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import ast
|
|
8
|
+
|
|
9
|
+
from builder.streaming_detector import StreamingEndpointConfig
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ASTMethodBuilder:
|
|
13
|
+
"""Builder for generating streaming method AST nodes."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, config: StreamingEndpointConfig):
|
|
16
|
+
"""
|
|
17
|
+
Initialize builder with streaming endpoint configuration.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
config: Configuration for the streaming endpoint
|
|
21
|
+
"""
|
|
22
|
+
self.config = config
|
|
23
|
+
|
|
24
|
+
def build_method(self) -> ast.FunctionDef:
|
|
25
|
+
"""
|
|
26
|
+
Build complete streaming method as AST node.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
AST FunctionDef node for the streaming method
|
|
30
|
+
"""
|
|
31
|
+
# Path params are always required (used in URL formatting)
|
|
32
|
+
path_params = self.config.path_params
|
|
33
|
+
|
|
34
|
+
# Query params are all optional (added to params dict with defaults)
|
|
35
|
+
query_params = self.config.query_params
|
|
36
|
+
|
|
37
|
+
# Build function signature
|
|
38
|
+
args = self._build_arguments(path_params, query_params)
|
|
39
|
+
|
|
40
|
+
# Build function body
|
|
41
|
+
body = self._build_body(path_params, query_params)
|
|
42
|
+
|
|
43
|
+
# Build docstring
|
|
44
|
+
docstring_node = self._build_docstring(path_params, query_params)
|
|
45
|
+
|
|
46
|
+
# Combine docstring + body
|
|
47
|
+
full_body = [docstring_node] + body
|
|
48
|
+
|
|
49
|
+
# Build return annotation
|
|
50
|
+
return_annotation = self._build_return_annotation()
|
|
51
|
+
|
|
52
|
+
return ast.FunctionDef(
|
|
53
|
+
name=self.config.stream_method,
|
|
54
|
+
args=args,
|
|
55
|
+
body=full_body,
|
|
56
|
+
decorator_list=[],
|
|
57
|
+
returns=return_annotation,
|
|
58
|
+
lineno=0,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def _build_arguments(
|
|
62
|
+
self,
|
|
63
|
+
path_params: list[tuple[str, str]],
|
|
64
|
+
query_params: list[tuple[str, str]],
|
|
65
|
+
) -> ast.arguments:
|
|
66
|
+
"""Build function arguments with defaults."""
|
|
67
|
+
# Start with self
|
|
68
|
+
args = [ast.arg(arg="self", annotation=None)]
|
|
69
|
+
|
|
70
|
+
# Add path params (required positional - contract_id, order_id, etc.)
|
|
71
|
+
for name, type_ in path_params:
|
|
72
|
+
args.append(ast.arg(arg=name, annotation=parse_type_annotation(type_)))
|
|
73
|
+
|
|
74
|
+
# Add output_path (required positional)
|
|
75
|
+
args.append(
|
|
76
|
+
ast.arg(
|
|
77
|
+
arg="output_path",
|
|
78
|
+
annotation=ast.BinOp(
|
|
79
|
+
left=ast.Name(id="Path", ctx=ast.Load()),
|
|
80
|
+
op=ast.BitOr(),
|
|
81
|
+
right=ast.Name(id="str", ctx=ast.Load()),
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Add query params as keyword-only with None defaults
|
|
87
|
+
positional_args = []
|
|
88
|
+
kw_args = []
|
|
89
|
+
kw_defaults: list[ast.expr | None] = []
|
|
90
|
+
|
|
91
|
+
for name, type_ in query_params:
|
|
92
|
+
kw_args.append(
|
|
93
|
+
ast.arg(
|
|
94
|
+
arg=name,
|
|
95
|
+
annotation=parse_type_annotation(type_),
|
|
96
|
+
)
|
|
97
|
+
)
|
|
98
|
+
# All query params default to None
|
|
99
|
+
kw_defaults.append(ast.Constant(value=None))
|
|
100
|
+
|
|
101
|
+
# Add standard streaming params (chunk_size, progress_callback, timeout)
|
|
102
|
+
kw_args.extend(
|
|
103
|
+
[
|
|
104
|
+
ast.arg(
|
|
105
|
+
arg="chunk_size", annotation=ast.Name(id="int", ctx=ast.Load())
|
|
106
|
+
),
|
|
107
|
+
ast.arg(
|
|
108
|
+
arg="progress_callback",
|
|
109
|
+
annotation=ast.BinOp(
|
|
110
|
+
left=ast.Subscript(
|
|
111
|
+
value=ast.Name(id="Callable", ctx=ast.Load()),
|
|
112
|
+
slice=ast.Tuple(
|
|
113
|
+
elts=[
|
|
114
|
+
ast.List(
|
|
115
|
+
elts=[
|
|
116
|
+
ast.Name(id="int", ctx=ast.Load()),
|
|
117
|
+
ast.BinOp(
|
|
118
|
+
left=ast.Name(id="int", ctx=ast.Load()),
|
|
119
|
+
op=ast.BitOr(),
|
|
120
|
+
right=ast.Constant(value=None),
|
|
121
|
+
),
|
|
122
|
+
],
|
|
123
|
+
ctx=ast.Load(),
|
|
124
|
+
),
|
|
125
|
+
ast.Constant(value=None),
|
|
126
|
+
],
|
|
127
|
+
ctx=ast.Load(),
|
|
128
|
+
),
|
|
129
|
+
ctx=ast.Load(),
|
|
130
|
+
),
|
|
131
|
+
op=ast.BitOr(),
|
|
132
|
+
right=ast.Constant(value=None),
|
|
133
|
+
),
|
|
134
|
+
),
|
|
135
|
+
ast.arg(
|
|
136
|
+
arg="timeout",
|
|
137
|
+
annotation=ast.BinOp(
|
|
138
|
+
left=ast.Name(id="int", ctx=ast.Load()),
|
|
139
|
+
op=ast.BitOr(),
|
|
140
|
+
right=ast.Constant(value=None),
|
|
141
|
+
),
|
|
142
|
+
),
|
|
143
|
+
]
|
|
144
|
+
)
|
|
145
|
+
kw_defaults.extend(
|
|
146
|
+
[
|
|
147
|
+
ast.Constant(value=self.config.default_chunk_size),
|
|
148
|
+
ast.Constant(value=None), # progress_callback default
|
|
149
|
+
ast.Constant(value=None), # timeout default
|
|
150
|
+
]
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
return ast.arguments(
|
|
154
|
+
posonlyargs=positional_args,
|
|
155
|
+
args=args,
|
|
156
|
+
kwonlyargs=kw_args,
|
|
157
|
+
kw_defaults=kw_defaults,
|
|
158
|
+
defaults=[],
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def _build_body(
|
|
162
|
+
self,
|
|
163
|
+
path_params: list[tuple[str, str]],
|
|
164
|
+
query_params: list[tuple[str, str]],
|
|
165
|
+
) -> list[ast.stmt]:
|
|
166
|
+
"""Build method body statements."""
|
|
167
|
+
body: list[ast.stmt] = []
|
|
168
|
+
|
|
169
|
+
# 1. Build params dict (redirect + all query params)
|
|
170
|
+
params_dict_keys: list[ast.expr | None] = [ast.Constant(value="redirect")]
|
|
171
|
+
params_dict_values: list[ast.expr] = [ast.Constant(value=True)]
|
|
172
|
+
|
|
173
|
+
# Add all query params to params dict
|
|
174
|
+
for name, _ in query_params:
|
|
175
|
+
params_dict_keys.append(ast.Constant(value=name))
|
|
176
|
+
params_dict_values.append(ast.Name(id=name, ctx=ast.Load()))
|
|
177
|
+
|
|
178
|
+
body.append(
|
|
179
|
+
ast.Assign(
|
|
180
|
+
targets=[ast.Name(id="params", ctx=ast.Store())],
|
|
181
|
+
value=ast.Dict(keys=params_dict_keys, values=params_dict_values),
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# 2. Build URL format args (only path params)
|
|
186
|
+
url_format_keywords = [
|
|
187
|
+
ast.keyword(arg=name, value=ast.Name(id=name, ctx=ast.Load()))
|
|
188
|
+
for name, _ in path_params
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
# 3. Call self.make_request()
|
|
192
|
+
body.append(
|
|
193
|
+
ast.Assign(
|
|
194
|
+
targets=[ast.Name(id="result", ctx=ast.Store())],
|
|
195
|
+
value=ast.Call(
|
|
196
|
+
func=ast.Attribute(
|
|
197
|
+
value=ast.Name(id="self", ctx=ast.Load()),
|
|
198
|
+
attr="make_request",
|
|
199
|
+
ctx=ast.Load(),
|
|
200
|
+
),
|
|
201
|
+
args=[],
|
|
202
|
+
keywords=[
|
|
203
|
+
ast.keyword(arg="method", value=ast.Constant(value="get")),
|
|
204
|
+
ast.keyword(
|
|
205
|
+
arg="url",
|
|
206
|
+
value=ast.Call(
|
|
207
|
+
func=ast.Attribute(
|
|
208
|
+
value=ast.Constant(value=self.config.url_pattern),
|
|
209
|
+
attr="format",
|
|
210
|
+
ctx=ast.Load(),
|
|
211
|
+
),
|
|
212
|
+
args=[],
|
|
213
|
+
keywords=url_format_keywords,
|
|
214
|
+
),
|
|
215
|
+
),
|
|
216
|
+
ast.keyword(
|
|
217
|
+
arg="params", value=ast.Name(id="params", ctx=ast.Load())
|
|
218
|
+
),
|
|
219
|
+
ast.keyword(
|
|
220
|
+
arg="follow_redirects", value=ast.Constant(value=True)
|
|
221
|
+
),
|
|
222
|
+
ast.keyword(
|
|
223
|
+
arg="timeout", value=ast.Name(id="timeout", ctx=ast.Load())
|
|
224
|
+
),
|
|
225
|
+
],
|
|
226
|
+
),
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
# 4. Error check: if is_err(result): return ResultErr(result.error())
|
|
231
|
+
# We use the is_err() type guard function (not the method) so pyright can
|
|
232
|
+
# narrow the type and know result.error() is valid
|
|
233
|
+
body.append(
|
|
234
|
+
ast.If(
|
|
235
|
+
test=ast.Call(
|
|
236
|
+
func=ast.Name(id="is_err", ctx=ast.Load()),
|
|
237
|
+
args=[ast.Name(id="result", ctx=ast.Load())],
|
|
238
|
+
keywords=[],
|
|
239
|
+
),
|
|
240
|
+
body=[
|
|
241
|
+
ast.Return(
|
|
242
|
+
value=ast.Call(
|
|
243
|
+
func=ast.Name(id="ResultErr", ctx=ast.Load()),
|
|
244
|
+
args=[
|
|
245
|
+
ast.Call(
|
|
246
|
+
func=ast.Attribute(
|
|
247
|
+
value=ast.Name(id="result", ctx=ast.Load()),
|
|
248
|
+
attr="error",
|
|
249
|
+
ctx=ast.Load(),
|
|
250
|
+
),
|
|
251
|
+
args=[],
|
|
252
|
+
keywords=[],
|
|
253
|
+
)
|
|
254
|
+
],
|
|
255
|
+
keywords=[],
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
],
|
|
259
|
+
orelse=[],
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# 5. Unwrap response
|
|
264
|
+
body.append(
|
|
265
|
+
ast.Assign(
|
|
266
|
+
targets=[ast.Name(id="response", ctx=ast.Store())],
|
|
267
|
+
value=ast.Call(
|
|
268
|
+
func=ast.Attribute(
|
|
269
|
+
value=ast.Name(id="result", ctx=ast.Load()),
|
|
270
|
+
attr="unwrap",
|
|
271
|
+
ctx=ast.Load(),
|
|
272
|
+
),
|
|
273
|
+
args=[],
|
|
274
|
+
keywords=[],
|
|
275
|
+
),
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# 6. Call self.stream_to_file()
|
|
280
|
+
body.append(
|
|
281
|
+
ast.Assign(
|
|
282
|
+
targets=[ast.Name(id="downloaded_path", ctx=ast.Store())],
|
|
283
|
+
value=ast.Call(
|
|
284
|
+
func=ast.Attribute(
|
|
285
|
+
value=ast.Name(id="self", ctx=ast.Load()),
|
|
286
|
+
attr="stream_to_file",
|
|
287
|
+
ctx=ast.Load(),
|
|
288
|
+
),
|
|
289
|
+
args=[],
|
|
290
|
+
keywords=[
|
|
291
|
+
ast.keyword(
|
|
292
|
+
arg="response",
|
|
293
|
+
value=ast.Name(id="response", ctx=ast.Load()),
|
|
294
|
+
),
|
|
295
|
+
ast.keyword(
|
|
296
|
+
arg="output_path",
|
|
297
|
+
value=ast.Name(id="output_path", ctx=ast.Load()),
|
|
298
|
+
),
|
|
299
|
+
ast.keyword(
|
|
300
|
+
arg="chunk_size",
|
|
301
|
+
value=ast.Name(id="chunk_size", ctx=ast.Load()),
|
|
302
|
+
),
|
|
303
|
+
ast.keyword(
|
|
304
|
+
arg="progress_callback",
|
|
305
|
+
value=ast.Name(id="progress_callback", ctx=ast.Load()),
|
|
306
|
+
),
|
|
307
|
+
],
|
|
308
|
+
),
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# 7. Return ResultOk(downloaded_path)
|
|
313
|
+
body.append(
|
|
314
|
+
ast.Return(
|
|
315
|
+
value=ast.Call(
|
|
316
|
+
func=ast.Name(id="ResultOk", ctx=ast.Load()),
|
|
317
|
+
args=[ast.Name(id="downloaded_path", ctx=ast.Load())],
|
|
318
|
+
keywords=[],
|
|
319
|
+
)
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
return body
|
|
324
|
+
|
|
325
|
+
def _build_docstring(
|
|
326
|
+
self,
|
|
327
|
+
path_params: list[tuple[str, str]],
|
|
328
|
+
query_params: list[tuple[str, str]],
|
|
329
|
+
) -> ast.Expr:
|
|
330
|
+
"""Build docstring as AST Expr node."""
|
|
331
|
+
|
|
332
|
+
# Build sections
|
|
333
|
+
sections = [
|
|
334
|
+
# Summary
|
|
335
|
+
[self.config.docstring],
|
|
336
|
+
# Description
|
|
337
|
+
[
|
|
338
|
+
"Downloads directly to disk using streaming, avoiding loading",
|
|
339
|
+
"the entire file into memory. Ideal for large files (1GB+).",
|
|
340
|
+
],
|
|
341
|
+
# Args
|
|
342
|
+
["Args:"]
|
|
343
|
+
+ [
|
|
344
|
+
f" {name} ({type_}): {_get_param_description(name)}"
|
|
345
|
+
for name, type_ in path_params
|
|
346
|
+
]
|
|
347
|
+
+ [" output_path (Path | str): Where to save the downloaded file."]
|
|
348
|
+
+ [
|
|
349
|
+
f" {name} ({type_}): {_get_param_description(name)}"
|
|
350
|
+
for name, type_ in query_params
|
|
351
|
+
]
|
|
352
|
+
+ [
|
|
353
|
+
f" chunk_size (int): Bytes per chunk (default: {self.config.default_chunk_size}). Use 64KB+ for faster downloads.",
|
|
354
|
+
" progress_callback: Optional callback for download progress tracking.",
|
|
355
|
+
" Signature: callback(bytes_downloaded: int, total_bytes: int | None)",
|
|
356
|
+
" timeout: Optional request timeout in seconds. Overrides the instance timeout.",
|
|
357
|
+
],
|
|
358
|
+
# Returns
|
|
359
|
+
[
|
|
360
|
+
"Returns:",
|
|
361
|
+
" Result[Path, HttpError]: Ok(Path) on success, Err(HttpError) on failure",
|
|
362
|
+
],
|
|
363
|
+
]
|
|
364
|
+
|
|
365
|
+
# Flatten sections with blank lines between them
|
|
366
|
+
parts = []
|
|
367
|
+
for i, section in enumerate(sections):
|
|
368
|
+
parts.extend(section)
|
|
369
|
+
if i < len(sections) - 1: # Add blank line between sections
|
|
370
|
+
parts.append("")
|
|
371
|
+
|
|
372
|
+
return ast.Expr(value=ast.Constant(value="\n".join(parts)))
|
|
373
|
+
|
|
374
|
+
def _build_return_annotation(self) -> ast.Subscript:
|
|
375
|
+
"""Build Result[Path, HttpError] return annotation."""
|
|
376
|
+
return ast.Subscript(
|
|
377
|
+
value=ast.Name(id="Result", ctx=ast.Load()),
|
|
378
|
+
slice=ast.Tuple(
|
|
379
|
+
elts=[
|
|
380
|
+
ast.Name(id="Path", ctx=ast.Load()),
|
|
381
|
+
ast.Name(id="HttpError", ctx=ast.Load()),
|
|
382
|
+
],
|
|
383
|
+
ctx=ast.Load(),
|
|
384
|
+
),
|
|
385
|
+
ctx=ast.Load(),
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def parse_type_annotation(type_str: str) -> ast.expr:
|
|
390
|
+
"""
|
|
391
|
+
Parse type annotation string into AST expression.
|
|
392
|
+
|
|
393
|
+
Handles common patterns:
|
|
394
|
+
- UUID → ast.Name('UUID')
|
|
395
|
+
- str → ast.Name('str')
|
|
396
|
+
- list[Foo] → ast.Subscript(...)
|
|
397
|
+
- Union[A, B] → ast.BinOp with BitOr
|
|
398
|
+
- None | int → ast.BinOp with BitOr
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
type_str: Type annotation as string (e.g., 'UUID', 'list[str]')
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
AST expression node representing the type
|
|
405
|
+
"""
|
|
406
|
+
# Use ast.parse to handle complex type annotations
|
|
407
|
+
# Wrap in a function signature to parse the type annotation
|
|
408
|
+
try:
|
|
409
|
+
module = ast.parse(f"def f(x: {type_str}): pass")
|
|
410
|
+
func = module.body[0]
|
|
411
|
+
if not isinstance(func, ast.FunctionDef):
|
|
412
|
+
return ast.Name(id=type_str, ctx=ast.Load())
|
|
413
|
+
annotation = func.args.args[0].annotation
|
|
414
|
+
if annotation is None:
|
|
415
|
+
return ast.Name(id=type_str, ctx=ast.Load())
|
|
416
|
+
return annotation
|
|
417
|
+
except (SyntaxError, IndexError):
|
|
418
|
+
# Fallback to simple Name node if parsing fails
|
|
419
|
+
return ast.Name(id=type_str, ctx=ast.Load())
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _get_param_description(param_name: str) -> str:
|
|
423
|
+
"""Get a generic description for a parameter."""
|
|
424
|
+
descriptions = {
|
|
425
|
+
"contract_id": "The contract ID",
|
|
426
|
+
"order_id": "The order ID",
|
|
427
|
+
"item_id": "The item ID",
|
|
428
|
+
"collections": "Optional subset of collections to download",
|
|
429
|
+
"primary_formats": "Optional file format(s) to download",
|
|
430
|
+
}
|
|
431
|
+
return descriptions.get(param_name, "Parameter")
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def generate_streaming_method(config: StreamingEndpointConfig) -> str:
|
|
435
|
+
"""
|
|
436
|
+
Generate streaming method code from config using AST.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
config: Streaming endpoint configuration
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
Generated method code as string
|
|
443
|
+
"""
|
|
444
|
+
builder = ASTMethodBuilder(config)
|
|
445
|
+
method_node = builder.build_method()
|
|
446
|
+
|
|
447
|
+
# Fix missing line numbers and column offsets
|
|
448
|
+
ast.fix_missing_locations(method_node)
|
|
449
|
+
|
|
450
|
+
# Convert AST to code string
|
|
451
|
+
code = ast.unparse(method_node)
|
|
452
|
+
|
|
453
|
+
return code
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def add_imports_to_ast(
|
|
457
|
+
tree: ast.Module, imports_to_add: dict[str, list[tuple[str, str | None]]]
|
|
458
|
+
) -> ast.Module:
|
|
459
|
+
"""
|
|
460
|
+
Add missing imports to AST module.
|
|
461
|
+
|
|
462
|
+
Args:
|
|
463
|
+
tree: Existing module AST
|
|
464
|
+
imports_to_add: Dict mapping module names to list of (name, alias) tuples
|
|
465
|
+
e.g., {'pathlib': [('Path', None)],
|
|
466
|
+
'satvu.result': [('Result', None), ('Ok', 'ResultOk')]}
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
Modified AST module with imports added
|
|
470
|
+
"""
|
|
471
|
+
# Find where to insert imports (after existing imports)
|
|
472
|
+
insert_idx = 0
|
|
473
|
+
for i, node in enumerate(tree.body):
|
|
474
|
+
if isinstance(node, ast.Import | ast.ImportFrom):
|
|
475
|
+
insert_idx = i + 1
|
|
476
|
+
|
|
477
|
+
# Check what imports already exist (including aliases)
|
|
478
|
+
existing_imports: dict[str, set[tuple[str, str | None]]] = {}
|
|
479
|
+
for node in tree.body:
|
|
480
|
+
if isinstance(node, ast.ImportFrom) and node.module:
|
|
481
|
+
if node.module not in existing_imports:
|
|
482
|
+
existing_imports[node.module] = set()
|
|
483
|
+
for alias in node.names:
|
|
484
|
+
if alias.name != "*":
|
|
485
|
+
existing_imports[node.module].add((alias.name, alias.asname))
|
|
486
|
+
|
|
487
|
+
# Build new import nodes for missing imports
|
|
488
|
+
new_imports = []
|
|
489
|
+
for module, names_with_aliases in imports_to_add.items():
|
|
490
|
+
existing = existing_imports.get(module, set())
|
|
491
|
+
missing = [
|
|
492
|
+
(name, alias)
|
|
493
|
+
for name, alias in names_with_aliases
|
|
494
|
+
if (name, alias) not in existing
|
|
495
|
+
]
|
|
496
|
+
|
|
497
|
+
if missing:
|
|
498
|
+
# Create ImportFrom node
|
|
499
|
+
import_node = ast.ImportFrom(
|
|
500
|
+
module=module,
|
|
501
|
+
names=[ast.alias(name=name, asname=alias) for name, alias in missing],
|
|
502
|
+
level=0,
|
|
503
|
+
)
|
|
504
|
+
new_imports.append(import_node)
|
|
505
|
+
|
|
506
|
+
# Insert new imports
|
|
507
|
+
tree.body[insert_idx:insert_idx] = new_imports
|
|
508
|
+
|
|
509
|
+
return tree
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def insert_method_after_base(
|
|
513
|
+
tree: ast.Module, base_method: str, new_method_code: str
|
|
514
|
+
) -> ast.Module:
|
|
515
|
+
"""
|
|
516
|
+
Insert new method after base method in module AST.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
tree: Module AST
|
|
520
|
+
base_method: Name of base method to insert after
|
|
521
|
+
new_method_code: Generated method code as string
|
|
522
|
+
|
|
523
|
+
Returns:
|
|
524
|
+
Modified AST module with new method inserted
|
|
525
|
+
"""
|
|
526
|
+
# Parse the new method code
|
|
527
|
+
new_method_tree = ast.parse(new_method_code)
|
|
528
|
+
new_method_node = new_method_tree.body[0]
|
|
529
|
+
if not isinstance(new_method_node, ast.FunctionDef):
|
|
530
|
+
return tree # Safety check - should never happen with our generated code
|
|
531
|
+
|
|
532
|
+
# Find the class definition (should be first class in module)
|
|
533
|
+
class_node = None
|
|
534
|
+
for node in tree.body:
|
|
535
|
+
if isinstance(node, ast.ClassDef):
|
|
536
|
+
class_node = node
|
|
537
|
+
break
|
|
538
|
+
|
|
539
|
+
if not class_node:
|
|
540
|
+
return tree
|
|
541
|
+
|
|
542
|
+
# Find base method in class
|
|
543
|
+
base_method_idx = None
|
|
544
|
+
for i, node in enumerate(class_node.body):
|
|
545
|
+
if isinstance(node, ast.FunctionDef) and node.name == base_method:
|
|
546
|
+
base_method_idx = i
|
|
547
|
+
break
|
|
548
|
+
|
|
549
|
+
if base_method_idx is None:
|
|
550
|
+
return tree
|
|
551
|
+
|
|
552
|
+
# Insert new method after base method
|
|
553
|
+
class_node.body.insert(base_method_idx + 1, new_method_node)
|
|
554
|
+
|
|
555
|
+
return tree
|