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)