fastapi_swagger2 0.2.4__tar.gz → 0.2.6__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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: fastapi_swagger2
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
4
  Summary: Swagger2 support for FastAPI framework
5
5
  Project-URL: Homepage, https://github.com/virajkanwade/fastapi_swagger2
6
6
  Project-URL: Documentation, https://github.com/virajkanwade/fastapi_swagger2
@@ -40,10 +40,11 @@ Classifier: Operating System :: OS Independent
40
40
  Classifier: Programming Language :: Python
41
41
  Classifier: Programming Language :: Python :: 3
42
42
  Classifier: Programming Language :: Python :: 3 :: Only
43
- Classifier: Programming Language :: Python :: 3.8
44
43
  Classifier: Programming Language :: Python :: 3.9
45
44
  Classifier: Programming Language :: Python :: 3.10
46
45
  Classifier: Programming Language :: Python :: 3.11
46
+ Classifier: Programming Language :: Python :: 3.12
47
+ Classifier: Programming Language :: Python :: 3.13
47
48
  Classifier: Topic :: Internet
48
49
  Classifier: Topic :: Internet :: WWW/HTTP
49
50
  Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
@@ -51,20 +52,20 @@ Classifier: Topic :: Software Development
51
52
  Classifier: Topic :: Software Development :: Libraries
52
53
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
53
54
  Classifier: Typing :: Typed
54
- Requires-Python: >=3.8
55
- Requires-Dist: fastapi>=0.100.0
55
+ Requires-Python: >=3.9
56
+ Requires-Dist: fastapi<0.119.0,>=0.100.0
56
57
  Provides-Extra: all
57
- Requires-Dist: httpx>=0.23.0; extra == 'all'
58
+ Requires-Dist: httpx>=0.28.1; extra == 'all'
58
59
  Provides-Extra: dev
59
- Requires-Dist: ruff==0.0.272; extra == 'dev'
60
+ Requires-Dist: ruff==0.14.10; extra == 'dev'
60
61
  Provides-Extra: test
61
- Requires-Dist: black==23.3.0; extra == 'test'
62
- Requires-Dist: coverage[toml]<8.0,>=6.5.0; extra == 'test'
63
- Requires-Dist: httpx<0.24.0,>=0.23.0; extra == 'test'
64
- Requires-Dist: isort<6.0.0,>=5.0.6; extra == 'test'
65
- Requires-Dist: mypy==1.3.0; extra == 'test'
66
- Requires-Dist: pytest<8.0.0,>=7.1.3; extra == 'test'
67
- Requires-Dist: ruff==0.0.272; extra == 'test'
62
+ Requires-Dist: black==25.12.0; extra == 'test'
63
+ Requires-Dist: coverage[toml]<8.0,>=7.13.1; extra == 'test'
64
+ Requires-Dist: httpx>=0.28.1; extra == 'test'
65
+ Requires-Dist: isort>=7.0.0; extra == 'test'
66
+ Requires-Dist: mypy==1.19.1; extra == 'test'
67
+ Requires-Dist: pytest<10.0.0,>=9.0.2; extra == 'test'
68
+ Requires-Dist: ruff==0.14.10; extra == 'test'
68
69
  Description-Content-Type: text/markdown
69
70
 
70
71
  # fastapi_swagger2
@@ -90,11 +91,12 @@ Few API GW services like Google Cloud API GW still support only Swagger 2.0 spec
90
91
 
91
92
  ## Requirements
92
93
 
93
- Python 3.8+
94
+ Python 3.9+
94
95
 
95
96
  * 0.0.3 - FastAPI >= 0.79.0, <= 0.98.0
96
97
  * 0.1.1 - FastAPI >= 0.99.0, <= 0.99.1
97
98
  * 0.2.4 - FastAPI >= 0.100.0
99
+ * 0.2.6 - FastAPI >= 0.100.0, < 0.199.0 > + Pydantic v1/v2
98
100
 
99
101
  ## Installation
100
102
 
@@ -21,11 +21,12 @@ Few API GW services like Google Cloud API GW still support only Swagger 2.0 spec
21
21
 
22
22
  ## Requirements
23
23
 
24
- Python 3.8+
24
+ Python 3.9+
25
25
 
26
26
  * 0.0.3 - FastAPI >= 0.79.0, <= 0.98.0
