Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
c6df0c6
Add code for compatibility matrix
lord-haffi Mar 23, 2024
f874a84
Include in docs
lord-haffi Mar 23, 2024
ae6bfa7
Generate JSON-Schemas on RTD
lord-haffi Mar 24, 2024
ad7e5e7
🩹
lord-haffi Mar 24, 2024
cb88abc
🩹
lord-haffi Mar 24, 2024
7fac167
🩹cmon, fcking shell
lord-haffi Mar 24, 2024
ba0452e
🩹
lord-haffi Mar 24, 2024
86970ac
debug
lord-haffi Mar 24, 2024
c49324b
Maybe single quotes?
lord-haffi Mar 24, 2024
dd67f7d
Why the fck it doesnt work
lord-haffi Mar 24, 2024
cefa0a1
🩹
lord-haffi Mar 24, 2024
da7e75a
really?
lord-haffi Mar 24, 2024
aa28c46
sgtkrwn
lord-haffi Mar 24, 2024
989bee1
sg<
lord-haffi Mar 24, 2024
4d46da0
As env var?
lord-haffi Mar 24, 2024
4351af2
Now, please do it
lord-haffi Mar 24, 2024
7b2a070
🩹
lord-haffi Mar 24, 2024
0ccf474
more debug
lord-haffi Mar 24, 2024
667c26d
✨Add field documentation to description of the JSON-schemas
lord-haffi Apr 4, 2024
d2745d9
Merge branch 'json_schema_docs' into compatibility_matrix
lord-haffi Apr 4, 2024
9df09f1
Fix exponential backtracking
lord-haffi Apr 4, 2024
a958409
Merge remote-tracking branch 'origin/main' into json_schema_docs
lord-haffi Apr 4, 2024
a9978db
Merge branch 'json_schema_docs' into compatibility_matrix
lord-haffi Apr 4, 2024
171acb8
Merge remote-tracking branch 'origin/main' into compatibility_matrix
lord-haffi Apr 9, 2024
43d75f4
Merge branch 'main' into compatibility_matrix
lord-haffi Apr 9, 2024
5135ddc
Merge remote-tracking branch 'origin/main' into compatibility_matrix
lord-haffi Apr 18, 2024
b2c8621
Correct version regex
lord-haffi Apr 22, 2024
919ed8c
Add Json Schema build for /latest
lord-haffi Apr 22, 2024
c2aeec8
Bump bost
lord-haffi Apr 22, 2024
fd68edb
Merge remote-tracking branch 'origin/main' into compatibility_matrix
lord-haffi Apr 22, 2024
2fd0325
🩹
lord-haffi Apr 22, 2024
bbe2a8b
🩹 local test
lord-haffi Apr 22, 2024
61920a6
📄
lord-haffi Apr 22, 2024
9aea8e2
Allow testing via workflow_dispatch
lord-haffi Apr 22, 2024
bff6e7d
Merge remote-tracking branch 'origin/main' into compatibility_matrix
lord-haffi Apr 22, 2024
5be9c4b
📄
lord-haffi Apr 22, 2024
e314822
Test using env vars
lord-haffi Apr 29, 2024
c574b02
Use env vars
lord-haffi Apr 29, 2024
54f4d9f
Always update local compiled JSONs
lord-haffi Apr 29, 2024
dd9d03c
More last versions to compare
lord-haffi Apr 29, 2024
ffaca4e
Always update local compiled JSONs - Delete old folder
lord-haffi Apr 29, 2024
78e2245
Use local links if build locally
lord-haffi Apr 29, 2024
1b96421
Fix local docs building - make it easier
lord-haffi Apr 29, 2024
b56e825
Get all versions since `v202401.0.0`
lord-haffi Apr 29, 2024
6561843
Add new Code to linting and type checking
lord-haffi Apr 29, 2024
7e97912
🚨linter + type checker
lord-haffi Apr 29, 2024
096f0d1
Merge branch 'main' into compatibility_matrix
lord-haffi Apr 29, 2024
e138f7a
📄from code review
lord-haffi Apr 29, 2024
076311a
Merge remote-tracking branch 'origin/main' into compatibility_matrix
lord-haffi Apr 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions .github/workflows/docs_latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ on:
branches: [main]

