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.
Files changed (56) hide show
  1. mgf_common-0.1.0/.gitignore +55 -0
  2. mgf_common-0.1.0/.import_linter_cache/.gitignore +2 -0
  3. mgf_common-0.1.0/.import_linter_cache/064c9363bb2a6e3bc05545c81c5891a901373aa6.data.json +1 -0
  4. mgf_common-0.1.0/.import_linter_cache/4d7cd651a3735c71269a0e4b8186cb8d7092dd66.data.json +1 -0
  5. mgf_common-0.1.0/.import_linter_cache/CACHEDIR.TAG +3 -0
  6. mgf_common-0.1.0/.import_linter_cache/mgf.common.meta.json +1 -0
  7. mgf_common-0.1.0/.importlinter +52 -0
  8. mgf_common-0.1.0/.woodpecker.yml +105 -0
  9. mgf_common-0.1.0/CHANGELOG.md +93 -0
  10. mgf_common-0.1.0/COMMON.md +825 -0
  11. mgf_common-0.1.0/LICENSE +21 -0
  12. mgf_common-0.1.0/PKG-INFO +143 -0
  13. mgf_common-0.1.0/README.md +100 -0
  14. mgf_common-0.1.0/docs/standards/COMMON_COMPONENTS.md +625 -0
  15. mgf_common-0.1.0/docs/standards/CONFIGURATION.md +852 -0
  16. mgf_common-0.1.0/docs/standards/DESIGN_PRINCIPLES.md +511 -0
  17. mgf_common-0.1.0/docs/standards/ERROR_HANDLING.md +955 -0
  18. mgf_common-0.1.0/docs/standards/LOGGING.md +1084 -0
  19. mgf_common-0.1.0/docs/standards/README.md +212 -0
  20. mgf_common-0.1.0/pyproject.toml +199 -0
  21. mgf_common-0.1.0/src/mgf/common/__init__.py +35 -0
  22. mgf_common-0.1.0/src/mgf/common/_identity.py +69 -0
  23. mgf_common-0.1.0/src/mgf/common/config/__init__.py +43 -0
  24. mgf_common-0.1.0/src/mgf/common/config/_loader.py +185 -0
  25. mgf_common-0.1.0/src/mgf/common/config/_paths.py +114 -0
  26. mgf_common-0.1.0/src/mgf/common/config/_settings.py +185 -0
  27. mgf_common-0.1.0/src/mgf/common/exceptions/__init__.py +65 -0
  28. mgf_common-0.1.0/src/mgf/common/exceptions/_base.py +49 -0
  29. mgf_common-0.1.0/src/mgf/common/exceptions/_well_known.py +213 -0
  30. mgf_common-0.1.0/src/mgf/common/observability/__init__.py +44 -0
  31. mgf_common-0.1.0/src/mgf/common/observability/crash_reporter.py +234 -0
  32. mgf_common-0.1.0/src/mgf/common/observability/excepthook.py +79 -0
  33. mgf_common-0.1.0/src/mgf/common/observability/json_formatter.py +171 -0
  34. mgf_common-0.1.0/src/mgf/common/observability/logging_setup.py +258 -0
  35. mgf_common-0.1.0/src/mgf/common/observability/otel_setup.py +206 -0
  36. mgf_common-0.1.0/src/mgf/common/observability/redaction.py +134 -0
  37. mgf_common-0.1.0/src/mgf/common/observability/text_formatter.py +111 -0
  38. mgf_common-0.1.0/src/mgf/common/observability/threading_excepthook.py +62 -0
  39. mgf_common-0.1.0/src/mgf/common/py.typed +0 -0
  40. mgf_common-0.1.0/tests/__init__.py +0 -0
  41. mgf_common-0.1.0/tests/unit/__init__.py +0 -0
  42. mgf_common-0.1.0/tests/unit/config/__init__.py +0 -0
  43. mgf_common-0.1.0/tests/unit/config/test_loader.py +367 -0
  44. mgf_common-0.1.0/tests/unit/config/test_paths.py +148 -0
  45. mgf_common-0.1.0/tests/unit/observability/__init__.py +0 -0
  46. mgf_common-0.1.0/tests/unit/observability/conftest.py +85 -0
  47. mgf_common-0.1.0/tests/unit/observability/test_crash_reporter.py +159 -0
  48. mgf_common-0.1.0/tests/unit/observability/test_excepthooks.py +162 -0
  49. mgf_common-0.1.0/tests/unit/observability/test_json_formatter.py +124 -0
  50. mgf_common-0.1.0/tests/unit/observability/test_logging_setup.py +203 -0
  51. mgf_common-0.1.0/tests/unit/observability/test_otel_setup.py +165 -0
  52. mgf_common-0.1.0/tests/unit/observability/test_redaction.py +166 -0
  53. mgf_common-0.1.0/tests/unit/observability/test_text_formatter.py +82 -0
  54. mgf_common-0.1.0/tests/unit/test_exceptions_base.py +190 -0
  55. mgf_common-0.1.0/tests/unit/test_identity.py +69 -0
  56. 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,2 @@
1
+ # Automatically created by Grimp.
2
+ *
@@ -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,3 @@
1
+ Signature: 8a477f597d28d172789f06886806bc55
2
+ # This file is a cache directory tag automatically created by Grimp.
3
+ # For information about cache directory tags see https://bford.info/cachedir/
@@ -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