27
27
  * 0.1.1 - FastAPI >= 0.99.0, <= 0.99.1
28
28
  * 0.2.4 - FastAPI >= 0.100.0
29
+ * 0.2.6 - FastAPI >= 0.100.0, < 0.199.0 > + Pydantic v1/v2
29
30
 
30
31
  ## Installation
31
32
 
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
  name = "fastapi_swagger2"
7
7
  description = "Swagger2 support for FastAPI framework"
8
8
  readme = "README.md"
9
- requires-python = ">=3.8"
9
+ requires-python = ">=3.9"
10
10
  license = { file="LICENSE" }
11
11
  authors = [
12
12
  { name = "Viraj Kanwade", email = "virajk.oib@gmail.com" },
@@ -30,15 +30,16 @@ classifiers = [
30
30
  "Intended Audience :: Developers",
31
31
  "License :: OSI Approved :: MIT License",
32
32
  "Programming Language :: Python :: 3 :: Only",
33
- "Programming Language :: Python :: 3.8",
34
33
  "Programming Language :: Python :: 3.9",
35
34
  "Programming Language :: Python :: 3.10",
36
35
  "Programming Language :: Python :: 3.11",
36
+ "Programming Language :: Python :: 3.12",
37
+ "Programming Language :: Python :: 3.13",
37
38
  "Topic :: Internet :: WWW/HTTP :: HTTP Servers",
38
39
  "Topic :: Internet :: WWW/HTTP",
39
40
  ]
40
41
  dependencies = [
41
- "fastapi >=0.100.0",
42
+ "fastapi >=0.100.0,<0.119.0",
42
43
  ]
43
44
  dynamic = ["version"]
44
45
 
@@ -48,19 +49,19 @@ Documentation = "https://github.com/virajkanwade/fastapi_swagger2"
48
49
 
49
50
  [project.optional-dependencies]
50
51
  test = [
51
- "pytest >=7.1.3,<8.0.0",
52
- "coverage[toml] >= 6.5.0,< 8.0",
53
- "mypy ==1.3.0",
54
- "ruff ==0.0.272",
55
- "black == 23.3.0",
56
- "isort >=5.0.6,<6.0.0",
57
- "httpx >=0.23.0,<0.24.0",
52
+ "pytest >=9.0.2,<10.0.0",
53
+ "coverage[toml] >= 7.13.1,< 8.0",
54
+ "mypy ==1.19.1",
55
+ "ruff ==0.14.10",
56
+ "black == 25.12.0",
57
+ "isort >=7.0.0",
58
+ "httpx >=0.28.1",
58
59
  ]
59
60
  dev = [
60
- "ruff ==0.0.272",
61
+ "ruff ==0.14.10",
61
62
  ]
62
63
  all = [
63
- "httpx >=0.23.0",
64
+ "httpx >=0.28.1",
64
65
  ]
65
66
 
66
67
  [tool.hatch.version]
@@ -73,7 +74,7 @@ known_third_party = ["fastapi", "pydantic", "starlette"]
73
74
  [tool.mypy]
74
75
  strict = true
75
76
 
76
- [tool.ruff]
77
+ [tool.ruff.lint]
77
78
  select = [
78
79
  "E", # pycodestyle errors
79
80
  "W", # pycodestyle warnings
@@ -87,9 +88,5 @@ ignore = [
87
88
  "B008", # do not perform function calls in argument defaults
88
89
  "C901", # too complex
89
90
  ]
90
-
91
- [tool.ruff.per-file-ignores]
92
- "__init__.py" = ["F401"]
93
-
94
- [tool.ruff.isort]
95
- known-third-party = ["fastapi", "pydantic", "starlette"]
91
+ per-file-ignores = { "__init__.py" = ["F401"] }
92
+ isort = { known-third-party = ["fastapi", "pydantic", "starlette"] }
@@ -1,6 +1,6 @@
1
1
  #!/bin/sh -e
2
2
  set -x
3
3
 
4
- ruff src/fastapi_swagger2 tests scripts --fix
4
+ ruff check src/fastapi_swagger2 tests scripts --fix
5
5
  black src/fastapi_swagger2 tests scripts
6
6
  isort src/fastapi_swagger2 tests scripts
@@ -4,6 +4,6 @@ set -e
4
4
  set -x
5
5
 
6
6
  mypy src/fastapi_swagger2
7
- ruff src/fastapi_swagger2 tests scripts
7
+ ruff check src/fastapi_swagger2 tests scripts
8
8
  black src/fastapi_swagger2 tests --check
9
9
  isort src/fastapi_swagger2 tests scripts --check-only
@@ -1,4 +1,4 @@
1
- __version__ = "0.2.4"
1
+ __version__ = "0.2.6"
2
2
 
3
3
  from typing import Any, Dict, List, Optional, TypeVar
4
4
 
@@ -1,17 +1,17 @@
1
1
  import http.client
2
2
  import inspect
3
+ from enum import Enum
3
4
  from typing import Any, Dict, List, Optional, Sequence, Set, Tuple, Type, Union, cast
4
5
  from urllib.parse import ParseResult, urlparse
5
6
 
6
7
  from fastapi import routing
7
8
  from fastapi._compat import (
9
+ PYDANTIC_V2,
8
10
  GenerateJsonSchema,
9
11
  JsonSchemaValue,
10
12
  ModelField,
11
13
  Undefined,
12
14
  get_compat_model_name_map,
13
- get_definitions,
14
- get_schema_from_model_field,
15
15
  lenient_issubclass,
16
16
  )
17
17
  from fastapi.datastructures import DefaultPlaceholder
@@ -29,6 +29,7 @@ from fastapi.params import Body, Param
29
29
  from fastapi.responses import Response
30
30
  from fastapi.types import ModelNameMap
31
31
  from fastapi.utils import deep_dict_update, is_body_allowed_for_status_code
32
+ from pydantic import BaseModel
32
33
  from starlette.responses import JSONResponse
33
34
  from starlette.routing import BaseRoute
34
35
  from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY
@@ -37,6 +38,65 @@ from typing_extensions import Literal
37
38
  from fastapi_swagger2.constants import REF_PREFIX, REF_TEMPLATE
38
39
  from fastapi_swagger2.models import Swagger2
39
40
 
41
+ if PYDANTIC_V2:
42
+ from fastapi._compat import get_definitions, get_schema_from_model_field
43
+ else:
44
+ from pydantic.schema import (
45
+ field_schema,
46
+ get_flat_models_from_fields,
47
+ model_process_schema,
48
+ )
49
+
50
+ def get_model_definitions(
51
+ *,
52
+ flat_models: Set[Union[Type[BaseModel], Type[Enum]]],
53
+ model_name_map: Dict[Union[Type[BaseModel], Type[Enum]], str],
54
+ ) -> Dict[str, Any]:
55
+ definitions: Dict[str, Dict[str, Any]] = {}
56
+ for model in flat_models:
57
+ m_schema, m_definitions, m_nested_models = model_process_schema(
58
+ model, model_name_map=model_name_map, ref_prefix=REF_PREFIX
59
+ )
60
+ definitions.update(m_definitions)
61
+ model_name = model_name_map[model]
62
+ if "description" in m_schema:
63
+ m_schema["description"] = m_schema["description"].split("\f")[0]
64
+ definitions[model_name] = m_schema
65
+ return definitions
66
+
67
+ def get_definitions(
68
+ *,
69
+ fields: List[ModelField],
70
+ schema_generator: GenerateJsonSchema,
71
+ model_name_map: ModelNameMap,
72
+ separate_input_output_schemas: bool = True,
73
+ ) -> Tuple[
74
+ Dict[
75
+ Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
76
+ ],
77
+ Dict[str, Dict[str, Any]],
78
+ ]:
79
+ models = get_flat_models_from_fields(fields, known_models=set())
80
+ return {}, get_model_definitions(
81
+ flat_models=models, model_name_map=model_name_map
82
+ )
83
+
84
+ def get_schema_from_model_field(
85
+ *,
86
+ field: ModelField,
87
+ schema_generator: GenerateJsonSchema,
88
+ model_name_map: ModelNameMap,
89
+ field_mapping: Dict[
90
+ Tuple[ModelField, Literal["validation", "serialization"]], JsonSchemaValue
91
+ ],
92
+ separate_input_output_schemas: bool = True,
93
+ ) -> Dict[str, Any]:
94
+ # This expects that GenerateJsonSchema was already used to generate the definitions
95
+ return field_schema( # type: ignore[no-any-return]
96
+ field, model_name_map=model_name_map, ref_prefix=REF_PREFIX
97
+ )[0]
98
+
99
+
40
100
  validation_error_definition = {
41
101
  "title": "ValidationError",
42
102
  "type": "object",
@@ -188,7 +248,10 @@ def get_swagger2_operation_parameters(
188
248
  }
189
249
  schema: Dict[str, Any] = param_schema
190
250
  if field_info.in_.value == "body":
191
- parameter["schema"] = schema
251
+ if "$ref" in schema:
252
+ parameter["schema"] = {"$ref", schema["$ref"]}
253
+ else:
254
+ parameter["schema"] = schema
192
255
  else:
193
256
  parameter.update({k: v for (k, v) in schema.items() if k != "title"})
194
257
  if field_info.description:
@@ -226,7 +289,14 @@ def get_swagger2_operation_request_body(
226
289
  if required:
227
290
  request_body_oai["required"] = required
228
291
 
229
- request_media_content: Dict[str, Any] = {"schema": body_schema}
292
+ request_media_content: Dict[str, Any] = {}
293
+ if "$ref" in body_schema:
294
+ request_media_content["schema"] = {"$ref": body_schema["$ref"]}
295
+ request_media_content.update(
296
+ {k: v for (k, v) in body_schema.items() if k != "$ref"}
297
+ )
298
+ else:
299
+ request_media_content["schema"] = body_schema
230
300
  if field_info.example != Undefined:
231
301
  request_media_content["example"] = jsonable_encoder(field_info.example)
232
302
  # request_body_oai["content"] = {request_media_type: request_media_content}
@@ -281,6 +351,7 @@ def get_swagger2_path(
281
351
  model_name_map=model_name_map,
282
352
  field_mapping=field_mapping,
283
353
  )
354
+
284
355
  parameters.extend(operation_parameters)
285
356
  if parameters:
286
357
  all_parameters = {
@@ -489,6 +560,14 @@ def get_swagger2(
489
560
  if result:
490
561
  path, security_schemes, path_definitions = result
491
562
 
563
+ for k, v in path.items():
564
+ for param in v["parameters"]:
565
+ if "$ref" in param and "in" in param and param["in"] != "body":
566
+ definition = definitions[param.pop("$ref").split("/")[2]]
567
+ param.update(
568
+ {k: v for (k, v) in definition.items() if k != "title"}
569
+ )
570
+
492
571
  if path:
493
572
  paths.setdefault(route.path_format, {}).update(path)
494
573
 
@@ -508,16 +587,33 @@ def get_swagger2(
508
587
  for k in sorted(definitions):
509
588
  properties = definitions[k].get("properties", [])
510
589
  for p in properties:
511
- if "anyOf" in properties[p].keys():
590
+ if "anyOf" in properties[p]:
512
591
  any_of = properties[p].pop("anyOf")
513
- if len(any_of) <= 2:
514
- for _any_of in any_of:
515
- if _any_of == {"type": "null"}:
516
- properties[p]["x-nullable"] = True
592
+
593
+ if len(any_of) == 1:
594
+ # Single item - just use it directly
595
+ properties[p].update(any_of[0])
596
+ elif len(any_of) == 2:
597
+ # Handle the 2-item case (type + null)
598
+ ref_item = None
599
+ has_null = False
600
+
601
+ for item in any_of:
602
+ if item == {"type": "null"}:
603
+ has_null = True
604
+ elif "$ref" in item:
605
+ ref_item = item
517
606
  else:
518
- properties[p].update(_any_of)
607
+ properties[p].update(item)
608
+
609
+ if ref_item and has_null:
610
+ properties[p]["allOf"] = [ref_item]
611
+ properties[p]["x-nullable"] = True
612
+ elif has_null:
613
+ properties[p]["x-nullable"] = True
519
614
  else:
520
- properties[p].update({"type": "string"})
615
+ # Fallback for complex anyOf cases (len > 2) or empty (len == 0)
616
+ properties[p]["type"] = "string"
521
617
  logger.warning(
522
618
  f"fastapi_swagger2: Unable to handle anyOf in definitions {any_of}, defaulting to string type."
523
619
  )