diff --git a/.flake8 b/.flake8
index 292d685..508fa68 100644
--- a/.flake8
+++ b/.flake8
@@ -1,7 +1,9 @@
[flake8]
+exclude = __pycache__,built,build,venv
ignore = E203, E266, W503
max-line-length = 88
max-complexity = 18
per-file-ignores =
tests/test_v2.py: E501
tests/test_v3.py: E501
+select = B,C,E,F,W,T4,B9
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 9346c1f..d877899 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -83,19 +83,19 @@ jobs:
bash <(curl -s https://codecov.io/bash)
- name: Install distribution dependencies
- run: pip install --upgrade twine setuptools wheel
- if: matrix.python-version == 3.10
+ run: pip install build
+ if: matrix.python-version == 3.11
- name: Create distribution package
- run: python setup.py sdist bdist_wheel
- if: matrix.python-version == 3.10
+ run: python -m build
+ if: matrix.python-version == 3.11
- name: Upload distribution package
uses: actions/upload-artifact@master
with:
- name: dist-package-${{ matrix.python-version }}
+ name: dist
path: dist
- if: matrix.python-version == 3.10
+ if: matrix.python-version == 3.11
publish:
runs-on: ubuntu-latest
@@ -105,17 +105,28 @@ jobs:
- name: Download a distribution artifact
uses: actions/download-artifact@v2
with:
- name: dist-package-3.10
+ name: dist
path: dist
- - name: Publish distribution ๐ฆ to Test PyPI
- uses: pypa/gh-action-pypi-publish@master
+
+ - name: Use Python 3.11
+ uses: actions/setup-python@v1
with:
- skip_existing: true
- user: __token__
- password: ${{ secrets.test_pypi_password }}
- repository_url: https://test.pypi.org/legacy/
+ python-version: '3.11'
+
+ - name: Install dependencies
+ run: |
+ pip install twine
+
+ - name: Publish distribution ๐ฆ to Test PyPI
+ run: |
+ twine upload -r testpypi dist/*
+ env:
+ TWINE_USERNAME: __token__
+ TWINE_PASSWORD: ${{ secrets.test_pypi_password }}
+
- name: Publish distribution ๐ฆ to PyPI
- uses: pypa/gh-action-pypi-publish@master
- with:
- user: __token__
- password: ${{ secrets.pypi_password }}
+ run: |
+ twine upload -r pypi dist/*
+ env:
+ TWINE_USERNAME: __token__
+ TWINE_PASSWORD: ${{ secrets.pypi_password }}
diff --git a/.gitignore b/.gitignore
index de72d6a..a470096 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,5 @@ htmlcov
__pycache__
*.egg-info
*.tar.gz
+_test_files/
+dist/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 926e152..17416a7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,14 @@ 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.0.7] - 2023-05-01 :toolbox:
+- Fixes [Markdown missing a newline after simple response types](https://github.com/Neoteroi/essentials-openapi/issues/27).
+- Fixes [Empty header when operations don't have any tag.](https://github.com/Neoteroi/essentials-openapi/issues/28).
+- When operations don't have any tag, the `h2` element
+- Improves tests.
+- Adopts `pyproject.toml`.
+- Workflow maintenance.
+
## [1.0.6] - 2023-03-19 :snail:
- Fixes a bug happening when trying to serialize examples in JSON, when they
contain datetimes and are provided in YAML;
diff --git a/Makefile b/Makefile
index 7839676..874eeee 100644
--- a/Makefile
+++ b/Makefile
@@ -6,19 +6,23 @@ test-debug:
artifacts: test
- python setup.py sdist
+ python -m build
prepforbuild:
- pip install --upgrade twine setuptools wheel
+ pip install build
-testrelease:
- twine upload --repository-url https://test.pypi.org/legacy/ dist/*
+build:
+ python -m build
-release: artifacts
- twine upload --repository-url https://upload.pypi.org/legacy/ dist/*
+test-release:
+ twine upload --repository testpypi dist/*
+
+
+release:
+ twine upload --repository pypi dist/*
test:
@@ -41,8 +45,25 @@ test-cov:
pytest --cov-report html --cov=openapidocs
+lint: check-flake8 check-isort check-black
+
+
+check-flake8:
+ @echo "$(BOLD)Checking flake8$(RESET)"
+ @flake8 . 2>&1
+
+
+check-isort:
+ @echo "$(BOLD)Checking isort$(RESET)"
+ @isort --check-only . 2>&1
+
+
+check-black:
+ @echo "$(BOLD)Checking black$(RESET)"
+ @black --check . 2>&1
+
+
format:
- isort openapidocs
- isort tests
- black openapidocs
- black tests
+ @echo "$(BOLD)Formatting code ๐งน ๐งผ$(RESET)"
+ @black . 2>&1
+ @isort . 2>&1
diff --git a/openapidocs/__init__.py b/openapidocs/__init__.py
index 89bec30..d6780ef 100644
--- a/openapidocs/__init__.py
+++ b/openapidocs/__init__.py
@@ -1 +1,2 @@
-VERSION = "1.0.6"
+__version__ = "1.0.7"
+VERSION = __version__
diff --git a/openapidocs/mk/v3/views_markdown/partial/path-items.html b/openapidocs/mk/v3/views_markdown/partial/path-items.html
index e4c7bfe..f309b34 100644
--- a/openapidocs/mk/v3/views_markdown/partial/path-items.html
+++ b/openapidocs/mk/v3/views_markdown/partial/path-items.html
@@ -1,9 +1,9 @@
{% for tag, operations in handler.get_operations().items() %}
-## {{tag}}
+## {{tag or "Endpoints"}}
{% for path, definition in operations %}
-{%- for http_method, operation in definition.items() -%}
+{%- for http_method, operation in definition.items() %}
### {{http_method.upper()}} {{path | safe}}
{% if "summary" in operation -%}
diff --git a/openapidocs/mk/v3/views_mkdocs/partial/path-items.html b/openapidocs/mk/v3/views_mkdocs/partial/path-items.html
index cbf777c..3f47cca 100644
--- a/openapidocs/mk/v3/views_mkdocs/partial/path-items.html
+++ b/openapidocs/mk/v3/views_mkdocs/partial/path-items.html
@@ -1,8 +1,8 @@
{% for tag, operations in handler.get_operations().items() %}
-## {{tag}}
+## {{tag or "Endpoints"}}
{% for path, definition in operations %}
-{%- for http_method, operation in definition.items() -%}
+{%- for http_method, operation in definition.items() %}
### {{http_method.upper()}} {{path | route | safe}}
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..e825ebe
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,47 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "essentials-openapi"
+dynamic = ["version"]
+authors = [{ name = "Roberto Prevato", email = "roberto.prevato@gmail.com" }]
+description = "Classes to generate OpenAPI Documentation v3 and v2, in JSON and YAML."
+readme = "README.md"
+requires-python = ">=3.7"
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Operating System :: OS Independent",
+]
+keywords = ["openapi", "docs", "swagger", "api", "documentation", "v3", "v2", "json", "yaml", "Markdown"]
+
+dependencies = ["PyYAML>=6", "essentials>=1.1.5"]
+
+[tool.hatch.version]
+path = "openapidocs/__init__.py"
+
+[project.optional-dependencies]
+full = ["click~=8.1.3", "Jinja2~=3.1.2", "MarkupSafe==2.1.2", "rich~=12.6.0", "httpx<1"]
+
+[project.scripts]
+openapidocs = "openapidocs.main:main"
+oad = "openapidocs.main:main"
+
+[tool.hatch.build.targets.wheel]
+packages = ["openapidocs"]
+
+[tool.hatch.build.targets.sdist]
+exclude = ["tests"]
+
+[tool.hatch.build]
+only-packages = true
+
+[project.urls]
+"Homepage" = "https://github.com/Neoteroi/essentials-openapi"
+"Bug Tracker" = "https://github.com/Neoteroi/essentials-openapi/issues"
diff --git a/requirements.txt b/requirements.txt
index bd9c0a1..1a28bc8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,7 @@
anyio==3.6.2
attrs==22.1.0
black==22.10.0
+build==0.10.0
certifi==2022.9.24
charset-normalizer==3.0.0
click==8.1.3
@@ -11,14 +12,16 @@ flake8==5.0.4
Flask==2.2.2
h11==0.12.0
httpcore==0.15.0
-httpx==0.23.0
+httpx==0.24.0
idna==3.4
iniconfig==1.1.1
isort==5.10.1
itsdangerous==2.1.2
Jinja2==3.1.2
-MarkupSafe==2.1.1
+markdown-it-py==2.2.0
+MarkupSafe==2.1.2
mccabe==0.7.0
+mdurl==0.1.2
mypy-extensions==0.4.3
packaging==21.3
pathspec==0.10.1
@@ -30,12 +33,13 @@ pydantic==1.10.2
pyflakes==2.5.0
Pygments==2.13.0
pyparsing==3.0.9
+pyproject_hooks==1.0.0
pytest==7.2.0
pytest-cov==4.0.0
PyYAML==6.0
regex==2022.10.31
rfc3986==1.5.0
-rich==12.6.0
+rich==13.3.5
sniffio==1.3.0
toml==0.10.2
tomli==2.0.1
diff --git a/setup.py b/setup.py
deleted file mode 100644
index fd63037..0000000
--- a/setup.py
+++ /dev/null
@@ -1,56 +0,0 @@
-from setuptools import setup
-
-from openapidocs import VERSION
-
-
-def readme():
- with open("README.md") as f:
- return f.read()
-
-
-setup(
- name="essentials-openapi",
- version=VERSION,
- description="Classes to generate OpenAPI Documentation v3 and v2, in JSON and YAML",
- long_description=readme(),
- long_description_content_type="text/markdown",
- classifiers=[
- "Development Status :: 5 - Production/Stable",
- "License :: OSI Approved :: MIT License",
- "Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.7",
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- "Operating System :: OS Independent",
- ],
- url="https://github.com/Neoteroi/essentials-openapi",
- author="RobertoPrevato",
- author_email="roberto.prevato@gmail.com",
- keywords="openapi docs swagger api documentation v3 v2 json yaml Markdown",
- license="MIT",
- packages=[
- "openapidocs",
- "openapidocs.commands",
- "openapidocs.mk",
- "openapidocs.mk.v3",
- "openapidocs.utils",
- ],
- install_requires=[
- "PyYAML>=6",
- "essentials>=1.1.5",
- "dataclasses==0.7;python_version<'3.7'",
- ],
- extras_require={
- "full": ["click~=8.1.3", "Jinja2~=3.1.2", "rich~=12.6.0", "httpx<1"]
- },
- entry_points={
- "console_scripts": [
- "openapidocs=openapidocs.main:main",
- "oad=openapidocs.main:main",
- ]
- },
- include_package_data=True,
- zip_safe=False,
-)
diff --git a/tests/common.py b/tests/common.py
index e8c1416..f96e5ca 100644
--- a/tests/common.py
+++ b/tests/common.py
@@ -1,5 +1,6 @@
import json
import os
+import re
from typing import Any
import pkg_resources
@@ -46,3 +47,21 @@ def get_file_json(file_name) -> Any:
def get_file_yaml(file_name) -> Any:
return yaml.safe_load(get_resource_file_content(file_name))
+
+
+def normalize_str(value: str) -> str:
+ return re.sub("\r?\n{2,}", "\n", value.strip())
+
+
+def compatible_str(value_one: str, value_two: str) -> bool:
+ """
+ Compares two strings ignoring multiple carriage returns and trailing carriage
+ returns. In HTML or Markdown it does not matter if you have multiple carriage
+ returns.
+ """
+ # first check if the two strings are equals: there is no point in stripping carriage
+ # returns otherwise
+ if value_one == value_two:
+ return True
+
+ return normalize_str(value_one) == normalize_str(value_two)
diff --git a/tests/conftest.py b/tests/conftest.py
index a2dff0a..df312e0 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,5 +1,8 @@
+import shutil
from uuid import UUID
+from essentials.folders import ensure_folder
+
from openapidocs.mk.v3.examples import IntegerExampleHandler, StringExampleHandler
# override the UUID example generator to return the same value, so that tests can have
@@ -12,3 +15,10 @@
"int32": lambda: 26,
"int64": lambda: 26,
}
+
+try:
+ shutil.rmtree("_test_files")
+except OSError:
+ pass
+
+ensure_folder("_test_files")
diff --git a/tests/res/example1-output-plain.md b/tests/res/example1-output-plain.md
index 3a85bba..035351c 100644
--- a/tests/res/example1-output-plain.md
+++ b/tests/res/example1-output-plain.md
@@ -27,6 +27,8 @@ Optional multiline or single-line description in
## Blobs
+
+
### POST /api/blobs/initialize-upload
Initializes a file upload operation.
@@ -144,6 +146,8 @@ Refer to the common response description: [Unauthorized](#unauthorized)
## Categories
+
+
### GET /api/categories
Gets the list of categories supported by the system.
@@ -253,6 +257,8 @@ _Other possible types: text/json, text/plain_
## Countries
+
+
### GET /api/countries
Gets a list of countries of the World with English dislay names and ISO country
codes.
@@ -292,6 +298,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### GET /api/system-countries
Gets a list of countries supported by the system, with English dislay names and
ISO country codes.
@@ -335,6 +343,8 @@ _Other possible types: text/json, text/plain_
## Downloads
+
+
### GET /api/downloads
Gets a paginated set of downloads records.
@@ -473,6 +483,8 @@ _Other possible types: text/json, text/plain_
## Health
+
+
### GET /api/health
API health check
@@ -524,6 +536,8 @@ _Other possible types: text/json, text/plain_
## Professionals
+
+
### GET /api/pro/own-context
@@ -596,6 +610,8 @@ _Other possible types: text/json, text/plain_
## Info
+
+
### GET /api/info
Returns information about the API itself.
@@ -648,6 +664,8 @@ _Other possible types: text/json, text/plain_
## Releases
+
+
### GET /api/releases/{releaseId}
Returns details about a release by id.
@@ -840,6 +858,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### DELETE /api/releases/{releaseId}
Deletes a release by id.
@@ -850,7 +870,9 @@ Deletes a release by id.
| AAD | header | string | N/A | No | Access token issued by Azure Active Directory. |
| releaseId | path | string | | No | |
-### Response 200 OK### PATCH /api/releases/{releaseId}
+### Response 200 OK
+
+### PATCH /api/releases/{releaseId}
**Input parameters**
@@ -1105,6 +1127,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### GET /api/releases
@@ -1229,6 +1253,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### POST /api/releases
@@ -1320,6 +1346,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### GET /api/current-releases
@@ -1361,6 +1389,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### GET /api/orgs/current-releases
@@ -1402,6 +1432,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### GET /api/releases/{releaseId}/history
@@ -1504,6 +1536,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### GET /api/releases/{releaseId}/file/{nodeId}
@@ -1544,6 +1578,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### GET /api/releases/{releaseId}/file/{nodeId}/downloads
@@ -1584,6 +1620,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### PUT /api/releases/{releaseId}/files
@@ -1808,6 +1846,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### DELETE /api/releases/{releaseId}/files
@@ -2029,6 +2069,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### PUT /api/releases/{releaseId}/orgs
@@ -2259,6 +2301,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### DELETE /api/releases/{releaseId}/orgs
@@ -2479,6 +2523,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### POST /api/releases/{releaseId}/clone
@@ -2671,6 +2717,8 @@ _Other possible types: text/json, text/plain_
}
```
+
+
### POST /api/releases/{releaseId}/publish
diff --git a/tests/res/example6-output.md b/tests/res/example6-output.md
index bf946d0..53c6a54 100644
--- a/tests/res/example6-output.md
+++ b/tests/res/example6-output.md
@@ -8,7 +8,7 @@ Most likely, it is not desirable to edit this file by hand!
# Test v1
-##
+## Endpoints
diff --git a/tests/res/example7-output.md b/tests/res/example7-output.md
index a81d35f..ac2bb4a 100644
--- a/tests/res/example7-output.md
+++ b/tests/res/example7-output.md
@@ -39,7 +39,7 @@ Most likely, it is not desirable to edit this file by hand!
-##
+## Endpoints
diff --git a/tests/test_cli.py b/tests/test_cli.py
index f917f42..0e955fb 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -11,7 +11,7 @@
from openapidocs.mk.jinja import OutputStyle
from openapidocs.utils.source import SourceError, read_from_source, read_from_url
from openapidocs.utils.web import FailedRequestError, ensure_success, http_get
-from tests.common import get_file_json
+from tests.common import compatible_str, get_file_json
from .serverfixtures import * # noqa
from .serverfixtures import BASE_URL
@@ -35,7 +35,7 @@ def remove_file(file_path: Path):
def contents_equals(file_path_1, file_path_2):
- assert read_file(file_path_1) == read_file(file_path_2)
+ assert compatible_str(read_file(file_path_1), read_file(file_path_2))
def test_fetch_json(example_1_data):
@@ -107,7 +107,7 @@ def test_generate_docs_command_invalid_source():
],
)
def test_main_command_gen_mkdocs_docs(valid_source):
- test_output = Path(f"{uuid4()}.md")
+ test_output = Path(f"_test_files/{uuid4()}.md")
assert test_output.exists() is False
runner = CliRunner()
@@ -184,7 +184,7 @@ def test_main_command_gen_plantuml_api_docs(valid_source):
def test_main_command_gen_plain_markdown_docs():
valid_source = "tests/res/example1-openapi.json"
- test_output = Path(f"{uuid4()}.md")
+ test_output = Path(f"_test_files/{uuid4()}.md")
assert test_output.exists() is False
runner = CliRunner()
diff --git a/tests/test_mk_v3.py b/tests/test_mk_v3.py
index 95442a0..7404d5e 100644
--- a/tests/test_mk_v3.py
+++ b/tests/test_mk_v3.py
@@ -7,6 +7,7 @@
)
from openapidocs.mk.v3.examples import ObjectExampleHandler
from tests.common import (
+ compatible_str,
get_file_json,
get_file_yaml,
get_resource_file_content,
@@ -22,7 +23,7 @@ def test_v3_markdown_gen(example_file):
handler = OpenAPIV3DocumentationHandler(data)
html = handler.write()
- assert html == expected_result
+ assert compatible_str(html, expected_result)
def test_v3_markdown_gen_split_file():
@@ -37,7 +38,7 @@ def test_v3_markdown_gen_split_file():
html = handler.write()
- assert html == expected_result
+ assert compatible_str(html, expected_result)
def test_file_ref_raises_for_missing_file():
@@ -308,4 +309,4 @@ def test_v3_markdown_yaml(example_file):
html = handler.write()
- assert html == expected_result
+ compatible_str(html, expected_result)