# Allows you to run this workflow manually from the Actions tab
# If the workflow is triggered (manually, through workflow_dispatch) on another branch than the main-branch,
# then it will be published not under /latest but under test-XXXXXX where the X's are a 6-digit random number
# (starting with non-zero).
# You should remember to delete the generated test-XXXXXX folder on the gh-pages branch after you are done with them.
workflow_dispatch:

jobs:
Expand All @@ -18,6 +22,12 @@ jobs:
python-version: ["3.12"]
os: [ubuntu-latest]
steps:
- name: Set routing name to latest
if: github.ref == 'refs/heads/main'
run: echo "REF_NAME=latest" >> "$GITHUB_ENV"
- name: Set routing name to test-XXXXXX
if: github.ref != 'refs/heads/main'
run: echo "REF_NAME=test-$(shuf -i 100000-999999 -n 1)" >> "$GITHUB_ENV"
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
Expand All @@ -28,11 +38,13 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox
pip install -r requirements.txt
# Note: The sphinx action below can only install a single requirements file.
- name: Write version to conf.py
run: |
echo -e "version = release = \"latest\"\n" | cat - docs/conf.py > /tmp/conf.py && mv /tmp/conf.py docs/conf.py
- name: Build JSON Schemas
run: tox -e generate_json_schemas
env:
TARGET_VERSION: ${{ env.REF_NAME }}
- name: Run kroki with docker
run: |
docker compose up -d
Expand All @@ -43,9 +55,12 @@ jobs:
with:
requirements_path: docs/requirements.txt
documentation_path: docs/
target_path: latest/
target_path: ${{ env.REF_NAME }}
target_branch: gh-pages
sphinx_options: -W -j auto
env:
SPHINX_DOCS_RELEASE: ${{ env.REF_NAME }}
SPHINX_DOCS_VERSION: ${{ env.REF_NAME }}
- name: Push changes
uses: ad-m/github-push-action@master
with:
Expand Down
7 changes: 3 additions & 4 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,6 @@ jobs:
python -m pip install --upgrade pip
pip install -r requirements.txt
# Note: The sphinx action below can only install a single requirements file.
- name: Write version to conf.py
run: |
echo -e "version = release = \"${{ github.ref_name }}\"\n" | cat - docs/conf.py > /tmp/conf.py
mv /tmp/conf.py docs/conf.py
- name: Run kroki with docker
run: |
docker compose up -d
Expand All @@ -106,6 +102,9 @@ jobs:
target_path: ${{ github.ref_name }}
target_branch: gh-pages
sphinx_options: -W -j auto
env:
SPHINX_DOCS_RELEASE: ${{ github.ref_name }}
SPHINX_DOCS_VERSION: ${{ github.ref_name }}
- id: latest_bo4e
name: Get latest BO4E release tag
uses: pozetroninc/github-action-get-latest-release@master
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ Temporary Items
docs/api
docs/plantuml.jar
docs/_static/images
docs/compatibility_matrix.csv

# version number for bo4e-python; gets auto-generated during the command
# python -m build
Expand All @@ -175,3 +176,5 @@ src/_bo4e_python_version.py
# the autogenerated JSON schemas will be build and pushed to BO4E-Schemas
# on release
json_schemas/**/*.json

tmp/
35 changes: 35 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,2 +1,37 @@
.. _changes:

================
Compatibility
================

The table below shows the compatibility matrix of the last BO4E versions.

Legend:

+------+------------------------------------------------------+
| 🟢 | | Compatible |
| | | No changes in the data model |
+------+------------------------------------------------------+
| 🟡 | | Compatible |
| | | Only non-critical changes in the data model |
| | | e.g. added fields, changed doc strings |
+------+------------------------------------------------------+
| 🔴 | | Incompatible |
| | | Critical changes in the data model |
| | | e.g. removed fields, changed types |
+------+------------------------------------------------------+
| ➕ | | Compatible |
| | | Data model was added in this version |
+------+------------------------------------------------------+
| ➖ | | Incompatible |
| | | Data model was removed in this version |
+------+------------------------------------------------------+
| \- | | Data model not existent in this version |
| | | was removed before or will be added in future |
+------+------------------------------------------------------+

.. csv-table:: Compatibility matrix
:file: compatibility_matrix.csv
:header-rows: 1

.. include:: ../CHANGELOG.rst
Empty file added docs/compatibility/__init__.py
Empty file.
228 changes: 228 additions & 0 deletions docs/compatibility/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
"""
This module provides functions to compare the BO4E JSON schemas of different versions.
It also contains functions to query GitHub for the latest BO4E versions to compare with the schemas of the current
work tree.
Additionally, it implements a little cache functionality to avoid multiple downloads of the same versions e.g.
if you're testing locally.
"""

