pyside-hatch-project 0.1.0__tar.gz
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.
- pyside_hatch_project-0.1.0/.gitignore +8 -0
- pyside_hatch_project-0.1.0/LICENSE.txt +19 -0
- pyside_hatch_project-0.1.0/PKG-INFO +112 -0
- pyside_hatch_project-0.1.0/README.md +83 -0
- pyside_hatch_project-0.1.0/pyproject.toml +84 -0
- pyside_hatch_project-0.1.0/pyside_hatch_project.py +254 -0
- pyside_hatch_project-0.1.0/tests/__init__.py +0 -0
- pyside_hatch_project-0.1.0/tests/conftest.py +14 -0
- pyside_hatch_project-0.1.0/tests/dummy_project/.gitignore +4 -0
- pyside_hatch_project-0.1.0/tests/dummy_project/pyproject.toml +17 -0
- pyside_hatch_project-0.1.0/tests/dummy_project/src/dummy_app/__init__.py +0 -0
- pyside_hatch_project-0.1.0/tests/dummy_project/src/dummy_app/main_window.py +6 -0
- pyside_hatch_project-0.1.0/tests/dummy_project/src/dummy_app/main_window.ui +27 -0
- pyside_hatch_project-0.1.0/tests/dummy_project/src/dummy_app/resources.qrc +6 -0
- pyside_hatch_project-0.1.0/tests/dummy_project/src/dummy_app/translations/app_cs.ts +11 -0
- pyside_hatch_project-0.1.0/tests/test_cli.py +102 -0
- pyside_hatch_project-0.1.0/tests/test_config.py +133 -0
- pyside_hatch_project-0.1.0/tests/test_e2e.py +66 -0
- pyside_hatch_project-0.1.0/tests/test_hatch_build.py +75 -0
- pyside_hatch_project-0.1.0/tests/util.py +30 -0
- pyside_hatch_project-0.1.0/uv.lock +321 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2026 Tomas Karabela
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
|
11
|
+
all copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pyside-hatch-project
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Hatchling build hook and CLI that compiles PySide6 .ui, .ts and .qrc files
|
|
5
|
+
Project-URL: Homepage, https://github.com/tkarabela/pyside-hatch-project
|
|
6
|
+
Project-URL: Issues, https://github.com/tkarabela/pyside-hatch-project/issues
|
|
7
|
+
Author-email: Tomas Karabela <tkarabela@seznam.cz>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE.txt
|
|
10
|
+
Keywords: hatch,hatchling,lrelease,lupdate,pyside6,qt,rcc,uic
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Plugins
|
|
13
|
+
Classifier: Framework :: Hatch
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
22
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
23
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Requires-Dist: hatchling>=1.12
|
|
27
|
+
Requires-Dist: pyside6-essentials>=6.6
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
[](https://github.com/tkarabela/pyside-hatch-project/actions)
|
|
31
|
+
[](https://app.codecov.io/github/tkarabela/pyside-hatch-project)
|
|
32
|
+
[](https://github.com/tkarabela/pyside-hatch-project/actions)
|
|
33
|
+
[](https://pypi.org/project/pyside-hatch-project/)
|
|
34
|
+
[](https://pypi.org/project/pyside-hatch-project/)
|
|
35
|
+
[](https://pypi.org/project/pyside-hatch-project/)
|
|
36
|
+
[](LICENSE.txt)
|
|
37
|
+
|
|
38
|
+
# pyside-hatch-project
|
|
39
|
+
|
|
40
|
+
A Hatch-friendly alternative to `pyside6-project` for managing UI files, resource files and translations.
|
|
41
|
+
This library provides a Hatchling plugin as well as stand-alone CLI tool, both called `pyside-hatch-project`.
|
|
42
|
+
It reimplements a subset of `pyside6-project` functionality.
|
|
43
|
+
|
|
44
|
+
What this library gives you over `pyside6-project`:
|
|
45
|
+
- Compile `.ui`/`.ts`/`.qrc` as part of a standard Python build, no extra steps needed.
|
|
46
|
+
- Specify paths to `.ui`/`.ts`/`.qrc` using glob patterns in `pyproject.toml`.
|
|
47
|
+
|
|
48
|
+
If you'd rather just call `pyside6-project` from Hatchling, there is a similar project that does this:
|
|
49
|
+
[hatch-pyside](https://github.com/s-ball/hatch-pyside).
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
Example `pyproject.toml`:
|
|
54
|
+
|
|
55
|
+
```toml
|
|
56
|
+
[project]
|
|
57
|
+
name = "example-project"
|
|
58
|
+
|
|
59
|
+
[dependency-groups]
|
|
60
|
+
dev = [
|
|
61
|
+
"pyside-hatch-project",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
[build-system]
|
|
65
|
+
requires = ["hatchling>=1.27", "pyside-hatch-project"]
|
|
66
|
+
build-backend = "hatchling.build"
|
|
67
|
+
|
|
68
|
+
[tool.hatch.build.hooks.pyside-hatch-project]
|
|
69
|
+
ui_files = ["src/**/*.ui"]
|
|
70
|
+
ts_files = ["src/**/*.ts"]
|
|
71
|
+
qrc_files = ["src/**/*.qrc"]
|
|
72
|
+
|
|
73
|
+
# -- If you don't want to use the Hatchling plugin:
|
|
74
|
+
# [tool.pyside-hatch-project]
|
|
75
|
+
# ui_files = ["src/**/*.ui"]
|
|
76
|
+
# ts_files = ["src/**/*.ts"]
|
|
77
|
+
# qrc_files = ["src/**/*.qrc"]
|
|
78
|
+
|
|
79
|
+
# -- If you want to use standard pyside6-project definitions:
|
|
80
|
+
# [tool.hatch.build.hooks.pyside-hatch-project]
|
|
81
|
+
#
|
|
82
|
+
# [tool.pyside6-project]
|
|
83
|
+
# files = [
|
|
84
|
+
# "src/main.py",
|
|
85
|
+
# "src/my_widget.ui",
|
|
86
|
+
# "src/my_translation.ts",
|
|
87
|
+
# "src/my_resource.qrc",
|
|
88
|
+
# ]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
From the terminal, you can use `pyside6-project` compatible commands:
|
|
92
|
+
|
|
93
|
+
```shell
|
|
94
|
+
pyside-hatch-project build # compile .ui/.ts/.qrc files
|
|
95
|
+
pyside-hatch-project lupdate # extract strings from .py/.ui files into .ts files
|
|
96
|
+
pyside-hatch-project clean # remove compilation outputs (ui_*.py, rc_*.py, *.qm)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The compiled files can be `.gitignore`d - the build hook will add them as artifacts explicitly.
|
|
100
|
+
|
|
101
|
+
## Development
|
|
102
|
+
|
|
103
|
+
```shell
|
|
104
|
+
uv sync
|
|
105
|
+
uv run pytest # unit and integration tests
|
|
106
|
+
uv run pytest -m e2e # end-to-end builds with pip and uv in fresh environments (slow, needs network)
|
|
107
|
+
uv run ruff check && uv run pyrefly check
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT – see [LICENSE.txt](./LICENSE.txt).
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
[](https://github.com/tkarabela/pyside-hatch-project/actions)
|
|
2
|
+
[](https://app.codecov.io/github/tkarabela/pyside-hatch-project)
|
|
3
|
+
[](https://github.com/tkarabela/pyside-hatch-project/actions)
|
|
4
|
+
[](https://pypi.org/project/pyside-hatch-project/)
|
|
5
|
+
[](https://pypi.org/project/pyside-hatch-project/)
|
|
6
|
+
[](https://pypi.org/project/pyside-hatch-project/)
|
|
7
|
+
[](LICENSE.txt)
|
|
8
|
+
|
|
9
|
+
# pyside-hatch-project
|
|
10
|
+
|
|
11
|
+
A Hatch-friendly alternative to `pyside6-project` for managing UI files, resource files and translations.
|
|
12
|
+
This library provides a Hatchling plugin as well as stand-alone CLI tool, both called `pyside-hatch-project`.
|
|
13
|
+
It reimplements a subset of `pyside6-project` functionality.
|
|
14
|
+
|
|
15
|
+
What this library gives you over `pyside6-project`:
|
|
16
|
+
- Compile `.ui`/`.ts`/`.qrc` as part of a standard Python build, no extra steps needed.
|
|
17
|
+
- Specify paths to `.ui`/`.ts`/`.qrc` using glob patterns in `pyproject.toml`.
|
|
18
|
+
|
|
19
|
+
If you'd rather just call `pyside6-project` from Hatchling, there is a similar project that does this:
|
|
20
|
+
[hatch-pyside](https://github.com/s-ball/hatch-pyside).
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Example `pyproject.toml`:
|
|
25
|
+
|
|
26
|
+
```toml
|
|
27
|
+
[project]
|
|
28
|
+
name = "example-project"
|
|
29
|
+
|
|
30
|
+
[dependency-groups]
|
|
31
|
+
dev = [
|
|
32
|
+
"pyside-hatch-project",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["hatchling>=1.27", "pyside-hatch-project"]
|
|
37
|
+
build-backend = "hatchling.build"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.hooks.pyside-hatch-project]
|
|
40
|
+
ui_files = ["src/**/*.ui"]
|
|
41
|
+
ts_files = ["src/**/*.ts"]
|
|
42
|
+
qrc_files = ["src/**/*.qrc"]
|
|
43
|
+
|
|
44
|
+
# -- If you don't want to use the Hatchling plugin:
|
|
45
|
+
# [tool.pyside-hatch-project]
|
|
46
|
+
# ui_files = ["src/**/*.ui"]
|
|
47
|
+
# ts_files = ["src/**/*.ts"]
|
|
48
|
+
# qrc_files = ["src/**/*.qrc"]
|
|
49
|
+
|
|
50
|
+
# -- If you want to use standard pyside6-project definitions:
|
|
51
|
+
# [tool.hatch.build.hooks.pyside-hatch-project]
|
|
52
|
+
#
|
|
53
|
+
# [tool.pyside6-project]
|
|
54
|
+
# files = [
|
|
55
|
+
# "src/main.py",
|
|
56
|
+
# "src/my_widget.ui",
|
|
57
|
+
# "src/my_translation.ts",
|
|
58
|
+
# "src/my_resource.qrc",
|
|
59
|
+
# ]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
From the terminal, you can use `pyside6-project` compatible commands:
|
|
63
|
+
|
|
64
|
+
```shell
|
|
65
|
+
pyside-hatch-project build # compile .ui/.ts/.qrc files
|
|
66
|
+
pyside-hatch-project lupdate # extract strings from .py/.ui files into .ts files
|
|
67
|
+
pyside-hatch-project clean # remove compilation outputs (ui_*.py, rc_*.py, *.qm)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The compiled files can be `.gitignore`d - the build hook will add them as artifacts explicitly.
|
|
71
|
+
|
|
72
|
+
## Development
|
|
73
|
+
|
|
74
|
+
```shell
|
|
75
|
+
uv sync
|
|
76
|
+
uv run pytest # unit and integration tests
|
|
77
|
+
uv run pytest -m e2e # end-to-end builds with pip and uv in fresh environments (slow, needs network)
|
|
78
|
+
uv run ruff check && uv run pyrefly check
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT – see [LICENSE.txt](./LICENSE.txt).
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pyside-hatch-project"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Hatchling build hook and CLI that compiles PySide6 .ui, .ts and .qrc files"
|
|
5
|
+
requires-python = ">=3.12"
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE.txt"]
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Tomas Karabela", email = "tkarabela@seznam.cz" },
|
|
11
|
+
]
|
|
12
|
+
keywords = ["hatch", "hatchling", "pyside6", "qt", "uic", "rcc", "lrelease", "lupdate"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Environment :: Plugins",
|
|
16
|
+
"Framework :: Hatch",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Programming Language :: Python :: 3.14",
|
|
24
|
+
"Programming Language :: Python :: 3.15",
|
|
25
|
+
"Topic :: Software Development :: Build Tools",
|
|
26
|
+
"Topic :: Software Development :: User Interfaces",
|
|
27
|
+
"Typing :: Typed",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
# 1.12 is the first release where BuildHookInterface is Generic (subscripted at runtime)
|
|
31
|
+
"hatchling>=1.12",
|
|
32
|
+
# 6.6 is the first release installable on Python 3.12
|
|
33
|
+
"pyside6-essentials>=6.6",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/tkarabela/pyside-hatch-project"
|
|
38
|
+
Issues = "https://github.com/tkarabela/pyside-hatch-project/issues"
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
pyside-hatch-project = "pyside_hatch_project:main"
|
|
42
|
+
|
|
43
|
+
[project.entry-points.hatch]
|
|
44
|
+
pyside-hatch-project = "pyside_hatch_project"
|
|
45
|
+
|
|
46
|
+
[dependency-groups]
|
|
47
|
+
dev = [
|
|
48
|
+
"pyrefly>=1.4.0",
|
|
49
|
+
"pytest>=8.4",
|
|
50
|
+
"pytest-cov>=7.1.0",
|
|
51
|
+
"ruff>=0.17.1",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[build-system]
|
|
55
|
+
requires = ["hatchling>=1.27", "hatch-vcs>=0.5"]
|
|
56
|
+
build-backend = "hatchling.build"
|
|
57
|
+
|
|
58
|
+
[tool.hatch.version]
|
|
59
|
+
source = "vcs"
|
|
60
|
+
|
|
61
|
+
[tool.hatch.build.targets.sdist]
|
|
62
|
+
exclude = ["/.github"]
|
|
63
|
+
|
|
64
|
+
[tool.uv]
|
|
65
|
+
# Rebuild the editable install when the commit or tags change, so the version stays current
|
|
66
|
+
cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true, tags = true } }]
|
|
67
|
+
|
|
68
|
+
[tool.pyrefly]
|
|
69
|
+
preset = "strict"
|
|
70
|
+
project-excludes = ["tests/dummy_project/**"]
|
|
71
|
+
|
|
72
|
+
[tool.pytest.ini_options]
|
|
73
|
+
testpaths = ["tests"]
|
|
74
|
+
addopts = ["-m", "not e2e", "--strict-markers"]
|
|
75
|
+
markers = [
|
|
76
|
+
"e2e: end-to-end builds in fresh virtual environments (slow, needs network)",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
[tool.coverage.run]
|
|
80
|
+
branch = true
|
|
81
|
+
|
|
82
|
+
[tool.coverage.report]
|
|
83
|
+
show_missing = true
|
|
84
|
+
skip_covered = true
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import logging
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import sysconfig
|
|
7
|
+
import tomllib
|
|
8
|
+
from collections.abc import Iterable
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import StrEnum
|
|
11
|
+
from importlib.metadata import version
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, NoReturn, override
|
|
14
|
+
|
|
15
|
+
from hatchling.bridge.app import Application
|
|
16
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
17
|
+
from hatchling.plugin import hookimpl
|
|
18
|
+
|
|
19
|
+
__version__ = version("pyside-hatch-project")
|
|
20
|
+
HATCH_PLUGIN_NAME = "pyside-hatch-project"
|
|
21
|
+
logger = logging.getLogger(HATCH_PLUGIN_NAME)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _traverse_dict(d: dict[str, Any], keys: tuple[str, ...]) -> dict[str, Any]:
|
|
25
|
+
for key in keys:
|
|
26
|
+
d = d.get(key, {})
|
|
27
|
+
return d
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _ui_output_path(path: Path) -> Path:
|
|
31
|
+
return path.with_name(f"ui_{path.stem}.py")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _ts_output_path(path: Path) -> Path:
|
|
35
|
+
return path.with_suffix(".qm")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _qrc_output_path(path: Path) -> Path:
|
|
39
|
+
return path.with_name(f"rc_{path.stem}.py")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class Config:
|
|
44
|
+
files: list[Path] = field(default_factory=list)
|
|
45
|
+
|
|
46
|
+
def add(self, files: Iterable[Path]) -> None:
|
|
47
|
+
registered = set(self.files)
|
|
48
|
+
|
|
49
|
+
for path in files:
|
|
50
|
+
if path.suffix in (".ui", ".ts", ".qrc") and path not in registered:
|
|
51
|
+
registered.add(path)
|
|
52
|
+
self.files.append(path)
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def ui_files(self) -> Iterable[Path]:
|
|
56
|
+
return (path for path in self.files if path.suffix == ".ui")
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def ts_files(self) -> Iterable[Path]:
|
|
60
|
+
return (path for path in self.files if path.suffix == ".ts")
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def qrc_files(self) -> Iterable[Path]:
|
|
64
|
+
return (path for path in self.files if path.suffix == ".qrc")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Project:
|
|
68
|
+
def __init__(self, root: Path) -> None:
|
|
69
|
+
self.root = root
|
|
70
|
+
self.config = self.read_config(root)
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def read_config(root: Path) -> Config:
|
|
74
|
+
config = Config()
|
|
75
|
+
pyproject = tomllib.loads(root.joinpath("pyproject.toml").read_text(encoding="utf-8"))
|
|
76
|
+
|
|
77
|
+
# [tool.pyside6-project]
|
|
78
|
+
config.add(
|
|
79
|
+
root.joinpath(relpath)
|
|
80
|
+
for relpath in _traverse_dict(pyproject, ("tool", "pyside6-project")).get("files", [])
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# [tool.pyside-hatch-project]
|
|
84
|
+
# [tool.hatch.build.hooks.pyside-hatch-project]
|
|
85
|
+
for toml_path in (
|
|
86
|
+
("tool", "pyside-hatch-project"),
|
|
87
|
+
("tool", "hatch", "build", "hooks", HATCH_PLUGIN_NAME),
|
|
88
|
+
):
|
|
89
|
+
for attribute in ("ui_files", "ts_files", "qrc_files"):
|
|
90
|
+
for relpath in _traverse_dict(pyproject, toml_path).get(attribute, []):
|
|
91
|
+
config.add(root.glob(relpath))
|
|
92
|
+
|
|
93
|
+
return config
|
|
94
|
+
|
|
95
|
+
def list_output_files(self) -> Iterable[Path]:
|
|
96
|
+
yield from (_ui_output_path(path) for path in self.config.ui_files)
|
|
97
|
+
yield from (_ts_output_path(path) for path in self.config.ts_files)
|
|
98
|
+
yield from (_qrc_output_path(path) for path in self.config.qrc_files)
|
|
99
|
+
|
|
100
|
+
def list_python_sources(self) -> Iterable[Path]:
|
|
101
|
+
src_dir = self.root.joinpath("src")
|
|
102
|
+
source_dir = src_dir if src_dir.exists() else self.root
|
|
103
|
+
return (
|
|
104
|
+
path
|
|
105
|
+
for path in sorted(source_dir.rglob("*.py"))
|
|
106
|
+
if not path.name.startswith(("ui_", "rc_")) and "__pycache__" not in path.parts
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
def compile_input_files(self) -> Iterable[Path]:
|
|
110
|
+
if ui_files := list(self.config.ui_files):
|
|
111
|
+
self.info(f"Compiling {len(ui_files)} .ui files")
|
|
112
|
+
for path in ui_files:
|
|
113
|
+
output_path = _ui_output_path(path)
|
|
114
|
+
self.run_tool("pyside6-uic", path, "-o", output_path)
|
|
115
|
+
yield output_path
|
|
116
|
+
|
|
117
|
+
if ts_files := list(self.config.ts_files):
|
|
118
|
+
self.info(f"Compiling {len(ts_files)} .ts files")
|
|
119
|
+
for path in ts_files:
|
|
120
|
+
output_path = _ts_output_path(path)
|
|
121
|
+
self.run_tool("pyside6-lrelease", path, "-qm", output_path)
|
|
122
|
+
yield output_path
|
|
123
|
+
|
|
124
|
+
# compile .qrc files last, in case they reference compiled translations (.qm files)
|
|
125
|
+
if qrc_files := list(self.config.qrc_files):
|
|
126
|
+
self.info(f"Compiling {len(qrc_files)} .qrc files")
|
|
127
|
+
for path in qrc_files:
|
|
128
|
+
output_path = _qrc_output_path(path)
|
|
129
|
+
self.run_tool("pyside6-rcc", path, "-o", output_path)
|
|
130
|
+
yield output_path
|
|
131
|
+
|
|
132
|
+
def clean_compiled_files(self) -> None:
|
|
133
|
+
for path in self.list_output_files():
|
|
134
|
+
path.unlink(missing_ok=True)
|
|
135
|
+
|
|
136
|
+
def update_translations(self) -> None:
|
|
137
|
+
if not (ts_files := list(self.config.ts_files)):
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
self.info(f"Updating {len(ts_files)} .ts files")
|
|
141
|
+
|
|
142
|
+
input_paths = [*self.config.ui_files, *self.list_python_sources()]
|
|
143
|
+
|
|
144
|
+
for ts in self.config.ts_files:
|
|
145
|
+
self.run_tool("pyside6-lupdate", *input_paths, "-ts", ts)
|
|
146
|
+
|
|
147
|
+
def run_tool(self, tool_name: str, *args: str | Path) -> None:
|
|
148
|
+
# prefer tools installed alongside the running interpreter, but fall back to PATH: pip's isolated
|
|
149
|
+
# build environment is not a venv, its scripts directory is only made available through PATH
|
|
150
|
+
tool = shutil.which(tool_name, path=sysconfig.get_path("scripts")) or shutil.which(tool_name)
|
|
151
|
+
if tool is None:
|
|
152
|
+
self.fail(f"{tool_name} not found (is pyside6-essentials installed?)")
|
|
153
|
+
cmd = [tool, *map(str, args)]
|
|
154
|
+
self.debug(f"Running: {cmd}")
|
|
155
|
+
rv = subprocess.run(cmd, check=False).returncode
|
|
156
|
+
if rv != 0:
|
|
157
|
+
self.fail(f"{' '.join(cmd)} failed with exit code {rv}")
|
|
158
|
+
|
|
159
|
+
def info(self, msg: str) -> None:
|
|
160
|
+
logger.info(msg)
|
|
161
|
+
|
|
162
|
+
def debug(self, msg: str) -> None:
|
|
163
|
+
logger.debug(msg)
|
|
164
|
+
|
|
165
|
+
def fail(self, msg: str) -> NoReturn:
|
|
166
|
+
logger.fatal(msg)
|
|
167
|
+
raise RuntimeError(msg)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class HatchProject(Project):
|
|
171
|
+
def __init__(self, hook: BuildHookInterface[Any]) -> None:
|
|
172
|
+
self.hook = hook
|
|
173
|
+
self.app: Application = hook.app
|
|
174
|
+
super().__init__(root=Path(hook.root))
|
|
175
|
+
|
|
176
|
+
@override
|
|
177
|
+
def info(self, msg: str) -> None:
|
|
178
|
+
self.app.display_info(msg)
|
|
179
|
+
|
|
180
|
+
@override
|
|
181
|
+
def debug(self, msg: str) -> None:
|
|
182
|
+
self.app.display_debug(msg)
|
|
183
|
+
|
|
184
|
+
@override
|
|
185
|
+
def fail(self, msg: str) -> NoReturn:
|
|
186
|
+
self.app.abort(msg)
|
|
187
|
+
raise SystemExit(1) # not used, Application.abort() is just missing NoReturn annotation
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class PysideHatchProjectBuildHook(BuildHookInterface[Any]):
|
|
191
|
+
PLUGIN_NAME = HATCH_PLUGIN_NAME
|
|
192
|
+
|
|
193
|
+
@override
|
|
194
|
+
def clean(self, versions: list[str]) -> None:
|
|
195
|
+
self.__project.clean_compiled_files()
|
|
196
|
+
|
|
197
|
+
@override
|
|
198
|
+
def initialize(self, version: str, build_data: dict[str, Any]) -> None:
|
|
199
|
+
project = self.__project
|
|
200
|
+
output_files = list(project.compile_input_files())
|
|
201
|
+
# artifacts are gitignore-style patterns relative to the project root, anchored to match only these files
|
|
202
|
+
build_data["artifacts"].extend(f"/{path.relative_to(project.root).as_posix()}" for path in output_files)
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def __project(self) -> HatchProject:
|
|
206
|
+
return HatchProject(self)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class _Command(StrEnum):
|
|
210
|
+
BUILD = "build"
|
|
211
|
+
CLEAN = "clean"
|
|
212
|
+
LUPDATE = "lupdate"
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class CLI:
|
|
216
|
+
def __init__(self) -> None:
|
|
217
|
+
self.parser = argparse.ArgumentParser()
|
|
218
|
+
self.parser.add_argument("command", choices=[_Command.BUILD, _Command.CLEAN, _Command.LUPDATE])
|
|
219
|
+
self.parser.add_argument("-v", "--verbose", action="store_true", default=False)
|
|
220
|
+
self.parser.add_argument("-V", "--version", action="version", version=__version__)
|
|
221
|
+
|
|
222
|
+
def run(self, argv: list[str]) -> int:
|
|
223
|
+
args = self.parser.parse_args(argv)
|
|
224
|
+
command = _Command(args.command)
|
|
225
|
+
verbose: bool = args.verbose
|
|
226
|
+
|
|
227
|
+
logging.basicConfig(level=logging.DEBUG if verbose else logging.INFO)
|
|
228
|
+
project = Project(Path.cwd().resolve())
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
match command:
|
|
232
|
+
case _Command.BUILD:
|
|
233
|
+
_ = list(project.compile_input_files())
|
|
234
|
+
case _Command.CLEAN:
|
|
235
|
+
project.clean_compiled_files()
|
|
236
|
+
case _Command.LUPDATE:
|
|
237
|
+
project.update_translations()
|
|
238
|
+
return 0
|
|
239
|
+
except Exception:
|
|
240
|
+
logger.exception("pyside-hatch-project failed")
|
|
241
|
+
return 1
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@hookimpl
|
|
245
|
+
def hatch_register_build_hook():
|
|
246
|
+
return PysideHatchProjectBuildHook
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def main() -> int:
|
|
250
|
+
return CLI().run(sys.argv[1:])
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
if __name__ == "__main__":
|
|
254
|
+
sys.exit(main())
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from tests.util import DUMMY_PROJECT
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@pytest.fixture
|
|
10
|
+
def dummy_project(tmp_path: Path) -> Path:
|
|
11
|
+
"""Fresh copy of the dummy project, so that compiled outputs never land in the repository."""
|
|
12
|
+
root = tmp_path / "dummy_project"
|
|
13
|
+
shutil.copytree(DUMMY_PROJECT, root)
|
|
14
|
+
return root
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dummy-app"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
requires-python = ">=3.12"
|
|
5
|
+
dependencies = ["pyside6-essentials"]
|
|
6
|
+
|
|
7
|
+
[build-system]
|
|
8
|
+
requires = ["hatchling", "pyside-hatch-project"]
|
|
9
|
+
build-backend = "hatchling.build"
|
|
10
|
+
|
|
11
|
+
[tool.hatch.build.targets.wheel]
|
|
12
|
+
packages = ["src/dummy_app"]
|
|
13
|
+
|
|
14
|
+
[tool.hatch.build.hooks.pyside-hatch-project]
|
|
15
|
+
ui_files = ["src/**/*.ui"]
|
|
16
|
+
ts_files = ["src/**/*.ts"]
|
|
17
|
+
qrc_files = ["src/**/*.qrc"]
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<ui version="4.0">
|
|
3
|
+
<class>MainWindow</class>
|
|
4
|
+
<widget class="QMainWindow" name="MainWindow">
|
|
5
|
+
<property name="geometry">
|
|
6
|
+
<rect>
|
|
7
|
+
<x>0</x>
|
|
8
|
+
<y>0</y>
|
|
9
|
+
<width>320</width>
|
|
10
|
+
<height>240</height>
|
|
11
|
+
</rect>
|
|
12
|
+
</property>
|
|
13
|
+
<widget class="QWidget" name="centralwidget">
|
|
14
|
+
<layout class="QVBoxLayout" name="verticalLayout">
|
|
15
|
+
<item>
|
|
16
|
+
<widget class="QLabel" name="label">
|
|
17
|
+
<property name="text">
|
|
18
|
+
<string>Hello</string>
|
|
19
|
+
</property>
|
|
20
|
+
</widget>
|
|
21
|
+
</item>
|
|
22
|
+
</layout>
|
|
23
|
+
</widget>
|
|
24
|
+
</widget>
|
|
25
|
+
<resources/>
|
|
26
|
+
<connections/>
|
|
27
|
+
</ui>
|