Skip to content

Commit

Permalink
Add fmt command
Browse files Browse the repository at this point in the history
  • Loading branch information
ofek committed Nov 26, 2023
1 parent 6c252f8 commit 165deb1
Show file tree
Hide file tree
Showing 32 changed files with 1,991 additions and 1,240 deletions.
8 changes: 5 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ jobs:
pip install -e .
pip install -e ./backend
- if: ${{ !startsWith(matrix.python-version, 'pypy') }}
name: Lint
run: hatch run lint:all
- name: Run static analysis
run: hatch fmt --check

- name: Check types
run: hatch run types:check

- name: Run tests
run: hatch run full
Expand Down
4 changes: 4 additions & 0 deletions .linkcheckerrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
# https://linkchecker.github.io/linkchecker/man/linkcheckerrc.html
[filtering]
ignore=
https://docs.astral.sh/ruff/rules/.+

[AnchorCheck]
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ Hatch is a modern, extensible Python project manager.
- Standardized [build system](https://hatch.pypa.io/latest/build/#packaging-ecosystem) with reproducible builds by default
- Robust [environment management](https://hatch.pypa.io/latest/environment/) with support for custom scripts
- Configurable [Python distribution management](https://hatch.pypa.io/dev/cli/reference/#hatch-python)
- [Static analysis](https://hatch.pypa.io/dev/config/static-analysis/) with sane defaults
- Easy [publishing](https://hatch.pypa.io/latest/publish/) to PyPI or other indexes
- [Version](https://hatch.pypa.io/latest/version/) management
- Configurable [project generation](https://hatch.pypa.io/latest/config/project-templates/) with sane defaults
- Best practice [project generation](https://hatch.pypa.io/latest/config/project-templates/)
- Responsive [CLI](https://hatch.pypa.io/latest/cli/about/), ~2-3x [faster](https://github.com/pypa/hatch/blob/hatch-v1.5.0/.github/workflows/test.yml#L76-L108) than equivalent tools

## Documentation
Expand Down
95 changes: 95 additions & 0 deletions docs/.hooks/render_ruff_defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
from __future__ import annotations

import os
import re
from collections import defaultdict
from functools import cache

MARKER_VERSION = '<HATCH_RUFF_VERSION>'
MARKER_SELECTED_RULES = '<HATCH_RUFF_SELECTED_RULES>'
MARKER_PER_FILE_IGNORED_RULES = '<HATCH_RUFF_PER_FILE_IGNORED_RULES>'
RULE_URLS = {'S': 'https://docs.astral.sh/ruff/rules/#flake8-bandit-s'}


@cache
def ruff_data():
generated_file = os.path.join(os.getcwd(), 'src', 'hatch', 'env', 'internal', 'fmt.py')
with open(generated_file, encoding='utf-8') as f:
lines = f.read().splitlines()

for i, line in enumerate(lines):
if line.startswith('RUFF_MINIMUM_VERSION'):
block_start = i
break
else:
message = f'Could not find RUFF_MINIMUM_VERSION in {generated_file}'
raise RuntimeError(message)

data = {}
exec('\n'.join(lines[block_start:]), None, data) # noqa: S102
return data


@cache
def get_ruff_version():
return ruff_data()['RUFF_MINIMUM_VERSION']


@cache
def get_selected_rules():
selected_rules = defaultdict(list)
separator = re.compile(r'^(\D+)(\d+)$')

data = ruff_data()
for rules, preview in ((data['STABLE_RULES'], False), (data['PREVIEW_RULES'], True)):
for rule in rules:
match = separator.search(rule)
if match is None:
message = f'Could not parse rule {rule}'
raise RuntimeError(message)

group, number = match.groups()
selected_rules[group].append((number, preview))

lines = []
for group, rule_data in sorted(selected_rules.items()):
rule_data.sort(key=lambda x: int(x[0]))

parts = []
for number, preview in rule_data:
rule = f'{group}{number}'
part = f'[{rule}](https://docs.astral.sh/ruff/rules/{rule})'
if preview:
part += '^P^'
parts.append(part)

lines.append(f'- {", ".join(parts)}')

return '\n'.join(lines)


@cache
def get_per_file_ignored_rules():
lines = []
for glob, rules in sorted(ruff_data()['PER_FILE_IGNORED_RULES'].items()):
parts = []
for rule in rules:
url = RULE_URLS.get(rule) or f'https://docs.astral.sh/ruff/rules/{rule}'
parts.append(f'[{rule}]({url})')

lines.append(f'- `{glob}`: {", ".join(parts)}')

return '\n'.join(lines)


def on_page_read_source(
page,
config, # noqa: ARG001
):
with open(page.file.abs_src_path, encoding='utf-8') as f:
return (
f.read()
.replace(MARKER_VERSION, get_ruff_version())
.replace(MARKER_SELECTED_RULES, get_selected_rules())
.replace(MARKER_PER_FILE_IGNORED_RULES, get_per_file_ignored_rules())
)
101 changes: 101 additions & 0 deletions docs/config/static-analysis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Static analysis configuration

-----

Static analysis performed by the [`fmt`](../cli/reference.md#hatch-fmt) command is backed entirely by [Ruff](https://github.com/astral-sh/ruff).

Hatch provides [default settings](#default-settings) that user configuration can [extend](#extending-config).

## Extending config

When defining your configuration, be sure to use options that are prefixed by `extend-` such as [`extend-select`](https://docs.astral.sh/ruff/settings/#extend-select), for example:

=== ":octicons-file-code-16: pyproject.toml"

```toml
[tool.ruff.lint]
preview = true
extend-select = ["C901"]

[tool.ruff.lint.extend-per-file-ignores]
"docs/.hooks/*" = ["INP001", "T201"]

[tool.ruff.lint.isort]
known-first-party = ["foo", "bar"]

[tool.ruff.format]
preview = true
quote-style = "single"
```

=== ":octicons-file-code-16: ruff.toml"

```toml
[lint]
preview = true
extend-select = ["C901"]

[lint.extend-per-file-ignores]
"docs/.hooks/*" = ["INP001", "T201"]

[lint.isort]
known-first-party = ["foo", "bar"]

[format]
preview = true
quote-style = "single"
```

!!! note
When not [persisting config](#persistent-config), there is no need to explicitly [extend](https://docs.astral.sh/ruff/settings/#extend) the defaults as Hatch automatically handles that.

## Persistent config

If you want to store the default configuration in the project, set an explicit path like so:

```toml config-example
[tool.hatch.format]
config-path = "ruff_defaults.toml"
```

Then instruct Ruff to consider your configuration as an extension of the default file:

=== ":octicons-file-code-16: pyproject.toml"

```toml
[tool.ruff]
extend = "ruff_defaults.toml"
```

=== ":octicons-file-code-16: ruff.toml"

```toml
extend = "ruff_defaults.toml"
```

Anytime you wish to update the defaults (such as when upgrading Hatch), you must run the [`fmt`](../cli/reference.md#hatch-fmt) command once with the `--sync` flag e.g.:

```
hatch fmt --check --sync
```

!!! tip
This is the recommended approach since it allows other tools like IDEs to use the default configuration.

## Default settings

### Non-rule settings

- [Line length](https://docs.astral.sh/ruff/settings/#line-length) set to 120
- Only absolute imports [are allowed](https://docs.astral.sh/ruff/settings/#flake8-tidy-imports-ban-relative-imports), [except for tests](#per-file-ignored-rules)
- The normalized [project name](metadata.md#name) is a [known first party](https://docs.astral.sh/ruff/settings/#isort-known-first-party) import

### Per-file ignored rules

<HATCH_RUFF_PER_FILE_IGNORED_RULES>

### Selected rules

The following rules are based on version <HATCH_RUFF_VERSION> of Ruff.

<HATCH_RUFF_SELECTED_RULES>
2 changes: 1 addition & 1 deletion docs/history/hatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Support Python 3.12
- Add installers and standalone binaries
- Add the ability to manage Python installations
- Add `fmt` command
- The `virtual` environment type can now automatically download requested versions of Python that are not installed
- Add `dependency_hash` method to the `environment` interface
- The state of installed dependencies for environments is saved as metadata so if dependency definitions have not changed then no checking is performed, which can be computationally expensive
- The `build` command now supports backends other than Hatchling
- For new project templates rely only on `requires-python` for configuring the target version Ruff and Black
- The default is now `__TOKEN__` when prompting for a username for the `publish` command
- Bump the minimum supported version of Hatchling to 1.17.1
- Bump the minimum supported version of `click` to 8.0.6
Expand Down
3 changes: 2 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ Hatch is a modern, extensible Python project manager.
- Standardized [build system](build.md#packaging-ecosystem) with reproducible builds by default
- Robust [environment management](environment.md) with support for custom scripts
- Configurable [Python distribution management](cli/reference.md#hatch-python)
- [Static analysis](config/static-analysis.md) with sane defaults
- Easy [publishing](publish.md) to PyPI or other indexes
- [Version management](version.md)
- Configurable [project generation](config/project-templates.md) with sane defaults
- Best practice [project generation](config/project-templates.md)
- Responsive [CLI](cli/about.md), ~2-3x [faster](https://github.com/pypa/hatch/blob/hatch-v1.5.0/.github/workflows/test.yml#L76-L108) than equivalent tools

## License
Expand Down
26 changes: 9 additions & 17 deletions hatch.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,26 +44,12 @@ report-uncovered-html = "coverage html --skip-covered --skip-empty"
generate-summary = "python scripts/generate_coverage_summary.py"
write-summary-report = "python scripts/write_coverage_summary_report.py"

[envs.lint]
detached = true
[envs.types]
dependencies = [
"mypy>=1.0.0",
"ruff==0.1.6",
]
[envs.lint.scripts]
typing = "mypy --install-types --non-interactive {args:backend/src/hatchling src/hatch tests}"
style = [
"ruff check {args:.}",
"ruff format --check --diff {args:.}",
]
fmt = [
"ruff check --fix {args:.}",
"ruff format {args:.}",
]
all = [
"style",
"typing",
]
[envs.types.scripts]
check = "mypy --install-types --non-interactive {args:backend/src/hatchling src/hatch tests}"

[envs.docs]
dependencies = [
Expand Down Expand Up @@ -126,16 +112,22 @@ version = "cd backend && hatch version {args}"
detached = true
dependencies = [
"httpx",
"ruff",
]
[envs.upkeep.scripts]
update-hatch = [
"update-distributions",
"update-ruff",
]
update-hatchling = [
"update-licenses",
]
update-distributions = "python scripts/update_distributions.py"
update-licenses = "python backend/scripts/update_licenses.py"
update-ruff = [
"python -m pip install --disable-pip-version-check --upgrade ruff",
"python scripts/update_ruff.py",
]

[envs.release]
detached = true
Expand Down
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ nav:
- Environments:
- Overview: config/environment/overview.md
- Advanced: config/environment/advanced.md
- Static analysis: config/static-analysis.md
- Context formatting: config/context.md
- Project templates: config/project-templates.md
- Hatch: config/hatch.md
Expand Down Expand Up @@ -127,6 +128,7 @@ watch:
hooks:
- docs/.hooks/expand_blocks.py
- docs/.hooks/inject_version.py
- docs/.hooks/render_ruff_defaults.py

plugins:
# Built-in
Expand Down
Loading

0 comments on commit 165deb1

Please sign in to comment.