import itertools
import logging
import re
import shutil
from pathlib import Path
from typing import Any as _Any
from typing import Iterable

import bost.operations
from bost import main as bost_main
from bost.operations import update_references as bost_update_references
from bost.pull import OWNER, REPO, SchemaMetadata, get_source_repo

from . import change_schemas, diff, loader, matrix

BO4E_BASE_DIR = Path(__file__).parents[2] / "tmp/bo4e_json_schemas"
LOCAL_JSON_SCHEMA_DIR = Path(__file__).parents[2] / "json_schemas"
logger = logging.getLogger(__name__)


def pull_bo4e_version(version: str, output: Path, gh_token: str | None = None) -> None:
"""
Pull the BO4E version from the given version string.
"""
bost_main(
output=output,
target_version=version,
update_refs=True,
set_default_version=False,
clear_output=True,
token=gh_token,
)


def update_references(path: Path, version: str) -> None:
"""
Update the references in the given path. This step is needed for the local build.
"""
schema_namespace = {}
for schema_path in loader.get_namespace(path):
local_path = Path(path, *schema_path).with_suffix(".json")
schema_namespace[schema_path[-1]] = SchemaMetadata(
class_name=schema_path[-1],
download_url="",
module_path=schema_path,
file_path=local_path,
cached_path=local_path,
token=None,
)
for schema_metadata in schema_namespace.values():
bost_update_references(schema_metadata, schema_namespace, version)
schema_metadata.save()


def pull_or_reuse_bo4e_version(version: str, gh_token: str | None = None, from_local: bool = False) -> Path:
"""
Pull the BO4E version from the given version string or reuse the version if it was already pulled before.
If version is None use the BO4E version of the checkout working directory by assuming the compiled json
schemas in /json_schemas.
Returns the path of the bo4e directory.
"""
bo4e_dir = BO4E_BASE_DIR / version

if from_local:
if not any(LOCAL_JSON_SCHEMA_DIR.rglob("*.json")):
raise ValueError(
"No local json schemas found in /json_schemas. "
"Please ensure that the json schemas are build on beforehand."
)
if bo4e_dir.exists():
shutil.rmtree(bo4e_dir)
shutil.copytree(LOCAL_JSON_SCHEMA_DIR, bo4e_dir)
update_references(bo4e_dir, version)
elif any(bo4e_dir.rglob("*.json")):
return bo4e_dir
else:
pull_bo4e_version(version, bo4e_dir, gh_token)
return bo4e_dir


def compare_bo4e_versions(
version_old: str, version_new: str, gh_token: str | None = None, from_local: bool = False
) -> Iterable[change_schemas.Change]:
"""
Compare the old version with the new version.
If version_new is None use the BO4E version of the checkout working directory by assuming the compiled json
schemas in /json_schemas.
"""
dir_old_schemas = pull_or_reuse_bo4e_version(version_old, gh_token)
dir_new_schemas = pull_or_reuse_bo4e_version(version_new, gh_token, from_local=from_local)
print(f"Comparing {version_old} with {version_new}")
yield from diff.diff_schemas(dir_old_schemas, dir_new_schemas)


def compare_bo4e_versions_iteratively(
versions: Iterable[str], cur_version: str | None = None, gh_token: str | None = None
) -> dict[tuple[str, str], Iterable[change_schemas.Change]]:
"""
Compare the versions iteratively. Each version at index i will be compared to the version at index i+1.
Additionally, if cur_version is provided, the last version in the list will be compared to the version
in the checkout working directory. The value of cur_version will be used to set the key in the returned
dict.
Note:
- versions must contain at least one element.
- versions should be sorted in ascending order.
- if using cur_version, ensure that the json schemas of the checkout working directory
were build on beforehand. They should be located in /json_schemas.
"""
print(f"Comparing versions {versions} with cur_version {cur_version}")
changes = {}
last_version: str = "" # This value is never used but makes mypy and pylint happy
for version_old, version_new in itertools.pairwise(versions):
last_version = version_new
changes[version_old, version_new] = compare_bo4e_versions(version_old, version_new, gh_token)
if cur_version is not None:
changes[last_version, cur_version] = compare_bo4e_versions(last_version, cur_version, gh_token, from_local=True)
print("Comparisons finished.")
return changes


