@@ -1774,72 +1774,114 @@ def get_openapi_schema(
17741774 OpenAPI: pydantic model
17751775 The OpenAPI schema as a pydantic model.
17761776 """
1777+ # Resolve configuration with fallbacks to openapi_config
1778+ config = self ._resolve_openapi_config (
1779+ title = title ,
1780+ version = version ,
1781+ openapi_version = openapi_version ,
1782+ summary = summary ,
1783+ description = description ,
1784+ tags = tags ,
1785+ servers = servers ,
1786+ terms_of_service = terms_of_service ,
1787+ contact = contact ,
1788+ license_info = license_info ,
1789+ security_schemes = security_schemes ,
1790+ security = security ,
1791+ external_documentation = external_documentation ,
1792+ openapi_extensions = openapi_extensions ,
1793+ )
17771794
1778- # DEPRECATION: Will be removed in v4.0.0. Use configure_api() instead.
1779- # Maintained for backwards compatibility.
1780- # See: https://github.com/aws-powertools/powertools-lambda-python/issues/6122
1781- if title == DEFAULT_OPENAPI_TITLE and self .openapi_config .title :
1782- title = self .openapi_config .title
1783-
1784- if version == DEFAULT_API_VERSION and self .openapi_config .version :
1785- version = self .openapi_config .version
1786-
1787- if openapi_version == DEFAULT_OPENAPI_VERSION and self .openapi_config .openapi_version :
1788- openapi_version = self .openapi_config .openapi_version
1789-
1790- summary = summary or self .openapi_config .summary
1791- description = description or self .openapi_config .description
1792- tags = tags or self .openapi_config .tags
1793- servers = servers or self .openapi_config .servers
1794- terms_of_service = terms_of_service or self .openapi_config .terms_of_service
1795- contact = contact or self .openapi_config .contact
1796- license_info = license_info or self .openapi_config .license_info
1797- security_schemes = security_schemes or self .openapi_config .security_schemes
1798- security = security or self .openapi_config .security
1799- external_documentation = external_documentation or self .openapi_config .external_documentation
1800- openapi_extensions = openapi_extensions or self .openapi_config .openapi_extensions
1795+ # Build base OpenAPI structure
1796+ output = self ._build_base_openapi_structure (config )
18011797
1802- from pydantic .json_schema import GenerateJsonSchema
1798+ # Process routes and build paths/components
1799+ paths , definitions = self ._process_routes_for_openapi (config ["security_schemes" ])
18031800
1804- from aws_lambda_powertools .event_handler .openapi .compat import (
1805- get_compat_model_name_map ,
1806- get_definitions ,
1807- )
1808- from aws_lambda_powertools .event_handler .openapi .models import OpenAPI , PathItem , Tag
1809- from aws_lambda_powertools .event_handler .openapi .types import (
1810- COMPONENT_REF_TEMPLATE ,
1811- )
1801+ # Build final components and paths
1802+ components = self ._build_openapi_components (definitions , config ["security_schemes" ])
1803+ output .update (self ._finalize_openapi_output (components , config ["tags" ], paths , config ["external_documentation" ]))
18121804
1813- openapi_version = self ._determine_openapi_version (openapi_version )
1805+ # Apply schema fixes and return result
1806+ return self ._apply_schema_fixes (output )
1807+
1808+ def _resolve_openapi_config (self , ** kwargs ) -> dict [str , Any ]:
1809+ """Resolve OpenAPI configuration with fallbacks to openapi_config."""
1810+ # DEPRECATION: Will be removed in v4.0.0. Use configure_api() instead.
1811+ # Maintained for backwards compatibility.
1812+ # See: https://github.com/aws-powertools/powertools-lambda-python/issues/6122
1813+ resolved = {}
1814+
1815+ # Handle title with fallback
1816+ resolved ["title" ] = kwargs ["title" ]
1817+ if kwargs ["title" ] == DEFAULT_OPENAPI_TITLE and self .openapi_config .title :
1818+ resolved ["title" ] = self .openapi_config .title
1819+
1820+ # Handle version with fallback
1821+ resolved ["version" ] = kwargs ["version" ]
1822+ if kwargs ["version" ] == DEFAULT_API_VERSION and self .openapi_config .version :
1823+ resolved ["version" ] = self .openapi_config .version
1824+
1825+ # Handle openapi_version with fallback
1826+ resolved ["openapi_version" ] = kwargs ["openapi_version" ]
1827+ if kwargs ["openapi_version" ] == DEFAULT_OPENAPI_VERSION and self .openapi_config .openapi_version :
1828+ resolved ["openapi_version" ] = self .openapi_config .openapi_version
1829+
1830+ # Resolve other fields with fallbacks
1831+ resolved .update ({
1832+ "summary" : kwargs ["summary" ] or self .openapi_config .summary ,
1833+ "description" : kwargs ["description" ] or self .openapi_config .description ,
1834+ "tags" : kwargs ["tags" ] or self .openapi_config .tags ,
1835+ "servers" : kwargs ["servers" ] or self .openapi_config .servers ,
1836+ "terms_of_service" : kwargs ["terms_of_service" ] or self .openapi_config .terms_of_service ,
1837+ "contact" : kwargs ["contact" ] or self .openapi_config .contact ,
1838+ "license_info" : kwargs ["license_info" ] or self .openapi_config .license_info ,
1839+ "security_schemes" : kwargs ["security_schemes" ] or self .openapi_config .security_schemes ,
1840+ "security" : kwargs ["security" ] or self .openapi_config .security ,
1841+ "external_documentation" : kwargs ["external_documentation" ] or self .openapi_config .external_documentation ,
1842+ "openapi_extensions" : kwargs ["openapi_extensions" ] or self .openapi_config .openapi_extensions ,
1843+ })
1844+
1845+ return resolved
1846+
1847+ def _build_base_openapi_structure (self , config : dict [str , Any ]) -> dict [str , Any ]:
1848+ """Build the base OpenAPI structure with info, servers, and security."""
1849+ openapi_version = self ._determine_openapi_version (config ["openapi_version" ])
18141850
18151851 # Start with the bare minimum required for a valid OpenAPI schema
1816- info : dict [str , Any ] = {"title" : title , "version" : version }
1852+ info : dict [str , Any ] = {"title" : config [ " title" ] , "version" : config [ " version" ] }
18171853
18181854 optional_fields = {
1819- "summary" : summary ,
1820- "description" : description ,
1821- "termsOfService" : terms_of_service ,
1822- "contact" : contact ,
1823- "license" : license_info ,
1855+ "summary" : config [ " summary" ] ,
1856+ "description" : config [ " description" ] ,
1857+ "termsOfService" : config [ " terms_of_service" ] ,
1858+ "contact" : config [ " contact" ] ,
1859+ "license" : config [ " license_info" ] ,
18241860 }
18251861
18261862 info .update ({field : value for field , value in optional_fields .items () if value })
18271863
1864+ openapi_extensions = config ["openapi_extensions" ]
18281865 if not isinstance (openapi_extensions , dict ):
18291866 openapi_extensions = {}
18301867
1831- output : dict [ str , Any ] = {
1868+ return {
18321869 "openapi" : openapi_version ,
18331870 "info" : info ,
1834- "servers" : self ._get_openapi_servers (servers ),
1835- "security" : self ._get_openapi_security (security , security_schemes ),
1871+ "servers" : self ._get_openapi_servers (config [ " servers" ] ),
1872+ "security" : self ._get_openapi_security (config [ " security" ], config [ " security_schemes" ] ),
18361873 ** openapi_extensions ,
18371874 }
18381875
1839- if external_documentation :
1840- output ["externalDocs" ] = external_documentation
1876+ def _process_routes_for_openapi (self , security_schemes : dict [str , SecurityScheme ] | None ) -> tuple [dict [str , dict [str , Any ]], dict [str , dict [str , Any ]]]:
1877+ """Process all routes and build paths and definitions."""
1878+ from pydantic .json_schema import GenerateJsonSchema
1879+ from aws_lambda_powertools .event_handler .openapi .compat import (
1880+ get_compat_model_name_map ,
1881+ get_definitions ,
1882+ )
1883+ from aws_lambda_powertools .event_handler .openapi .types import COMPONENT_REF_TEMPLATE
18411884
1842- components : dict [str , dict [str , Any ]] = {}
18431885 paths : dict [str , dict [str , Any ]] = {}
18441886 operation_ids : set [str ] = set ()
18451887
@@ -1857,15 +1899,8 @@ def get_openapi_schema(
18571899
18581900 # Add routes to the OpenAPI schema
18591901 for route in all_routes :
1860- if route .security and not _validate_openapi_security_parameters (
1861- security = route .security ,
1862- security_schemes = security_schemes ,
1863- ):
1864- raise SchemaValidationError (
1865- "Security configuration was not found in security_schemas or security_schema was not defined. "
1866- "See: https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#security-schemes" ,
1867- )
1868-
1902+ self ._validate_route_security (route , security_schemes )
1903+
18691904 if not route .include_in_schema :
18701905 continue
18711906
@@ -1883,19 +1918,50 @@ def get_openapi_schema(
18831918 if path_definitions :
18841919 definitions .update (path_definitions )
18851920
1921+ return paths , definitions
1922+
1923+ def _validate_route_security (self , route , security_schemes : dict [str , SecurityScheme ] | None ) -> None :
1924+ """Validate route security configuration."""
1925+ if route .security and not _validate_openapi_security_parameters (
1926+ security = route .security ,
1927+ security_schemes = security_schemes ,
1928+ ):
1929+ raise SchemaValidationError (
1930+ "Security configuration was not found in security_schemas or security_schema was not defined. "
1931+ "See: https://docs.powertools.aws.dev/lambda/python/latest/core/event_handler/api_gateway/#security-schemes" ,
1932+ )
1933+
1934+ def _build_openapi_components (self , definitions : dict [str , dict [str , Any ]], security_schemes : dict [str , SecurityScheme ] | None ) -> dict [str , dict [str , Any ]]:
1935+ """Build the components section of the OpenAPI schema."""
1936+ components : dict [str , dict [str , Any ]] = {}
1937+
18861938 if definitions :
18871939 components ["schemas" ] = self ._generate_schemas (definitions )
18881940 if security_schemes :
18891941 components ["securitySchemes" ] = security_schemes
1942+
1943+ return components
1944+
1945+ def _finalize_openapi_output (self , components : dict [str , dict [str , Any ]], tags , paths : dict [str , dict [str , Any ]], external_documentation ) -> dict [str , Any ]:
1946+ """Finalize the OpenAPI output with components, tags, and paths."""
1947+ from aws_lambda_powertools .event_handler .openapi .models import PathItem , Tag
1948+
1949+ output = {}
1950+
18901951 if components :
18911952 output ["components" ] = components
18921953 if tags :
18931954 output ["tags" ] = [Tag (name = tag ) if isinstance (tag , str ) else tag for tag in tags ]
1955+ if external_documentation :
1956+ output ["externalDocs" ] = external_documentation
18941957
18951958 output ["paths" ] = {k : PathItem (** v ) for k , v in paths .items ()}
1959+
1960+ return output
18961961
1897- # Apply patches to fix any issues with the OpenAPI schema
1898- # Import here to avoid circular imports
1962+ def _apply_schema_fixes (self , output : dict [str , Any ]) -> OpenAPI :
1963+ """Apply schema fixes and return the final OpenAPI model."""
1964+ from aws_lambda_powertools .event_handler .openapi .models import OpenAPI
18991965 from aws_lambda_powertools .event_handler .openapi .upload_file_fix import fix_upload_file_schema
19001966
19011967 # First create the OpenAPI model
@@ -1906,9 +1972,7 @@ def get_openapi_schema(
19061972 fixed_dict = fix_upload_file_schema (result_dict )
19071973
19081974 # Reconstruct the model with the fixed dict
1909- result = OpenAPI (** fixed_dict )
1910-
1911- return result
1975+ return OpenAPI (** fixed_dict )
19121976
19131977 @staticmethod
19141978 def _get_openapi_servers (servers : list [Server ] | None ) -> list [Server ]:
0 commit comments