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.
Files changed (28) hide show
  1. livery/extensions/python/__init__.py +51 -0
  2. livery/extensions/python/_answers.py +127 -0
  3. livery/extensions/python/_backend.py +1532 -0
  4. livery/extensions/python/_declared.py +252 -0
  5. livery/extensions/python/_docs/index.md +165 -0
  6. livery/extensions/python/_docs/nav.toml +14 -0
  7. livery/extensions/python/_installed.py +219 -0
  8. livery/extensions/python/_parse.py +324 -0
  9. livery/extensions/python/_reaches.py +165 -0
  10. livery/extensions/python/_steps.py +71 -0
  11. livery/extensions/python/_terminal.py +118 -0
  12. livery/extensions/python/_verify.py +112 -0
  13. livery/extensions/python/content/templates/python-library/LICENSE.jinja +21 -0
  14. livery/extensions/python/content/templates/python-library/README.md.jinja +7 -0
  15. livery/extensions/python/content/templates/python-library/pyproject.toml.jinja +43 -0
  16. livery/extensions/python/content/templates/python-library/src/{{ source_path }}/__init__.py.jinja +15 -0
  17. livery/extensions/python/content/templates/python-library/src/{{ source_path }}/__main__.py.jinja +30 -0
  18. livery/extensions/python/content/templates/python-library/src/{{ source_path }}/py.typed +0 -0
  19. livery/extensions/python/content/templates/python-library/tests/test_{{ project_slug }}_cli.py.jinja +39 -0
  20. livery/extensions/python/content/templates/python-library/tests/test_{{ project_slug }}_package.py.jinja +22 -0
  21. livery/extensions/python/content/templates/python-library/workshop.toml.jinja +10 -0
  22. livery/extensions/python/extension.toml +165 -0
  23. livery/extensions/python/py.typed +0 -0
  24. livery_extensions_python-0.0.0.dist-info/METADATA +31 -0
  25. livery_extensions_python-0.0.0.dist-info/RECORD +28 -0
  26. livery_extensions_python-0.0.0.dist-info/WHEEL +4 -0
  27. livery_extensions_python-0.0.0.dist-info/entry_points.txt +3 -0
  28. 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,7 @@
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
+ -->
5
+ # {{ package_name }}
6
+
7
+ {{ package_description }}
@@ -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 }}"
@@ -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 }}"
@@ -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 -%}
@@ -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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.12.7
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [workshop.extensions]
2
+ python = livery.extensions.python
3
+
@@ -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.