diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 0ad0bd9e..00000000 --- a/.coveragerc +++ /dev/null @@ -1,6 +0,0 @@ -[run] -omit = - tests/* - .tox/* - setup.py - *.egg/* diff --git a/.flake8 b/.flake8 index 35a15583..f81cf2c7 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,3 @@ [flake8] -ignore = E203, E266, E501, W503 -max-line-length = 80 -max-complexity = 18 -select = B,C,E,F,W,T4,B9 -exclude = docs/conf.py,.tox +max-line-length = 79 +extend-ignore = E203, E501 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0d78aa51..28b19a44 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,40 +1,101 @@ -# This workflow will install Python dependencies, run tests and lint with a variety of Python versions -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - -name: Python package +--- +name: CI on: push: - branches: [ master ] + branches: ["master"] pull_request: - branches: [ master ] + branches: ["master"] + workflow_dispatch: jobs: - build: + tests: + name: "Python ${{ matrix.python-version }} on ${{ matrix.platform }}" + runs-on: "${{ matrix.platform }}" + env: + USING_COVERAGE: '3.7,3.8' - runs-on: ubuntu-latest strategy: matrix: - python-version: [3.5, 3.6, 3.7, 3.8] + platform: ["ubuntu-latest", "windows-latest"] + python-version: ["3.6", "3.7", "3.8"] steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - - name: Lint with flake8 - run: | - pip install flake8 - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - - name: Test with pytest - run: | - pip install pytest - pytest + - uses: "actions/checkout@v2" + - uses: "actions/setup-python@v2" + with: + python-version: "${{ matrix.python-version }}" + + - name: "Install dependencies" + run: | + python -VV + python -m site + python -m pip install --upgrade pip setuptools wheel + python -m pip install --upgrade coverage[toml] virtualenv tox tox-gh-actions + + - name: "Run tox targets for ${{ matrix.python-version }}" + run: "python -m tox" + env: + PLATFORM: ${{ matrix.platform }} + + # We always use a modern Python version for combining coverage to prevent + # parsing errors in older versions for modern code. + - uses: "actions/setup-python@v2" + with: + python-version: "3.8" + + - name: "Combine coverage" + run: | + set -xe + python -m pip install coverage[toml] + python -m coverage combine + python -m coverage xml + if: "contains(env.USING_COVERAGE, matrix.python-version) && matrix.platform == 'ubuntu-latest'" + + - name: "Upload coverage to Codecov" + if: "contains(env.USING_COVERAGE, matrix.python-version) && matrix.platform == 'ubuntu-latest'" + uses: "codecov/codecov-action@v1" + with: + fail_ci_if_error: true + + package: + name: "Build & verify package" + runs-on: "ubuntu-latest" + + steps: + - uses: "actions/checkout@v2" + - uses: "actions/setup-python@v2" + with: + python-version: "3.8" + + - name: "Install pep517 and twine" + run: "python -m pip install pep517 twine" + + - name: "Build package" + run: "python -m pep517.build --source --binary ." + + - name: "List result" + run: "ls -l dist" + + - name: "Check long_description" + run: "python -m twine check dist/*" + + install-dev: + strategy: + matrix: + os: ["ubuntu-latest", "windows-latest", "macos-latest"] + + name: "Verify dev env" + runs-on: "${{ matrix.os }}" + + steps: + - uses: "actions/checkout@v2" + - uses: "actions/setup-python@v2" + with: + python-version: "3.8" + + - name: "Install in dev mode" + run: "python -m pip install -e .[dev]" + + - name: "Import package" + run: "python -c 'import jwt; print(jwt.__version__)'" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2617e46b..53c271ed 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,18 @@ repos: - repo: https://github.com/psf/black - rev: 19.3b0 + rev: 19.10b0 hooks: - id: black - language_version: python3.7 + language_version: python3.8 - repo: https://gitlab.com/pycqa/flake8 - rev: 3.7.8 + rev: 3.7.9 hooks: - id: flake8 - language_version: python3.7 + language_version: python3.8 - repo: https://github.com/asottile/seed-isort-config - rev: v1.9.3 + rev: v1.9.4 hooks: - id: seed-isort-config @@ -21,10 +21,10 @@ repos: hooks: - id: isort additional_dependencies: [toml] - language_version: python3.7 + language_version: python3.8 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.3.0 + rev: v2.4.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 5e2dd0c4..00000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: python -matrix: - include: - - python: 3.5 - env: TOXENV=py35-crypto,py35-nocrypto,py35-contrib_crypto - - python: 3.6 - env: TOXENV=py36-crypto,py36-nocrypto,py36-contrib_crypto - - python: 3.7 - env: TOXENV=lint,typing,py37-crypto,py37-nocrypto,py37-contrib_crypto - - python: 3.8 - env: TOXENV=py38-crypto,py38-nocrypto,py38-contrib_crypto -install: - - pip install -U pip - - pip install -U tox coveralls -script: - - tox -after_success: - - coveralls diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index ac139e97..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,38 +0,0 @@ -environment: - matrix: - - PYTHON: "C:\\Python35-x64" - TOX_ENV: "py35-crypto" - - - PYTHON: "C:\\Python36-x64" - TOX_ENV: "py36-crypto" - - - PYTHON: "C:\\Python37-x64" - TOX_ENV: "py37-crypto" - -init: - - SET PATH=%PYTHON%;%PATH% - - python -c "import sys;sys.stdout.write(sys.version)" - - ECHO . - - python -m pip list - -# pip & virtualenv are pre-installed -install: - - python -m pip install -U setuptools - - python -m pip install -U pip - - python -m pip install -U wheel - - python -m pip install -U tox - -build: false # Not a C# project, build stuff at the test step instead. - -test_script: - - python -m tox -e %TOX_ENV% - -after_test: - - python setup.py bdist_wheel - - ps: "ls dist" - -artifacts: - - path: dist\* - -#on_success: -# - TODO: upload the content of dist/*.whl to a public wheelhouse diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..60a1e5c1 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,10 @@ +--- +comment: false +coverage: + status: + patch: + default: + target: "100" + project: + default: + target: "100" diff --git a/docs/conf.py b/docs/conf.py index 39f24b8a..42a978d9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,38 +1,45 @@ -# PyJWT documentation build configuration file, created by -# sphinx-quickstart on Thu Oct 22 18:11:10 2015. -# -# This file is execfile()d with the current directory set to its -# containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - +import codecs import os import re -import shlex -import sys -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. import sphinx_rtd_theme -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) -# -- General configuration ------------------------------------------------ +def read(*parts): + """ + Build an absolute path from *parts* and and return the contents of the + resulting file. Assume UTF-8 encoding. + """ + here = os.path.abspath(os.path.dirname(__file__)) + with codecs.open(os.path.join(here, *parts), "rb", "utf-8") as f: + return f.read() -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' + +def find_version(*file_paths): + """ + Build a path from *file_paths* and search for a ``__version__`` + string inside. + """ + version_file = read(*file_paths) + version_match = re.search( + r"^__version__ = ['\"]([^'\"]*)['\"]", version_file, re.M + ) + if version_match: + return version_match.group(1) + raise RuntimeError("Unable to find version string.") + + +# -- General configuration ------------------------------------------------ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = [] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", +] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -42,9 +49,6 @@ # source_suffix = ['.rst', '.md'] source_suffix = ".rst" -# The encoding of source files. -# source_encoding = 'utf-8-sig' - # The master toctree document. master_doc = "index" @@ -57,19 +61,11 @@ # |version| and |release|, also used in various other places throughout the # built documents. # -# The short X.Y version. -def get_version(package): - """ - Return package version as listed in `__version__` in `init.py`. - """ - with open(os.path.join("..", package, "__init__.py"), "rb") as init_py: - src = init_py.read().decode("utf-8") - return re.search("__version__ = ['\"]([^'\"]+)['\"]", src).group(1) - - -version = get_version("jwt") # The full version, including alpha/beta/rc tags. -release = version +release = find_version("../jwt/__init__.py") + +# The short X.Y version. +version = release.rsplit(u".", 1)[0] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -78,40 +74,13 @@ def get_version(package): # Usually you set "language" from the command line for these cases. language = None -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build"] -# The reST default role (used for this markup: `text`) to use for all -# documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# If true, keep warnings as "system message" paragraphs in the built documents. -# keep_warnings = False - # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -123,30 +92,6 @@ def get_version(package): html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -# html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None - # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". @@ -159,118 +104,15 @@ def get_version(package): ] } -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -# html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -# html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -# html_search_scorer = 'scorer.js' - # Output file base name for HTML help builder. htmlhelp_basename = "PyJWTdoc" -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. - #'preamble': '', - # Latex figure (float) alignment - #'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). -latex_documents = [ - (master_doc, "PyJWT.tex", "PyJWT Documentation", "José Padilla", "manual") -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -# latex_domain_indices = True - # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [(master_doc, "pyjwt", "PyJWT Documentation", [author], 1)] - -# If true, show URL addresses after external links. -# man_show_urls = False +man_pages = [(master_doc, "pyjwt", u"PyJWT Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -282,22 +124,10 @@ def get_version(package): ( master_doc, "PyJWT", - "PyJWT Documentation", + u"PyJWT Documentation", author, "PyJWT", "One line description of project.", "Miscellaneous", ) ] - -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] - -# If false, no module index is generated. -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' - -# If true, do not generate a @detailmenu in the "Top" node's menu. -# texinfo_no_detailmenu = False diff --git a/pyproject.toml b/pyproject.toml index 4efdeb32..49b360f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,20 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + + +[tool.coverage.run] +parallel = true +branch = true +source = ["jwt"] + +[tool.coverage.paths] +source = ["jwt", ".tox/*/site-packages"] + +[tool.coverage.report] +show_missing = true + + [tool.black] line-length = 79 diff --git a/pytest.ini b/pytest.ini deleted file mode 100644 index fb1850e4..00000000 --- a/pytest.ini +++ /dev/null @@ -1,2 +0,0 @@ -[tool:pytest] -addopts = --cov-report term-missing --cov-config=.coveragerc --cov . diff --git a/setup.cfg b/setup.cfg index 6f6616b4..7981bfa7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,11 +37,24 @@ console_scripts = pyjwt = jwt.__main__:main [options.extras_require] +docs = + sphinx + sphinx-rtd-theme + zope.interface crypto = - cryptography >= 1.4 + cryptography>=1.4 tests = - pytest>=4.0.1,<5.0.0 - pytest-cov>=2.6.0,<3.0.0 + pytest>=6.0.0,<7.0.0 + coverage[toml]==5.0.4 +dev = + sphinx + sphinx-rtd-theme + zope.interface + cryptography>=1.4 + pytest>=6.0.0,<7.0.0 + coverage[toml]==5.0.4 + mypy + pre-commit [options.packages.find] exclude = diff --git a/tests/utils.py b/tests/utils.py index ad39f759..463deb06 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -29,7 +29,7 @@ def int_from_bytes(data, byteorder, signed=False): result = 0 while len(data) > 0: - digit, = struct.unpack(">I", data[:4]) + (digit,) = struct.unpack(">I", data[:4]) result = (result << 32) + digit data = data[4:] diff --git a/tox.ini b/tox.ini index 8e14a535..159d7a63 100644 --- a/tox.ini +++ b/tox.ini @@ -1,27 +1,93 @@ +[pytest] +strict = true +addopts = -ra +testpaths = tests +filterwarnings = + once::Warning + ignore:::pympler[.*] + + +[gh-actions] +python = + 3.6: py36 + 3.7: py37, docs + 3.8: py38, lint, manifest, typing + + +[gh-actions:env] +PLATFORM = + ubuntu-latest: linux + windows-latest: windows + + [tox] envlist = lint typing - py{35,36,37,38}-crypto - py{35,36,37,38}-contrib_crypto - py{35,36,37,38}-nocrypto + py{36,37,38}-crypto-{linux,windows} + py{36,37,38}-nocrypto-{linux,windows} + manifest + docs + pypi-description + coverage-report +isolated_build = True [testenv] -extras = tests -commands = pytest -deps = +# Prevent random setuptools/pip breakages like +# https://github.com/pypa/setuptools/issues/1042 from breaking our builds. +setenv = + VIRTUALENV_NO_DOWNLOAD=1 +extras = + tests crypto: cryptography - contrib_crypto: pycrypto - contrib_crypto: ecdsa +commands = coverage run -m pytest {posargs} + + +[testenv:docs] +basepython = python3.7 +extras = docs +commands = + sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs docs/_build/html + sphinx-build -n -T -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html + python -m doctest README.rst [testenv:typing] -deps = mypy -commands = mypy --python-version 3.5 --ignore-missing-imports jwt +basepython = python3.8 +extras = dev +commands = mypy --ignore-missing-imports jwt [testenv:lint] -deps = pre-commit +basepython = python3.8 +extras = dev passenv = HOMEPATH # needed on Windows commands = pre-commit run --all-files + + +[testenv:manifest] +basepython = python3.8 +deps = check-manifest +skip_install = true +commands = check-manifest + + +[testenv:pypi-description] +basepython = python3.8 +skip_install = true +deps = + twine + pip >= 18.0.0 +commands = + pip wheel -w {envtmpdir}/build --no-deps . + twine check {envtmpdir}/build/* + + +[testenv:coverage-report] +basepython = python3.8 +skip_install = true +deps = coverage[toml]==5.0.4 +commands = + coverage combine + coverage report