diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8dd2abe01..ec0e5cdb5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,6 +35,14 @@ jobs: with: python-version: ${{ matrix.python-version }} - name: Install tox - run: pip install tox + run: | + python -m pip install tox - name: Run bake test suite - run: tox -e ${{ matrix.tox-env }} + run: + python -m tox -e ${{ matrix.tox-env }} + - name: Archive package + if: ${{ matrix.tox-env == 'py39' }} + uses: actions/upload-artifact@v2 + with: + name: cookie-cutter + path: src/dist diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index e1c923482..aa3eea6e9 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -92,7 +92,7 @@ Ready to contribute? Here's how to set up `cookiecutter-pypackage` for local dev .. code-block:: bash - $ pip install -rrequirements_dev.txt + $ pip install -r requirements_dev.txt $ pytest ./tests If you get any errors while installing cryptography package (something like `#include `). Please update your pip version and try again: diff --git a/Makefile b/Makefile index ba420157f..ae0c0155e 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,14 @@ BAKE_OPTIONS=--no-input help: - @echo "bake generate project using defaults" - @echo "watch generate project using defaults and watch for changes" - @echo "replay replay last cookiecutter run and watch for changes" + @echo "bake Generate project using defaults" + @echo "help Show this help" + @echo "test Run the tests" + @echo "replay Replay last cookiecutter run and watch for changes" + @echo "watch Generate project using defaults and watch for changes" + -bake: +bake: # Generate project using defaults cookiecutter $(BAKE_OPTIONS) . --overwrite-if-exists watch: bake @@ -14,3 +17,6 @@ watch: bake replay: BAKE_OPTIONS=--replay replay: watch ; + +test: + pytest \ No newline at end of file diff --git a/README.rst b/README.rst index 26c600885..5d2c6ff7c 100644 --- a/README.rst +++ b/README.rst @@ -9,6 +9,7 @@ Cookiecutter_ template for a Python package. * GitHub repo (fork): https://github.com/Ouranosinc/cookiecutter-pypackage/ * Documentation (upstream): https://cookiecutter-pypackage.readthedocs.io/ * Free software: BSD license +* Discord: https://discord.gg/PWXJr3upUE Features -------- diff --git a/cookiecutter.json b/cookiecutter.json index c918373c9..9e9e24eec 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -13,7 +13,7 @@ "add_pyup_badge": "n", "make_docs": "y", "add_translations": "y", - "command_line_interface": ["Click", "Argparse", "No command-line interface"], + "command_line_interface": ["Typer", "Click", "Argparse", "No command-line interface"], "create_author_file": "y", "open_source_license": [ "MIT license", @@ -23,5 +23,6 @@ "GNU General Public License v3", "Not open source" ], - "generated_with_cruft": "y" + "generated_with_cruft": "y", + "__gh_slug": "{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}" } diff --git a/docs/conf.py b/docs/conf.py index 4a08eac57..4a764c003 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,9 +12,8 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys -import os -import shlex +# import sys +# import os # 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 diff --git a/docs/tutorial.rst b/docs/tutorial.rst index 10b90c7d3..bab58fa7c 100644 --- a/docs/tutorial.rst +++ b/docs/tutorial.rst @@ -36,6 +36,9 @@ On Windows, activate it like this. You may find that using a Command Prompt wind > \path\to\env\Scripts\activate +.. note:: + + If you create your virtual environment folder in a different location within your project folder, be sure to add that path to your .gitignore file. Install cookiecutter: @@ -55,8 +58,8 @@ Use cookiecutter, pointing it at the cookiecutter-pypackage repo: cookiecutter https://github.com/audreyfeldroy/cookiecutter-pypackage.git -You'll be asked to enter a bunch of values to set the package up. -If you don't know what to enter, stick with the defaults. +You'll be asked to enter various values to set the package up. +If you don't know what to enter, press Enter to stick with the defaults. Step 3: Create a GitHub Repo @@ -64,8 +67,6 @@ Step 3: Create a GitHub Repo Go to your GitHub account and create a new repo named ``mypackage``, where ``mypackage`` matches the ``[project_slug]`` from your answers to running cookiecutter. This is so that Travis CI and pyup.io can find it when we get to Step 5. -``If your virtualenv folder is within your project folder, be sure to add the virtualenv folder name to your .gitignore file.`` - You will find one folder named after the ``[project_slug]``. Move into this folder, and then setup git to use your GitHub repo and upload the code: .. code-block:: bash @@ -77,10 +78,6 @@ You will find one folder named after the ``[project_slug]``. Move into this fold git remote add origin git@github.com:myusername/mypackage.git git push -u origin main - .. note:: - - GitHub has changed the default branch name from 'master' to 'main'. If you are using another Git repository hosting service that uses the Git branch naming defaults, you might need to use 'master' instead of 'main'. - Where ``myusername`` and ``mypackage`` are adjusted for your username and package name. You'll need a ssh key to push the repo. You can `Generate`_ a key or `Add`_ an existing one. diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 2c8bf20ac..8ea97f524 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -65,7 +65,7 @@ def replace_contents(filepath): remove_file("environment-dev.yml") if "no" in "{{ cookiecutter.command_line_interface|lower }}": - cli_file = Path("{{ cookiecutter.project_slug }}").joinpath("cli.py") + cli_file = Path("src/{{ cookiecutter.project_slug }}").joinpath("cli.py") remove_file(cli_file) if "Not open source" == "{{ cookiecutter.open_source_license }}": diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index bf5155dcf..d1703d3ef 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -14,4 +14,3 @@ # Exit to cancel project sys.exit(1) -1 diff --git a/requirements_dev.txt b/requirements_dev.txt index d7f167fbe..35fc4c56e 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,11 +1,11 @@ -alabaster -build -cookiecutter -coverage -flit -pre-commit -pytest-cookies -pytest -tox>=4.0 -twine -watchdog>=0.9.0 +alabaster>=0.7.13 +build>=1.2.1 +cookiecutter>=2.6.0 +coverage>=7.5.1 +flit>=3.9.0 +pre-commit>=3.5.0 +pytest-cookies>=0.7.0 +pytest>=8.2.0 +tox>=4.15.0 +twine>=5.0.0 +watchdog>=4.0.0 diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..aca233bab --- /dev/null +++ b/ruff.toml @@ -0,0 +1,3 @@ +exclude = [ + "*cookiecutter.project_slug*" + ] diff --git a/setup.cfg b/setup.cfg index 73fb93384..5f774da91 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,7 +11,8 @@ search = version='{current_version}' replace = version='{new_version}' [tool:pytest] -addopts = --verbose +addopts = + --verbose markers = - precommit: mark tests that can only be run with precommit present + precommit: mark tests that can only be run with precommit present testpaths = tests diff --git a/setup.py b/setup.py index 0d3d599f4..f7456e4c8 100644 --- a/setup.py +++ b/setup.py @@ -6,10 +6,10 @@ packages=[], version='0.1.0', description='Cookiecutter template for a Python package', - author='Audrey Roy Greenfeld', + author='Audrey M. Roy Greenfeld', license='BSD', - author_email='aroy@alum.mit.edu', - url='https://github.com/audreyr/cookiecutter-pypackage', + author_email='audrey@feldroy.com', + url='https://github.com/audreyfeldroy/cookiecutter-pypackage', keywords=['cookiecutter', 'template', 'package', ], python_requires='>=3.8', classifiers=[ @@ -20,10 +20,12 @@ 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3', '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 :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Software Development', diff --git a/tests/test_bake_project.py b/tests/test_bake_project.py index 1b8ed6bd1..6cec49a0c 100644 --- a/tests/test_bake_project.py +++ b/tests/test_bake_project.py @@ -66,9 +66,12 @@ def test_year_compute_in_license_file(cookies): def project_info(result): """Get toplevel dir, project_slug, and project dir from baked cookies""" - project_path = str(result.project_path) - project_slug = os.path.split(project_path)[-1] - project_dir = os.path.join(project_path, project_slug) + assert result.exception is None + assert result.project_path.is_dir() + + project_path = result.project_path + project_slug = project_path.name + project_dir = project_path.joinpath("src").joinpath(project_slug) return project_path, project_slug, project_dir @@ -79,9 +82,13 @@ def test_bake_with_defaults(cookies): assert result.exception is None found_toplevel_files = [f.name for f in result.project_path.iterdir()] + assert "LICENSE" in found_toplevel_files + assert "Makefile" in found_toplevel_files + assert "README.rst" in found_toplevel_files assert "environment-dev.yml" in found_toplevel_files assert "pyproject.toml" in found_toplevel_files - assert "python_boilerplate" in found_toplevel_files + assert "src" in found_toplevel_files + assert "python_boilerplate" in next(result.project_path.joinpath("src").iterdir()).name assert "tests" in found_toplevel_files assert "tox.ini" in found_toplevel_files @@ -235,6 +242,11 @@ def test_bake_not_open_source(cookies): assert "License" not in result.project_path.joinpath("README.rst").read_text() +def _running_tox(): + return os.getenv("TOX", False) + + +@pytest.mark.skipif("not _running_tox()", reason="Not running on tox") def test_using_pytest(cookies): with bake_in_temp_dir(cookies, extra_context={"use_pytest": "y"}) as result: assert result.project_path.is_dir() @@ -244,6 +256,7 @@ def test_using_pytest(cookies): text = test_file_path.read_text() assert "import pytest" in text # Test the new pytest target + assert run_inside_dir("pip install --editable .", str(result.project_path)) == 0 assert run_inside_dir("pytest", str(result.project_path)) == 0 @@ -292,7 +305,7 @@ def test_bake_with_no_console_script(cookies): assert "[project.scripts]" not in setup_file.read() -@pytest.mark.parametrize("option", ["Click", "Argparse"]) +@pytest.mark.parametrize("option", ["Click", "Argparse", "Typer"]) def test_bake_with_console_options_script_files(cookies, option): context = {"command_line_interface": option} result = cookies.bake( diff --git a/tox.ini b/tox.ini index 949c728b9..5eaa84060 100644 --- a/tox.ini +++ b/tox.ini @@ -17,6 +17,7 @@ commands = setenv = PYTEST_ADDOPTS = "--color=yes" PYTHONPATH = {toxinidir} + TOX = 1 deps = -r{toxinidir}/requirements_dev.txt download = True diff --git a/{{cookiecutter.project_slug}}/.gitignore b/{{cookiecutter.project_slug}}/.gitignore index ab7e509ef..4bafdc8b0 100644 --- a/{{cookiecutter.project_slug}}/.gitignore +++ b/{{cookiecutter.project_slug}}/.gitignore @@ -75,6 +75,9 @@ target/ # Jupyter Notebook .ipynb_checkpoints +# Dask worker cache +dask-worker-space/ + # pyenv .python-version diff --git a/{{cookiecutter.project_slug}}/CODE_OF_CONDUCT.rst b/{{cookiecutter.project_slug}}/CODE_OF_CONDUCT.rst new file mode 100644 index 000000000..d11c563e2 --- /dev/null +++ b/{{cookiecutter.project_slug}}/CODE_OF_CONDUCT.rst @@ -0,0 +1,84 @@ +==================================== +Contributor Covenant Code of Conduct +==================================== + +Our Pledge +---------- + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to make participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +Our Standards +------------- + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +Our Responsibilities +-------------------- + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +Scope +----- + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [INSERT EMAIL ADDRESS]. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +Attribution +----------- + +This Code of Conduct is adapted from the `Contributor Covenant`_, version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq + +.. _`Contributor Covenant`: https://www.contributor-covenant.org diff --git a/{{cookiecutter.project_slug}}/CONTRIBUTING.rst b/{{cookiecutter.project_slug}}/CONTRIBUTING.rst index 951cc9e7f..2ac004c7c 100644 --- a/{{cookiecutter.project_slug}}/CONTRIBUTING.rst +++ b/{{cookiecutter.project_slug}}/CONTRIBUTING.rst @@ -117,9 +117,12 @@ Ready to contribute? Here's how to set up ``{{ cookiecutter.project_slug }}`` fo #. When you're done making changes, we **strongly** suggest running the tests in your environment or with the help of ``tox``:: - $ python -m pytest - # Or, to run multiple build tests - $ tox + $ make lint + $ make test + Or + $ make test-all + Or + $ python -m tox #. Commit your changes and push your branch to GitHub:: @@ -139,7 +142,7 @@ Ready to contribute? Here's how to set up ``{{ cookiecutter.project_slug }}`` fo $ make autodoc $ make -C docs html # To simply test that the docs pass build checks - $ tox -e docs + $ python -m tox -e docs #. Once your Pull Request has been accepted and merged to the ``main`` branch, several automated workflows will be triggered: @@ -158,7 +161,7 @@ Before you submit a pull request, check that it meets these guidelines: #. If the pull request adds functionality, the docs should also be updated. Put your new functionality into a function with a docstring, and add the feature to the list in ``README.rst``. -#. The pull request should work for Python 3.8, 3.9, 3.10, 3.11, and 3.12. Check that the tests pass for all supported Python versions. +#. The pull request should work for Python 3.8, 3.9, 3.10, 3.11, 3.12 and PyPy. Check that the tests pass for all supported Python versions. Tips ---- @@ -167,7 +170,7 @@ To run a subset of tests:: {% if cookiecutter.use_pytest == 'y' -%} $ pytest tests.test_{{ cookiecutter.project_slug }} -{%- else -%} +{% else %} $ python -m unittest tests.test_{{ cookiecutter.project_slug }} {%- endif %} @@ -181,8 +184,8 @@ To run specific code style checks:: To get ``black``, ``isort``, ``blackdoc``, ``ruff``, and ``flake8`` (with plugins ``flake8-alphabetize`` and ``flake8-rst-docstrings``) simply install them with `pip` {% if cookiecutter.use_conda == 'y' %}(or `conda`) {% endif %}into your environment. -Versioning/Tagging ------------------- +Deployment +---------- A reminder for the **maintainers** on how to deploy. This section is only relevant when producing a new point release for the package. @@ -285,3 +288,11 @@ This will then place two files in `{{ cookiecutter.project_slug }}/dist/` ("{{ c We can now leave our docker container (`$ exit`) and continue with uploading the files to PyPI:: $ twine upload dist/* + +Code of Conduct +--------------- + +Please note that this project is released with a `Contributor Code of Conduct`_. +By participating in this project you agree to abide by its terms. + +.. _`Contributor Code of Conduct`: CODE_OF_CONDUCT.rst diff --git a/{{cookiecutter.project_slug}}/Makefile b/{{cookiecutter.project_slug}}/Makefile index a1f6a5670..471cf6d57 100644 --- a/{{cookiecutter.project_slug}}/Makefile +++ b/{{cookiecutter.project_slug}}/Makefile @@ -138,7 +138,7 @@ release: dist ## package and upload a release python -m flit publish dist/* install: clean ## install the package to the active Python's site-packages - python -m flit install + python -m pip install . dev: clean ## install the package to the active Python's site-packages - python -m flit install --symlink + python -m pip install --editable . diff --git a/{{cookiecutter.project_slug}}/pyproject.toml b/{{cookiecutter.project_slug}}/pyproject.toml index 851e84a03..44880b318 100644 --- a/{{cookiecutter.project_slug}}/pyproject.toml +++ b/{{cookiecutter.project_slug}}/pyproject.toml @@ -14,7 +14,9 @@ name = "{{ cookiecutter.project_slug }}" authors = [ {name = "{{ cookiecutter.full_name.replace('\"', '\\\"') }}", email = "{{ cookiecutter.email }}"} ] -maintainers = [] +maintainers = [ + {name = "{{ cookiecutter.full_name.replace('\"', '\\\"') }}", email = "{{ cookiecutter.email }}"} +] readme = {file = "README.rst", content-type = "text/x-rst"} requires-python = ">=3.8.0" keywords = ["{{ cookiecutter.project_slug }}"] @@ -40,7 +42,8 @@ classifiers = [ ] dynamic = ["description", "version"] dependencies = [ - {% if cookiecutter.command_line_interface|lower == 'click' %}"click >=8.1.7"{%- endif %} + {% if cookiecutter.command_line_interface|lower == 'click' %}"click >=8.1.7" + {% elif cookiecutter.command_line_interface|lower == 'typer' %}"typer >=0.12.3"{%- endif %} ] [project.optional-dependencies] @@ -54,11 +57,9 @@ dev = [ "flake8-rst-docstrings >=0.3.0", "flit >=3.9.0", "tox >=4.5.1", - "coverage >=6.2.2,<7.0.0", + "coverage >=7.0.0", "coveralls >=3.3.1", - {%- if cookiecutter.command_line_interface|lower == 'click' %} - "click >=8.1.7", - {%- endif %} + "mypy", {%- if cookiecutter.use_pytest == 'y' %} "pytest >=7.3.1", "pytest-cov >=4.0.0", @@ -68,7 +69,7 @@ dev = [ "blackdoc ==0.3.9", "isort ==5.13.2", {%- endif %} - "ruff >=0.2.0", + "ruff >=0.3.0", "pre-commit >=3.3.2" ] {%- if cookiecutter.make_docs == 'y' %} @@ -91,18 +92,19 @@ docs = [ "jupyter_client" ] {%- endif %} -{%- if cookiecutter.command_line_interface != 'No command-line interface' %} +{%- if cookiecutter.command_line_interface != "No command-line interface" %} [project.scripts] -{{ cookiecutter.project_slug }} = "{{ cookiecutter.project_slug }}.cli:cli" +{{cookiecutter.project_slug}} = "{{cookiecutter.project_slug}}.cli:app" {%- endif %} [project.urls] # "Homepage" = "https://{{ cookiecutter.project_slug }}.readthedocs.io/" # "Changelog" = "https://{{ cookiecutter.project_slug }}.readthedocs.io/en/stable/history.html" # "About Ouranos" = "https://www.ouranos.ca/en/" -"Source" = "https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}" -"Issue tracker" = "https://github.com/{{ cookiecutter.github_username }}/{{ cookiecutter.project_slug }}/issues" +"Issue tracker" = "https://github.com/{{ cookiecutter.__gh_slug }}/issues" +"changelog" = "https://github.com/{{ cookiecutter.__gh_slug }}/blob/main/changelog.rst" +"homepage" = "https://github.com/{{ cookiecutter.__gh_slug }}" [tool] {%- if cookiecutter.use_black == 'y' %} @@ -228,13 +230,22 @@ py_version = 38 {%- endif %} [tool.mypy] -python_version = 3.8 +files = "." +python_version = 3.9 show_error_codes = true +strict = true +warn_no_return = true warn_return_any = true +warn_unreachable = true warn_unused_configs = true [[tool.mypy.overrides]] -module = [] +module = [ + # Don't require test functions to include types + "tests.*" +] +allow_untyped_defs = true +disable_error_code = "attr-defined" ignore_missing_imports = true {% if cookiecutter.use_pytest == 'y' -%} @@ -289,7 +300,8 @@ no-lines-before = ["future", "standard-library"] max-complexity = 15 [tool.ruff.lint.per-file-ignores] -"{{ cookiecutter.project_slug }}/**/__init__.py" = ["F401", "F403"] +"docs/**" = ["E402"] +"src/{{ cookiecutter.project_slug }}/**/__init__.py" = ["F401", "F403"] [tool.ruff.lint.pycodestyle] max-doc-length = 180 diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py b/{{cookiecutter.project_slug}}/src/{{cookiecutter.project_slug}}/__init__.py similarity index 100% rename from {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/__init__.py rename to {{cookiecutter.project_slug}}/src/{{cookiecutter.project_slug}}/__init__.py diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/cli.py b/{{cookiecutter.project_slug}}/src/{{cookiecutter.project_slug}}/cli.py similarity index 59% rename from {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/cli.py rename to {{cookiecutter.project_slug}}/src/{{cookiecutter.project_slug}}/cli.py index 0e58b2ba9..5147a8337 100644 --- a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/cli.py +++ b/{{cookiecutter.project_slug}}/src/{{cookiecutter.project_slug}}/cli.py @@ -1,15 +1,29 @@ """Console script for {{cookiecutter.project_slug}}.""" - {% if cookiecutter.command_line_interface|lower == 'argparse' -%} import argparse -{%- endif -%} import sys -{%- if cookiecutter.command_line_interface|lower == 'click' %} + +def main(): + """Console script for {{cookiecutter.project_slug}}.""" + parser = argparse.ArgumentParser() + parser.add_argument('_', nargs='*') + args = parser.parse_args() + + print("Arguments: " + str(args._)) + print("Replace this message by putting your code into " + "{{cookiecutter.project_slug}}.cli.main") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) # pragma: no cover + +{%- elif cookiecutter.command_line_interface|lower == 'click' -%} +import sys import click -{%- endif %} -{% if cookiecutter.command_line_interface|lower == 'click' %} + @click.command() def main(args=None): """Console script for {{cookiecutter.project_slug}}.""" @@ -18,20 +32,26 @@ def main(args=None): ) click.echo("See click documentation at https://click.palletsprojects.com/") return 0 -{%- endif %} -{%- if cookiecutter.command_line_interface|lower == 'argparse' %} + +if __name__ == "__main__": + sys.exit(main()) # pragma: no cover +{%- elif cookiecutter.command_line_interface|lower == 'typer' %} +import typer +from rich.console import Console + +app = typer.Typer() +console = Console() + + +@app.command() def main(): """Console script for {{cookiecutter.project_slug}}.""" - parser = argparse.ArgumentParser() - parser.add_argument('_', nargs='*') - args = parser.parse_args() - - print("Arguments: " + str(args._)) - print("Replace this message by putting your code into " - "{{cookiecutter.project_slug}}.cli.main") - return 0 -{%- endif %} + console.print( + "Replace this message by putting your code into {{cookiecutter.project_slug}}.cli.main" + ) + console.print("See Typer documentation at https://typer.tiangolo.com/") if __name__ == "__main__": - sys.exit(main()) # pragma: no cover + app() +{%- endif %} diff --git a/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.py b/{{cookiecutter.project_slug}}/src/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.py similarity index 100% rename from {{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.py rename to {{cookiecutter.project_slug}}/src/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.py diff --git a/{{cookiecutter.project_slug}}/tests/test_{{cookiecutter.project_slug}}.py b/{{cookiecutter.project_slug}}/tests/test_{{cookiecutter.project_slug}}.py index ce3e73141..d984f362e 100644 --- a/{{cookiecutter.project_slug}}/tests/test_{{cookiecutter.project_slug}}.py +++ b/{{cookiecutter.project_slug}}/tests/test_{{cookiecutter.project_slug}}.py @@ -3,7 +3,7 @@ """Tests for `{{ cookiecutter.project_slug }}` package.""" import pathlib -import pkgutil +from importlib.util import find_spec {% if cookiecutter.use_pytest == 'n' -%} import unittest {% else %} @@ -12,7 +12,6 @@ {%- if cookiecutter.command_line_interface|lower == 'click' %} from click.testing import CliRunner {%- endif %} - {% if cookiecutter.command_line_interface|lower == 'click' %}import {{ cookiecutter.project_slug }}.cli as cli{%- endif %} from {{ cookiecutter.project_slug }} import {{ cookiecutter.project_slug }} # noqa: F401 {%- if cookiecutter.use_pytest == 'y' %} @@ -76,9 +75,9 @@ def test_command_line_interface(self): def test_package_metadata(): """Test the package metadata.""" - project = pkgutil.get_loader("{{ cookiecutter.project_slug }}").get_filename() + project = find_spec("{{ cookiecutter.project_slug }}").submodule_search_locations[0] - metadata = pathlib.Path(project).resolve().parent.joinpath("__init__.py") + metadata = pathlib.Path(project).resolve().joinpath("__init__.py") with open(metadata) as f: contents = f.read()