mgf-common 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.
- mgf_common-0.1.0/.gitignore +55 -0
- mgf_common-0.1.0/.import_linter_cache/.gitignore +2 -0
- mgf_common-0.1.0/.import_linter_cache/064c9363bb2a6e3bc05545c81c5891a901373aa6.data.json +1 -0
- mgf_common-0.1.0/.import_linter_cache/4d7cd651a3735c71269a0e4b8186cb8d7092dd66.data.json +1 -0
- mgf_common-0.1.0/.import_linter_cache/CACHEDIR.TAG +3 -0
- mgf_common-0.1.0/.import_linter_cache/mgf.common.meta.json +1 -0
- mgf_common-0.1.0/.importlinter +52 -0
- mgf_common-0.1.0/.woodpecker.yml +105 -0
- mgf_common-0.1.0/CHANGELOG.md +93 -0
- mgf_common-0.1.0/COMMON.md +825 -0
- mgf_common-0.1.0/LICENSE +21 -0
- mgf_common-0.1.0/PKG-INFO +143 -0
- mgf_common-0.1.0/README.md +100 -0
- mgf_common-0.1.0/docs/standards/COMMON_COMPONENTS.md +625 -0
- mgf_common-0.1.0/docs/standards/CONFIGURATION.md +852 -0
- mgf_common-0.1.0/docs/standards/DESIGN_PRINCIPLES.md +511 -0
- mgf_common-0.1.0/docs/standards/ERROR_HANDLING.md +955 -0
- mgf_common-0.1.0/docs/standards/LOGGING.md +1084 -0
- mgf_common-0.1.0/docs/standards/README.md +212 -0
- mgf_common-0.1.0/pyproject.toml +199 -0
- mgf_common-0.1.0/src/mgf/common/__init__.py +35 -0
- mgf_common-0.1.0/src/mgf/common/_identity.py +69 -0
- mgf_common-0.1.0/src/mgf/common/config/__init__.py +43 -0
- mgf_common-0.1.0/src/mgf/common/config/_loader.py +185 -0
- mgf_common-0.1.0/src/mgf/common/config/_paths.py +114 -0
- mgf_common-0.1.0/src/mgf/common/config/_settings.py +185 -0
- mgf_common-0.1.0/src/mgf/common/exceptions/__init__.py +65 -0
- mgf_common-0.1.0/src/mgf/common/exceptions/_base.py +49 -0
- mgf_common-0.1.0/src/mgf/common/exceptions/_well_known.py +213 -0
- mgf_common-0.1.0/src/mgf/common/observability/__init__.py +44 -0
- mgf_common-0.1.0/src/mgf/common/observability/crash_reporter.py +234 -0
- mgf_common-0.1.0/src/mgf/common/observability/excepthook.py +79 -0
- mgf_common-0.1.0/src/mgf/common/observability/json_formatter.py +171 -0
- mgf_common-0.1.0/src/mgf/common/observability/logging_setup.py +258 -0
- mgf_common-0.1.0/src/mgf/common/observability/otel_setup.py +206 -0
- mgf_common-0.1.0/src/mgf/common/observability/redaction.py +134 -0
- mgf_common-0.1.0/src/mgf/common/observability/text_formatter.py +111 -0
- mgf_common-0.1.0/src/mgf/common/observability/threading_excepthook.py +62 -0
- mgf_common-0.1.0/src/mgf/common/py.typed +0 -0
- mgf_common-0.1.0/tests/__init__.py +0 -0
- mgf_common-0.1.0/tests/unit/__init__.py +0 -0
- mgf_common-0.1.0/tests/unit/config/__init__.py +0 -0
- mgf_common-0.1.0/tests/unit/config/test_loader.py +367 -0
- mgf_common-0.1.0/tests/unit/config/test_paths.py +148 -0
- mgf_common-0.1.0/tests/unit/observability/__init__.py +0 -0
- mgf_common-0.1.0/tests/unit/observability/conftest.py +85 -0
- mgf_common-0.1.0/tests/unit/observability/test_crash_reporter.py +159 -0
- mgf_common-0.1.0/tests/unit/observability/test_excepthooks.py +162 -0
- mgf_common-0.1.0/tests/unit/observability/test_json_formatter.py +124 -0
- mgf_common-0.1.0/tests/unit/observability/test_logging_setup.py +203 -0
- mgf_common-0.1.0/tests/unit/observability/test_otel_setup.py +165 -0
- mgf_common-0.1.0/tests/unit/observability/test_redaction.py +166 -0
- mgf_common-0.1.0/tests/unit/observability/test_text_formatter.py +82 -0
- mgf_common-0.1.0/tests/unit/test_exceptions_base.py +190 -0
- mgf_common-0.1.0/tests/unit/test_identity.py +69 -0
- mgf_common-0.1.0/uv.lock +1252 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# IDE
|
|
2
|
+
.idea/
|
|
3
|
+
.vscode/
|
|
4
|
+
*.swp
|
|
5
|
+
*.swo
|
|
6
|
+
|
|
7
|
+
# Python
|
|
8
|
+
__pycache__/
|
|
9
|
+
*.py[cod]
|
|
10
|
+
*$py.class
|
|
11
|
+
*.so
|
|
12
|
+
.Python
|
|
13
|
+
build/
|
|
14
|
+
develop-eggs/
|
|
15
|
+
dist/
|
|
16
|
+
downloads/
|
|
17
|
+
eggs/
|
|
18
|
+
.eggs/
|
|
19
|
+
lib/
|
|
20
|
+
lib64/
|
|
21
|
+
parts/
|
|
22
|
+
sdist/
|
|
23
|
+
var/
|
|
24
|
+
wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
|
|
29
|
+
# Virtual environments
|
|
30
|
+
.venv/
|
|
31
|
+
venv/
|
|
32
|
+
env/
|
|
33
|
+
ENV/
|
|
34
|
+
|
|
35
|
+
# Testing and coverage
|
|
36
|
+
.pytest_cache/
|
|
37
|
+
.hypothesis/
|
|
38
|
+
.coverage
|
|
39
|
+
.coverage.*
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
coverage.xml
|
|
44
|
+
*.cover
|
|
45
|
+
|
|
46
|
+
# Type checking
|
|
47
|
+
.mypy_cache/
|
|
48
|
+
.pyre/
|
|
49
|
+
.pytype/
|
|
50
|
+
|
|
51
|
+
# Ruff
|
|
52
|
+
.ruff_cache/
|
|
53
|
+
|
|
54
|
+
# Packaging
|
|
55
|
+
MANIFEST
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mgf.common._identity":[],"mgf.common":[["mgf.common._identity",24,"from mgf.common._identity import app_name, app_version, configure"]]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mgf.common.observability.excepthook":[["sys",22,"import sys"],["logging",21,"import logging"],["mgf.common._identity",25,"from mgf.common._identity import app_name"],["types",29,"from types import TracebackType"],["typing",23,"from typing import TYPE_CHECKING"],["__future__",19,"from __future__ import annotations"],["mgf.common.observability.crash_reporter",26,"from mgf.common.observability.crash_reporter import report_now"]],"mgf.common.observability.json_formatter":[["__future__",15,"from __future__ import annotations"],["typing",20,"from typing import TYPE_CHECKING, Any"],["mgf.common.observability.redaction",23,"from mgf.common.observability.redaction import Redactor"],["logging",18,"import logging"],["json",17,"import json"],["opentelemetry",158,"from opentelemetry import trace"],["time",19,"import time"]],"mgf.common.observability.redaction":[["__future__",18,"from __future__ import annotations"],["re",20,"import re"],["dataclasses",21,"from dataclasses import dataclass, field"],["typing",22,"from typing import Any"]],"mgf.common.observability.threading_excepthook":[["mgf.common.observability.crash_reporter",18,"from mgf.common.observability.crash_reporter import report_now"],["mgf.common._identity",17,"from mgf.common._identity import app_name"],["__future__",12,"from __future__ import annotations"],["logging",14,"import logging"],["threading",15,"import threading"]],"mgf.common.observability.crash_reporter":[["logging",30,"import logging"],["sys",33,"import sys"],["mgf.common._identity",40,"from mgf.common._identity import app_name, app_version"],["platform",32,"import platform"],["mgf.common.observability.otel_setup",191,"from mgf.common.observability.otel_setup import emit_log_event"],["uuid",35,"import uuid"],["traceback",34,"import traceback"],["pathlib",37,"from pathlib import Path"],["datetime",36,"from datetime import UTC, datetime"],["json",29,"import json"],["sentry_sdk",170,"import sentry_sdk"],["typing",38,"from typing import Any"],["__future__",27,"from __future__ import annotations"],["urllib",208,"import urllib.request"],["os",31,"import os"]],"mgf.common.config._settings":[["pathlib",36,"from pathlib import Path"],["pydantic_settings",47,"from pydantic_settings import PydanticBaseSettingsSource"],["pydantic_settings",40,"from pydantic_settings import ("],["pydantic",39,"from pydantic import field_validator"],["__future__",34,"from __future__ import annotations"],["typing",37,"from typing import TYPE_CHECKING, Any, ClassVar, cast"]],"mgf.common.exceptions._base":[["__future__",24,"from __future__ import annotations"]],"mgf.common.observability.text_formatter":[["mgf.common.observability.redaction",24,"from mgf.common.observability.redaction import Redactor"],["__future__",15,"from __future__ import annotations"],["logging",17,"import logging"],["time",20,"import time"],["os",18,"import os"],["typing",21,"from typing import TYPE_CHECKING, Any"],["sys",19,"import sys"]],"mgf.common.config._loader":[["mgf.common.config._settings",37,"from mgf.common.config._settings import BaseAppSettings"],["os",29,"import os"],["tempfile",30,"import tempfile"],["yaml",34,"import yaml"],["pydantic",35,"from pydantic import ValidationError"],["collections",41,"from collections.abc import Callable"],["__future__",27,"from __future__ import annotations"],["pathlib",31,"from pathlib import Path"],["typing",32,"from typing import TYPE_CHECKING, Any, TypeVar"],["mgf.common.exceptions",38,"from mgf.common.exceptions import AppConfigError"]],"mgf.common.exceptions._well_known":[["mgf.common.exceptions._base",30,"from mgf.common.exceptions._base import AppError"],["__future__",28,"from __future__ import annotations"]],"mgf.common._identity":[["__future__",22,"from __future__ import annotations"]],"mgf.common.config._paths":[["typing",20,"from typing import Literal"],["os",17,"import os"],["mgf.common._identity",81,"from mgf.common._identity import app_name"],["sys",18,"import sys"],["__future__",15,"from __future__ import annotations"],["pathlib",19,"from pathlib import Path"]],"mgf.common.exceptions":[["mgf.common.exceptions._well_known",32,"from mgf.common.exceptions._well_known import ("],["mgf.common.exceptions._base",31,"from mgf.common.exceptions._base import AppError"],["__future__",29,"from __future__ import annotations"]],"mgf.common.observability.otel_setup":[["opentelemetry",137,"from opentelemetry.trace import Status, StatusCode"],["typing",27,"from typing import TYPE_CHECKING, Any"],["opentelemetry",61,"from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter"],["opentelemetry",62,"from opentelemetry.sdk.resources import Resource"],["logging",25,"import logging"],["__future__",22,"from __future__ import annotations"],["mgf.common._identity",29,"from mgf.common._identity import app_name"],["opentelemetry",95,"from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor"],["opentelemetry",136,"from opentelemetry import trace"],["opentelemetry",170,"from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter"],["opentelemetry",60,"from opentelemetry import trace"],["opentelemetry",190,"from opentelemetry._logs import SeverityNumber, get_logger"],["contextlib",24,"import contextlib"],["opentelemetry",171,"from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler"],["opentelemetry",63,"from opentelemetry.sdk.trace import TracerProvider"],["opentelemetry",64,"from opentelemetry.sdk.trace.export import BatchSpanProcessor"],["opentelemetry",172,"from opentelemetry.sdk._logs.export import BatchLogRecordProcessor"],["collections",32,"from collections.abc import Iterator"],["os",26,"import os"]],"mgf.common.observability.logging_setup":[["logging",207,"from logging.handlers import SysLogHandler"],["mgf.common.observability.text_formatter",46,"from mgf.common.observability.text_formatter import TextFormatter"],["urllib",242,"from urllib.parse import urlparse"],["__future__",34,"from __future__ import annotations"],["logging",241,"from logging.handlers import HTTPHandler"],["mgf.common.observability.otel_setup",225,"from mgf.common.observability.otel_setup import build_otlp_log_handler"],["logging",39,"from logging.handlers import RotatingFileHandler"],["mgf.common._identity",43,"from mgf.common._identity import app_name"],["mgf.common.observability.redaction",45,"from mgf.common.observability.redaction import Redactor"],["mgf.common.observability.json_formatter",44,"from mgf.common.observability.json_formatter import JsonFormatter"],["sys",38,"import sys"],["systemd",195,"from systemd.journal import JournalHandler"],["logging",36,"import logging"],["os",37,"import os"],["typing",41,"from typing import Literal"],["pathlib",40,"from pathlib import Path"]],"mgf.common.config":[["mgf.common.config._paths",26,"from mgf.common.config._paths import ("],["__future__",23,"from __future__ import annotations"],["mgf.common.config._settings",32,"from mgf.common.config._settings import BaseAppSettings, make_settings_config"],["mgf.common.config._loader",25,"from mgf.common.config._loader import load_settings, save_settings"]],"mgf.common":[["mgf.common._identity",27,"from mgf.common._identity import app_name, app_version, configure"],["__future__",25,"from __future__ import annotations"]],"mgf.common.observability":[["mgf.common.observability.crash_reporter",28,"from mgf.common.observability.crash_reporter import report_now"],["mgf.common.observability.excepthook",29,"from mgf.common.observability.excepthook import install_excepthook"],["mgf.common.observability.otel_setup",31,"from mgf.common.observability.otel_setup import operation_span, setup_otel"],["mgf.common.observability.redaction",32,"from mgf.common.observability.redaction import Redactor"],["__future__",26,"from __future__ import annotations"],["mgf.common.observability.threading_excepthook",33,"from mgf.common.observability.threading_excepthook import install_threading_excepthook"],["mgf.common.observability.logging_setup",30,"from mgf.common.observability.logging_setup import LogFormat, setup_logging"]]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"mgf.common.observability.crash_reporter": 1776847582.9769654, "mgf.common": 1776852806.7539823, "mgf.common.observability.threading_excepthook": 1776847614.9621625, "mgf.common.observability.excepthook": 1776847601.8450816, "mgf.common.observability": 1776847665.0852346, "mgf.common.config._paths": 1776848268.334705, "mgf.common.config._settings": 1776849070.3382397, "mgf.common.exceptions._base": 1776847106.1362891, "mgf.common.exceptions": 1776848235.215801, "mgf.common.observability.json_formatter": 1776847468.170272, "mgf.common.observability.redaction": 1776847318.282407, "mgf.common.observability.otel_setup": 1776848268.334705, "mgf.common.config": 1776848845.0275648, "mgf.common._identity": 1776846821.1630905, "mgf.common.observability.logging_setup": 1776848268.334705, "mgf.common.config._loader": 1776849009.6930761, "mgf.common.observability.text_formatter": 1776847490.1654031, "mgf.common.exceptions._well_known": 1776847208.45681}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Layering contracts for mgf.common — enforces the dependency
|
|
2
|
+
# direction documented in docs/standards/DESIGN_PRINCIPLES.md
|
|
3
|
+
# (rule DP-01) and the lift-readiness rule (LIFT-1) from
|
|
4
|
+
# docs/STANDARD_GAP.md.
|
|
5
|
+
#
|
|
6
|
+
# Run locally: lint-imports
|
|
7
|
+
# Run in CI: see .woodpecker.yml lint step
|
|
8
|
+
#
|
|
9
|
+
# Adding contracts? Each contract is phrased as a hard MUST NOT —
|
|
10
|
+
# import-linter fails the CI build on any violation. When adding a
|
|
11
|
+
# new rule, add a ``reason = ...`` comment so a future engineer
|
|
12
|
+
# (or AI) knows why it exists before deciding to relax it.
|
|
13
|
+
|
|
14
|
+
[importlinter]
|
|
15
|
+
root_packages =
|
|
16
|
+
mgf.common
|
|
17
|
+
# Required because the contracts below name external packages
|
|
18
|
+
# (vmanager, mgf.cli, mgf.gui) as forbidden — without this flag,
|
|
19
|
+
# import-linter refuses to start.
|
|
20
|
+
include_external_packages = True
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Contract 1 — mgf.common is a leaf (the lift-readiness invariant).
|
|
24
|
+
#
|
|
25
|
+
# Reason: mgf.common is the SHARED library that every consumer
|
|
26
|
+
# project depends on. The dependency direction MUST be one-way:
|
|
27
|
+
# consumers (vmanager, future projects) depend on mgf.common, never
|
|
28
|
+
# the reverse. Without this contract, a casual ``import vmanager.X``
|
|
29
|
+
# inside mgf.common would create a cycle that breaks every other
|
|
30
|
+
# consumer. Better to fail loudly in CI here than at integration
|
|
31
|
+
# time in some downstream project.
|
|
32
|
+
#
|
|
33
|
+
# The "forbidden_modules" list is deliberately broad — anything not
|
|
34
|
+
# in mgf.common's own deps (pydantic, pydantic-settings, pyyaml,
|
|
35
|
+
# stdlib, opentelemetry, sentry_sdk) is by definition off-limits.
|
|
36
|
+
# The contract relies on import-linter's behaviour: only top-level
|
|
37
|
+
# packages we control or explicitly forbid are checked; third-party
|
|
38
|
+
# deps not listed here are fine.
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
[importlinter:contract:mgf-common-is-a-leaf]
|
|
42
|
+
name = mgf.common does not import any consumer project
|
|
43
|
+
type = forbidden
|
|
44
|
+
source_modules =
|
|
45
|
+
mgf.common
|
|
46
|
+
forbidden_modules =
|
|
47
|
+
vmanager
|
|
48
|
+
# NOTE: future ``mgf.cli`` / ``mgf.gui`` siblings would also be
|
|
49
|
+
# forbidden in principle, but import-linter rejects "subpackages of
|
|
50
|
+
# external packages" in the forbidden_modules list. Consumers in the
|
|
51
|
+
# ``mgf.<sibling>`` namespace will be added as separate top-level
|
|
52
|
+
# entries here when they exist.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Codeberg / Woodpecker CI for mgf-common.
|
|
2
|
+
#
|
|
3
|
+
# Four steps on every push / PR / tag:
|
|
4
|
+
#
|
|
5
|
+
# 1. lint — ruff check on src + tests + import-linter contracts
|
|
6
|
+
# 2. typecheck — mypy --strict on src
|
|
7
|
+
# 3. test — pytest matrix (Python 3.11 / 3.12 / 3.13);
|
|
8
|
+
# coverage gate enforced on 3.12 only
|
|
9
|
+
# 4. build — sdist + wheel via `python -m build`; runs on every
|
|
10
|
+
# push so a packaging regression fails fast
|
|
11
|
+
#
|
|
12
|
+
# **No PyPI publish step.** PyPI's trusted-publisher OIDC does not
|
|
13
|
+
# (yet) support Codeberg / Forgejo as an issuer — the supported list
|
|
14
|
+
# is GitHub, GitLab, Google, ActiveState. Until that changes, the
|
|
15
|
+
# release workflow is manual `twine upload` from a developer machine
|
|
16
|
+
# (see COMMON.md §8.2). The `build` step here exists to catch
|
|
17
|
+
# packaging regressions on every push, NOT to gate the release.
|
|
18
|
+
#
|
|
19
|
+
# All steps run in fresh python:X-slim containers; the workspace dir
|
|
20
|
+
# is mounted across all steps but each container does its own pip
|
|
21
|
+
# install so the failure surface is per-step in the Woodpecker UI.
|
|
22
|
+
#
|
|
23
|
+
# No real-libvirt smoke (mgf.common doesn't touch libvirt). No
|
|
24
|
+
# install-script step. No GUI test (mgf.common doesn't ship a GUI
|
|
25
|
+
# today — the GUI worker / log viewer / Qt excepthook stay in
|
|
26
|
+
# vmanager per COMMON.md §12.2).
|
|
27
|
+
#
|
|
28
|
+
# The Python version matrix: mgf-common declares Python 3.11+ but
|
|
29
|
+
# is developed against 3.12 day-to-day. The matrix verifies every
|
|
30
|
+
# supported version so a 3.11-incompat construct (or a 3.13-only
|
|
31
|
+
# stdlib feature) fails CI loudly.
|
|
32
|
+
|
|
33
|
+
when:
|
|
34
|
+
- event: push
|
|
35
|
+
- event: pull_request
|
|
36
|
+
- event: tag
|
|
37
|
+
- event: manual
|
|
38
|
+
|
|
39
|
+
steps:
|
|
40
|
+
- name: lint
|
|
41
|
+
image: python:3.12-slim
|
|
42
|
+
commands:
|
|
43
|
+
- pip install --quiet --upgrade pip
|
|
44
|
+
- pip install --quiet -e ".[dev]"
|
|
45
|
+
- ruff check src tests
|
|
46
|
+
# Layering enforcement (rule DP-01) — see ``.importlinter``.
|
|
47
|
+
# Catches any new code that breaks "mgf.common is a leaf".
|
|
48
|
+
- lint-imports
|
|
49
|
+
|
|
50
|
+
- name: typecheck
|
|
51
|
+
image: python:3.12-slim
|
|
52
|
+
commands:
|
|
53
|
+
- pip install --quiet --upgrade pip
|
|
54
|
+
- pip install --quiet -e ".[dev,observability]"
|
|
55
|
+
- mypy src
|
|
56
|
+
|
|
57
|
+
# ---------------------------------------------------------------------
|
|
58
|
+
# Test matrix — runs unit tests on every supported Python version.
|
|
59
|
+
# Coverage is reported on the 3.12 job only (it is identical across
|
|
60
|
+
# versions and re-collecting it three times wastes CI minutes).
|
|
61
|
+
#
|
|
62
|
+
# The 80% floor — versus VManager's 85% — reflects that mgf.common
|
|
63
|
+
# has a higher proportion of conditional sink code (Sentry, OTLP,
|
|
64
|
+
# syslog, journald, HTTP webhook) that can only be meaningfully
|
|
65
|
+
# exercised against real backends. The lazy-import design that
|
|
66
|
+
# makes the [observability] extra optional is precisely what
|
|
67
|
+
# depresses the line-coverage number; raising the gate would force
|
|
68
|
+
# us to mock SDKs we already trust. Today we sit at ~85%; the 80%
|
|
69
|
+
# floor catches a real regression without forcing test churn.
|
|
70
|
+
# ---------------------------------------------------------------------
|
|
71
|
+
- name: test
|
|
72
|
+
image: python:${PYTHON_VERSION}-slim
|
|
73
|
+
matrix:
|
|
74
|
+
PYTHON_VERSION:
|
|
75
|
+
- "3.11"
|
|
76
|
+
- "3.12"
|
|
77
|
+
- "3.13"
|
|
78
|
+
commands:
|
|
79
|
+
- pip install --quiet --upgrade pip
|
|
80
|
+
- pip install --quiet -e ".[dev,observability]"
|
|
81
|
+
- |
|
|
82
|
+
if [ "${PYTHON_VERSION}" = "3.12" ]; then
|
|
83
|
+
pytest --tb=short -q --cov --cov-report=term-missing --cov-fail-under=80
|
|
84
|
+
else
|
|
85
|
+
pytest --tb=short -q
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# ---------------------------------------------------------------------
|
|
89
|
+
# Build — verifies pyproject.toml, the hatchling wheel target, and
|
|
90
|
+
# the py.typed PEP 561 marker still produce a valid sdist + wheel.
|
|
91
|
+
# Runs on every push so a packaging regression fails fast. `twine
|
|
92
|
+
# check` validates the metadata PyPI will reject if malformed.
|
|
93
|
+
# ---------------------------------------------------------------------
|
|
94
|
+
- name: build
|
|
95
|
+
image: python:3.12-slim
|
|
96
|
+
commands:
|
|
97
|
+
- pip install --quiet --upgrade pip
|
|
98
|
+
- pip install --quiet build twine
|
|
99
|
+
- python -m build
|
|
100
|
+
- twine check dist/*
|
|
101
|
+
# Inspection: confirm the wheel ships py.typed (we shipped the
|
|
102
|
+
# marker in commit 2bd2fe4 to make `mypy --strict` happy on
|
|
103
|
+
# consumers; if hatchling ever stops bundling it, this catches
|
|
104
|
+
# the regression).
|
|
105
|
+
- unzip -l dist/mgf_common-*.whl | grep py.typed
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `mgf-common` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
For the version-bump workflow + scope policy (what counts as a major
|
|
9
|
+
vs minor change in the 0.x series), see
|
|
10
|
+
[`COMMON.md`](COMMON.md) §8.3 + §8.4.
|
|
11
|
+
|
|
12
|
+
## [Unreleased]
|
|
13
|
+
|
|
14
|
+
(Nothing yet.)
|
|
15
|
+
|
|
16
|
+
## [0.1.0] — 2026-04-22
|
|
17
|
+
|
|
18
|
+
First public release. Extracted from
|
|
19
|
+
[VManager](https://codeberg.org/magogi-admin/VManager) per the Round 5
|
|
20
|
+
plan in [`COMMON.md`](COMMON.md). VManager is the reference consumer
|
|
21
|
+
and the only consumer to date; the API is "lifted-from-VManager-shaped"
|
|
22
|
+
and may need adjustment when a second project arrives — the 0.x major
|
|
23
|
+
zero exists to make that future churn possible.
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **`mgf.common.exceptions`** — typed exception hierarchy.
|
|
28
|
+
`AppError` base + 7 well-known category bases (`ConfigError`,
|
|
29
|
+
`ProviderError`, `OperationError`, `GuestError`, `EnvironmentError`
|
|
30
|
+
[deliberately shadows the builtin alias], `VaultError`,
|
|
31
|
+
`SchedulerError`) + 6 generic concretes that carry machine-readable
|
|
32
|
+
fields (`SchemaValidationError.errors`, `AppConfigError`,
|
|
33
|
+
`CommandError.argv/.returncode/.stderr`, `ResourceNotFoundError.name`,
|
|
34
|
+
`ResourceAlreadyExistsError.name`, `GuestUnreachableError`,
|
|
35
|
+
`GuestCommandError`). Consumer projects subclass per their domain.
|
|
36
|
+
|
|
37
|
+
- **`mgf.common.config`** — reusable typed configuration.
|
|
38
|
+
`BaseAppSettings` (pydantic-settings base with the layered source
|
|
39
|
+
order `kwargs > env > .env > YAML > defaults`, YAML override hook,
|
|
40
|
+
version-check validator, `to_yaml_dict` helper),
|
|
41
|
+
`make_settings_config(env_prefix=...)` (helper that builds a
|
|
42
|
+
`SettingsConfigDict` with the recommended baseline plus consumer
|
|
43
|
+
overrides — the canonical way to set `env_prefix` on a subclass;
|
|
44
|
+
the `**BaseAppSettings.model_config` spread does NOT work),
|
|
45
|
+
`load_settings` / `save_settings` (typed loader with optional
|
|
46
|
+
migration callback + atomic temp-file writer at mode `0o600`),
|
|
47
|
+
`platform_dir(kind)` + `default_config_path(app)` (cross-OS XDG /
|
|
48
|
+
macOS Application Support / Windows AppData resolution),
|
|
49
|
+
`expand_path` (validator helper for `~` and `$VARS` expansion).
|
|
50
|
+
|
|
51
|
+
- **`mgf.common.observability`** — full observability stack.
|
|
52
|
+
`setup_logging` (single-point logging with console + rotating file
|
|
53
|
+
+ opt-in journald / syslog / OTLP / HTTP webhook sinks),
|
|
54
|
+
`JsonFormatter` (one JSON object per line, OTel-aware, redacted),
|
|
55
|
+
`TextFormatter` (TTY-friendly with ANSI colour, honours `NO_COLOR`),
|
|
56
|
+
`Redactor` (sensitive-field redaction policy covering `password`,
|
|
57
|
+
`token`, `api_key`, `Bearer ...`, JWTs, PEM blocks; configurable
|
|
58
|
+
extras), `setup_otel` + `operation_span` (lazy OpenTelemetry — no-op
|
|
59
|
+
without the `[observability]` extra; `operation_span` records
|
|
60
|
+
exceptions and skips `None` attributes), `report_now` (local crash
|
|
61
|
+
report under `<state>/crashes/` + opt-in remote ship via
|
|
62
|
+
`<APP>_CRASH_SINK=sentry|otlp|https://...`),
|
|
63
|
+
`install_excepthook` + `install_threading_excepthook` (idempotent
|
|
64
|
+
process-wide hooks).
|
|
65
|
+
|
|
66
|
+
- **`mgf.common.configure(app_name, app_version)`** — process-wide
|
|
67
|
+
identity cache. Every observability function reads `app_name()` to
|
|
68
|
+
derive log paths, env-var prefixes, OTel service name, and the
|
|
69
|
+
crash report `app` field. Call once per entry point, before any
|
|
70
|
+
other `mgf.common.*` function that emits diagnostics.
|
|
71
|
+
|
|
72
|
+
- **`docs/standards/`** — cross-project engineering standards
|
|
73
|
+
(DESIGN_PRINCIPLES, ERROR_HANDLING, LOGGING, CONFIGURATION,
|
|
74
|
+
COMMON_COMPONENTS, plus the master README with conformance
|
|
75
|
+
keywords + L0 / L1 / L2 levels). Lifted from VManager in Phase 4.
|
|
76
|
+
|
|
77
|
+
- **PEP 561 `py.typed` marker** so downstream `mypy --strict`
|
|
78
|
+
consumers see fully-typed inline annotations instead of
|
|
79
|
+
`module is installed, but missing library stubs or py.typed marker`
|
|
80
|
+
warnings (and the cascade of "cannot subclass" errors that follows).
|
|
81
|
+
|
|
82
|
+
- **`[observability]` extra** — pulls in `opentelemetry-api`,
|
|
83
|
+
`opentelemetry-sdk`, `opentelemetry-exporter-otlp-proto-http`,
|
|
84
|
+
`opentelemetry-instrumentation-httpx`, and `sentry-sdk`. Default
|
|
85
|
+
install stays slim; consumers opt in when they want L2 conformance.
|
|
86
|
+
|
|
87
|
+
- **Quality gates wired:** `mypy --strict` clean on 18 source files,
|
|
88
|
+
157 tests pass, `ruff` clean, `import-linter` enforces the single
|
|
89
|
+
contract `mgf-common-is-a-leaf` (forbids any import of a consumer
|
|
90
|
+
project from inside `mgf.common`).
|
|
91
|
+
|
|
92
|
+
[Unreleased]: https://codeberg.org/magogi-admin/mgf_common/compare/v0.1.0...HEAD
|
|
93
|
+
[0.1.0]: https://codeberg.org/magogi-admin/mgf_common/releases/tag/v0.1.0
|