diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 0c0032bb..52d69a0a 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -3,17 +3,6 @@ on: push: pull_request: jobs: - lint: - name: lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3.1.0 - - uses: actions/setup-python@v4.3.0 - with: - python-version: '3.11' - - run: python -m pip install --upgrade pip wheel - - run: pip install tox - - run: tox -elint tests: name: ${{ matrix.name }} runs-on: ubuntu-latest @@ -21,29 +10,68 @@ jobs: fail-fast: false matrix: include: - - {name: '3.8', python: '3.8', tox: py38} - - {name: '3.12', python: '3.12', tox: py312} + - { name: '3.8', python: '3.8', tox: py38 } + - { name: '3.12', python: '3.12', tox: py312 } steps: - - uses: actions/checkout@v3.1.0 - - uses: actions/setup-python@v4.3.0 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} + allow-prereleases: true - name: Run test run: | - python -m pip install --upgrade pip wheel - pip install tox - tox -e${{ matrix.tox }} + python -m pip install tox + python -m tox -e ${{ matrix.tox }} - name: Upload coverage uses: codecov/codecov-action@v3 - release: - needs: [lint, tests] - name: PyPI release + build: + name: Build package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Install pypa/build + run: python -m pip install build + - name: Build a binary wheel and a source tarball + run: python -m build + - name: Install twine + run: python -m pip install twine + - name: Check build + run: python -m twine check --strict dist/* + - name: Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: dist/ + # this duplicates pre-commit.ci, so only run it on tags + # it guarantees that linting is passing prior to a release + lint-pre-release: if: startsWith(github.ref, 'refs/tags') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3.1.0 - - uses: actions/setup-python@v4.3.0 - - run: python -m pip install --upgrade pip wheel - - run: pip install twine - - run: python setup.py sdist bdist_wheel - - run: twine upload -u __token__ -p ${{ secrets.PYPI_API_TOKEN }} dist/* + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + - run: python -m pip install tox + - run: python -m tox -e lint + publish-to-pypi: + name: PyPI release + if: startsWith(github.ref, 'refs/tags/') + needs: [build, tests, lint-pre-release] + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/flask-smorest + permissions: + id-token: write + steps: + - name: Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d55b7d38..478a7f4c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,21 +1,16 @@ repos: -- repo: https://github.com/asottile/pyupgrade - rev: v3.15.1 +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.2.2 hooks: - - id: pyupgrade - args: [--py38-plus] -- repo: https://github.com/psf/black - rev: 24.2.0 + - id: ruff + - id: ruff-format +- repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.28.0 hooks: - - id: black - language_version: python3 -- repo: https://github.com/pycqa/flake8 - rev: 7.0.0 - hooks: - - id: flake8 - additional_dependencies: [flake8-bugbear==24.2.6] + - id: check-github-workflows + - id: check-readthedocs - repo: https://github.com/asottile/blacken-docs rev: 1.16.0 hooks: - id: blacken-docs - additional_dependencies: [black==22.3.0] + additional_dependencies: [black==23.12.1] diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 5879f5cd..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,11 +0,0 @@ -include *.rst LICENSE - -graft flask_smorest - -graft docs -prune docs/_build - -graft tests -include tox.ini - -global-exclude *.py[co] diff --git a/README.rst b/README.rst index 1ab625c5..b8fd6786 100644 --- a/README.rst +++ b/README.rst @@ -30,6 +30,10 @@ flask-smorest :target: https://codecov.io/gh/marshmallow-code/flask-smorest :alt: Code coverage +.. image:: https://results.pre-commit.ci/badge/github/marshmallow-code/flask-smorest/dev.svg + :target: https://results.pre-commit.ci/latest/github/marshmallow-code/flask-smorest/dev + :alt: pre-commit.ci status + .. image:: https://readthedocs.org/projects/flask-smorest/badge/ :target: http://flask-smorest.readthedocs.io/ :alt: Documentation diff --git a/docs/openapi.rst b/docs/openapi.rst index df3ff962..54213a8a 100644 --- a/docs/openapi.rst +++ b/docs/openapi.rst @@ -214,6 +214,7 @@ The :meth:`Api.register_converter` allows to register a converter in the # Register MongoDB's ObjectId converter in Flask application app.url_map.converters["objectid"] = ObjectIdConverter + # Define custom converter to schema function def objectidconverter2paramschema(converter): return {"type": "string", "format": "ObjectID"} diff --git a/flask_smorest/arguments.py b/flask_smorest/arguments.py index b6cdaeed..21b83a42 100644 --- a/flask_smorest/arguments.py +++ b/flask_smorest/arguments.py @@ -25,7 +25,7 @@ def arguments( description=None, example=None, examples=None, - **kwargs + **kwargs, ): """Decorator specifying the schema used to deserialize parameters diff --git a/flask_smorest/etag.py b/flask_smorest/etag.py index 5fcef904..9ef3f832 100644 --- a/flask_smorest/etag.py +++ b/flask_smorest/etag.py @@ -244,7 +244,7 @@ def _prepare_etag_doc(self, doc, doc_info, *, api, spec, method, **kwargs): for success_status_code in success_status_codes: doc["responses"][success_status_code].setdefault("headers", {})[ "ETag" - ] = (ETAG_HEADER if spec.openapi_version.major < 3 else "ETAG") + ] = ETAG_HEADER if spec.openapi_version.major < 3 else "ETAG" if responses: doc = deepupdate(doc, {"responses": responses}) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..eeafec41 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,97 @@ +[project] +name = "flask-smorest" +version = "0.44.0" +description = "Flask/Marshmallow-based REST API framework" +readme = "README.rst" +license = { file = "LICENSE" } +authors = [{ name = "Jérôme Lafréchoux", email = "jerome@jolimont.fr" }] +maintainers = [ + { name = "Jérôme Lafréchoux", email = "jerome@jolimont.fr" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Internet :: WWW/HTTP", + "Environment :: Web Environment", + "Framework :: Flask", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +requires-python = ">=3.8" +dependencies = [ + "werkzeug>=3.0.1,<4", + "flask>=3.0.2,<4", + "marshmallow>=3.18.0,<4", + "webargs>=8.0.0,<9", + "apispec[marshmallow]>=6.0.0,<7", +] + +[project.urls] +Changelog = "https://flask-smorest.readthedocs.io/en/latest/changelog.html" +Funding = "https://opencollective.com/marshmallow" +Issues = "https://github.com/marshmallow-code/flask-smorest/issues" +Source = "https://github.com/marshmallow-code/flask-smorest" +Tidelift = "https://tidelift.com/subscription/pkg/pypi-marshmallow?utm_source=pypi-marshmallow&utm_medium=pypi" + +[project.optional-dependencies] +docs = [ + "sphinx==7.2.6", + "sphinx-issues==4.0.0", + "alabaster==0.7.16", +] +tests = [ + "pytest==8.0.2", + "pytest-cov==4.1.0", + "coverage==7.4.3", + "werkzeug==3.0.1", + "flask==3.0.2", + "marshmallow==3.20.2", + "webargs==8.4.0", + "apispec==6.4.0", + "PyYAML==6.0.1", +] +dev = [ + "flask-smorest[tests]", + "tox", + "pre-commit~=3.6", +] + +[build-system] +requires = ["flit_core<4"] +build-backend = "flit_core.buildapi" + +[tool.flit.sdist] +include = [ + "docs/", + "tests/", + "CHANGELOG.rst", + "CONTRIBUTING.rst", + "tox.ini", +] +exclude = ["docs/_build/"] + +[tool.ruff] +src = ["src"] +fix = true +show-fixes = true +output-format = "full" + +[tool.ruff.lint] +ignore = ["I001", "E203", "E266", "E501", "E731"] +select = [ + "B", # flake8-bugbear + "E", # pycodestyle error + "F", # pyflakes + "I", # isort + "UP", # pyupgrade + "W", # pycodestyle warning +] + +[tool.pytest.ini_options] +norecursedirs = ".git .ropeproject .tox docs env venv" +addopts = "-v --tb=short" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 336cd435..00000000 --- a/setup.cfg +++ /dev/null @@ -1,17 +0,0 @@ -[bumpversion] -current_version = 0.44.0 -commit = True -tag = True -tag_name = {new_version} - -[flake8] -max-line-length = 88 -extend-ignore = E203 - -[bumpversion:file:setup.py] -search = version="{current_version}" -replace = version="{new_version}" - -[bumpversion:file:flask_smorest/__init__.py] -search = __version__ = "{current_version}" -replace = __version__ = "{new_version}" diff --git a/setup.py b/setup.py deleted file mode 100644 index 17483257..00000000 --- a/setup.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python3 - -from setuptools import setup, find_packages - -EXTRAS_REQUIRE = { - "tests": [ - "pytest==8.0.2", - "pytest-cov==4.1.0", - "coverage==7.4.3", - "werkzeug==3.0.1", - "flask==3.0.2", - "marshmallow==3.20.2", - "webargs==8.4.0", - "apispec==6.4.0", - "PyYAML==6.0.1", - ], - "lint": [ - "pre-commit==3.6.2", - ], -} -EXTRAS_REQUIRE["dev"] = EXTRAS_REQUIRE["tests"] + EXTRAS_REQUIRE["lint"] - - -# Get the long description from the README file -with open("README.rst", encoding="utf-8") as f: - long_description = f.read() - -setup( - name="flask-smorest", - version="0.44.0", - description="Flask/Marshmallow-based REST API framework", - long_description=long_description, - url="https://github.com/marshmallow-code/flask-smorest", - author="Jérôme Lafréchoux", - author_email="jerome@jolimont.fr", - license="MIT", - # See https://pypi.python.org/pypi?%3Aaction=list_classifiers - classifiers=[ - "Development Status :: 4 - Beta", - "Intended Audience :: Developers", - "Topic :: Internet :: WWW/HTTP", - "Environment :: Web Environment", - "Framework :: Flask", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3 :: Only", - ], - keywords=[ - "REST", - "openapi", - "swagger", - "flask", - "marshmallow", - "apispec", - "webargs", - ], - packages=find_packages(exclude=["tests*"]), - include_package_data=True, - package_data={ - "": ["spec/templates/*"], - }, - python_requires=">=3.8", - install_requires=[ - "werkzeug>=3.0.1,<4", - "flask>=3.0.2,<4", - "marshmallow>=3.18.0,<4", - "webargs>=8.0.0,<9", - "apispec[marshmallow]>=6.0.0,<7", - ], - extras_require=EXTRAS_REQUIRE, -) diff --git a/tests/test_response.py b/tests/test_response.py index a7dd1b30..85047c15 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -172,8 +172,9 @@ def func(): else: assert "produces" not in get assert get["responses"]["200"]["content"] == { - content_type - or "application/json": {"schema": build_ref(api.spec, "schema", "Doc")} + content_type or "application/json": { + "schema": build_ref(api.spec, "schema", "Doc") + } } @pytest.mark.parametrize("openapi_version", ["2.0", "3.0.2"])