mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-02-24 01:46:21 +00:00
fix: Adapt versioning scheme to Home Assistant and switch to uv (#896)
Some checks are pending
Bump Version / Bump Version Workflow (push) Waiting to run
docker-build / platform-excludes (push) Waiting to run
docker-build / build (push) Blocked by required conditions
docker-build / merge (push) Blocked by required conditions
pre-commit / pre-commit (push) Waiting to run
Run Pytest on Pull Request / test (push) Waiting to run
Some checks are pending
Bump Version / Bump Version Workflow (push) Waiting to run
docker-build / platform-excludes (push) Waiting to run
docker-build / build (push) Blocked by required conditions
docker-build / merge (push) Blocked by required conditions
pre-commit / pre-commit (push) Waiting to run
Run Pytest on Pull Request / test (push) Waiting to run
Home Assistant expects versioning always increases numbers. Add a date component to the development version to comply with this expectation. The scheme is now 0.0.0.dev<date><hash>. Use uv for creating and managing the virtual environment for developement. This enourmously speeds up dependency updates. For this change dependency requirements are now solely handled in pyproject.toml. requirements.tx and requirements-dev.txt are deleted. Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com>
This commit is contained in:
2
.env
2
.env
@@ -11,7 +11,7 @@ DOCKER_COMPOSE_DATA_DIR=${HOME}/.local/share/net.akkudoktor.eos
|
||||
# -----------------------------------------------------------------------------
|
||||
# Image / build
|
||||
# -----------------------------------------------------------------------------
|
||||
VERSION=0.2.0.dev58204789
|
||||
VERSION=config.yaml
|
||||
PYTHON_VERSION=3.13.9
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
13
.github/workflows/pytest.yml
vendored
13
.github/workflows/pytest.yml
vendored
@@ -18,15 +18,18 @@ jobs:
|
||||
with:
|
||||
python-version: "3.13.9"
|
||||
|
||||
- name: Install dependencies
|
||||
- name: Install uv
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements-dev.txt
|
||||
pip install uv
|
||||
|
||||
- name: Run Pytest
|
||||
- name: Sync environment with uv
|
||||
run: |
|
||||
pip install -e .
|
||||
python -m pytest --finalize --check-config-side-effect -vs --cov src --cov-report term-missing
|
||||
uv sync --extra dev
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
uv run pytest --finalize --check-config-side-effect -vs --cov src --cov-report term-missing
|
||||
|
||||
- name: Upload test artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
@@ -31,14 +31,14 @@ repos:
|
||||
|
||||
# --- Static type checking ---
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.18.2
|
||||
rev: v1.19.1
|
||||
hooks:
|
||||
- id: mypy
|
||||
additional_dependencies:
|
||||
- types-requests==2.32.4.20250913
|
||||
- pandas-stubs==2.3.2.250926
|
||||
- pandas-stubs==3.0.0.260204
|
||||
- tokenize-rt==6.2.0
|
||||
- types-docutils==0.22.2.20251006
|
||||
- types-docutils==0.22.3.20251115
|
||||
- types-PyYaml==6.0.12.20250915
|
||||
pass_filenames: false
|
||||
|
||||
@@ -59,23 +59,29 @@ repos:
|
||||
# Validate commit messages (using Python wrapper)
|
||||
- id: commitizen-commit
|
||||
name: Commitizen (venv-aware)
|
||||
entry: python3 scripts/cz_check_commit_message.py
|
||||
language: system
|
||||
entry: scripts/cz_check_commit_message.py
|
||||
language: python
|
||||
additional_dependencies:
|
||||
- .
|
||||
stages: [commit-msg]
|
||||
pass_filenames: false
|
||||
|
||||
# Branch name check on push (using Python wrapper)
|
||||
- id: commitizen-branch
|
||||
name: Commitizen branch check
|
||||
entry: python3 scripts/cz_check_branch.py
|
||||
language: system
|
||||
entry: scripts/cz_check_branch.py
|
||||
language: python
|
||||
additional_dependencies:
|
||||
- .
|
||||
stages: [pre-push]
|
||||
pass_filenames: false
|
||||
|
||||
# Validate new commit messages before push (using Python wrapper)
|
||||
- id: commitizen-new-commits
|
||||
name: Commitizen (check new commits only, .venv aware)
|
||||
entry: python3 -m scripts.cz_check_new_commits
|
||||
language: system
|
||||
entry: scripts/cz_check_new_commits.py
|
||||
language: python
|
||||
additional_dependencies:
|
||||
- .
|
||||
stages: [pre-push]
|
||||
pass_filenames: false
|
||||
|
||||
@@ -16,7 +16,7 @@ in Home Assistant.
|
||||
The prediction and measurement data can now be backed by a database. The database allows
|
||||
to keep historic prediction data and measurement data for long time without keeping
|
||||
it in memory. The database supports backend selection, compression, incremental data load,
|
||||
automatic data saving to storage, automatic vaccum and compaction. Two database backends
|
||||
automatic data saving to storage, automatic vacuum and compaction. Two database backends
|
||||
are integrated and can be configured, LMDB and SQLight3.
|
||||
|
||||
In addition, bugs were fixed and new features were added.
|
||||
@@ -63,7 +63,7 @@ In addition, bugs were fixed and new features were added.
|
||||
real (test) environment pathes.
|
||||
- development version scheme
|
||||
The development versioning scheme is adaptet to fit to docker and
|
||||
home assistant expectations. The new scheme is x.y.z and x.y.z.dev<hash>.
|
||||
home assistant expectations. The new scheme is x.y.z and x.y.z.dev'date''hash'.
|
||||
Hash is only digits as expected by home assistant. Development version
|
||||
is appended by .dev as expected by docker.
|
||||
- use mean value in interval on resampling for array
|
||||
@@ -133,6 +133,8 @@ In addition, bugs were fixed and new features were added.
|
||||
- add home assistant add-on development environment
|
||||
Add VSCode devcontainer and task definition for home assistant add-on
|
||||
development.
|
||||
- Use uv to manage the virtual environment for development.
|
||||
This enormously increases dependency updates.
|
||||
- improve documentation
|
||||
|
||||
## 0.2.0 (2025-11-09)
|
||||
|
||||
@@ -59,20 +59,18 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libopenblas-dev liblapack-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# - Copy project metadata first (better Docker layer caching)
|
||||
COPY pyproject.toml .
|
||||
|
||||
# - Create venv
|
||||
RUN python3 -m venv ${VENV_PATH}
|
||||
|
||||
# - Upgrade pip inside venv
|
||||
RUN pip install --upgrade pip setuptools wheel
|
||||
|
||||
# - Install deps
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Install EOS/ EOSdash
|
||||
# - Copy source
|
||||
COPY src/ ./src
|
||||
COPY pyproject.toml .
|
||||
|
||||
# - Create version information
|
||||
COPY scripts/get_version.py ./scripts/get_version.py
|
||||
|
||||
123
Makefile
123
Makefile
@@ -1,8 +1,18 @@
|
||||
# Define the targets
|
||||
.PHONY: help venv pip install dist test test-full test-system test-ci test-profile docker-run docker-build docs read-docs clean format gitlint mypy run run-dev run-dash run-dash-dev prepare-version test-version
|
||||
.PHONY: help install dist test test-system test-ci test-profile \
|
||||
docker-run docker-build docs read-docs clean format gitlint mypy \
|
||||
run run-dev run-dash run-dash-dev prepare-version test-version uv-update
|
||||
|
||||
# Use uv for all program actions
|
||||
UV := uv
|
||||
PYTHON := $(UV) run python
|
||||
PYTEST := $(UV) run pytest
|
||||
MYPY := $(UV) run mypy
|
||||
PRECOMMIT := $(UV) run pre-commit
|
||||
COMMITIZEN := $(UV) run cz
|
||||
|
||||
# - Take VERSION from version.py
|
||||
VERSION := $(shell python3 scripts/get_version.py)
|
||||
VERSION := $(shell $(PYTHON) scripts/get_version.py)
|
||||
|
||||
# Default target
|
||||
all: help
|
||||
@@ -10,13 +20,11 @@ all: help
|
||||
# Target to display help information
|
||||
help:
|
||||
@echo "Available targets:"
|
||||
@echo " venv - Set up a Python 3 virtual environment."
|
||||
@echo " pip - Install dependencies from requirements.txt."
|
||||
@echo " pip-dev - Install dependencies from requirements-dev.txt."
|
||||
@echo " format - Format source code."
|
||||
@echo " gitlint - Lint last commit message."
|
||||
@echo " mypy - Run mypy."
|
||||
@echo " install - Install EOS in editable form (development mode) into virtual environment."
|
||||
@echo " update-env - Update virtual environmenr to match pyproject.toml."
|
||||
@echo " docker-run - Run entire setup on docker"
|
||||
@echo " docker-build - Rebuild docker image"
|
||||
@echo " docs - Generate HTML documentation (in build/docs/html/)."
|
||||
@@ -36,57 +44,48 @@ help:
|
||||
@echo " clean - Remove generated documentation, distribution and virtual environment."
|
||||
@echo " prepare-version - Prepare a version defined in setup.py."
|
||||
|
||||
# Target to set up a Python 3 virtual environment
|
||||
venv:
|
||||
python3 -m venv .venv
|
||||
@PYVER=$$(./.venv/bin/python --version) && \
|
||||
echo "Virtual environment created in '.venv' with $$PYVER. Activate it using 'source .venv/bin/activate'."
|
||||
|
||||
# Target to install dependencies from requirements.txt
|
||||
pip: venv
|
||||
.venv/bin/pip install --upgrade pip
|
||||
.venv/bin/pip install -r requirements.txt
|
||||
@echo "Dependencies installed from requirements.txt."
|
||||
|
||||
# Target to install dependencies from requirements.txt
|
||||
pip-dev: pip
|
||||
.venv/bin/pip install -r requirements-dev.txt
|
||||
@echo "Dependencies installed from requirements-dev.txt."
|
||||
|
||||
# Target to create a version.txt
|
||||
version-txt:
|
||||
echo "$(VERSION)" > version.txt
|
||||
# Get the version from the package for setuptools (and pip)
|
||||
VERSION=$$(${PYTHON} scripts/get_version.py)
|
||||
@echo "$(VERSION)" > version.txt
|
||||
@echo "version.txt set to '$(VERSION)'."
|
||||
|
||||
# Target to install EOS in editable form (development mode) into virtual environment.
|
||||
install: pip-dev version-txt
|
||||
.venv/bin/pip install build
|
||||
.venv/bin/pip install -e .
|
||||
@echo "EOS installed in editable form (development mode)."
|
||||
install: version-txt
|
||||
# Upgrade installation and dependencies
|
||||
$(UV) sync --extra dev
|
||||
@echo "EOS version $(VERSION) installed in editable form (development mode)."
|
||||
|
||||
# Target to rebuild the virtual environment.
|
||||
update-env:
|
||||
@echo "Rebuilding virtual environment to match pyproject.toml..."
|
||||
uv rebuild
|
||||
@echo "Environment rebuilt."
|
||||
|
||||
# Target to create a distribution.
|
||||
dist: pip
|
||||
.venv/bin/pip install build
|
||||
.venv/bin/python -m build --wheel
|
||||
dist: version-txt
|
||||
$(PIP) install build
|
||||
$(PYTHON) -m build --wheel
|
||||
@echo "Distribution created (see dist/)."
|
||||
|
||||
# Target to generate documentation
|
||||
gen-docs: pip-dev version-txt
|
||||
.venv/bin/pip install -e .
|
||||
.venv/bin/python ./scripts/generate_config_md.py --output-file docs/_generated/config.md
|
||||
.venv/bin/python ./scripts/generate_openapi_md.py --output-file docs/_generated/openapi.md
|
||||
.venv/bin/python ./scripts/generate_openapi.py --output-file openapi.json
|
||||
gen-docs: version-txt
|
||||
$(PYTHON) ./scripts/generate_config_md.py --output-file docs/_generated/config.md
|
||||
$(PYTHON) ./scripts/generate_openapi_md.py --output-file docs/_generated/openapi.md
|
||||
$(PYTHON) ./scripts/generate_openapi.py --output-file openapi.json
|
||||
@echo "Documentation generated to openapi.json and docs/_generated."
|
||||
|
||||
# Target to build HTML documentation
|
||||
docs: pip-dev
|
||||
.venv/bin/pytest --finalize tests/test_docsphinx.py
|
||||
docs: install
|
||||
$(PYTEST) --finalize tests/test_docsphinx.py
|
||||
@echo "Documentation build to build/docs/html/."
|
||||
|
||||
# Target to read the HTML documentation
|
||||
read-docs:
|
||||
@echo "Read the documentation in your browser"
|
||||
.venv/bin/pytest --finalize tests/test_docsphinx.py
|
||||
.venv/bin/python -m webbrowser build/docs/html/index.html
|
||||
$(PYTEST) --finalize tests/test_docsphinx.py
|
||||
$(PYTHON) -m webbrowser build/docs/html/index.html
|
||||
|
||||
# Clean Python bytecode
|
||||
clean-bytecode:
|
||||
@@ -104,64 +103,66 @@ clean-docs:
|
||||
clean: clean-docs
|
||||
@echo "Cleaning virtual env, distribution and build directories"
|
||||
rm -rf build .venv
|
||||
@echo "Cleaning uv environment"
|
||||
$(UV) clean
|
||||
@echo "Deletion complete."
|
||||
|
||||
run:
|
||||
@echo "Starting EOS production server, please wait..."
|
||||
.venv/bin/python -m akkudoktoreos.server.eos --startup_eosdash true
|
||||
$(PYTHON) -m akkudoktoreos.server.eos --startup_eosdash true
|
||||
|
||||
run-dev:
|
||||
@echo "Starting EOS development server, please wait..."
|
||||
.venv/bin/python -m akkudoktoreos.server.eos --host localhost --port 8503 --log_level DEBUG --startup_eosdash false --reload true
|
||||
$(PYTHON) -m akkudoktoreos.server.eos --host localhost --port 8503 --log_level DEBUG --startup_eosdash false --reload true
|
||||
|
||||
run-dash:
|
||||
@echo "Starting EOSdash production server, please wait..."
|
||||
.venv/bin/python -m akkudoktoreos.server.eosdash
|
||||
$(PYTHON) -m akkudoktoreos.server.eosdash
|
||||
|
||||
run-dash-dev:
|
||||
@echo "Starting EOSdash development server, please wait..."
|
||||
.venv/bin/python -m akkudoktoreos.server.eosdash --host localhost --port 8504 --log_level DEBUG --reload true
|
||||
$(PYTHON) -m akkudoktoreos.server.eosdash --host localhost --port 8504 --log_level DEBUG --reload true
|
||||
|
||||
# Target to setup tests.
|
||||
test-setup: pip-dev
|
||||
test-setup: install
|
||||
@echo "Setup tests"
|
||||
|
||||
# Target to run tests.
|
||||
test:
|
||||
@echo "Running tests..."
|
||||
.venv/bin/pytest -vs --cov src --cov-report term-missing
|
||||
$(PYTEST) -vs --cov src --cov-report term-missing
|
||||
|
||||
# Target to run tests as done by CI on Github.
|
||||
test-ci:
|
||||
@echo "Running tests as CI..."
|
||||
.venv/bin/pytest --finalize --check-config-side-effect -vs --cov src --cov-report term-missing
|
||||
$(PYTEST) --finalize --check-config-side-effect -vs --cov src --cov-report term-missing
|
||||
|
||||
# Target to run tests including the system tests.
|
||||
test-system:
|
||||
@echo "Running tests incl. system tests..."
|
||||
.venv/bin/pytest --system-test -vs --cov src --cov-report term-missing
|
||||
$(PYTEST) --system-test -vs --cov src --cov-report term-missing
|
||||
|
||||
# Target to run all tests.
|
||||
test-finalize:
|
||||
@echo "Running all tests..."
|
||||
.venv/bin/pytest --finalize
|
||||
$(PYTEST) --finalize
|
||||
|
||||
# Target to run tests including the single test optimization with profiling.
|
||||
test-profile:
|
||||
@echo "Running single test optimization with profiling..."
|
||||
.venv/bin/python tests/single_test_optimization.py --profile
|
||||
$(PYTHON) tests/single_test_optimization.py --profile
|
||||
|
||||
# Target to format code.
|
||||
format:
|
||||
.venv/bin/pre-commit run --all-files
|
||||
$(PRECOMMIT) run --all-files
|
||||
|
||||
# Target to trigger gitlint using pre-commit for the latest commit messages
|
||||
# Target to trigger git linting using commitizen for the latest commit messages
|
||||
gitlint:
|
||||
.venv/bin/cz check --rev-range main..HEAD
|
||||
$(COMMITIZEN) check --rev-range main..HEAD
|
||||
|
||||
# Target to format code.
|
||||
mypy:
|
||||
.venv/bin/mypy
|
||||
$(MYPY)
|
||||
|
||||
# Run entire setup on docker
|
||||
docker-run:
|
||||
@@ -180,15 +181,15 @@ docker-build:
|
||||
# Take UPDATE_FILES from GitHub action bump-version.yml
|
||||
UPDATE_FILES := $(shell sed -n 's/^[[:space:]]*UPDATE_FILES[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
|
||||
.github/workflows/bump-version.yml)
|
||||
prepare-version: #pip-dev
|
||||
prepare-version: install
|
||||
@echo "Update version to $(VERSION) from version.py in files $(UPDATE_FILES) and doc"
|
||||
.venv/bin/python ./scripts/update_version.py $(VERSION) $(UPDATE_FILES)
|
||||
.venv/bin/python ./scripts/convert_lightweight_tags.py
|
||||
.venv/bin/python ./scripts/generate_config_md.py --output-file docs/_generated/config.md
|
||||
.venv/bin/python ./scripts/generate_openapi_md.py --output-file docs/_generated/openapi.md
|
||||
.venv/bin/python ./scripts/generate_openapi.py --output-file openapi.json
|
||||
.venv/bin/pytest -vv --finalize tests/test_version.py
|
||||
$(PYTHON) ./scripts/update_version.py $(VERSION) $(UPDATE_FILES)
|
||||
$(PYTHON) ./scripts/convert_lightweight_tags.py
|
||||
$(PYTHON) ./scripts/generate_config_md.py --output-file docs/_generated/config.md
|
||||
$(PYTHON) ./scripts/generate_openapi_md.py --output-file docs/_generated/openapi.md
|
||||
$(PYTHON) ./scripts/generate_openapi.py --output-file openapi.json
|
||||
$(PYTEST) -vv --finalize tests/test_version.py
|
||||
|
||||
test-version:
|
||||
echo "Test version information to be correctly set in all version files"
|
||||
.venv/bin/pytest -vv tests/test_version.py
|
||||
$(PYTEST) -vv tests/test_version.py
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# the root directory (no add-on folder as usual).
|
||||
|
||||
name: "Akkudoktor-EOS"
|
||||
version: "0.2.0.dev58204789"
|
||||
version: "0.2.0.dev2602231150315077"
|
||||
slug: "eos"
|
||||
description: "Akkudoktor-EOS add-on"
|
||||
url: "https://github.com/Akkudoktor-EOS/EOS"
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"version": "0.2.0.dev58204789",
|
||||
"version": "0.2.0.dev2602231150315077",
|
||||
"data_folder_path": "/home/user/.local/share/net.akkudoktoreos.net",
|
||||
"data_output_subpath": "output",
|
||||
"latitude": 52.52,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
| latitude | `EOS_GENERAL__LATITUDE` | `Optional[float]` | `rw` | `52.52` | Latitude in decimal degrees between -90 and 90. North is positive (ISO 19115) (°) |
|
||||
| longitude | `EOS_GENERAL__LONGITUDE` | `Optional[float]` | `rw` | `13.405` | Longitude in decimal degrees within -180 to 180 (°) |
|
||||
| timezone | | `Optional[str]` | `ro` | `N/A` | Computed timezone based on latitude and longitude. |
|
||||
| version | `EOS_GENERAL__VERSION` | `str` | `rw` | `0.2.0.dev58204789` | Configuration file version. Used to check compatibility. |
|
||||
| version | `EOS_GENERAL__VERSION` | `str` | `rw` | `0.2.0.dev2602231150315077` | Configuration file version. Used to check compatibility. |
|
||||
:::
|
||||
<!-- pyml enable line-length -->
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
```json
|
||||
{
|
||||
"general": {
|
||||
"version": "0.2.0.dev58204789",
|
||||
"version": "0.2.0.dev2602231150315077",
|
||||
"data_folder_path": "/home/user/.local/share/net.akkudoktoreos.net",
|
||||
"data_output_subpath": "output",
|
||||
"latitude": 52.52,
|
||||
@@ -46,7 +46,7 @@
|
||||
```json
|
||||
{
|
||||
"general": {
|
||||
"version": "0.2.0.dev58204789",
|
||||
"version": "0.2.0.dev2602231150315077",
|
||||
"data_folder_path": "/home/user/.local/share/net.akkudoktoreos.net",
|
||||
"data_output_subpath": "output",
|
||||
"latitude": 52.52,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Akkudoktor-EOS
|
||||
|
||||
**Version**: `v0.2.0.dev58204789`
|
||||
**Version**: `v0.2.0.dev2602231150315077`
|
||||
|
||||
<!-- pyml disable line-length -->
|
||||
**Description**: This project provides a comprehensive solution for simulating and optimizing an energy system based on renewable energy sources. With a focus on photovoltaic (PV) systems, battery storage (batteries), load management (consumer requirements), heat pumps, electric vehicles, and consideration of electricity price data, this system enables forecasting and optimization of energy flow and costs over a specified period.
|
||||
|
||||
@@ -27,13 +27,13 @@ Example:
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
.venv\Scripts\python src/akkudoktoreos/server/eos.py --log-level DEBUG
|
||||
uv run python src/akkudoktoreos/server/eos.py --log-level DEBUG
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
.venv/bin/python src/akkudoktoreos/server/eos.py --log-level DEBUG
|
||||
uv run python src/akkudoktoreos/server/eos.py --log-level DEBUG
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -80,21 +80,15 @@ This is recommended for developers who want to modify the source code and test c
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
python -m venv .venv
|
||||
.venv\Scripts\pip install --upgrade pip
|
||||
.venv\Scripts\pip install -r requirements-dev.txt
|
||||
.venv\Scripts\pip install build
|
||||
.venv\Scripts\pip install -e .
|
||||
uv run python scripts/get_version.py > version.txt
|
||||
uv sync --extra dev
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install --upgrade pip
|
||||
.venv/bin/pip install -r requirements-dev.txt
|
||||
.venv/bin/pip install build
|
||||
.venv/bin/pip install -e .
|
||||
uv run python scripts/get_version.py > version.txt
|
||||
uv sync --extra dev
|
||||
|
||||
.. tab:: Linux Make
|
||||
|
||||
@@ -103,25 +97,7 @@ This is recommended for developers who want to modify the source code and test c
|
||||
make install
|
||||
```
|
||||
|
||||
### Step 2.2 – Activate the Virtual Environment
|
||||
|
||||
```{eval-rst}
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Windows
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
.venv\Scripts\activate.bat
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
### Step 2.3 - Install pre-commit
|
||||
### Step 2.2 - Install pre-commit
|
||||
|
||||
Our code style and commit message checks use [`pre-commit`](https://pre-commit.com).
|
||||
|
||||
@@ -157,13 +133,13 @@ Make EOS accessible at [http://localhost:8503/docs](http://localhost:8503/docs)
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
python -m akkudoktoreos.server.eos
|
||||
uv run python -m akkudoktoreos.server.eos
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python -m akkudoktoreos.server.eos
|
||||
uv run python -m akkudoktoreos.server.eos
|
||||
|
||||
.. tab:: Linux Make
|
||||
|
||||
@@ -194,13 +170,13 @@ interfere with the EOS server trying to start EOSdash.
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
python -m akkudoktoreos.server.eosdash --host localhost --port 8504 --log_level DEBUG --reload true
|
||||
uv run python -m akkudoktoreos.server.eosdash --host localhost --port 8504 --log_level DEBUG --reload true
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python -m akkudoktoreos.server.eosdash --host localhost --port 8504 --log_level DEBUG --reload true
|
||||
uv run python -m akkudoktoreos.server.eosdash --host localhost --port 8504 --log_level DEBUG --reload true
|
||||
|
||||
.. tab:: Linux Make
|
||||
|
||||
@@ -216,13 +192,13 @@ interfere with the EOS server trying to start EOSdash.
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
python -m akkudoktoreos.server.eos --host localhost --port 8503 --log_level DEBUG --startup_eosdash false --reload true
|
||||
uv run python -m akkudoktoreos.server.eos --host localhost --port 8503 --log_level DEBUG --startup_eosdash false --reload true
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python -m akkudoktoreos.server.eos --host localhost --port 8503 --log_level DEBUG --startup_eosdash false --reload true
|
||||
uv run python -m akkudoktoreos.server.eos --host localhost --port 8503 --log_level DEBUG --startup_eosdash false --reload true
|
||||
|
||||
.. tab:: Linux Make
|
||||
|
||||
@@ -381,13 +357,13 @@ At a minimum, you should run the module tests:
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
pytest -vs --cov src --cov-report term-missing
|
||||
uv run pytest -vs --cov src --cov-report term-missing
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest -vs --cov src --cov-report term-missing
|
||||
uv run pytest -vs --cov src --cov-report term-missing
|
||||
|
||||
.. tab:: Linux Make
|
||||
|
||||
@@ -413,13 +389,13 @@ resources:
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
pytest --system-test -vs --cov src --cov-report term-missing
|
||||
uv run pytest --system-test -vs --cov src --cov-report term-missing
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --system-test -vs --cov src --cov-report term-missing
|
||||
uv run pytest --system-test -vs --cov src --cov-report term-missing
|
||||
|
||||
.. tab:: Linux Make
|
||||
|
||||
@@ -437,13 +413,13 @@ To do profiling use:
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
python tests/single_test_optimization.py --profile
|
||||
uv run python tests/single_test_optimization.py --profile
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python tests/single_test_optimization.py --profile
|
||||
uv run python tests/single_test_optimization.py --profile
|
||||
|
||||
.. tab:: Linux Make
|
||||
|
||||
@@ -575,13 +551,13 @@ Ensure your changes do not break existing functionality:
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
pytest -vs --cov src --cov-report term-missing
|
||||
uv run pytest -vs --cov src --cov-report term-missing
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest -vs --cov src --cov-report term-missing
|
||||
uv run pytest -vs --cov src --cov-report term-missing
|
||||
|
||||
.. tab:: Linux Make
|
||||
|
||||
|
||||
@@ -25,8 +25,8 @@ Before installing, ensure you have the following:
|
||||
|
||||
### For Source / Release Installation (M1/M2)
|
||||
|
||||
- Python 3.10 or higher
|
||||
- pip
|
||||
- Python 3.11+
|
||||
- uv (recommended)
|
||||
- Git (only for source)
|
||||
- Tar/Zip (for release package)
|
||||
|
||||
@@ -52,6 +52,24 @@ Akkudoktor-EOS is a [Home Assistant add-on](https://www.home-assistant.io/addons
|
||||
have access to add-ons.
|
||||
:::
|
||||
|
||||
## Install uv (one-time setup)
|
||||
|
||||
```{eval-rst}
|
||||
.. tabs::
|
||||
|
||||
.. tab:: Windows
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
irm https://astral.sh/uv/install.ps1 | iex
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
## Installation from Source (GitHub) (M1)
|
||||
|
||||
Recommended for developers or users wanting the latest updates.
|
||||
@@ -85,17 +103,13 @@ Recommended for developers or users wanting the latest updates.
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
python -m venv .venv
|
||||
.venv\Scripts\pip install -r requirements.txt
|
||||
.venv\Scripts\pip install -e .
|
||||
uv sync --extra dev
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python -m venv .venv
|
||||
.venv/bin/pip install -r requirements.txt
|
||||
.venv/bin/pip install -e .
|
||||
uv sync --extra dev
|
||||
|
||||
```
|
||||
|
||||
@@ -108,13 +122,13 @@ Recommended for developers or users wanting the latest updates.
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
.venv\Scripts\python -m akkudoktoreos.server.eos
|
||||
uv run python -m akkudoktoreos.server.eos
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
.venv/bin/python -m akkudoktoreos.server.eos
|
||||
uv run python -m akkudoktoreos.server.eos
|
||||
|
||||
```
|
||||
|
||||
@@ -134,13 +148,13 @@ stage of the installation provide appropriate IP addresses on startup.
|
||||
|
||||
.. code-block:: powershell
|
||||
|
||||
.venv\Scripts\python -m akkudoktoreos.server.eos --host 0.0.0.0 --eosdash-host 0.0.0.0
|
||||
uv run python -m akkudoktoreos.server.eos --host 0.0.0.0 --eosdash-host 0.0.0.0
|
||||
|
||||
.. tab:: Linux
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
.venv/bin/python -m akkudoktoreos.server.eos --host 0.0.0.0 --eosdash-host 0.0.0.0
|
||||
uv run python -m akkudoktoreos.server.eos --host 0.0.0.0 --eosdash-host 0.0.0.0
|
||||
|
||||
```
|
||||
<!-- pyml enable line-length -->
|
||||
|
||||
@@ -51,7 +51,7 @@ git checkout v0.1.0
|
||||
Then reinstall dependencies:
|
||||
|
||||
```bash
|
||||
.venv/bin/pip install -r requirements.txt --upgrade
|
||||
uv sync
|
||||
```
|
||||
|
||||
#### Release package (M2)
|
||||
@@ -62,7 +62,7 @@ Refer to **Method 2** in the [Installation Guideline](install-page).
|
||||
### 3) Restart EOS (M1/M2)
|
||||
|
||||
```bash
|
||||
.venv/bin/python -m akkudoktoreos.server.eos
|
||||
uv run python -m akkudoktoreos.server.eos
|
||||
```
|
||||
|
||||
### 4) Restore configuration (optional) (M1/M2)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"name": "Apache 2.0",
|
||||
"url": "https://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
},
|
||||
"version": "v0.2.0.dev58204789"
|
||||
"version": "v0.2.0.dev2602231150315077"
|
||||
},
|
||||
"paths": {
|
||||
"/v1/admin/cache/clear": {
|
||||
@@ -4451,7 +4451,7 @@
|
||||
"type": "string",
|
||||
"title": "Version",
|
||||
"description": "Configuration file version. Used to check compatibility.",
|
||||
"default": "0.2.0.dev58204789"
|
||||
"default": "0.2.0.dev2602231150315077"
|
||||
},
|
||||
"data_folder_path": {
|
||||
"type": "string",
|
||||
@@ -4514,7 +4514,7 @@
|
||||
"type": "string",
|
||||
"title": "Version",
|
||||
"description": "Configuration file version. Used to check compatibility.",
|
||||
"default": "0.2.0.dev58204789"
|
||||
"default": "0.2.0.dev2602231150315077"
|
||||
},
|
||||
"data_folder_path": {
|
||||
"type": "string",
|
||||
|
||||
@@ -13,18 +13,87 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
dependencies = [
|
||||
"babel==2.18.0",
|
||||
"beautifulsoup4==4.14.3",
|
||||
"cachebox==5.2.2",
|
||||
"numpy==2.4.2",
|
||||
"numpydantic==1.7.0",
|
||||
"matplotlib==3.10.8",
|
||||
"contourpy==1.3.3",
|
||||
"fastapi[standard-no-fastapi-cloud-cli]==0.128.0",
|
||||
"fastapi_cli==0.0.21",
|
||||
"rich-toolkit==0.19.4",
|
||||
"python-fasthtml==0.12.47",
|
||||
"MonsterUI==1.0.43",
|
||||
"markdown-it-py==4.0.0",
|
||||
"mdit-py-plugins==0.5.0",
|
||||
"bokeh==3.8.2",
|
||||
"uvicorn==0.40.0",
|
||||
"scipy==1.17.0",
|
||||
"tzfpy==1.1.1",
|
||||
"deap==1.4.3",
|
||||
"requests==2.32.5",
|
||||
"pandas==3.0.0",
|
||||
"pendulum==3.2.0",
|
||||
"platformdirs==4.9.2",
|
||||
"psutil==7.2.2",
|
||||
"pvlib==0.15.0",
|
||||
"pydantic==2.12.5",
|
||||
"pydantic_extra_types==2.11.0",
|
||||
"statsmodels==0.14.6",
|
||||
"pydantic-settings==2.13.1",
|
||||
"linkify-it-py==2.0.3",
|
||||
"loguru==0.7.3",
|
||||
"lmdb==1.7.5",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
# Pre-commit framework - basic package requirements handled by pre-commit itself
|
||||
# - pre-commit-hooks
|
||||
# - isort
|
||||
# - ruff
|
||||
# - mypy (mirrors-mypy) - sync with requirements-dev.txt (if on pypi)
|
||||
# - pymarkdown
|
||||
# - commitizen - sync with requirements-dev.txt (if on pypi)
|
||||
#
|
||||
# !!! Sync .pre-commit-config.yaml with these dependencies !!!
|
||||
"pre-commit==4.5.1",
|
||||
"mypy==1.19.1",
|
||||
"types-requests==2.32.4.20260107", # for mypy
|
||||
"pandas-stubs==3.0.0.260204", # for mypy
|
||||
"tokenize-rt==6.2.0", # for mypy
|
||||
"types-docutils==0.22.3.20251115", # for mypy
|
||||
"types-PyYaml==6.0.12.20250915", # for mypy
|
||||
"commitizen==4.13.8",
|
||||
"deprecated==1.3.1", # for commitizen
|
||||
|
||||
# Sphinx
|
||||
"sphinx==9.0.4",
|
||||
"sphinx_rtd_theme==3.1.0",
|
||||
"sphinx-tabs==3.4.7",
|
||||
"GitPython==3.1.46",
|
||||
"myst-parser==5.0.0",
|
||||
"docutils==0.21.2",
|
||||
|
||||
# Pytest
|
||||
"pytest==9.0.2",
|
||||
"pytest-asyncio==1.3.0",
|
||||
"pytest-cov==7.0.0",
|
||||
"pytest-xprocess==1.0.2",
|
||||
"coverage==7.13.4",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/Akkudoktor-EOS/EOS"
|
||||
Issues = "https://github.com/Akkudoktor-EOS/EOS/issues"
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0"]
|
||||
requires = ["setuptools>=77", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools.dynamic]
|
||||
dependencies = {file = ["requirements.txt"]}
|
||||
optional-dependencies = {dev = { file = ["requirements-dev.txt"] }}
|
||||
# version.txt must be generated
|
||||
version = { file = "version.txt" }
|
||||
|
||||
@@ -92,6 +161,7 @@ markers = [
|
||||
]
|
||||
|
||||
[tool.mypy]
|
||||
mypy_path= "src"
|
||||
files = ["src", "tests"]
|
||||
exclude = "class_soc_calc\\.py$"
|
||||
check_untyped_defs = true
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
-r requirements.txt
|
||||
|
||||
# Pre-commit framework - basic package requirements handled by pre-commit itself
|
||||
# - pre-commit-hooks
|
||||
# - isort
|
||||
# - ruff
|
||||
# - mypy (mirrors-mypy) - sync with requirements-dev.txt (if on pypi)
|
||||
# - pymarkdown
|
||||
# - commitizen - sync with requirements-dev.txt (if on pypi)
|
||||
#
|
||||
# !!! Sync .pre-commit-config.yaml and requirements-dev.txt !!!
|
||||
pre-commit==4.5.1
|
||||
mypy==1.19.1
|
||||
types-requests==2.32.4.20260107 # for mypy
|
||||
pandas-stubs==3.0.0.260204 # for mypy
|
||||
tokenize-rt==6.2.0 # for mypy
|
||||
types-docutils==0.22.3.20251115 # for mypy
|
||||
types-PyYaml==6.0.12.20250915 # for mypy
|
||||
commitizen==4.13.8
|
||||
deprecated==1.3.1 # for commitizen
|
||||
|
||||
# Sphinx
|
||||
sphinx==9.0.4
|
||||
sphinx_rtd_theme==3.1.0
|
||||
sphinx-tabs==3.4.7
|
||||
GitPython==3.1.46
|
||||
myst-parser==5.0.0
|
||||
docutils==0.21.2
|
||||
|
||||
# Pytest
|
||||
pytest==9.0.2
|
||||
pytest-asyncio==1.3.0
|
||||
pytest-cov==7.0.0
|
||||
pytest-xprocess==1.0.2
|
||||
coverage==7.13.4
|
||||
@@ -1,32 +0,0 @@
|
||||
babel==2.18.0
|
||||
beautifulsoup4==4.14.3
|
||||
cachebox==5.2.2
|
||||
numpy==2.4.2
|
||||
numpydantic==1.7.0
|
||||
matplotlib==3.10.8
|
||||
contourpy==1.3.3
|
||||
fastapi[standard-no-fastapi-cloud-cli]==0.128.0
|
||||
fastapi_cli==0.0.21
|
||||
rich-toolkit==0.19.4
|
||||
python-fasthtml==0.12.47
|
||||
MonsterUI==1.0.43
|
||||
markdown-it-py==4.0.0
|
||||
mdit-py-plugins==0.5.0
|
||||
bokeh==3.8.2
|
||||
uvicorn==0.40.0
|
||||
scipy==1.17.0
|
||||
tzfpy==1.1.1
|
||||
deap==1.4.3
|
||||
requests==2.32.5
|
||||
pandas==3.0.0
|
||||
pendulum==3.2.0
|
||||
platformdirs==4.9.2
|
||||
psutil==7.2.2
|
||||
pvlib==0.15.0
|
||||
pydantic==2.12.5
|
||||
pydantic_extra_types==2.11.0
|
||||
statsmodels==0.14.6
|
||||
pydantic-settings==2.13.1
|
||||
linkify-it-py==2.0.3
|
||||
loguru==0.7.3
|
||||
lmdb==1.7.5
|
||||
42
scripts/cz_check_branch.py
Normal file → Executable file
42
scripts/cz_check_branch.py
Normal file → Executable file
@@ -11,16 +11,40 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def find_cz() -> str:
|
||||
venv = os.getenv("VIRTUAL_ENV")
|
||||
paths = [Path(venv)] if venv else []
|
||||
paths.append(Path.cwd() / ".venv")
|
||||
def find_cz() -> list[str]:
|
||||
"""Return command to invoke Commitizen via virtualenv or globally."""
|
||||
candidates = []
|
||||
|
||||
for base in paths:
|
||||
cz = base / ("Scripts" if os.name == "nt" else "bin") / ("cz.exe" if os.name == "nt" else "cz")
|
||||
if cz.exists():
|
||||
return str(cz)
|
||||
return "cz"
|
||||
# 1️⃣ Currently active virtualenv
|
||||
venv = os.getenv("VIRTUAL_ENV")
|
||||
if venv:
|
||||
candidates.append(Path(venv))
|
||||
|
||||
# 2️⃣ uv-managed virtualenv
|
||||
uv_venv = Path(".uv") / "venv"
|
||||
if uv_venv.exists():
|
||||
candidates.append(uv_venv)
|
||||
|
||||
# 3️⃣ traditional .venv
|
||||
dot_venv = Path(".venv")
|
||||
if dot_venv.exists():
|
||||
candidates.append(dot_venv)
|
||||
|
||||
# Check each candidate for Commitizen binary
|
||||
for base in candidates:
|
||||
cz_path = base / ("Scripts" if os.name == "nt" else "bin") / ("cz.exe" if os.name == "nt" else "cz")
|
||||
if cz_path.exists():
|
||||
return [str(cz_path)]
|
||||
|
||||
# 4️⃣ fallback to uv run cz
|
||||
try:
|
||||
subprocess.run(["uv", "run", "cz", "--version"], check=True, stdout=subprocess.DEVNULL)
|
||||
return ["uv", "run", "cz"]
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
# 5️⃣ fallback to system cz
|
||||
return ["cz"]
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
53
scripts/cz_check_commit_message.py
Normal file → Executable file
53
scripts/cz_check_commit_message.py
Normal file → Executable file
@@ -2,6 +2,12 @@
|
||||
"""Commitizen commit message checker that is .venv aware.
|
||||
|
||||
Works for commits with -m or commit message file.
|
||||
|
||||
Cross-platform + uv/.venv aware:
|
||||
- Prefers activated virtual environment (VIRTUAL_ENV)
|
||||
- Falls back to uv-managed .uv/venv
|
||||
- Falls back to .venv
|
||||
- Falls back to global cz
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -10,19 +16,40 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def find_cz() -> str:
|
||||
"""Find Commitizen executable, preferring virtualenv."""
|
||||
venv = os.getenv("VIRTUAL_ENV")
|
||||
paths = []
|
||||
if venv:
|
||||
paths.append(Path(venv))
|
||||
paths.append(Path.cwd() / ".venv")
|
||||
def find_cz() -> list[str]:
|
||||
"""Return command to invoke Commitizen via virtualenv or globally."""
|
||||
candidates = []
|
||||
|
||||
for base in paths:
|
||||
cz = base / ("Scripts" if os.name == "nt" else "bin") / ("cz.exe" if os.name == "nt" else "cz")
|
||||
if cz.exists():
|
||||
return str(cz)
|
||||
return "cz"
|
||||
# 1️⃣ Currently active virtualenv
|
||||
venv = os.getenv("VIRTUAL_ENV")
|
||||
if venv:
|
||||
candidates.append(Path(venv))
|
||||
|
||||
# 2️⃣ uv-managed virtualenv
|
||||
uv_venv = Path(".uv") / "venv"
|
||||
if uv_venv.exists():
|
||||
candidates.append(uv_venv)
|
||||
|
||||
# 3️⃣ traditional .venv
|
||||
dot_venv = Path(".venv")
|
||||
if dot_venv.exists():
|
||||
candidates.append(dot_venv)
|
||||
|
||||
# Check each candidate for Commitizen binary
|
||||
for base in candidates:
|
||||
cz_path = base / ("Scripts" if os.name == "nt" else "bin") / ("cz.exe" if os.name == "nt" else "cz")
|
||||
if cz_path.exists():
|
||||
return [str(cz_path)]
|
||||
|
||||
# 4️⃣ fallback to uv run cz
|
||||
try:
|
||||
subprocess.run(["uv", "run", "cz", "--version"], check=True, stdout=subprocess.DEVNULL)
|
||||
return ["uv", "run", "cz"]
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
# 5️⃣ fallback to system cz
|
||||
return ["cz"]
|
||||
|
||||
|
||||
def main():
|
||||
@@ -47,7 +74,7 @@ def main():
|
||||
print(f"🔍 Checking commit message using {cz}...")
|
||||
|
||||
try:
|
||||
subprocess.check_call([cz, "check", "--commit-msg-file", commit_msg_file])
|
||||
subprocess.check_call(cz + ["check", "--commit-msg-file", commit_msg_file])
|
||||
print("✅ Commit message follows Commitizen convention.")
|
||||
return 0
|
||||
except subprocess.CalledProcessError:
|
||||
|
||||
58
scripts/cz_check_new_commits.py
Normal file → Executable file
58
scripts/cz_check_new_commits.py
Normal file → Executable file
@@ -1,10 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pre-push hook: Commitizen check for *new commits only*.
|
||||
|
||||
Cross-platform + virtualenv-aware:
|
||||
Cross-platform + uv/.venv aware:
|
||||
- Prefers activated virtual environment (VIRTUAL_ENV)
|
||||
- Falls back to ./.venv if found
|
||||
- Falls back to global cz otherwise
|
||||
- Falls back to uv-managed .uv/venv
|
||||
- Falls back to .venv
|
||||
- Falls back to global cz
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -13,23 +14,40 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def find_cz_executable() -> str:
|
||||
"""Return path to Commitizen executable, preferring virtual environments."""
|
||||
# 1️⃣ Active virtual environment (if running inside one)
|
||||
venv_env = os.getenv("VIRTUAL_ENV")
|
||||
if venv_env:
|
||||
cz_path = Path(venv_env) / ("Scripts" if os.name == "nt" else "bin") / ("cz.exe" if os.name == "nt" else "cz")
|
||||
def find_cz() -> list[str]:
|
||||
"""Return command to invoke Commitizen via virtualenv or globally."""
|
||||
candidates = []
|
||||
|
||||
# 1️⃣ Currently active virtualenv
|
||||
venv = os.getenv("VIRTUAL_ENV")
|
||||
if venv:
|
||||
candidates.append(Path(venv))
|
||||
|
||||
# 2️⃣ uv-managed virtualenv
|
||||
uv_venv = Path(".uv") / "venv"
|
||||
if uv_venv.exists():
|
||||
candidates.append(uv_venv)
|
||||
|
||||
# 3️⃣ traditional .venv
|
||||
dot_venv = Path(".venv")
|
||||
if dot_venv.exists():
|
||||
candidates.append(dot_venv)
|
||||
|
||||
# Check each candidate for Commitizen binary
|
||||
for base in candidates:
|
||||
cz_path = base / ("Scripts" if os.name == "nt" else "bin") / ("cz.exe" if os.name == "nt" else "cz")
|
||||
if cz_path.exists():
|
||||
return str(cz_path)
|
||||
return [str(cz_path)]
|
||||
|
||||
# 2️⃣ Local .venv in repo root
|
||||
repo_venv = Path.cwd() / ".venv"
|
||||
cz_path = repo_venv / ("Scripts" if os.name == "nt" else "bin") / ("cz.exe" if os.name == "nt" else "cz")
|
||||
if cz_path.exists():
|
||||
return str(cz_path)
|
||||
# 4️⃣ fallback to uv run cz
|
||||
try:
|
||||
subprocess.run(["uv", "run", "cz", "--version"], check=True, stdout=subprocess.DEVNULL)
|
||||
return ["uv", "run", "cz"]
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
# 3️⃣ Global fallback
|
||||
return "cz"
|
||||
# 5️⃣ fallback to system cz
|
||||
return ["cz"]
|
||||
|
||||
|
||||
def get_merge_base() -> str | None:
|
||||
@@ -48,17 +66,17 @@ def get_merge_base() -> str | None:
|
||||
|
||||
|
||||
def main() -> int:
|
||||
cz = find_cz_executable()
|
||||
cz = find_cz()
|
||||
base = get_merge_base()
|
||||
|
||||
if not base:
|
||||
print("⚠️ No upstream found; skipping Commitizen check for new commits.")
|
||||
print("⚠️ No upstream found; skipping Commitizen check {cz} for new commits.")
|
||||
return 0
|
||||
|
||||
print(f"🔍 Using {cz} to check new commits from {base}..HEAD ...")
|
||||
|
||||
try:
|
||||
subprocess.check_call([cz, "check", "--rev-range", f"{base}..HEAD"])
|
||||
subprocess.check_call(cz + ["check", "--rev-range", f"{base}..HEAD"])
|
||||
print("✅ All new commits follow Commitizen conventions.")
|
||||
return 0
|
||||
except subprocess.CalledProcessError as e:
|
||||
|
||||
@@ -14,6 +14,11 @@ from loguru import logger
|
||||
from pydantic.fields import ComputedFieldInfo, FieldInfo
|
||||
from pydantic_core import PydanticUndefined
|
||||
|
||||
# Add the src directory to sys.path so import akkudoktoreos works in all cases
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
SRC_DIR = PROJECT_ROOT / "src"
|
||||
sys.path.insert(0, str(SRC_DIR))
|
||||
|
||||
from akkudoktoreos.config.config import ConfigEOS, default_data_folder_path
|
||||
from akkudoktoreos.core.coreabc import get_config, singletons_init
|
||||
from akkudoktoreos.core.pydantic import PydanticBaseModel
|
||||
|
||||
@@ -18,6 +18,12 @@ import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add the src directory to sys.path so import akkudoktoreos works in all cases
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
SRC_DIR = PROJECT_ROOT / "src"
|
||||
sys.path.insert(0, str(SRC_DIR))
|
||||
|
||||
from akkudoktoreos.core.coreabc import get_config
|
||||
from akkudoktoreos.server.eos import app
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
#!.venv/bin/python
|
||||
"""Get version of EOS"""
|
||||
|
||||
import sys
|
||||
|
||||
if sys.version_info < (3, 11):
|
||||
print(
|
||||
f"ERROR: Python >=3.11 is required. Found {sys.version_info.major}.{sys.version_info.minor}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Add the src directory to sys.path so Sphinx can import akkudoktoreos
|
||||
# Add the src directory to sys.path so import akkudoktoreos works in all cases
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
SRC_DIR = PROJECT_ROOT / "src"
|
||||
sys.path.insert(0, str(SRC_DIR))
|
||||
|
||||
from akkudoktoreos.core.version import __version__
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(__version__)
|
||||
# Import here to prevent mypy to execute the functions that evaluate __version__
|
||||
try:
|
||||
from akkudoktoreos.core.version import __version__
|
||||
version = __version__
|
||||
except Exception:
|
||||
# This may be a first time install
|
||||
raise RuntimeError("Can not find out version!")
|
||||
version = "0.0.0"
|
||||
|
||||
print(version)
|
||||
|
||||
@@ -11,6 +11,12 @@ import sys
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
# Add the src directory to sys.path so import akkudoktoreos works in all cases
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
SRC_DIR = PROJECT_ROOT / "src"
|
||||
sys.path.insert(0, str(SRC_DIR))
|
||||
|
||||
|
||||
# --- Patterns to match version strings ---
|
||||
VERSION_PATTERNS = [
|
||||
# Python: __version__ = "1.2.3"
|
||||
@@ -88,6 +94,21 @@ def update_version_in_file(file_path: Path, new_version: str) -> bool:
|
||||
return file_would_be_updated
|
||||
|
||||
|
||||
def update_version_date_file() -> str:
|
||||
"""Write current version date to __version_date__.py"""
|
||||
from akkudoktoreos.core.version import VERSION_DATE_FILE, _version_date_hash
|
||||
|
||||
version_date, _ = _version_date_hash()
|
||||
version_date_str = version_date.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
content = f'VERSION_DATE = "{version_date_str}"\n'
|
||||
|
||||
VERSION_DATE_FILE.write_text(content)
|
||||
|
||||
print(f"Updated {VERSION_DATE_FILE} with UTC date {version_date_str}")
|
||||
|
||||
return str(VERSION_DATE_FILE)
|
||||
|
||||
|
||||
def main(version: str, files: List[str]):
|
||||
if not version:
|
||||
raise ValueError("No version provided")
|
||||
@@ -103,6 +124,8 @@ def main(version: str, files: List[str]):
|
||||
if update_version_in_file(path, version):
|
||||
updated_files.append(str(path))
|
||||
|
||||
updated_files.append(update_version_date_file())
|
||||
|
||||
if updated_files:
|
||||
print(f"Updated files: {', '.join(updated_files)}")
|
||||
else:
|
||||
|
||||
1
src/akkudoktoreos/core/_version_date.py
Normal file
1
src/akkudoktoreos/core/_version_date.py
Normal file
@@ -0,0 +1 @@
|
||||
VERSION_DATE = "2026-02-23T11:41:01Z"
|
||||
@@ -1,8 +1,21 @@
|
||||
"""Version information for akkudoktoreos."""
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# version.py may be used __BEFORE__ the dependencies are installed.
|
||||
# Use only standard python libraries
|
||||
#
|
||||
# Several warnings/ erros are silenced because they are
|
||||
# non-critical in this context - see noqa.
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
import hashlib
|
||||
import re
|
||||
import subprocess
|
||||
from dataclasses import dataclass
|
||||
from datetime import ( # Don't use akkudoktoreos.utils.datetimeutil (-> pendulum)
|
||||
datetime,
|
||||
timezone,
|
||||
)
|
||||
from fnmatch import fnmatch
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
@@ -14,14 +27,18 @@ VERSION_BASE = "0.2.0.dev"
|
||||
# Project hash of relevant files
|
||||
HASH_EOS = ""
|
||||
|
||||
# Number of digits to append to .dev to identify a development version
|
||||
VERSION_DEV_PRECISION = 8
|
||||
# Number of hash digits to append to .dev to identify a development version
|
||||
VERSION_DEV_HASH_PRECISION = 8
|
||||
|
||||
# File to hold the date of the latest commit.
|
||||
VERSION_DATE_FILE = Path(__file__).parent / "_version_date.py"
|
||||
|
||||
# Hashing configuration
|
||||
DIR_PACKAGE_ROOT = Path(__file__).resolve().parent.parent
|
||||
ALLOWED_SUFFIXES: set[str] = {".py", ".md", ".json"}
|
||||
EXCLUDED_DIR_PATTERNS: set[str] = {"*_autosum", "*__pycache__", "*_generated"}
|
||||
EXCLUDED_FILES: set[Path] = set()
|
||||
# Excluded from hash/date calculation to avoid self-referencing loop
|
||||
EXCLUDED_FILES: set[Path] = {VERSION_DATE_FILE}
|
||||
|
||||
|
||||
# ------------------------------
|
||||
@@ -169,18 +186,78 @@ def hash_tree(
|
||||
return digest, files
|
||||
|
||||
|
||||
def newest_commit_or_dirty_datetime(files: list[Path]) -> datetime:
|
||||
"""Return the newest relevant datetime for the given files in UTC.
|
||||
|
||||
Checks for uncommitted changes among the given files first. If any file
|
||||
has staged or unstaged modifications, the current UTC datetime is returned
|
||||
to reflect that the working tree is ahead of the last commit. Otherwise,
|
||||
the datetime of the most recent git commit touching any of the given files
|
||||
is returned. If git is unavailable (e.g. after pip install), falls back to
|
||||
reading the date from VERSION_DATE_FILE.
|
||||
|
||||
Args:
|
||||
files: List of file paths to check for changes and commit history.
|
||||
|
||||
Returns:
|
||||
The current UTC datetime if any file has uncommitted changes, otherwise
|
||||
the UTC datetime of the most recent commit touching any of the given
|
||||
files, or the datetime stored in VERSION_DATE_FILE as a last resort.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If no version date can be determined from any source.
|
||||
"""
|
||||
# Check for uncommitted changes among watched files
|
||||
try:
|
||||
status_result = subprocess.run( # noqa: S603
|
||||
["git", "status", "--porcelain", "--"] + [str(f) for f in files],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
cwd=DIR_PACKAGE_ROOT,
|
||||
)
|
||||
if status_result.stdout.strip():
|
||||
return datetime.now(tz=timezone.utc)
|
||||
|
||||
result = subprocess.run( # noqa: S603
|
||||
["git", "log", "-1", "--format=%ct", "--"] + [str(f) for f in files],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
cwd=DIR_PACKAGE_ROOT,
|
||||
)
|
||||
ts = result.stdout.strip()
|
||||
if ts:
|
||||
return datetime.fromtimestamp(int(ts), tz=timezone.utc)
|
||||
except (subprocess.CalledProcessError, FileNotFoundError, ValueError): # noqa: S110
|
||||
pass
|
||||
|
||||
# Fallback to VERSION_DATE_FILE
|
||||
if VERSION_DATE_FILE.exists():
|
||||
try:
|
||||
ns: dict[str, str] = {}
|
||||
exec(VERSION_DATE_FILE.read_text(), {}, ns) # noqa: S102
|
||||
date_str = ns.get("VERSION_DATE")
|
||||
if date_str:
|
||||
return datetime.fromisoformat(date_str).astimezone(timezone.utc)
|
||||
except Exception: # noqa: S110
|
||||
pass
|
||||
|
||||
raise RuntimeError("This should not happen - No version date info available")
|
||||
|
||||
|
||||
# ---------------------
|
||||
# Version hash function
|
||||
# ---------------------
|
||||
|
||||
|
||||
def _version_hash() -> str:
|
||||
"""Calculate project hash.
|
||||
def _version_date_hash() -> tuple[datetime, str]:
|
||||
"""Calculate project date and hash.
|
||||
|
||||
Only package files in src/akkudoktoreos can be hashed to make it work also for packages.
|
||||
|
||||
Returns:
|
||||
SHA256 hash of the project files
|
||||
lattest commit date and SHA256 hash of the project files
|
||||
"""
|
||||
if not str(DIR_PACKAGE_ROOT).endswith("src/akkudoktoreos"):
|
||||
error_msg = f"DIR_PACKAGE_ROOT does not end with src/akkudoktoreos: {DIR_PACKAGE_ROOT}"
|
||||
@@ -197,23 +274,29 @@ def _version_hash() -> str:
|
||||
excluded_files=EXCLUDED_FILES,
|
||||
)
|
||||
|
||||
return hash_digest
|
||||
date = newest_commit_or_dirty_datetime(hashed_files)
|
||||
|
||||
return date, hash_digest
|
||||
|
||||
|
||||
def _version_calculate() -> str:
|
||||
"""Calculate the full version string.
|
||||
|
||||
For release versions: "x.y.z"
|
||||
For dev versions: "x.y.z.dev<hash>"
|
||||
For dev versions: "x.y.z.dev<date><hash>"
|
||||
|
||||
Returns:
|
||||
Full version string
|
||||
"""
|
||||
if VERSION_BASE.endswith(".dev"):
|
||||
# After dev only digits are allowed - convert hexdigest to digits
|
||||
hash_value = int(_version_hash(), 16)
|
||||
hash_digits = str(hash_value % (10**VERSION_DEV_PRECISION)).zfill(VERSION_DEV_PRECISION)
|
||||
return f"{VERSION_BASE}{hash_digits}"
|
||||
version_date, version_hash = _version_date_hash()
|
||||
hash_value = int(version_hash, 16)
|
||||
hash_digits = str(hash_value % (10**VERSION_DEV_HASH_PRECISION)).zfill(
|
||||
VERSION_DEV_HASH_PRECISION
|
||||
)
|
||||
date_digits = version_date.strftime("%y%m%d%H") if version_date else "00000000"
|
||||
return f"{VERSION_BASE}{date_digits}{hash_digits}"
|
||||
else:
|
||||
# Release version - use base as-is
|
||||
return VERSION_BASE
|
||||
@@ -234,10 +317,10 @@ __version__ = _version_calculate()
|
||||
# Regular expression to split the version string into pieces
|
||||
VERSION_RE = re.compile(
|
||||
r"""
|
||||
^(?P<base>\d+\.\d+\.\d+) # x.y.z
|
||||
(?:\. # .dev<hash> starts here
|
||||
(?P<dev>dev) # literal 'dev'
|
||||
(?P<hash>[a-f0-9]+)? # optional <hash> (hex digits)
|
||||
^(?P<base>\d+\.\d+\.\d+) # x.y.z
|
||||
(?:\.dev # literal '.dev' for development versions
|
||||
(?P<date>\d{8}) # 8-digit date: YYMMDDHH
|
||||
(?P<hash>[a-f0-9]+)? # hex hash
|
||||
)?
|
||||
$
|
||||
""",
|
||||
@@ -251,7 +334,7 @@ def version() -> dict[str, Optional[str]]:
|
||||
The version string shall be of the form:
|
||||
x.y.z
|
||||
x.y.z.dev
|
||||
x.y.z.dev<HASH>
|
||||
x.y.z.dev<date><hash>
|
||||
|
||||
Returns:
|
||||
.. code-block:: python
|
||||
|
||||
@@ -24,7 +24,7 @@ from xprocess import ProcessStarter, XProcess
|
||||
|
||||
from akkudoktoreos.config.config import ConfigEOS
|
||||
from akkudoktoreos.core.coreabc import get_config, get_prediction, singletons_init
|
||||
from akkudoktoreos.core.version import _version_hash, version
|
||||
from akkudoktoreos.core.version import _version_date_hash, version
|
||||
from akkudoktoreos.server.server import get_default_host
|
||||
|
||||
# -----------------------------------------------
|
||||
@@ -510,26 +510,41 @@ def server_base(
|
||||
if extra_env:
|
||||
env.update(extra_env)
|
||||
|
||||
# assure server to be installed
|
||||
try:
|
||||
project_dir = Path(__file__).parent.parent
|
||||
subprocess.run(
|
||||
[sys.executable, "-c", "import", "akkudoktoreos.server.eos"],
|
||||
check=True,
|
||||
env=env,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=project_dir,
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", "-e", str(project_dir)],
|
||||
env=env,
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=project_dir,
|
||||
)
|
||||
project_dir = Path(__file__).parent.parent
|
||||
|
||||
@staticmethod
|
||||
def _ensure_package(env: dict, project_dir: Path) -> None:
|
||||
"""Ensure 'akkudoktoreos' is importable in this Python environment."""
|
||||
try:
|
||||
subprocess.run(
|
||||
[sys.executable, "-c", "import akkudoktoreos.server.eos"],
|
||||
check=True,
|
||||
env=env,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=project_dir,
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
# If inside a normal venv or uv-managed environment, install in place
|
||||
uv_root = os.getenv("UV_VENV_ROOT") # set by uv if active
|
||||
venv_active = hasattr(sys, "real_prefix") or sys.prefix != sys.base_prefix
|
||||
if uv_root or venv_active:
|
||||
print("Package not found, installing in current environment...")
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "pip", "install", "-e", str(project_dir)],
|
||||
check=True,
|
||||
env=env,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=project_dir,
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Cannot import 'akkudoktoreos.server.eos' in the system Python. "
|
||||
"Activate a virtual environment first."
|
||||
)
|
||||
|
||||
_ensure_package(env, project_dir)
|
||||
|
||||
# Set command to start server process
|
||||
args = [
|
||||
@@ -644,7 +659,7 @@ def version_and_hash() -> Generator[dict[str, Optional[str]], None, None]:
|
||||
Runs once per test session.
|
||||
"""
|
||||
info = version()
|
||||
info["hash_current"] = _version_hash()
|
||||
_, info["hash_current"] = _version_date_hash()
|
||||
|
||||
yield info
|
||||
|
||||
|
||||
@@ -14,10 +14,11 @@ from akkudoktoreos.core.version import (
|
||||
EXCLUDED_FILES,
|
||||
HashConfig,
|
||||
_version_calculate,
|
||||
_version_hash,
|
||||
_version_date_hash,
|
||||
collect_files,
|
||||
hash_files,
|
||||
)
|
||||
from akkudoktoreos.utils.datetimeutil import to_datetime
|
||||
|
||||
DIR_PROJECT_ROOT = Path(__file__).parent.parent
|
||||
GET_VERSION_SCRIPT = DIR_PROJECT_ROOT / "scripts" / "get_version.py"
|
||||
@@ -127,7 +128,7 @@ def write_file(path: Path, content: str):
|
||||
|
||||
# -- Test version calculation ---
|
||||
|
||||
def test_version_hash() -> None:
|
||||
def test_version_date_hash() -> None:
|
||||
"""Test which files are used for version hash calculation."""
|
||||
|
||||
watched_paths = [DIR_PACKAGE_ROOT]
|
||||
@@ -196,22 +197,26 @@ def test_version_non_dev(monkeypatch):
|
||||
|
||||
def test_version_dev_precision_8(monkeypatch):
|
||||
"""Test that a dev version appends exactly 8 digits derived from the hash."""
|
||||
fake_hash = "abcdef1234567890" # deterministic fake digest
|
||||
fake_hash = "abcdef1234567890"
|
||||
fake_date = "2025-02-22T09:28:22Z"
|
||||
fake_date_hash = (to_datetime(fake_date), fake_hash) # deterministic fake digest
|
||||
|
||||
monkeypatch.setattr("akkudoktoreos.core.version._version_hash", lambda: fake_hash)
|
||||
monkeypatch.setattr("akkudoktoreos.core.version._version_date_hash", lambda: fake_date_hash)
|
||||
monkeypatch.setattr("akkudoktoreos.core.version.VERSION_BASE", "0.2.0.dev")
|
||||
monkeypatch.setattr("akkudoktoreos.core.version.VERSION_DEV_PRECISION", 8)
|
||||
monkeypatch.setattr("akkudoktoreos.core.version.VERSION_DEV_HASH_PRECISION", 8)
|
||||
|
||||
result = _version_calculate()
|
||||
|
||||
# Compute expected suffix using the same logic as _version_calculate
|
||||
hash_value = int(fake_hash, 16)
|
||||
expected_digits = str(hash_value % (10 ** 8)).zfill(8)
|
||||
expected_hash_digits = str(hash_value % (10 ** 8)).zfill(8)
|
||||
|
||||
expected = f"0.2.0.dev{expected_digits}"
|
||||
expected_date_digits = to_datetime(fake_date, as_string="YYMMDDHH")
|
||||
|
||||
expected = f"0.2.0.dev{expected_date_digits}{expected_hash_digits}"
|
||||
|
||||
assert result == expected
|
||||
assert len(expected_digits) == 8
|
||||
assert len(expected_hash_digits) == 8
|
||||
assert result.startswith("0.2.0.dev")
|
||||
assert result == expected
|
||||
|
||||
@@ -219,21 +224,25 @@ def test_version_dev_precision_8(monkeypatch):
|
||||
def test_version_dev_precision_8_different_hash(monkeypatch):
|
||||
"""A different hash must produce a different 8-digit suffix."""
|
||||
fake_hash = "1234abcd9999ffff"
|
||||
fake_date = "2025-02-22T09:28:22Z"
|
||||
fake_date_hash = (to_datetime(fake_date), fake_hash) # deterministic fake digest
|
||||
|
||||
monkeypatch.setattr("akkudoktoreos.core.version._version_hash", lambda: fake_hash)
|
||||
monkeypatch.setattr("akkudoktoreos.core.version._version_date_hash", lambda: fake_date_hash)
|
||||
monkeypatch.setattr("akkudoktoreos.core.version.VERSION_BASE", "0.2.0.dev")
|
||||
monkeypatch.setattr("akkudoktoreos.core.version.VERSION_DEV_PRECISION", 8)
|
||||
monkeypatch.setattr("akkudoktoreos.core.version.VERSION_DEV_HASH_PRECISION", 8)
|
||||
|
||||
result = _version_calculate()
|
||||
|
||||
# Compute expected suffix using the same logic as _version_calculate
|
||||
hash_value = int(fake_hash, 16)
|
||||
expected_digits = str(hash_value % (10 ** 8)).zfill(8)
|
||||
expected_hash_digits = str(hash_value % (10 ** 8)).zfill(8)
|
||||
|
||||
expected = f"0.2.0.dev{expected_digits}"
|
||||
expected_date_digits = to_datetime(fake_date, as_string="YYMMDDHH")
|
||||
|
||||
expected = f"0.2.0.dev{expected_date_digits}{expected_hash_digits}"
|
||||
|
||||
assert result == expected
|
||||
assert len(expected_digits) == 8
|
||||
assert len(expected_hash_digits) == 8
|
||||
assert result.startswith("0.2.0.dev")
|
||||
assert result == expected
|
||||
|
||||
|
||||
Reference in New Issue
Block a user