Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions copier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,18 @@ type_checker:
- pyright
- mypy

strict_typing:
type: bool
when: >-
{{ type_checker == 'pyright' }}
default: true
help: |
Would you like to run pyright in strict mode?
The recommended approach is to start with strict mode and disable it if it
becomes too costly to maintain.
See https://diamondlightsource.github.io/python-copier-template/main/how-to/strict-mode.html
for more information.

pypi:
type: bool
help: Would you like the wheel and source distribution to be automatically uploaded to PyPI when a release is made?
Expand Down
18 changes: 7 additions & 11 deletions docs/how-to/strict-mode.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
# Enable Pyright's Strict Mode
# Use Pyright's Strict Mode

For projects using pyright you can enable strict mode for stricter than normal type checking. See [the docs](https://github.com/microsoft/pyright/blob/main/docs/configuration.md) for a full breakdown. The primary benefits are increased confidence in code that has been more thoroughly analyzed and a shorter development time thanks to fast feedback from the type checker.
For projects using pyright you can enable strict mode for stricter than normal type checking. See [the docs](https://github.com/microsoft/pyright/blob/main/docs/configuration.md) for a full breakdown.

## Configuration
## How to Enable

Change the `typeCheckingMode` line to `"strict"` in `pyproject.toml` as follows:
When creating a template, select `pyright` as the type checker and type `y` when prompted to enable strict mode.

```toml
[tool.pyright]
typeCheckingMode = "strict"
reportMissingImports = false # Ignore missing stubs in imported modules
```
## Who Should Use Strict Mode?

## Third Party Libraries
Strict mode enforces good practices such as type hints on function signatures, providing increased confidence in code that has been more thoroughly analyzed and a shorter development time thanks to fast feedback from the type checker. Starting a new project and continually keeping it passing provides a long-term benefit when it comes to maintanability and robustness. However, adopting strict mode on top of legacy projects is likely to lead to lots of errors to work through - probably thousands. Additionally it does not usually work well with libraries that do not have [type stubs](https://github.com/microsoft/pyright/blob/main/docs/type-stubs.md), you will likely need a `# type: ignore` on any line that directly uses the library code. This may limit the usefulness of pyright but it can still be worth doing to ensure your own code is internally consistent.

Strict mode does not usually work well with libraries that do not have [type stubs](https://github.com/microsoft/pyright/blob/main/docs/type-stubs.md), you will likely need a `# type: ignore` on any line that directly uses the library code. This may limit the usefulness of pyright but it can still be worth doing to ensure your own code is internally consistent.
The recommended approach for brand new projects is to enable strict mode and stick with it for as long as is practical, moving away if it starts to cause more hindrance than help (e.g. because too many major dependencies do not support it).
1 change: 1 addition & 0 deletions example-answers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ github_org: DiamondLightSource
package_name: python_copier_template_example
repo_name: python-copier-template-example
type_checker: pyright
strict_typing: true
pypi: true
6 changes: 4 additions & 2 deletions template/pyproject.toml.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ name = "{{ author_name }}"
[tool.setuptools_scm]
version_file = "src/{{ package_name }}/_version.py"
{% if type_checker=="pyright" %}
[tool.pyright]
[tool.pyright]{% if strict_typing %}
typeCheckingMode = "strict"
{% else %}
typeCheckingMode = "standard"
reportMissingImports = false # Ignore missing stubs in imported modules
{% endif %}reportMissingImports = false # Ignore missing stubs in imported modules
{% endif %}{% if type_checker=="mypy" %}
[tool.mypy]
ignore_missing_imports = true # Ignore missing stubs in imported modules
Expand Down
31 changes: 23 additions & 8 deletions tests/test_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,11 @@ def test_template_defaults(tmp_path: Path):
copy_project(tmp_path)
run = make_venv(tmp_path)
container_doc = tmp_path / "docs" / "how-to" / "run-container.md"
pyproject_toml = tmp_path / "pyproject.toml"
assert container_doc.exists()
catalog_info = tmp_path / "catalog-info.yaml"
assert catalog_info.exists()
assert 'typeCheckingMode = "strict"' in pyproject_toml.read_text()
run("./venv/bin/tox -p")
if not run_pipe("git tag --points-at HEAD"):
# Only run linkcheck if not on a tag, as the CI might not have pushed
Expand All @@ -68,8 +70,16 @@ def test_template_with_extra_code_and_api_docs(tmp_path: Path):
init = tmp_path / "src" / "python_copier_template_example" / "__init__.py"
init.write_text(
init.read_text().replace(
"__all__ = [",
"""
from ._version import __version__

__all__ = [""",
'''
from python_copier_template_example import extra_pkg

from ._version import __version__


class TopCls:
"""A top level class."""

Expand Down Expand Up @@ -213,21 +223,26 @@ def __init__(self):
run("ruff check")


def test_works_in_pyright_strict_mode(tmp_path: Path):
copy_project(tmp_path)
def test_pyright_works_in_standard_typing_mode(tmp_path: Path):
copy_project(tmp_path, type_checker="pyright", strict_typing=False)
pyproject_toml = tmp_path / "pyproject.toml"

# Enable strict mode
run_pipe(
'sed -i \'s|typeCheckingMode = "standard"|typeCheckingMode = "strict"|\''
f" {pyproject_toml}"
)
# Check standard mode is configured
assert 'typeCheckingMode = "standard"' in pyproject_toml.read_text()

# Ensure pyright is still happy
run = make_venv(tmp_path)
run(f"./venv/bin/pyright {tmp_path}")


def test_ignores_mypy_strict_mode(tmp_path: Path):
copy_project(tmp_path, type_checker="mypy", strict_typing=True)
pyproject_toml = tmp_path / "pyproject.toml"

# Check strict mode is not configured
assert "typeCheckingMode =" not in pyproject_toml.read_text()


def test_works_with_pydocstyle(tmp_path: Path):
copy_project(tmp_path)
pyproject_toml = tmp_path / "pyproject.toml"
Expand Down
Loading