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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Added

- Opt-in scope-profiler regions, as in plasmacoll, wellbalanced and patchwork: `_profiling.py`
with the package's tag (`template_python`, renamed by `setup_project.sh`), registered on
import, and an example region on `main`. Off unless the application enables the tag (e.g.
`scope_profiler.setup(enable_tags=["template_python"])`). Adds the `scope-profiler>=0.7.5`
dependency.
- `PyPI dependencies` workflow and `scripts/check_pypi_dependencies.py`: on every pull request to
`main`, fail if a dependency is a direct reference (`name @ git+https://...`), is not on PyPI, or
has a version range that no release satisfies, instead of failing the release after the merge.
Expand Down
18 changes: 18 additions & 0 deletions docs/src/content/docs/getting-started/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,21 @@ main()
```

See the [API reference](/template-python/api/app/) for every module and function.

## Profiling

template-python's coarse operations are [scope-profiler](https://github.com/max-models/scope-profiler)
regions (`template_python.main` here), tagged with the opt-in tag `template_python`: they are
off by default and cost a fraction of a microsecond per call. A code that profiles itself with
scope-profiler turns them on with the tag:

```python
import scope_profiler as sp

sp.setup(enable_tags=["template_python"])
```

or without editing the code, with `SCOPE_PROFILER_ENABLE_TAGS=template_python`,
`scope-profiler run --enable-tag template_python` or `enable_tags = ["template_python"]` in the
TOML configuration. New regions use `@sp.profile("template_python.<name>", tags=PROFILE_TAGS)`
with `PROFILE_TAGS` from the package's `_profiling` module.
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ classifiers = [
# max-models packages come from their devel branches through [tool.uv.sources]
dependencies = [
"numpy",
"scope-profiler>=0.7.5",
]
optional-dependencies.dev = [
"jupyterlab",
Expand Down Expand Up @@ -81,3 +82,4 @@ report.show_missing = true
[tool.uv.sources]
# one entry per max-models package in [project], spelled exactly like this in every repo:
# cunumpy = { git = "https://github.com/max-models/cunumpy", branch = "devel" }
scope-profiler = { git = "https://github.com/max-models/scope-profiler", branch = "devel" }
1 change: 1 addition & 0 deletions setup_project.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ mv "src/${PACKAGE}" "src/${APPNAME_UNDERSCORE}"

# 3. Imports, the entry point, the API reference and the docs links
replace "from ${PACKAGE}\\.main import main" "from ${APPNAME_UNDERSCORE}.main import main"
replace "from ${PACKAGE}\\._profiling import" "from ${APPNAME_UNDERSCORE}._profiling import"
replace "${PACKAGE}\\.main:main" "${APPNAME_UNDERSCORE}.main:main"
replace "${PACKAGE}\\.main\\.main" "${APPNAME_UNDERSCORE}.main.main"
replace "/api/${PACKAGE}/" "/api/${APPNAME_UNDERSCORE}/"
Expand Down
7 changes: 7 additions & 0 deletions src/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

from importlib.metadata import PackageNotFoundError, version

import scope_profiler as sp

from app._profiling import PROFILE_TAGS

try:
__version__ = version("template-python")
except PackageNotFoundError: # not installed, e.g. run from a source tree
__version__ = "0.0.0+unknown"

# The decorated functions pick the tag up, so registering after the imports is in time.
sp.register_opt_in_tag(*PROFILE_TAGS)
9 changes: 9 additions & 0 deletions src/app/_profiling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""The scope-profiler tag of template_python's profiling regions.

The regions are opt-in: :mod:`template_python` registers the tag on import, so they
record nothing unless the application enables it, e.g. with
``scope_profiler.setup(enable_tags=["template_python"])``.
"""

#: Tags of every template_python region; the first is the opt-in tag.
PROFILE_TAGS = ("template_python",)
5 changes: 5 additions & 0 deletions src/app/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
"""Main module of the python package."""

import scope_profiler as sp

from app._profiling import PROFILE_TAGS


@sp.profile("template_python.main", tags=PROFILE_TAGS)
def main() -> None:
"""Print a greeting; the entry point of the ``template-python`` command."""
print("Hello, world")
35 changes: 35 additions & 0 deletions src/app/tests/unit/test_profiling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""The opt-in scope-profiler regions: off unless the application enables the tag."""

from __future__ import annotations

import pytest
import scope_profiler as sp

from app.main import main


@pytest.fixture(autouse=True)
def _fresh_profiler(monkeypatch):
"""Keep the profiling state of these tests out of the others."""
monkeypatch.delenv("SCOPE_PROFILER_ENABLE_TAGS", raising=False)
sp.reset()
yield
sp.reset()


def _region_names(**setup_kwargs) -> set[str]:
sp.setup(output=None, **setup_kwargs)
# an application region, recorded either way
with sp.region("user.run"):
main()
results = sp.finalize(verbose=False, return_results=True)
return set(results.region_names)


def test_regions_are_off_by_default() -> None:
assert _region_names() == {"user.run"}


@pytest.mark.parametrize("tags", [["template_python"], ["*"]])
def test_enabling_the_tag_records_the_regions(tags: list[str]) -> None:
assert _region_names(enable_tags=tags) == {"template_python.main", "user.run"}
Loading
Loading