REGEX_RELEASE_VERSION = re.compile(r"^v(\d{6}\.\d+\.\d+)$")
REGEX_RELEASE_CANDIDATE_VERSION = re.compile(r"^v(\d{6}\.\d+\.\d+)-rc\d+$")


def get_last_n_release_versions(n: int, include_rc: bool = False, gh_token: str | None = None) -> Iterable[str]:
"""
Get the last n release versions from the BO4E repository.
"""
repo = get_source_repo(gh_token)
releases = repo.get_releases()
counter = 0

for release in releases:
if not REGEX_RELEASE_VERSION.fullmatch(release.tag_name) and (
not include_rc or not REGEX_RELEASE_CANDIDATE_VERSION.fullmatch(release.tag_name)
):
continue
counter += 1
yield release.tag_name
if counter >= n:
return

logger.warning("Only %d matching releases found. Returning all releases.", counter)


def get_all_release_versions_since_20240100(include_rc: bool = False, gh_token: str | None = None) -> Iterable[str]:
"""
Get all release versions since v202401.0.0 from the BO4E repository.
"""
repo = get_source_repo(gh_token)
releases = repo.get_releases()
version_threshold = "v202401.0.0"

for release in releases:
if not REGEX_RELEASE_VERSION.fullmatch(release.tag_name) and (
not include_rc or not REGEX_RELEASE_CANDIDATE_VERSION.fullmatch(release.tag_name)
):
continue
yield release.tag_name
if release.tag_name == version_threshold:
return

logger.warning("Threshold version %s not found. Returned all matching releases.", version_threshold)


def _monkey_patch_bost_regex_if_local_testing(version: str) -> None:
regex_expected_version = re.compile(r"^v\d+\.\d+\.\d+(?:-rc\d+)?$")
if not regex_expected_version.fullmatch(version):
bost.operations.REF_ONLINE_REGEX = re.compile(
rf"^https://raw\.githubusercontent\.com/(?:{OWNER.upper()}|{OWNER.lower()}|Hochfrequenz)/{REPO}/"
rf"(?P<version>[^/]+)/"
r"src/bo4e_schemas/(?P<sub_path>(?:\w+/)*)(?P<model>\w+)\.json#?$"
)


def create_tables_for_doc(
compatibility_matrix_output_file: Path,
gh_version: str,
*,
gh_token: str | None = None,
last_n_versions: int = 2,
) -> None:
"""
Creates the compatibility matrix for the documentation. The output is a csv file. This can be referenced
inside Sphinx documentation. See https://sublime-and-sphinx-guide.readthedocs.io/en/latest/tables.html#csv-files
for more information.
If you have problems with rate limiting, please set gh_token.
The compatibility matrix will be built for last_n_versions + the current version in the checkout working directory.
If you set last_n_versions = 0 all versions since v202401.0.0 will be compared.
Note: The matrix will never contain the first version as column. Each column is a comparison to the version before.
Note: Release candidates are excluded.
"""
_monkey_patch_bost_regex_if_local_testing(gh_version)
logger.info("Retrieving the last %d release versions", last_n_versions)
if last_n_versions > 0:
versions = list(reversed(list(get_last_n_release_versions(last_n_versions, gh_token=gh_token))))
else:
versions = list(reversed(list(get_all_release_versions_since_20240100(gh_token=gh_token))))
logger.info("Comparing versions iteratively: %s", " -> ".join([*versions, gh_version]))
changes_iterables = compare_bo4e_versions_iteratively(versions, gh_version, gh_token=gh_token)
logger.info("Building namespaces")
changes = {key: list(value) for key, value in changes_iterables.items()}
namespaces = {version: list(loader.get_namespace(BO4E_BASE_DIR / version)) for version in versions}
namespaces[gh_version] = list(loader.get_namespace(BO4E_BASE_DIR / gh_version))
logger.info("Creating compatibility matrix")
matrix.create_compatibility_matrix_csv(
compatibility_matrix_output_file, [*versions, gh_version], namespaces, changes
)


def test_create_tables_for_doc() -> None:
"""
Test the create_tables_for_doc function locally without building the entire documentation.
Needs the JSON schemas to be present in /json_schemas with TARGET_VERSION set to "local".
"""
create_tables_for_doc(
Path(__file__).parents[1] / "compatibility_matrix.csv",
"local",
last_n_versions=3,
)
Loading