Skip to content

Commit c52e9d4

Browse files
committed
Complete stage 1 tooling and metadata migration
1 parent a2dcb71 commit c52e9d4

8 files changed

Lines changed: 1847 additions & 145 deletions

File tree

.github/workflows/docs.yml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ on:
66
- master
77
- main
88

9+
permissions:
10+
contents: write
11+
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.ref }}
14+
cancel-in-progress: true
15+
916
jobs:
1017
deploy:
1118
runs-on: ubuntu-latest
@@ -20,13 +27,12 @@ jobs:
2027
enable-cache: true
2128
cache-dependency-glob: |
2229
pyproject.toml
23-
setup.py
30+
uv.lock
2431
ignore-nothing-to-cache: true
2532

26-
- name: Install dependencies
33+
- name: Install docs dependencies
2734
run: |
28-
uv venv .venv --python 3.13 --clear
29-
uv pip install mkdocs mkdocs-material
35+
uv sync --extra docs --python 3.13 --locked
3036
3137
- name: Deploy docs
3238
run: uv run mkdocs gh-deploy --force

.github/workflows/test.yml

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ on:
44
push:
55
pull_request:
66

7+
permissions:
8+
contents: read
9+
10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
12+
cancel-in-progress: true
13+
714
jobs:
815
lint:
916
runs-on: ubuntu-latest
@@ -20,17 +27,21 @@ jobs:
2027
enable-cache: true
2128
cache-dependency-glob: |
2229
pyproject.toml
23-
setup.py
30+
uv.lock
2431
ignore-nothing-to-cache: true
2532

