diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 886c9c7..86a8c2a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.9, "3.10", "3.11", "3.12"] + python-version: [3.9, "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cf86b8..285a3ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.1] - 2025-03-20 + +- Fix bug in `MARKDOWN` view for `servers` (displaying twice the `description` + instead of `description` and `url`). +- Fix bug happening when a `path item` includes properties that are not of kind + `operation item` (https://github.com/Neoteroi/mkdocs-plugins/issues/5). +- Add support for handling `parameters` defined on `path items` (common + parameters for all operation under a certain path). + Refer to the [`Path Item` specification](https://swagger.io/specification/#path-item-object). +- Fix bug happening when a parameter has a non-str `name` property. +- Add Python 3.13 to the build matrix. + ## [1.1.0] - 2025-01-18 - Add additionalProperties to Schema object, by @tyzhnenko. diff --git a/openapidocs/__init__.py b/openapidocs/__init__.py index 43d7d68..2c30038 100644 --- a/openapidocs/__init__.py +++ b/openapidocs/__init__.py @@ -1,2 +1,2 @@ -__version__ = "1.1.0" +__version__ = "1.1.1" VERSION = __version__ diff --git a/openapidocs/mk/v3/__init__.py b/openapidocs/mk/v3/__init__.py index 8c5a920..72a9145 100644 --- a/openapidocs/mk/v3/__init__.py +++ b/openapidocs/mk/v3/__init__.py @@ -178,6 +178,8 @@ def get_operations(self): paths = data["paths"] for path, path_item in paths.items(): + if not isinstance(path_item, dict): + continue tag = self.get_tag(path_item) or "" for operation in path_item.values(): @@ -186,11 +188,32 @@ def get_operations(self): operation["requestBody"] = self._resolve_opt_ref( operation["requestBody"] ) - - groups[tag].append((path, path_item)) + groups[tag].append((path, self._keep_operations(path_item))) return groups + def _keep_operations(self, path_item): + # discard dictionary keys that are not of dict type + + # if the path item defines common parameters, merge them into each operation: + # https://swagger.io/specification/#path-item-object + common_parameters = path_item.get("parameters", []) + # Note: we don't need to resolve $ref here, because they are resolved in + # get_parameters + + return { + key: self._merge_common_parameters(value, common_parameters) + for key, value in path_item.items() + if isinstance(value, dict) + } + + def _merge_common_parameters(self, operation, common_parameters): + if not common_parameters: + return operation + data = copy.deepcopy(operation) + data["parameters"] = common_parameters + data.get("parameters", []) + return data + def get_schemas(self): schemas = read_dict(self.doc, "components", "schemas") @@ -215,8 +238,12 @@ def get_tag(self, path_item) -> Optional[str]: """ single_tag: Optional[str] = None - for operation in path_item.values(): - tags = operation.get("tags") + for prop in path_item.values(): + if not isinstance(prop, dict): + # This property is not an operation; in this context we ignore it. + # See Path Item Object here: https://swagger.io/specification/ + continue + tags = prop.get("tags") if not tags: continue @@ -383,6 +410,11 @@ def _resolve_opt_ref(self, obj): return self.resolve_reference(obj) return obj + def _lower(self, obj): + if isinstance(obj, str): + return obj.lower() + return str(obj) + def get_parameters(self, operation) -> List[dict]: """ Returns a list of objects describing the input parameters for a given operation. @@ -397,7 +429,7 @@ def get_parameters(self, operation) -> List[dict]: param for param in sorted( parameters, - key=lambda x: x["name"].lower() if (x and "name" in x) else "", + key=lambda x: self._lower(x["name"]) if (x and "name" in x) else "", ) if param ] diff --git a/openapidocs/mk/v3/views_markdown/partial/servers.html b/openapidocs/mk/v3/views_markdown/partial/servers.html index 6a77694..28dd45a 100644 --- a/openapidocs/mk/v3/views_markdown/partial/servers.html +++ b/openapidocs/mk/v3/views_markdown/partial/servers.html @@ -3,7 +3,7 @@ {% with rows = [[texts.description, texts.url]] %} {%- for server in servers -%} -{%- set _ = rows.append([server.description, server.description]) -%} +{%- set _ = rows.append([server.description, server.url]) -%} {%- endfor -%} {{ rows | table }} {%- endwith -%} diff --git a/requirements.txt b/requirements.txt index d99937e..af58338 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,8 +26,7 @@ pathspec==0.11.2 platformdirs==4.0.0 pluggy==1.3.0 pycodestyle==2.11.1 -pydantic==2.5.1 -pydantic_core==2.14.3 +pydantic==2.10.6 pyflakes==3.1.0 Pygments==2.17.1 pytest==7.4.3 @@ -35,5 +34,5 @@ pytest-cov==4.1.0 PyYAML==6.0.1 rich==13.7.0 sniffio==1.3.0 -typing_extensions==4.8.0 +typing_extensions>=4.8.0 Werkzeug==3.0.1 diff --git a/tests/res/example1-output-plain.md b/tests/res/example1-output-plain.md index 035351c..b239b18 100644 --- a/tests/res/example1-output-plain.md +++ b/tests/res/example1-output-plain.md @@ -20,9 +20,9 @@ Optional multiline or single-line description in ## Servers -| Description | URL | -| ----------------- | ----------------- | -| Production server | Production server | +| Description | URL | +| ----------------- | ------------------------------------------- | +| Production server | https://www.neoteroi.xyz/software-center/v1 | ## Blobs diff --git a/tests/res/example8-openapi.yaml b/tests/res/example8-openapi.yaml new file mode 100644 index 0000000..a831c2b --- /dev/null +++ b/tests/res/example8-openapi.yaml @@ -0,0 +1,128 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 + description: Petstore server +paths: + /pets: + summary: Everything about pets + description: This is a sample server Petstore server. You can find out more about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, #swagger](http://swagger.io/irc/). + parameters: + - name: X-Country + in: header + description: Country code. + required: false + schema: + type: string + default: PL + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + default: 100 + responses: + "200": + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + "201": + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + "200": + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + PetsIds: + type: array + items: + type: integer + format: int64 + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/tests/res/example8-output-plain.md b/tests/res/example8-output-plain.md new file mode 100644 index 0000000..4bcc5b9 --- /dev/null +++ b/tests/res/example8-output-plain.md @@ -0,0 +1,277 @@ + + +# Swagger Petstore 1.0.0 + + +**License:** MIT + + +## Servers + +| Description | URL | +| --------------- | ----------------------------- | +| Petstore server | http://petstore.swagger.io/v1 | + + +## pets + + + +### GET /pets +List all pets + +**Input parameters** + +| Parameter | In | Type | Default | Nullable | Description | +| --------- | ------ | ------- | ------- | -------- | ---------------------------------------------- | +| limit | query | integer | 100 | No | How many items to return at one time (max 100) | +| X-Country | header | string | PL | No | Country code. | + +### Response 200 OK + +**application/json** + +```json +[ + { + "id": 144, + "name": "string", + "tag": "string" + } +] +``` +_This example has been generated automatically from the schema and it is not +accurate. Refer to the schema for more information._ + + + +**Schema of the response body** + +```json +{ + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } +} +``` + + + +| Name | Description | Schema | +| ------ | ------------------------------------ | ------ | +| x-next | A link to the next page of responses | string | + +### Other responses + +**application/json** + +```json +{ + "code": 235, + "message": "string" +} +``` +_This example has been generated automatically from the schema and it is not +accurate. Refer to the schema for more information._ + + + +**Schema of the response body** + +```json +{ + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } +} +``` + + + +### POST /pets +Create a pet + +**Input parameters** + +| Parameter | In | Type | Default | Nullable | Description | +| --------- | ------ | ------ | ------- | -------- | ------------- | +| X-Country | header | string | PL | No | Country code. | + +### Response 201 Created + +### Other responses + +**application/json** + +```json +{ + "code": 154, + "message": "string" +} +``` +_This example has been generated automatically from the schema and it is not +accurate. Refer to the schema for more information._ + + + +**Schema of the response body** + +```json +{ + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } +} +``` + + + +### GET /pets/{petId} +Info for a specific pet + +**Input parameters** + +| Parameter | In | Type | Default | Nullable | Description | +| --------- | ---- | ------ | ------- | -------- | ----------------------------- | +| petId | path | string | | No | The id of the pet to retrieve | + +### Response 200 OK + +**application/json** + +```json +{ + "id": 258, + "name": "string", + "tag": "string" +} +``` +_This example has been generated automatically from the schema and it is not +accurate. Refer to the schema for more information._ + + + +**Schema of the response body** + +```json +{ + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } +} +``` + + + +### Other responses + +**application/json** + +```json +{ + "code": 182, + "message": "string" +} +``` +_This example has been generated automatically from the schema and it is not +accurate. Refer to the schema for more information._ + + + +**Schema of the response body** + +```json +{ + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } +} +``` + + + +--- +## Schemas + + +### Error + +| Name | Type | +| -- | -- | +| code | integer(int32) | +| message | string | + + + +### Pet + +| Name | Type | +| -- | -- | +| id | integer(int64) | +| name | string | +| tag | string | + + + +### Pets +Type: Array<[Pet](#pet)> + + + +### PetsIds +Type: Array<integer(int64)> + + diff --git a/tests/res/example8-output.md b/tests/res/example8-output.md new file mode 100644 index 0000000..309197d --- /dev/null +++ b/tests/res/example8-output.md @@ -0,0 +1,418 @@ + + +# Swagger Petstore 1.0.0 + + +
| Description | +URL | +
|---|---|
| Petstore server | ++ http://petstore.swagger.io/v1 + | +
| Parameter | +In | +Type | +Default | +Nullable | +Description | +
|---|---|---|---|---|---|
limit |
+ query | +integer | +100 | +No | +How many items to return at one time (max 100) | +
X-Country |
+ header | +string | +PL | +No | +Country code. | +
+ Response 200 OK +
+ +=== "application/json" + + + ```json + [ + { + "id": 171, + "name": "string", + "tag": "string" + } + ] + ``` + ⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information. + + + + ??? hint "Schema of the response body" + ```json + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + ``` + + +Response headers
+ +| Name | +Description | +Schema | +
|---|---|---|
x-next |
+ A link to the next page of responses | +string | +
+ Other responses +
+ +=== "application/json" + + + ```json + { + "code": 206, + "message": "string" + } + ``` + ⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information. + + + + ??? hint "Schema of the response body" + ```json + { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + ``` + + + +| Parameter | +In | +Type | +Default | +Nullable | +Description | +
|---|---|---|---|---|---|
X-Country |
+ header | +string | +PL | +No | +Country code. | +
+ Response 201 Created +
+ ++ Other responses +
+ +=== "application/json" + + + ```json + { + "code": 290, + "message": "string" + } + ``` + ⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information. + + + + ??? hint "Schema of the response body" + ```json + { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + ``` + + + + +| Parameter | +In | +Type | +Default | +Nullable | +Description | +
|---|---|---|---|---|---|
petId |
+ path | +string | ++ | No | +The id of the pet to retrieve | +
+ Response 200 OK +
+ +=== "application/json" + + + ```json + { + "id": 112, + "name": "string", + "tag": "string" + } + ``` + ⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information. + + + + ??? hint "Schema of the response body" + ```json + { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + }, + "tag": { + "type": "string" + } + } + } + ``` + + + ++ Other responses +
+ +=== "application/json" + + + ```json + { + "code": 175, + "message": "string" + } + ``` + ⚠️ This example has been generated automatically from the schema and it is not accurate. Refer to the schema for more information. + + + + ??? hint "Schema of the response body" + ```json + { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "message": { + "type": "string" + } + } + } + ``` + + + + + + +--- +## Schemas + + +### Error + +| Name | +Type | +
|---|---|
code |
+ integer(int32) | +
message |
+ string | +
| Name | +Type | +
|---|---|
id |
+ integer(int64) | +
name |
+ string | +
tag |
+ string | +