diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..c2486c7 --- /dev/null +++ b/.flake8 @@ -0,0 +1,7 @@ +[flake8] +max-line-length = 79 +# E203: whitespace before :, flake8 disagrees with PEP 8 +# W503: line break after binary operator, flake8 disagrees with PEP 8 +ignore = E203, W503 +exclude = + docs/conf.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4afeec1..bc0b8d6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,16 +11,16 @@ name: Python CI - "renovate/**" - "tickets/**" - "u/**" - tags: - - "*" pull_request: {} + release: + types: [published] jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true @@ -36,9 +36,11 @@ jobs: - "3.8" - "3.9" - "3.10" + - "3.11" + - "3.12" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true @@ -52,7 +54,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 @@ -62,7 +64,7 @@ jobs: - name: Run tox uses: lsst-sqre/run-tox@v1 with: - python-version: "3.10" + python-version: "3.12" tox-envs: "docs" # Only attempt documentation uploads for tagged releases and pull @@ -80,18 +82,42 @@ jobs: username: ${{ secrets.LTD_USERNAME }} password: ${{ secrets.LTD_PASSWORD }} + test-packaging: + name: Test packaging + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history for setuptools_scm + + - name: Build and publish + uses: lsst-sqre/build-and-publish-to-pypi@v2 + with: + python-version: '3.12' + upload: false + pypi: + # This job requires set up: + # 1. Set up a trusted publisher for PyPI + # 2. Set up a "pypi" environment in the repository + # See https://github.com/lsst-sqre/build-and-publish-to-pypi + name: Upload release to PyPI runs-on: ubuntu-latest - needs: [test, docs] - if: startsWith(github.ref, 'refs/tags/') + needs: [lint, test, docs, test-packaging] + environment: + name: pypi + url: https://pypi.org/p/templatekit + permissions: + id-token: write + if: github.event_name == 'release' && github.event.action == 'published' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # full history for setuptools_scm - - name: Upload - uses: lsst-sqre/build-and-publish-to-pypi@v1 + - name: Build and publish + uses: lsst-sqre/build-and-publish-to-pypi@v2 with: - pypi-token: ${{ secrets.PYPI_SQRE_ADMIN }} - python-version: "3.10" + python-version: '3.12' diff --git a/.github/workflows/dependencies.yaml b/.github/workflows/dependencies.yaml new file mode 100644 index 0000000..f57ecc5 --- /dev/null +++ b/.github/workflows/dependencies.yaml @@ -0,0 +1,33 @@ +name: Dependency Update + +'on': + schedule: + - cron: '0 12 * * 1' + workflow_dispatch: {} + +jobs: + update: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + + - name: Run neophile + uses: lsst-sqre/run-neophile@v1 + with: + python-version: '3.11' + mode: pr + types: pre-commit + app-id: ${{ secrets.NEOPHILE_APP_ID }} + app-secret: ${{ secrets.NEOPHILE_PRIVATE_KEY }} + + - name: Report status + if: always() + uses: ravsamhq/notify-slack-action@v2 + with: + status: ${{ job.status }} + notify_when: 'failure' + notification_title: 'Periodic dependency update for {repo} failed' + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_ALERT_WEBHOOK }} diff --git a/.github/workflows/periodic.yaml b/.github/workflows/periodic.yaml index 4aab78c..235f7c6 100644 --- a/.github/workflows/periodic.yaml +++ b/.github/workflows/periodic.yaml @@ -7,14 +7,14 @@ name: Periodic CI "on": schedule: - - cron: "0 12 * * 1" + - cron: "0 13 * * 1" jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true @@ -32,7 +32,7 @@ jobs: - "3.10" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: true @@ -48,17 +48,17 @@ jobs: python-version: ${{ matrix.python }} tox-envs: "py,typing" - pypi: + test-packaging: + name: Test packaging runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 # full history for setuptools_scm - - name: Upload - uses: lsst-sqre/build-and-publish-to-pypi@v1 + - name: Build and publish + uses: lsst-sqre/build-and-publish-to-pypi@v2 with: - pypi-token: ${{ secrets.PYPI_SQRE_ADMIN }} - python-version: "3.10" + python-version: '3.12' upload: false diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5b4802c..e973e19 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,17 @@ Change log ########## +0.6.0 (2023-10-13) +================== + +- Add a new ``-i/--ignore`` option to the ``templatekit check`` command. + This option allows the user to ignore certain files when checking for untracked files or dirty Git state after regenerating examples. + This is useful for files that have dynamic content, such as dates and times, that we do not expect to be consistent. +- Migrate the packaging to ``pyproject.toml`` and retire the ``setup.cfg`` and ``setup.py`` files. +- Migrate to the new Rubin user guide theme for documentation. +- Add GitHub Actions workflow to update pre-commit hooks with Neophile. +- Adopted the PyPI trusted publishers workflow. + 0.5.1 (2022-03-15) ================== diff --git a/docs/_rst_epilog.rst b/docs/_rst_epilog.rst new file mode 100644 index 0000000..68d72d4 --- /dev/null +++ b/docs/_rst_epilog.rst @@ -0,0 +1,7 @@ + +.. _Cookiecutter: https://cookiecutter.readthedocs.io/en/latest/ +.. _Jinja: http://jinja.pocoo.org +.. _lsst/templates: https://github.com/lsst/templates +.. _mypy: http://www.mypy-lang.org +.. _tox: https://tox.readthedocs.io/en/latest/ +.. _pytest: https://docs.pytest.org/en/latest/ diff --git a/docs/conf.py b/docs/conf.py index f3d0394..adc5988 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,139 +1 @@ -import os -import sys - -import lsst_sphinx_bootstrap_theme -from documenteer.sphinxconfig.utils import form_ltd_edition_name - -# Work around Sphinx bug related to large and highly-nested source files -sys.setrecursionlimit(2000) - -# -- Common links and substitutions --------------------------------------- - -rst_epilog = """ - -.. _Cookiecutter: https://cookiecutter.readthedocs.io/en/latest/ -.. _Jinja: http://jinja.pocoo.org -.. _lsst/templates: https://github.com/lsst/templates -.. _mypy: http://www.mypy-lang.org -.. _tox: https://tox.readthedocs.io/en/latest/ -.. _pytest: https://docs.pytest.org/en/latest/ -""" - -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "sphinx.ext.autodoc", - "sphinx.ext.doctest", - "sphinx.ext.intersphinx", - "sphinx.ext.todo", - "sphinx.ext.ifconfig", - "sphinx_click.ext", - "numpydoc", - "sphinx_automodapi.automodapi", - "sphinx_automodapi.smart_resolver", - "documenteer.sphinxext", -] - -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# source_suffix = ['.rst', '.md'] -source_suffix = ".rst" - -# The master toctree document. -master_doc = "index" - -# General information about the project. -project = "Templatekit" -copyright = "2018-2019 Association of Universities for Research in Astronomy" -author = "LSST Data Management" - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -if os.getenv("TRAVIS_BRANCH", default="master") == "master": - # Use the current release as the version tag if on master - version = "Current" - release = version -else: - # Use branch name as the version tag - version = form_ltd_edition_name( - git_ref_name=os.getenv("TRAVIS_BRANCH", default="master") - ) - release = version - -# 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", "README.rst"] - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - -# The reST default role cross-links Python (used for this markup: `text`) -default_role = "py:obj" - -# Intersphinx - -intersphinx_mapping = { - "python": ("https://docs.python.org/3/", None), - "cookiecutter": ("https://cookiecutter.readthedocs.io/en/latest/", None), -} - -# -- Options for linkcheck builder ---------------------------------------- - -linkcheck_retries = 2 - -# -- Options for HTML output ---------------------------------------------- - -templates_path = [ - "_templates", - lsst_sphinx_bootstrap_theme.get_html_templates_path(), -] - -html_theme = "lsst_sphinx_bootstrap_theme" -html_theme_path = [lsst_sphinx_bootstrap_theme.get_html_theme_path()] - - -html_context = { - # Enable "Edit in GitHub" link - "display_github": True, - # https://{{ github_host|default("github.com") }}/{{ github_user }}/ - # {{ github_repo }}/blob/ - # {{ github_version }}{{ conf_py_path }}{{ pagename }}{{ suffix }} - "github_user": "lsst-sqre", - "github_repo": "templatekit", - "conf_py_path": "docs/", - # TRAVIS_BRANCH is available in CI, but master is a safe default - "github_version": os.getenv("TRAVIS_BRANCH", default="master") + "/", -} - -# 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 = {"logotext": project} - -# 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 = project - -# 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". -html_static_path = [] - -# If true, links to the reST sources are added to the pages. -html_show_sourcelink = False +from documenteer.conf.guide import * # noqa: F401, F403 diff --git a/docs/dev/index.rst b/docs/dev/index.rst new file mode 100644 index 0000000..bc35216 --- /dev/null +++ b/docs/dev/index.rst @@ -0,0 +1,10 @@ +################# +Development guide +################# + +.. toctree:: + :maxdepth: 2 + :titlesonly: + + development + release diff --git a/docs/documenteer.toml b/docs/documenteer.toml new file mode 100644 index 0000000..7701376 --- /dev/null +++ b/docs/documenteer.toml @@ -0,0 +1,43 @@ +[project] +title = "Templatekit" +copyright = "2018-2023 Association of Universities for Research in Astronomy, Inc. (AURA)" + +[project.python] +package = "templatekit" + +[sphinx] +rst_epilog_file = "_rst_epilog.rst" +disable_primary_sidebars = [ + "index", + "changelog", +] +extensions = [ + "sphinx_click.ext" +] +# nitpick_ignore = [ +# ["py:class", "pydantic.main.BaseModel"], +# ["py:class", "pydantic.utils.Representation"], +# ["py:class", "pydantic.networks.HttpUrl"], +# ["py:class", "pydantic.networks.AnyHttpUrl"], +# ["py:class", "pydantic.networks.AnyUrl"], +# ["py:class", "pydantic.errors.UrlError"], +# ["py:class", "pydantic.errors.PydanticValueError"], +# ["py:class", "pydantic.errors.PydanticErrorMixin"], +# ["py:class", "pydantic.networks.HttpUrl"], +# ["py:class", "pydantic.networks.AnyHttpUrl"], +# ["py:class", "pydantic.networks.AnyUrl"], +# ["py:class", "pydantic.errors.UrlError"], +# ["py:class", "pydantic.errors.PydanticValueError"], +# ["py:class", "pydantic.errors.PydanticErrorMixin"], +# ["py:class", "unicode"], +# ] + +[sphinx.linkcheck] +ignore = [ + "demo/index.html", + "https://support.orcid.org", +] + +[sphinx.intersphinx.projects] +cookiecutter = "https://cookiecutter.readthedocs.io/en/latest/" +python = "https://docs.python.org/3/" diff --git a/docs/index.rst b/docs/index.rst index cb754fc..09baeff 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,30 +19,7 @@ Templatekit is available from `PyPI `_: .. toctree:: :hidden: + user-guide/index + Template guide changelog - - -User guide -========== - -.. toctree:: - :maxdepth: 1 - - cli-reference - -Template development guide -========================== - -.. toctree:: - :maxdepth: 2 - - template-guide/index - -Development guide -================= - -.. toctree:: - :maxdepth: 1 - - dev/development - dev/release + dev/index diff --git a/docs/cli-reference.rst b/docs/user-guide/cli-reference.rst similarity index 100% rename from docs/cli-reference.rst rename to docs/user-guide/cli-reference.rst diff --git a/docs/user-guide/index.rst b/docs/user-guide/index.rst new file mode 100644 index 0000000..024d2c7 --- /dev/null +++ b/docs/user-guide/index.rst @@ -0,0 +1,9 @@ +########## +User guide +########## + +.. toctree:: + :maxdepth: 2 + :titlesonly: + + cli-reference diff --git a/pyproject.toml b/pyproject.toml index ab54fed..8146f9b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,78 @@ -[build-system] -requires = [ - "setuptools>=42", - "wheel", - "setuptools_scm[toml]>=3.4" +[project] +# https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ +name = "templatekit" +description = "Tookit for rendering Rubin Observatory project templates" +license = { file = "LICENSE" } +readme = "README.rst" +keywords = ["rubin", "lsst"] +# https://pypi.org/classifiers/ +classifiers = [ + "Development Status :: 4 - Beta", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Intended Audience :: Developers", + "Natural Language :: English", + "Operating System :: POSIX", + "Typing :: Typed", +] +requires-python = ">=3.8" +dependencies = [ + "Cerberus>=1.2", + "click", + "cookiecutter>=1.6.0", + "GitPython>=3.0.0", + "Jinja2>=2.10", + "pyperclip>=1.6.0", + "PyYAML>=5.1", + "scons>=3.0.1", +] +dynamic = ["version"] + +[[project.authors]] +name = "Association of Universities for Research in Astronomy, Inc. (AURA)" +email = "sqre-admin@lists.lsst.org" + +[[project.authors]] +name = "Jonathan Sick" +email = "jsick@lsst.org" + + +[project.optional-dependencies] +dev = [ + "coverage[toml]", + "mypy", + "pre-commit", + "pytest", + "pytest-cov", + "types-PyYAML", + # documentation + "documenteer[guide]", + "sphinx-click", ] -build-backend = 'setuptools.build_meta' + +[project.urls] +Homepage = "https://templatekit.lsst.io" +Source = "https://github.com/lsst-sqre/templatekit" + +[project.scripts] +templatekit = "templatekit.scripts.main:main" + +[build-system] +requires = ["setuptools>=61", "wheel", "setuptools_scm[toml]>=6.2"] +build-backend = "setuptools.build_meta" [tool.setuptools_scm] +[tool.setuptools.packages.find] +# https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html +where = ["src"] +include = ["templatekit*"] + [tool.coverage.run] parallel = true branch = true @@ -49,9 +114,9 @@ exclude = ''' # Multi-line strings are implicitly treated by black as regular expressions [tool.isort] -include_trailing_comma = true -multi_line_output = 3 -known_first_party = ["templatekit", "tests"] +profile = "black" +line_length = 79 +known_first_party = "templatekit" skip = ["docs/conf.py"] [tool.pytest.ini_options] @@ -69,3 +134,16 @@ norecursedirs = [ python_files = [ "tests/*.py", ] + +[tool.mypy] +disallow_untyped_defs = true +disallow_incomplete_defs = true +ignore_missing_imports = true +show_error_codes = true +strict_equality = true +warn_redundant_casts = true +warn_unreachable = true +warn_unused_ignores = true +exclude = [ + "tests/data", +] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 9e5b357..0000000 --- a/setup.cfg +++ /dev/null @@ -1,83 +0,0 @@ -[metadata] -name = templatekit -description = Tookit for rendering Rubin Observatory project templates. -author = Association of Universities for Research in Astronomy, Inc. (AURA) -author_email = sqre-admin@lists.lsst.org -long_description = file: README.rst, CHANGELOG.rst, LICENSE -long_description_content_type = text/x-rst -license = MIT -url = https://github.com/lsst-sqre/templatekit -project_urls = - Change log = https://github.com/lsst-sqre/templatekit/main/blob/CHANGELOG.rst - Source code = https://github.com/lsst-sqre/templatekit - Issue tracker = https://github.com/lsst-sqre/templatekit/issues -classifiers = - Development Status :: 4 - Beta - License :: OSI Approved :: MIT License - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Natural Language :: English - Operating System :: POSIX -keywords = - lsst - cookiecutter - -[options] -zip_safe = False -include_package_data = True -package_dir = - = src -packages = find: -python_requires = >=3.8 -setup_requires = - setuptools_scm -install_requires = - Cerberus>=1.2 - click - cookiecutter>=1.6.0 - GitPython>=3.0.0 - Jinja2>=2.10 - pyperclip>=1.6.0 - PyYAML>=5.1 - scons>=3.0.1 - -[options.packages.find] -where = src - -[options.extras_require] -dev = - coverage[toml] - mypy - pre-commit - pytest - pytest-cov - types-PyYAML - # documentation - documenteer[pipelines] - sphinx-click - -[options.entry_points] -console_scripts = - templatekit = templatekit.scripts.main:main - -[flake8] -max-line-length = 79 -# E203: whitespace before :, flake8 disagrees with PEP-8 -# W503: line break after binary operator, flake8 disagrees with PEP-8 -ignore = E203, W503 -exclude = - docs/conf.py - -[mypy] -disallow_untyped_defs = True -disallow_incomplete_defs = True -exclude = tests/data -ignore_missing_imports = True -show_error_codes = True -strict_equality = True -warn_redundant_casts = True -warn_unreachable = True -warn_unused_ignores = True diff --git a/setup.py b/setup.py deleted file mode 100644 index d5d43d7..0000000 --- a/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -from setuptools import setup - -setup(use_scm_version=True) diff --git a/src/templatekit/scripts/check.py b/src/templatekit/scripts/check.py index b8a1bf4..5080f5e 100644 --- a/src/templatekit/scripts/check.py +++ b/src/templatekit/scripts/check.py @@ -5,7 +5,7 @@ __all__ = ("check",) import sys -from typing import Dict +from typing import Dict, List import click @@ -13,8 +13,16 @@ @click.command(short_help="Check the template repository") +@click.option( + "-i", + "--ignore", + "ignored_files", + multiple=True, + type=click.Path(exists=True, dir_okay=False), + help="Ignore a file when checking consistency of examples.", +) @click.pass_obj -def check(state: Dict[str, Repo]) -> None: +def check(state: Dict[str, Repo], ignored_files: List[str]) -> None: """Check the template repository for valid structure and operation. The following checks are performed: @@ -41,7 +49,7 @@ def check(state: Dict[str, Repo]) -> None: sys.exit(message.format(scons_result.returncode)) error_count = 0 - error_count += _test_git_state(repo) + error_count += _test_git_state(repo, ignored_files) if error_count == 1: sys.exit( @@ -57,7 +65,7 @@ def check(state: Dict[str, Repo]) -> None: print("✅ Passed!") -def _test_git_state(repo: Repo) -> int: +def _test_git_state(repo: Repo, ignored_files: List[str]) -> int: """Test if the Git repository of the template repository is clean. (no modified files and no untracked files). """ @@ -65,7 +73,7 @@ def _test_git_state(repo: Repo) -> int: if repo.is_git_dirty(): error_count += _test_untracked_files(repo) - error_count += _test_uncommitted_changes(repo) + error_count += _test_uncommitted_changes(repo, ignored_files) return error_count @@ -81,7 +89,7 @@ def _test_untracked_files(repo: Repo) -> int: return error_count -def _test_uncommitted_changes(repo: Repo) -> int: +def _test_uncommitted_changes(repo: Repo, ignored_files: List[str]) -> int: error_count = 0 # Get all uncommitted changes because we don't have a count of them # otherwise @@ -91,6 +99,8 @@ def _test_uncommitted_changes(repo: Repo) -> int: for change in diffindex.iter_change_type( changetype # type: ignore[arg-type] # GitPython typing bug ): + if change.a_path in ignored_files: + continue # For deleted files, we want to use the original ("a") path. # Otherwise, we tend to want to show the user the new ("b") path if changetype in ("D",): diff --git a/tox.ini b/tox.ini index a75a18f..4762493 100644 --- a/tox.ini +++ b/tox.ini @@ -20,7 +20,7 @@ commands = coverage report [testenv:typing] description = Run mypy. commands = - mypy src/templatekit tests setup.py + mypy src/templatekit tests [testenv:lint] description = Lint codebase by running pre-commit (Black, isort, Flake8).