33+
- name: Install development dependencies
34+
run: |
35+
uv sync --extra dev --python 3.13 --locked
36+
2637
- name: Run pre-commit on changed files
2738
run: |
2839
if [ "${{ github.event_name }}" = "pull_request" ]; then
29-
uvx pre-commit run --from-ref "${{ github.event.pull_request.base.sha }}" --to-ref "${{ github.event.pull_request.head.sha }}"
40+
uv run pre-commit run --from-ref "${{ github.event.pull_request.base.sha }}" --to-ref "${{ github.event.pull_request.head.sha }}"
3041
elif [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then
31-
uvx pre-commit run --from-ref "${{ github.event.before }}" --to-ref "${{ github.sha }}"
42+
uv run pre-commit run --from-ref "${{ github.event.before }}" --to-ref "${{ github.sha }}"
3243
else
33-
uvx pre-commit run --all-files
44+
uv run pre-commit run --all-files
3445
fi
3546
3647
test:
@@ -51,13 +62,12 @@ jobs:
5162
enable-cache: true
5263
cache-dependency-glob: |
5364
pyproject.toml
54-
setup.py
65+
uv.lock
5566
ignore-nothing-to-cache: true
5667

57-
- name: Install dependencies
68+
- name: Install test dependencies
5869
run: |
59-
uv venv .venv --python ${{ matrix.python-version }} --clear
60-
uv pip install -e ".[test]"
70+
uv sync --extra test --python ${{ matrix.python-version }} --locked
6171
6272
- name: Test with pytest
6373
run: |

HISTORY.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ and this project adheres to [Calendar Versioning](https://calver.org/)
1010

1111

1212
### Added
13+
* Added `scripts/release.sh` for maintainer-managed local releases using
14+
`uv build` and `twine` uploads.
1315

1416
### Changed
1517
* `DlmsClient` now tracks and increments invoke-id values and sets explicit
@@ -21,10 +23,16 @@ and this project adheres to [Calendar Versioning](https://calver.org/)
2123
* Migrated CI and docs workflows from `pip`-based installs to `uv`.
2224
* Updated GitHub Actions workflow action versions to Node 24-compatible releases
2325
and set explicit uv cache dependency globs to avoid cache invalidation warnings.
26+
* Migrated package metadata to `pyproject.toml` (PEP 621), added `uv.lock`, and
27+
switched CI/docs installs to `uv sync --locked`.
28+
* Added explicit GitHub Actions permissions and concurrency settings for safer and
29+
less redundant workflow execution.
2430

2531
### Deprecated
2632

2733
### Removed
34+
* Removed legacy `setup.py` build script in favor of `pyproject.toml`
35+
packaging metadata.
2836

2937
### Fixed
3038

@@ -129,7 +137,7 @@ will allow for more versions.
129137

130138
* Some DLMS over TCP implementations will return partial data. The
131139
`BlockingTcpTransport` now keeps on trying to read the data until all data is
132-
received. Fixes [#35](https://github.com/pwitab/dlms-cosem/issues/35).
140+
received. Fixes [#35](https://github.com/u9n/dlms-cosem/issues/35).
133141
* Fixed a bug in the HDLC layer that prevented correct sending of segmented information
134142
frames.
135143

@@ -144,11 +152,11 @@ will allow for more versions.
144152

145153
### Fixed
146154

147-
* Fixed [#23](https://github.com/pwitab/dlms-cosem/issues/23). Typo in A-XDR Parser.
155+
* Fixed [#23](https://github.com/u9n/dlms-cosem/issues/23). Typo in A-XDR Parser.
148156
Just referenced the function and did not call it. Now DLMS data is interpreted
149157
correctly.
150158

151-
* Fixed [#20](https://github.com/pwitab/dlms-cosem/issues/20). It was possible that not
159+
* Fixed [#20](https://github.com/u9n/dlms-cosem/issues/20). It was possible that not
152160
calling the .shutdown() on socket before disconnecting made remote modems on meters,
153161
that have an embedded TCP/IP stack, keep the socket open and blocking subsequent calls.
154162

README.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ If you are not using a project-managed environment, you can also run:
1818
uv pip install dlms-cosem
1919
```
2020

21+
Or with pip:
22+
23+
```bash
24+
pip install dlms-cosem
25+
```
26+
2127
# Documentation
2228

2329
Full documentation can be found at [www.dlms.dev](https://www.dlms.dev)
@@ -138,18 +144,46 @@ please contact us at `info(at)pwit.se`.
138144

139145
# Development
140146

147+
This library uses an `uv`-first development workflow.
148+
149+
```bash
150+
uv sync --extra dev
151+
uv run pytest
152+
uvx pre-commit run --all-files
153+
```
154+
155+
For maintainers, local releases can be built and uploaded with:
156+
157+
```bash
158+
scripts/release.sh --repository testpypi
159+
# or
160+
scripts/release.sh
161+
```
162+
141163
This library is developed by Palmlund Wahlgren Innovative Technology AB. We are
142164
based in Sweden and are members of the DLMS User Association.
143165

144166
If you find a bug please raise an issue on Github.
145167

168+
## Contribution policy
169+
170+
We welcome bug reports, feature requests, and design discussions through GitHub
171+
Issues.
172+
173+
To reduce security and maintenance risk in this protocol implementation, we do
174+
not directly accept external code contributions for merge.
175+
176+
You are still welcome to open a pull request to illustrate an approach,
177+
provide examples, or share tests/reproduction steps. We review these proposals,
178+
but the maintainers implement and merge the final production code.
179+
146180
We add features depending on our own, and our clients use cases. If you
147181
need a feature implemented please contact us.
148182

149183
# Training / Consultancy / Commercial Support / Services
150184

151185
We offer consultancy service and training services around this library and general DLMS/COSEM.
152-
If you are interested in our services just reach at `ìnfo(at)pwit.se`
186+
If you are interested in our services just reach at `info(at)pwit.se`
153187

154188
The library is an important part of our [Smart meter platform Utilitarian, https://utilitarian.io](https://utilitarian.io). If you need to
155189
collect data from a lot of DLMS devices or meters, deploying Utilitarian might be the smoothest

pyproject.toml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,67 @@
1+
[build-system]
2+
requires = ["setuptools>=68", "wheel"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "dlms-cosem"
7+
version = "2026.1.0"
8+
description = "A Python library for DLMS/COSEM"
9+
readme = "README.md"
10+
requires-python = ">=3.9"
11+
license = { text = "Business Source License 1.1" }
12+
authors = [
13+
{ name = "Henrik Palmlund Wahlgren @ Palmlund Wahlgren Innovative Technology AB", email = "henrik@pwit.se" },
14+
]
15+
maintainers = [
16+
{ name = "Henrik Palmlund Wahlgren @ Palmlund Wahlgren Innovative Technology AB", email = "henrik@pwit.se" },
17+
]
18+
keywords = ["AMR", "Metering", "smart meters", "MDM", "dlms", "cosem"]
19+
classifiers = [
20+
"Intended Audience :: Developers",
21+
"Natural Language :: English",
22+
"License :: Other/Proprietary License",
23+
"Operating System :: OS Independent",
24+
"Programming Language :: Python :: 3",
25+
"Programming Language :: Python :: 3 :: Only",
26+
"Programming Language :: Python :: 3.9",
27+
"Programming Language :: Python :: 3.10",
28+
"Programming Language :: Python :: 3.11",
29+
"Programming Language :: Python :: 3.12",
30+
"Programming Language :: Python :: 3.13",
31+
"Programming Language :: Python :: Implementation :: CPython",
32+
"Topic :: Software Development :: Libraries :: Python Modules",
33+
"Topic :: Communications",
34+
]
35+
dependencies = [
36+
"attrs>=22.2.0",
37+
"pyserial>=3.5",
38+
"cryptography>=35.0.0",
39+
"asn1crypto>=1.4.0",
40+
"python-dateutil>=2.8.1",
41+
"typing-extensions>=3.10",
42+
"structlog>=22.1.0",
43+
]
44+
45+
[project.optional-dependencies]
46+
docs = ["mkdocs", "mkdocs-material"]
47+
test = ["pytest", "pytest-cov", "pytest-sugar"]
48+
dev = [
49+
"pre-commit",
50+
"mkdocs",
51+
"mkdocs-material",
52+
"pytest",
53+
"pytest-cov",
54+
"pytest-sugar",
55+
]
56+
57+
[project.urls]
58+
Documentation = "https://www.dlms.dev/"
59+
"Bug Tracker" = "https://github.com/u9n/dlms-cosem/issues"
60+
"Source Code" = "https://github.com/u9n/dlms-cosem"
61+
62+
[tool.setuptools.packages.find]
63+
exclude = ["tests", "tests.*"]
64+
165
[tool.black]
266
target-version = ["py39"]
367
include = '\.pyi?$'

scripts/release.sh

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
5+
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
6+
cd "${REPO_ROOT}"
7+
8+
usage() {
9+
cat <<'EOF'
10+
Usage: scripts/release.sh [options]
11+
12+
Build and upload a release with uv + twine from a clean git working tree.
13+
14+
Options:
15+
--repository <name> Twine repository name (default: pypi, e.g. testpypi)
16+
--skip-checks Skip sync/tests/pre-commit checks before build
17+
--skip-tag Do not create git tag for the current project version
18+
-h, --help Show this help
19+
20+
Examples:
21+
scripts/release.sh
22+
scripts/release.sh --repository testpypi
23+
scripts/release.sh --skip-checks --skip-tag
24+
EOF
25+
}
26+
27+
repository="pypi"
28+
skip_checks="false"
29+
skip_tag="false"
30+
31+
while [[ $# -gt 0 ]]; do
32+
case "$1" in
33+
--repository)
34+
repository="$2"
35+
shift 2
36+
;;
37+
--skip-checks)
38+
skip_checks="true"
39+
shift
40+
;;
41+
--skip-tag)
42+
skip_tag="true"
43+
shift
44+
;;
45+
-h|--help)
46+
usage
47+
exit 0
48+
;;
49+
*)
50+
echo "Unknown argument: $1" >&2
51+
usage
52+
exit 1
53+
;;
54+
esac
55+
done
56+
57+
if ! command -v uv >/dev/null 2>&1; then
58+
echo "uv is required but was not found in PATH." >&2
59+
exit 1
60+
fi
61+
62+
if ! command -v git >/dev/null 2>&1; then
63+
echo "git is required but was not found in PATH." >&2
64+
exit 1
65+
fi
66+
67+
if [[ -n "$(git status --porcelain)" ]]; then
68+
echo "Working tree is not clean. Commit/stash changes before releasing." >&2
69+
git status --short
70+
exit 1
71+
fi
72+
73+
version="$({
74+
python3 - <<'PY'
75+
from pathlib import Path
76+
77+
try:
78+
import tomllib
79+
except ModuleNotFoundError: # pragma: no cover
80+
import tomli as tomllib
81+
82+
project = tomllib.loads(Path("pyproject.toml").read_text())
83+
print(project["project"]["version"])
84+
PY
85+
} | tr -d '[:space:]')"
86+
87+
if [[ -z "${version}" ]]; then
88+
echo "Unable to read project.version from pyproject.toml" >&2
89+
exit 1
90+
fi
91+
92+
echo "Preparing release ${version} (repository: ${repository})"
93+
94+
if [[ "${skip_checks}" != "true" ]]; then
95+
echo "Running quality checks..."
96+
uv sync --extra dev --locked
97+
uv run pytest -q
98+
uv run pre-commit run --all-files
99+
fi
100+
101+
echo "Building artifacts..."
102+
rm -rf dist build
103+
uv build
104+
105+
echo "Validating artifacts..."
106+
uvx twine check dist/*
107+
108+
echo "Uploading artifacts to ${repository}..."
109+
if [[ "${repository}" == "pypi" ]]; then
110+
uvx twine upload dist/*
111+
else
112+
uvx twine upload --repository "${repository}" dist/*
113+
fi
114+
115+
if [[ "${skip_tag}" != "true" ]]; then
116+
if git rev-parse -q --verify "refs/tags/${version}" >/dev/null; then
117+
echo "Tag ${version} already exists. Skipping tag creation." >&2
118+
else
119+
git tag -a "${version}" -m "Release ${version}"
120+
echo "Created tag ${version}."
121+
echo "Push it with: git push origin ${version}"
122+
fi
123+
fi
124+
125+
echo "Release ${version} complete."

0 commit comments

Comments
 (0)