livery-extensions-python 0.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- livery/extensions/python/__init__.py +51 -0
- livery/extensions/python/_answers.py +127 -0
- livery/extensions/python/_backend.py +1532 -0
- livery/extensions/python/_declared.py +252 -0
- livery/extensions/python/_docs/index.md +165 -0
- livery/extensions/python/_docs/nav.toml +14 -0
- livery/extensions/python/_installed.py +219 -0
- livery/extensions/python/_parse.py +324 -0
- livery/extensions/python/_reaches.py +165 -0
- livery/extensions/python/_steps.py +71 -0
- livery/extensions/python/_terminal.py +118 -0
- livery/extensions/python/_verify.py +112 -0
- livery/extensions/python/content/templates/python-library/LICENSE.jinja +21 -0
- livery/extensions/python/content/templates/python-library/README.md.jinja +7 -0
- livery/extensions/python/content/templates/python-library/pyproject.toml.jinja +43 -0
- livery/extensions/python/content/templates/python-library/src/{{ source_path }}/__init__.py.jinja +15 -0
- livery/extensions/python/content/templates/python-library/src/{{ source_path }}/__main__.py.jinja +30 -0
- livery/extensions/python/content/templates/python-library/src/{{ source_path }}/py.typed +0 -0
- livery/extensions/python/content/templates/python-library/tests/test_{{ project_slug }}_cli.py.jinja +39 -0
- livery/extensions/python/content/templates/python-library/tests/test_{{ project_slug }}_package.py.jinja +22 -0
- livery/extensions/python/content/templates/python-library/workshop.toml.jinja +10 -0
- livery/extensions/python/extension.toml +165 -0
- livery/extensions/python/py.typed +0 -0
- livery_extensions_python-0.0.0.dist-info/METADATA +31 -0
- livery_extensions_python-0.0.0.dist-info/RECORD +28 -0
- livery_extensions_python-0.0.0.dist-info/WHEEL +4 -0
- livery_extensions_python-0.0.0.dist-info/entry_points.txt +3 -0
- livery_extensions_python-0.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""The verify phase's python half: a released version installed alone and tested.
|
|
2
|
+
|
|
3
|
+
The verification checks the pairing a consumer gets: the distribution
|
|
4
|
+
the index serves, installed into a plain virtual environment beside
|
|
5
|
+
the tests recorded for that same version, the package imported from
|
|
6
|
+
site-packages and never from the workspace.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import TYPE_CHECKING
|
|
12
|
+
|
|
13
|
+
from livery.footman import fail
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from livery.workshop import Package
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def verify(
|
|
22
|
+
package: Package, *, tree: Path, version: str, python: str, index: str, extras: str
|
|
23
|
+
) -> int:
|
|
24
|
+
"""Install *package*'s released *version* alone, then run its tests at *tree*.
|
|
25
|
+
|
|
26
|
+
The plain environment sits beside *tree*, which is checked out at the
|
|
27
|
+
release's receipt tag. A red install runs no test.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
The exit code: the install's when it is red, the tests' otherwise.
|
|
31
|
+
"""
|
|
32
|
+
from dataclasses import replace
|
|
33
|
+
|
|
34
|
+
venv = tree.parent / ".verify"
|
|
35
|
+
requirement = f"{package.name}{f'[{extras}]' if extras else ''}=={version}"
|
|
36
|
+
module = import_name(replace(package, directory=tree / package.path))
|
|
37
|
+
code = install(venv, python, requirement, index)
|
|
38
|
+
if code != 0:
|
|
39
|
+
return code
|
|
40
|
+
return run_tests(venv, tree, package.member, module)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def import_name(package: Package) -> str:
|
|
44
|
+
"""The module *package*'s wheel ships, which the tests import from site-packages.
|
|
45
|
+
|
|
46
|
+
The first of the package's python module roots
|
|
47
|
+
([livery.extensions.python._backend.module_roots][]): the build
|
|
48
|
+
backend's ``module-name`` where the manifest declares one, which
|
|
49
|
+
names a module under a namespace too.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
Failed: when *package* ships no module, naming its ``src``.
|
|
53
|
+
"""
|
|
54
|
+
from livery.extensions.python._backend import module_roots
|
|
55
|
+
|
|
56
|
+
roots = module_roots(package)
|
|
57
|
+
if not roots:
|
|
58
|
+
fail(
|
|
59
|
+
f"{package.directory / 'src'} holds no package directory: nothing to import"
|
|
60
|
+
)
|
|
61
|
+
return roots[0]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def install(venv: Path, python: str, requirement: str, index: str) -> int:
|
|
65
|
+
"""Create the plain environment and install *requirement* into it; the exit code.
|
|
66
|
+
|
|
67
|
+
``uv`` makes the environment and installs, but the environment is
|
|
68
|
+
a plain one outside the workspace: no lock, no editable members,
|
|
69
|
+
so the package can only come from the index.
|
|
70
|
+
"""
|
|
71
|
+
import livery.toolroom.tools as toolroom
|
|
72
|
+
|
|
73
|
+
made = toolroom.uv.opts(nofail=True)("venv", str(venv), "--python", python)
|
|
74
|
+
if made.code != 0:
|
|
75
|
+
return made.code
|
|
76
|
+
args = ["pip", "install", "--python", str(venv / "bin" / "python")]
|
|
77
|
+
if index:
|
|
78
|
+
args += ["--index", index]
|
|
79
|
+
args += [requirement, "pytest", "pytest-xdist"]
|
|
80
|
+
return toolroom.uv.opts(nofail=True)(*args).code
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def run_tests(venv: Path, tree: Path, member: str, module: str) -> int:
|
|
84
|
+
"""Prove the import comes from site-packages, then run the member's tests."""
|
|
85
|
+
from livery.footman import run
|
|
86
|
+
|
|
87
|
+
python = str(venv / "bin" / "python")
|
|
88
|
+
probe = run(
|
|
89
|
+
[
|
|
90
|
+
python,
|
|
91
|
+
"-c",
|
|
92
|
+
# The first portion of the package's path: a namespace root
|
|
93
|
+
# has no __file__, a regular package's path is its directory.
|
|
94
|
+
f"import {module} as m, pathlib; p = pathlib.Path(list(m.__path__)[0]);"
|
|
95
|
+
" assert 'site-packages' in str(p), p; print('testing', p)",
|
|
96
|
+
],
|
|
97
|
+
cwd=tree,
|
|
98
|
+
nofail=True,
|
|
99
|
+
capture=False,
|
|
100
|
+
)
|
|
101
|
+
if probe.code != 0:
|
|
102
|
+
print(
|
|
103
|
+
f" {module} does not import from site-packages; the verification"
|
|
104
|
+
" proves nothing"
|
|
105
|
+
)
|
|
106
|
+
return probe.code
|
|
107
|
+
return run(
|
|
108
|
+
[python, "-m", "pytest", f"packages/{member}/tests", "-p", "no:cacheprovider"],
|
|
109
|
+
cwd=tree,
|
|
110
|
+
nofail=True,
|
|
111
|
+
capture=False,
|
|
112
|
+
).code
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) {{ copyright_year }} {{ author_name }}
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Seeded from the python-library template at
|
|
2
|
+
# birth; this file is the workspace's own. Edit it directly:
|
|
3
|
+
# nothing rewrites it.
|
|
4
|
+
[build-system]
|
|
5
|
+
requires = ["uv_build>=0.11,<0.13"]
|
|
6
|
+
build-backend = "uv_build"
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "{{ package_name }}"
|
|
10
|
+
version = "0.0.0"
|
|
11
|
+
description = "{{ package_description }}"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
requires-python = ">={{ python_floor }}"
|
|
16
|
+
authors = [{ name = "{{ author_name }}", email = "{{ author_email }}" }]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
{%- if command %}
|
|
20
|
+
"Environment :: Console",
|
|
21
|
+
{%- endif %}
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
+
"Typing :: Typed",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
{%- if command %}
|
|
28
|
+
|
|
29
|
+
# The command line: installing the package puts {{ command }} on PATH,
|
|
30
|
+
# and `fm run` starts it from the workspace's environment.
|
|
31
|
+
[project.scripts]
|
|
32
|
+
{{ command }} = "{{ import_path }}.__main__:main"
|
|
33
|
+
{%- endif %}
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
{%- set public_host = {'github': 'https://github.com', 'gitea': 'https://gitea.com', 'gitlab': 'https://gitlab.com'} %}
|
|
37
|
+
{%- set web_root = forge_url.rstrip('/') if forge_url else public_host[forge_kind] %}
|
|
38
|
+
Repository = "{{ web_root }}/{{ forge_owner }}/{{ project_name }}"
|
|
39
|
+
|
|
40
|
+
[tool.uv.build-backend]
|
|
41
|
+
# PEP 420 namespace: the importable package is {{ import_path }} and
|
|
42
|
+
# there is no {{ namespace_package.split('.')[0] }}/__init__.py anywhere in this repository.
|
|
43
|
+
module-name = "{{ import_path }}"
|
livery/extensions/python/content/templates/python-library/src/{{ source_path }}/__init__.py.jinja
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Seeded from the python-library template at
|
|
2
|
+
# birth; this file is the workspace's own. Edit it directly:
|
|
3
|
+
# nothing rewrites it.
|
|
4
|
+
"""{{ package_description }}"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
__all__ = ["__version__", "hello"]
|
|
9
|
+
|
|
10
|
+
__version__ = "0.0.0"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def hello() -> str:
|
|
14
|
+
"""Return the placeholder greeting; replace with the real API."""
|
|
15
|
+
return "hello from {{ package_name }}"
|
livery/extensions/python/content/templates/python-library/src/{{ source_path }}/__main__.py.jinja
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{% if command -%}
|
|
2
|
+
# Seeded from the python-library template at
|
|
3
|
+
# birth; this file is the workspace's own. Edit it directly:
|
|
4
|
+
# nothing rewrites it.
|
|
5
|
+
"""The package's command line, the script its installation puts on PATH."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from {{ import_path }} import hello
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main(argv: list[str] | None = None) -> int:
|
|
15
|
+
"""Print the greeting and the words given; the exit code is the verdict.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
argv: The words after the script's name; the process's own when None.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
The exit code, 0.
|
|
22
|
+
"""
|
|
23
|
+
words = sys.argv[1:] if argv is None else argv
|
|
24
|
+
print(" ".join([hello(), *words]))
|
|
25
|
+
return 0
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
sys.exit(main())
|
|
30
|
+
{% endif -%}
|
|
File without changes
|
livery/extensions/python/content/templates/python-library/tests/test_{{ project_slug }}_cli.py.jinja
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{% if command -%}
|
|
2
|
+
# Seeded from the python-library template at
|
|
3
|
+
# birth; this file is the workspace's own. Edit it directly:
|
|
4
|
+
# nothing rewrites it.
|
|
5
|
+
"""The command line prints the greeting and the words it was given."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import runpy
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from {{ import_path }}.__main__ import main
|
|
15
|
+
|
|
16
|
+
#: The package's distribution and its import path.
|
|
17
|
+
NAME = "{{ package_name }}"
|
|
18
|
+
MODULE = "{{ import_path }}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_main_prints_the_greeting_and_the_words(
|
|
22
|
+
capsys: pytest.CaptureFixture[str],
|
|
23
|
+
) -> None:
|
|
24
|
+
assert main(["one", "two"]) == 0
|
|
25
|
+
assert capsys.readouterr().out == f"hello from {NAME} one two\n"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_the_module_run_as_a_script_exits_with_main_s_code(
|
|
29
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
30
|
+
) -> None:
|
|
31
|
+
# The import above loaded the module; a run as __main__ executes it
|
|
32
|
+
# afresh, as `python -m` does.
|
|
33
|
+
monkeypatch.setattr(sys, "argv", ["{{ command }}", "three"])
|
|
34
|
+
monkeypatch.delitem(sys.modules, f"{MODULE}.__main__", raising=False)
|
|
35
|
+
with pytest.raises(SystemExit) as exited:
|
|
36
|
+
runpy.run_module(MODULE, run_name="__main__")
|
|
37
|
+
assert exited.value.code == 0
|
|
38
|
+
assert capsys.readouterr().out == f"hello from {NAME} three\n"
|
|
39
|
+
{% endif -%}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Seeded from the python-library template at
|
|
2
|
+
# birth; this file is the workspace's own. Edit it directly:
|
|
3
|
+
# nothing rewrites it.
|
|
4
|
+
"""The package imports, carries its version, and pins its surface."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import {{ import_path }} as package
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_imports_and_carries_a_version() -> None:
|
|
12
|
+
from importlib.metadata import version
|
|
13
|
+
|
|
14
|
+
# Against the installed metadata, never a literal: the release
|
|
15
|
+
# train stamps the version, and a spelled copy here would need a
|
|
16
|
+
# hand edit every release.
|
|
17
|
+
assert package.__version__ == version("{{ package_name }}")
|
|
18
|
+
assert package.hello() == "hello from {{ package_name }}"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_the_public_surface_is_pinned() -> None:
|
|
22
|
+
assert set(package.__all__) == {"__version__", "hello"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Seeded from the python-library template at
|
|
2
|
+
# birth; this file is the workspace's own. Edit it directly:
|
|
3
|
+
# nothing rewrites it.
|
|
4
|
+
# The package contract: everything the workshop knows about this
|
|
5
|
+
# package it learns here. Declare dependencies on other workspace
|
|
6
|
+
# members as [[depends]] edges with a kind and a floor.
|
|
7
|
+
# The extensions this package is composed of.
|
|
8
|
+
extensions = {{ package_extensions }}
|
|
9
|
+
name = "{{ package_name }}"
|
|
10
|
+
description = "{{ package_description }}"
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Python: building, versioning, publishing and testing a python
|
|
2
|
+
# distribution, for each package whose set holds it.
|
|
3
|
+
|
|
4
|
+
[extension]
|
|
5
|
+
description = "Python distributions: each package's version stamped, its wheel and sdist built, published and verified, its suite run against the installed wheel, and the layering rules over the python sources. Its run phase starts the scripts a pyproject.toml names."
|
|
6
|
+
api-version = 1
|
|
7
|
+
|
|
8
|
+
# The questions about a package this extension answers. Its requirement
|
|
9
|
+
# writers write into the pyproject for a sibling that holds python: a
|
|
10
|
+
# build or runtime requirement into its dependencies, and a test
|
|
11
|
+
# requirement into its test extra, which is its test requirements; the
|
|
12
|
+
# package releases its python distribution, and the release train probes
|
|
13
|
+
# the python index the registry ladder resolves for it. Each script the
|
|
14
|
+
# pyproject names under [project.scripts] is an executable fm run starts.
|
|
15
|
+
[queries]
|
|
16
|
+
current-version = "livery.extensions.python._answers:current_version"
|
|
17
|
+
version-files = "livery.extensions.python._answers:version_files"
|
|
18
|
+
requirements = "livery.extensions.python._answers:requirements"
|
|
19
|
+
requirement-writers = "livery.extensions.python._answers:requirement_writers"
|
|
20
|
+
test-requirements = "livery.extensions.python._answers:test_requirements"
|
|
21
|
+
test-requirement-writers = "livery.extensions.python._answers:test_requirement_writers"
|
|
22
|
+
module-roots = "livery.extensions.python._answers:module_roots"
|
|
23
|
+
public-modules = "livery.extensions.python._answers:public_modules"
|
|
24
|
+
references = "livery.extensions.python._answers:references"
|
|
25
|
+
distributions = "livery.extensions.python._answers:distributions"
|
|
26
|
+
registry = "livery.extensions.python._answers:registry"
|
|
27
|
+
executables = "livery.extensions.python._answers:executables"
|
|
28
|
+
|
|
29
|
+
# Who prints a machine's coverage preview after its suites ran, a query
|
|
30
|
+
# a listed extension answers with the function that prints it, given
|
|
31
|
+
# the workspace root and the packages the run tested: one answer, the
|
|
32
|
+
# nearest. With no answer, a machine's run prints no preview.
|
|
33
|
+
[queries."python.test.preview"]
|
|
34
|
+
level = "workspace"
|
|
35
|
+
combine = "nearest"
|
|
36
|
+
|
|
37
|
+
# How the python packages' suites run, several packages in one call,
|
|
38
|
+
# and how one package's documentation examples run: two questions
|
|
39
|
+
# about the workspace, each answered with the function that runs them,
|
|
40
|
+
# the nearest answer. pytest's test and examples steps ask them, and
|
|
41
|
+
# with no answer they run nothing and say so.
|
|
42
|
+
[queries."python.test.runner"]
|
|
43
|
+
level = "workspace"
|
|
44
|
+
combine = "nearest"
|
|
45
|
+
|
|
46
|
+
[queries."python.examples.runner"]
|
|
47
|
+
level = "workspace"
|
|
48
|
+
combine = "nearest"
|
|
49
|
+
|
|
50
|
+
# The steps this extension adds to a package's lifecycle phases, one
|
|
51
|
+
# per phase, each named for the extension, and the test phase's
|
|
52
|
+
# isolated side, which the release train runs once per resolution it
|
|
53
|
+
# proves.
|
|
54
|
+
[phases.stamp.python]
|
|
55
|
+
main = "livery.extensions.python._steps:stamp"
|
|
56
|
+
reads = ["version"]
|
|
57
|
+
provides = { changed = "strs" }
|
|
58
|
+
|
|
59
|
+
# The release's wheel and sdist, built on the build phase's isolated
|
|
60
|
+
# side; the gate builds on the tree side, where the suites run from the
|
|
61
|
+
# sources and no distribution is built.
|
|
62
|
+
[phases.build.python]
|
|
63
|
+
main = "livery.extensions.python._steps:build"
|
|
64
|
+
isolated = true
|
|
65
|
+
reads = ["epoch"]
|
|
66
|
+
provides = { dist = "path" }
|
|
67
|
+
|
|
68
|
+
[phases.publish.python]
|
|
69
|
+
main = "livery.extensions.python._steps:publish"
|
|
70
|
+
reads = ["version", "registry-target"]
|
|
71
|
+
provides = { published = "bool" }
|
|
72
|
+
|
|
73
|
+
# The built wheel in a fresh venv at the engine's resolution, its tests
|
|
74
|
+
# run against the installed copy: the release's isolated test, which
|
|
75
|
+
# the gate's tree side never runs.
|
|
76
|
+
[phases.test.python]
|
|
77
|
+
main = "livery.extensions.python._steps:isolated_test"
|
|
78
|
+
isolated = true
|
|
79
|
+
scope = "package"
|
|
80
|
+
extensions = ["python"]
|
|
81
|
+
reads = ["resolution", "release-dirs"]
|
|
82
|
+
provides = { resolved = "table" }
|
|
83
|
+
|
|
84
|
+
[phases.verify.python]
|
|
85
|
+
main = "livery.extensions.python._steps:verify"
|
|
86
|
+
reads = ["version", "tree", "interpreter", "index", "extras"]
|
|
87
|
+
provides = { exit-code = "int" }
|
|
88
|
+
|
|
89
|
+
# fm run starts the script the engine names from the workspace's
|
|
90
|
+
# environment, with the words after the dashes, and exits with its code.
|
|
91
|
+
[phases.run.python]
|
|
92
|
+
main = "livery.extensions.python._steps:run"
|
|
93
|
+
reads = ["executable"]
|
|
94
|
+
|
|
95
|
+
# What a python package's paths are to it: a change to a test runs that
|
|
96
|
+
# test alone, and a change to anything else its suite and its
|
|
97
|
+
# dependents'.
|
|
98
|
+
[categories]
|
|
99
|
+
test = ["tests/**/test_*.py", "tests/**/*_test.py"]
|
|
100
|
+
test-support = ["tests/**"]
|
|
101
|
+
source = ["src/**"]
|
|
102
|
+
|
|
103
|
+
# The layering phase's steps over the one parse of the python sources:
|
|
104
|
+
# a narrowed run judges the changed sources, and every one when a
|
|
105
|
+
# contract, a manifest or a root src/ changed, as the base's layering
|
|
106
|
+
# steps do.
|
|
107
|
+
#
|
|
108
|
+
# No package's source reaches another distribution's private names; the
|
|
109
|
+
# root contract's [[python.private-reaches]] lists the reaches allowed.
|
|
110
|
+
[phases.layering.private-reaches]
|
|
111
|
+
main = "livery.extensions.python._reaches:private_reaches"
|
|
112
|
+
inputs = { reads = ["tasks.py", "packages/**/*.py"], widen = "livery.extensions.python._parse:graph_changed", ignores = ["**/*.md", "LICENSE", "**/LICENSE"] }
|
|
113
|
+
|
|
114
|
+
# A module that imports the runner asks livery.footman.attended(), never
|
|
115
|
+
# the terminal: the root's tasks.py and every package's src, apart from
|
|
116
|
+
# the runner's own.
|
|
117
|
+
[phases.layering.runner-terminal]
|
|
118
|
+
main = "livery.extensions.python._terminal:runner_terminal"
|
|
119
|
+
inputs = { reads = ["tasks.py", "packages/**/*.py"], widen = "livery.extensions.python._parse:graph_changed", ignores = ["**/*.md", "LICENSE", "**/LICENSE"] }
|
|
120
|
+
|
|
121
|
+
# What a package's src imports at import time is the standard library,
|
|
122
|
+
# a member's, or a distribution its pyproject.toml declares in
|
|
123
|
+
# [project.dependencies]; an optional extra's only inside a function; a
|
|
124
|
+
# plugin module's also what the program that loads it brings.
|
|
125
|
+
[phases.layering.declared-imports]
|
|
126
|
+
main = "livery.extensions.python._declared:declared_imports"
|
|
127
|
+
inputs = { reads = ["packages/**/*.py"], widen = "livery.extensions.python._parse:graph_changed", ignores = ["**/*.md", "LICENSE", "**/LICENSE"] }
|
|
128
|
+
|
|
129
|
+
# The root contract's [python] table.
|
|
130
|
+
[contract.root.python]
|
|
131
|
+
types = ["table"]
|
|
132
|
+
doc = "the python extension's settings for the workspace"
|
|
133
|
+
|
|
134
|
+
[contract.root.python.private-reaches]
|
|
135
|
+
types = ["list"]
|
|
136
|
+
doc = "each reach into another distribution's private name the private-reaches rule allows, with its reason; an entry no source uses any more refuses"
|
|
137
|
+
|
|
138
|
+
[contract.root.python."private-reaches[]"]
|
|
139
|
+
types = ["table"]
|
|
140
|
+
doc = "one allowed reach: the distribution that reaches, the private it reaches, and why"
|
|
141
|
+
from = { types = ["str"], doc = "the distribution whose source reaches" }
|
|
142
|
+
reaches = { types = ["str"], doc = "the private it reaches, by its dotted path" }
|
|
143
|
+
reason = { types = ["str"], doc = "why the reach stays, and the public seam that will replace it" }
|
|
144
|
+
|
|
145
|
+
# While pytest is listed, its steps run the suites and the examples
|
|
146
|
+
# through this extension's functions, which call pytest from the
|
|
147
|
+
# workspace's environment. The answers sit in this table, not under
|
|
148
|
+
# [queries]: there each name already holds its declaration's table, and
|
|
149
|
+
# one key cannot hold both.
|
|
150
|
+
[for.pytest.queries]
|
|
151
|
+
"python.test.runner" = "livery.extensions.python._answers:suite_runner"
|
|
152
|
+
"python.examples.runner" = "livery.extensions.python._answers:examples_runner"
|
|
153
|
+
|
|
154
|
+
# The template fm new.package.python-library creates a python package from; its
|
|
155
|
+
# files live under content/templates/<name>/ beside this file.
|
|
156
|
+
[templates.python-library]
|
|
157
|
+
doc = "a python library: a typed module, its tests, a pyproject.toml uv builds, and with --command a script on PATH"
|
|
158
|
+
extensions = ["python"]
|
|
159
|
+
|
|
160
|
+
# The answer names the console script the package's installation puts
|
|
161
|
+
# on PATH, which runs the main() of its __main__ module.
|
|
162
|
+
[templates.python-library.options.command]
|
|
163
|
+
doc = "the name of a script on PATH that runs the package's main(); none by default"
|
|
164
|
+
type = "str"
|
|
165
|
+
default = ""
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: livery-extensions-python
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Python distributions, as an extension of the livery workshop: each package's version stamped, its wheel and sdist built, published and verified, its suite run against the installed wheel, and the layering rules over the python sources.
|
|
5
|
+
Author: Willem Kokke
|
|
6
|
+
Author-email: Willem Kokke <mail@willem.net>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Typing :: Typed
|
|
13
|
+
Requires-Dist: livery-footman>=0.61.0
|
|
14
|
+
Requires-Dist: livery-forge>=0.8.0
|
|
15
|
+
Requires-Dist: livery-toolroom>=0.12.0
|
|
16
|
+
Requires-Dist: livery-workshop>=0.11.0
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Project-URL: Repository, https://github.com/willemkokke/livery
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# livery-extensions-python
|
|
22
|
+
|
|
23
|
+
Python distributions, as an extension of the livery workshop. Listed as
|
|
24
|
+
`python` in a package's `extensions`, it stamps the version in
|
|
25
|
+
`pyproject.toml`, builds the wheel and the sdist, publishes them to a
|
|
26
|
+
python index, and tests the installed wheel in a fresh environment for
|
|
27
|
+
a release. Its layering rules judge the python sources: no reach into
|
|
28
|
+
another distribution's private names, no import a package does not
|
|
29
|
+
declare, and no terminal check beside the runner's own.
|
|
30
|
+
`fm new.package.python-library <name>` creates a python package while a
|
|
31
|
+
workspace lists the extension.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
livery/extensions/python/__init__.py,sha256=bDhPwzw2kUAMw2qIs7eXKA0jNiuUhMImW9k6lI-MuYU,1914
|
|
2
|
+
livery/extensions/python/_answers.py,sha256=7KJQnKrnI0Yn_v69QDxptQ5Y1FrgcL9I0wpQ29fIPgk,4710
|
|
3
|
+
livery/extensions/python/_backend.py,sha256=xRYEb_Am_z29nMAKmS_b5L_hI2rZszFL8Y0hh4kk7Gc,61663
|
|
4
|
+
livery/extensions/python/_declared.py,sha256=qMkK3QurtUtWx0C2qu6W4XtcJ_gcTE4MsUUlf_tSuXY,9854
|
|
5
|
+
livery/extensions/python/_docs/index.md,sha256=quhIKmvYVPvnZvfkjrfw_9C4x3ocBy-J0VYfgKdz0J4,7535
|
|
6
|
+
livery/extensions/python/_docs/nav.toml,sha256=Dm_gWMqt69blmu9jb7MAxfCWnZhZ_OlW7RzLwmOrhj4,632
|
|
7
|
+
livery/extensions/python/_installed.py,sha256=u28SHniK0sTmMv6v9LmBj-V-UhAGv_4qoH0hWmH7Rx8,8413
|
|
8
|
+
livery/extensions/python/_parse.py,sha256=GW7ZhrFpWjx0zfj87FfHf4gLTp8KSxVlVrPkkQQiMAU,11220
|
|
9
|
+
livery/extensions/python/_reaches.py,sha256=QWVeee6v4guVuTJP_RG5_HX_8xMoCv89pTsoB_tBdvk,6469
|
|
10
|
+
livery/extensions/python/_steps.py,sha256=_J6y0KNJGSLJehXqdmVzD8d1McNHBDbFKo9Urf4apnQ,2507
|
|
11
|
+
livery/extensions/python/_terminal.py,sha256=zQsNjUERA_DojJPfnDtQVDM8tu2CHn_N3Pufqc_eB9M,4415
|
|
12
|
+
livery/extensions/python/_verify.py,sha256=MbV0Cq1DskwZzmHiH0JVdLizjeHTaxUxiZJ0d5RU1iA,3827
|
|
13
|
+
livery/extensions/python/content/templates/python-library/LICENSE.jinja,sha256=UBuNDUN2Gp65xS1IiTc27qDm3IPMjBfpv-1plzkcCmQ,1090
|
|
14
|
+
livery/extensions/python/content/templates/python-library/README.md.jinja,sha256=bH-mRmarQq2OHAKwT7hZqT5Bg47O_Q2rTL4i5v-_KHo,190
|
|
15
|
+
livery/extensions/python/content/templates/python-library/pyproject.toml.jinja,sha256=n_zGYhPq4Bz1Y4nMBZ2x7B2jc62mmX6H15Ekr3nwN-Q,1461
|
|
16
|
+
livery/extensions/python/content/templates/python-library/src/{{ source_path }}/__init__.py.jinja,sha256=mFgXp5i22v-qUfNHuSysh7ANQUZgXPXv-xElGCEPT-0,391
|
|
17
|
+
livery/extensions/python/content/templates/python-library/src/{{ source_path }}/__main__.py.jinja,sha256=rqaTA93M6aUXbnzeBldK7GzqELLPfK9eJc0UEZobDm4,738
|
|
18
|
+
livery/extensions/python/content/templates/python-library/src/{{ source_path }}/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
livery/extensions/python/content/templates/python-library/tests/test_{{ project_slug }}_cli.py.jinja,sha256=AcgCuseHbNVuFX9OtRvRDmRi714Fh4QFHLH9ZKPLeYc,1261
|
|
20
|
+
livery/extensions/python/content/templates/python-library/tests/test_{{ project_slug }}_package.py.jinja,sha256=8fHd_Ve6bexi1-5_IgHNlXXsJg-UfUFJZzsI8LEcFOE,772
|
|
21
|
+
livery/extensions/python/content/templates/python-library/workshop.toml.jinja,sha256=JhYOCcvCY5DOUHNONmxGqtiz6F1OuqDSb7zqmbGeLLs,470
|
|
22
|
+
livery/extensions/python/extension.toml,sha256=7a6jnvaUFHnRBnM3OmNl7g_usiBdUZYNG6Bqwx6PZ3M,7761
|
|
23
|
+
livery/extensions/python/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
+
livery_extensions_python-0.0.0.dist-info/licenses/LICENSE,sha256=skeoKc29nrrs52hgN_ibxDdBGLSpkqBF9S18MIJtWZY,1069
|
|
25
|
+
livery_extensions_python-0.0.0.dist-info/WHEEL,sha256=ZFFp7t7R4RYQ5KYZkmiFWoQvHay7SrTmn-6ZYfoFZ3U,80
|
|
26
|
+
livery_extensions_python-0.0.0.dist-info/entry_points.txt,sha256=1tLt7Z9uKOiUF_wXrlnl0jx1ldQ-vPCbXJI8PhjPDkQ,57
|
|
27
|
+
livery_extensions_python-0.0.0.dist-info/METADATA,sha256=Snr6fNEQ3ip-GVfbQqrGOmjMIovSHW0mK28lv4dtWNk,1480
|
|
28
|
+
livery_extensions_python-0.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Willem Kokke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|