macpkg-migrate-core 0.3.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.
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: macpkg-migrate-core
3
+ Version: 0.3.0
4
+ Summary: Manager-neutral planning and safety primitives for Mac package migration
5
+ License: MIT
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+
9
+ # macpkg-migrate-core
10
+
11
+ Lightweight, manager-neutral planning and safety primitives shared by
12
+ `brew2port`, `brew2fink`, and `macpkg-migrate`.
13
+
14
+ The package handles catalog snapshot loading, exact candidate selection,
15
+ review-only near-hit policy, serializable migration plan records, and the
16
+ shared no-implicit-removal safety rule. It never invokes Homebrew, MacPorts,
17
+ Fink, or any other package manager.
18
+
19
+ The public API is available from `macpkg_migrate_core`:
20
+
21
+ ```python
22
+ from macpkg_migrate_core import Identity, candidates_for, plan_record
23
+ ```
24
+
25
+ Manager-specific applications retain inventory, target availability checks,
26
+ installation, verification, rollback guidance, and user-facing workflows.
27
+
28
+ Releases are published to PyPI from the `pypi` GitHub environment using
29
+ trusted publishing. Configure `tomck/macpkg-migrate-core` as a trusted
30
+ publisher on PyPI before publishing the first release.
31
+
32
+ ## First PyPI release
33
+
34
+ Publishing uses PyPI Trusted Publishing through GitHub Actions; no API token,
35
+ GPG key, or local signing setup is required. A repository administrator must
36
+ first create the project on PyPI (or configure its pending publisher) with:
37
+
38
+ - Owner: `tomck`
39
+ - Repository: `macpkg-migrate-core`
40
+ - Workflow: `publish.yml`
41
+ - GitHub environment: `pypi`
42
+
43
+ The workflow requests the required OIDC permission and publishes only when a
44
+ GitHub Release is marked published. After the PyPI publisher is saved, create
45
+ a GitHub Release for tag `v0.3.0`. The action builds and publishes the package
46
+ without storing a PyPI credential in GitHub.
47
+
48
+ After the workflow succeeds, verify the public installation in a clean
49
+ environment:
50
+
51
+ ```sh
52
+ python3 -m venv /tmp/macpkg-migrate-core-check
53
+ . /tmp/macpkg-migrate-core-check/bin/activate
54
+ python -m pip install --upgrade pip
55
+ python -m pip install macpkg-migrate-core==0.3.0
56
+ python -c 'import macpkg_migrate_core; print(macpkg_migrate_core.__version__)'
57
+ ```
58
+
59
+ Update consumer lock/checksum or Homebrew formula metadata only after this
60
+ clean installation succeeds.
@@ -0,0 +1,52 @@
1
+ # macpkg-migrate-core
2
+
3
+ Lightweight, manager-neutral planning and safety primitives shared by
4
+ `brew2port`, `brew2fink`, and `macpkg-migrate`.
5
+
6
+ The package handles catalog snapshot loading, exact candidate selection,
7
+ review-only near-hit policy, serializable migration plan records, and the
8
+ shared no-implicit-removal safety rule. It never invokes Homebrew, MacPorts,
9
+ Fink, or any other package manager.
10
+
11
+ The public API is available from `macpkg_migrate_core`:
12
+
13
+ ```python
14
+ from macpkg_migrate_core import Identity, candidates_for, plan_record
15
+ ```
16
+
17
+ Manager-specific applications retain inventory, target availability checks,
18
+ installation, verification, rollback guidance, and user-facing workflows.
19
+
20
+ Releases are published to PyPI from the `pypi` GitHub environment using
21
+ trusted publishing. Configure `tomck/macpkg-migrate-core` as a trusted
22
+ publisher on PyPI before publishing the first release.
23
+
24
+ ## First PyPI release
25
+
26
+ Publishing uses PyPI Trusted Publishing through GitHub Actions; no API token,
27
+ GPG key, or local signing setup is required. A repository administrator must
28
+ first create the project on PyPI (or configure its pending publisher) with:
29
+
30
+ - Owner: `tomck`
31
+ - Repository: `macpkg-migrate-core`
32
+ - Workflow: `publish.yml`
33
+ - GitHub environment: `pypi`
34
+
35
+ The workflow requests the required OIDC permission and publishes only when a
36
+ GitHub Release is marked published. After the PyPI publisher is saved, create
37
+ a GitHub Release for tag `v0.3.0`. The action builds and publishes the package
38
+ without storing a PyPI credential in GitHub.
39
+
40
+ After the workflow succeeds, verify the public installation in a clean
41
+ environment:
42
+
43
+ ```sh
44
+ python3 -m venv /tmp/macpkg-migrate-core-check
45
+ . /tmp/macpkg-migrate-core-check/bin/activate
46
+ python -m pip install --upgrade pip
47
+ python -m pip install macpkg-migrate-core==0.3.0
48
+ python -c 'import macpkg_migrate_core; print(macpkg_migrate_core.__version__)'
49
+ ```
50
+
51
+ Update consumer lock/checksum or Homebrew formula metadata only after this
52
+ clean installation succeeds.
@@ -0,0 +1,22 @@
1
+ """Lightweight shared planning and safety primitives for Mac package migration."""
2
+
3
+ __version__ = "0.3.0"
4
+
5
+ from .core import (
6
+ AUTOMATIC,
7
+ REVIEW,
8
+ Candidate,
9
+ Identity,
10
+ candidates_for,
11
+ choose_candidate,
12
+ dry_run,
13
+ install_allowed,
14
+ load_snapshot,
15
+ plan_record,
16
+ )
17
+
18
+ __all__ = [
19
+ "AUTOMATIC", "REVIEW", "Candidate", "Identity", "candidates_for",
20
+ "choose_candidate", "dry_run", "install_allowed", "load_snapshot",
21
+ "plan_record",
22
+ ]
@@ -0,0 +1,177 @@
1
+ """Manager-neutral migration planning primitives.
2
+
3
+ This package deliberately knows nothing about Homebrew, MacPorts, or Fink
4
+ commands. It handles catalog records, candidate selection, plan records, and
5
+ the shared safety policy used by manager-specific front ends.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import sqlite3
12
+ from dataclasses import asdict, dataclass
13
+ from pathlib import Path
14
+ from typing import Any, Iterable, Mapping
15
+
16
+ AUTOMATIC = "automatic"
17
+ REVIEW = "needs-review"
18
+ SAFE_STATUSES = frozenset({AUTOMATIC, "installed"})
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class Identity:
23
+ manager: str
24
+ package_type: str
25
+ native_name: str
26
+
27
+ @classmethod
28
+ def from_record(cls, record: Mapping[str, Any]) -> "Identity":
29
+ return cls(
30
+ record.get("manager", ""),
31
+ record.get("package_type", record.get("type", "")),
32
+ record.get("native_name", record.get("name", "")),
33
+ )
34
+
35
+ def as_dict(self) -> dict[str, str]:
36
+ return asdict(self)
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class Candidate:
41
+ target: Identity
42
+ relation_type: str
43
+ confidence: float
44
+ review_status: str
45
+ matching_method: str
46
+ evidence: tuple[Any, ...]
47
+ source_catalog_versions: Mapping[str, Any]
48
+
49
+ @classmethod
50
+ def from_relation(cls, relation: Mapping[str, Any]) -> "Candidate":
51
+ return cls(
52
+ target=Identity.from_record(relation.get("target", {})),
53
+ relation_type=relation.get("type", "equivalent"),
54
+ confidence=float(relation.get("confidence", 0)),
55
+ review_status=relation.get("review_status", REVIEW),
56
+ matching_method=relation.get("matching_method", "unknown"),
57
+ evidence=tuple(relation.get("evidence", ())),
58
+ source_catalog_versions=relation.get("source_catalog_versions", {}),
59
+ )
60
+
61
+ def as_dict(self) -> dict[str, Any]:
62
+ result = asdict(self)
63
+ result["target"] = self.target.as_dict()
64
+ result["evidence"] = list(self.evidence)
65
+ result["source_catalog_versions"] = dict(self.source_catalog_versions)
66
+ return result
67
+
68
+
69
+ def load_snapshot(path: str | Path) -> dict[str, Any]:
70
+ """Load a catalog JSON snapshot or SQLite export without network access."""
71
+ path = Path(path)
72
+ if path.suffix in {".sqlite", ".db"}:
73
+ with sqlite3.connect(path) as connection:
74
+ packages = [dict(row) for row in connection.execute("SELECT * FROM packages")]
75
+ relations = [dict(row) for row in connection.execute("SELECT * FROM relations")]
76
+ metadata = {
77
+ key: json.loads(value)
78
+ for key, value in connection.execute("SELECT key, value FROM metadata")
79
+ } if _has_table(connection, "metadata") else {}
80
+ return {"packages": packages, "relations": relations, **metadata}
81
+ data = json.loads(path.read_text())
82
+ if isinstance(data, list):
83
+ return {"relations": data}
84
+ if not isinstance(data, dict):
85
+ raise ValueError("catalog snapshot must be a JSON object or relation array")
86
+ return data
87
+
88
+
89
+ def _has_table(connection: sqlite3.Connection, name: str) -> bool:
90
+ return connection.execute(
91
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", (name,)
92
+ ).fetchone() is not None
93
+
94
+
95
+ def candidates_for(
96
+ relations: Iterable[Mapping[str, Any]], source: Identity
97
+ ) -> list[Candidate]:
98
+ """Return catalog candidates whose source exactly matches ``source``."""
99
+ candidates = []
100
+ for relation in relations:
101
+ if Identity.from_record(relation.get("source", {})) == source:
102
+ candidate = Candidate.from_relation(relation)
103
+ if candidate.target.native_name:
104
+ candidates.append(candidate)
105
+ return candidates
106
+
107
+
108
+ def choose_candidate(
109
+ candidates: Iterable[Candidate], preference: Iterable[str] = ()
110
+ ) -> Candidate | None:
111
+ """Choose only an automatic candidate, never a review-only near-hit."""
112
+ preference = tuple(preference)
113
+ eligible = [candidate for candidate in candidates if candidate.review_status == AUTOMATIC]
114
+ eligible.sort(
115
+ key=lambda candidate: (
116
+ -candidate.confidence,
117
+ preference.index(candidate.target.manager)
118
+ if candidate.target.manager in preference else len(preference),
119
+ candidate.target.native_name,
120
+ )
121
+ )
122
+ return eligible[0] if eligible else None
123
+
124
+
125
+ def plan_record(
126
+ source: Identity,
127
+ candidates: Iterable[Candidate],
128
+ catalog_version: str | None,
129
+ preference: Iterable[str] = (),
130
+ ) -> dict[str, Any]:
131
+ """Build a serializable, manager-neutral migration plan record."""
132
+ candidates = list(candidates)
133
+ chosen = choose_candidate(candidates, preference)
134
+ recommendation = None
135
+ if chosen:
136
+ recommendation = {
137
+ "manager": chosen.target.manager,
138
+ "type": chosen.target.package_type,
139
+ "name": chosen.target.native_name,
140
+ "confidence": chosen.confidence,
141
+ "status": chosen.review_status,
142
+ "method": chosen.matching_method,
143
+ "relation_type": chosen.relation_type,
144
+ "evidence": list(chosen.evidence),
145
+ "source_catalog_versions": dict(chosen.source_catalog_versions),
146
+ "review_status": chosen.review_status,
147
+ "target": chosen.target.as_dict(),
148
+ }
149
+ return {
150
+ "source": source.as_dict(),
151
+ "catalog_version": catalog_version,
152
+ "candidates": [candidate.as_dict() for candidate in candidates],
153
+ "recommendation": recommendation,
154
+ "action": "consolidate" if chosen else "review",
155
+ "install_authorized": bool(chosen),
156
+ }
157
+
158
+
159
+ def install_allowed(record: Mapping[str, Any]) -> bool:
160
+ """Return whether shared policy permits unattended installation."""
161
+ recommendation = record.get("recommendation") or {}
162
+ return bool(
163
+ record.get("install_authorized")
164
+ and recommendation.get("review_status") == AUTOMATIC
165
+ and recommendation.get("target", {}).get("manager") in {"macports", "fink"}
166
+ )
167
+
168
+
169
+ def dry_run(record: Mapping[str, Any]) -> dict[str, Any]:
170
+ """Return a safe preview result; this function never invokes a manager."""
171
+ return {
172
+ "source": record.get("source"),
173
+ "recommendation": record.get("recommendation"),
174
+ "would_install": install_allowed(record),
175
+ "would_remove": False,
176
+ "rollback": "No packages changed; discard the plan or uninstall only after explicit review.",
177
+ }
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: macpkg-migrate-core
3
+ Version: 0.3.0
4
+ Summary: Manager-neutral planning and safety primitives for Mac package migration
5
+ License: MIT
6
+ Requires-Python: >=3.9
7
+ Description-Content-Type: text/markdown
8
+
9
+ # macpkg-migrate-core
10
+
11
+ Lightweight, manager-neutral planning and safety primitives shared by
12
+ `brew2port`, `brew2fink`, and `macpkg-migrate`.
13
+
14
+ The package handles catalog snapshot loading, exact candidate selection,
15
+ review-only near-hit policy, serializable migration plan records, and the
16
+ shared no-implicit-removal safety rule. It never invokes Homebrew, MacPorts,
17
+ Fink, or any other package manager.
18
+
19
+ The public API is available from `macpkg_migrate_core`:
20
+
21
+ ```python
22
+ from macpkg_migrate_core import Identity, candidates_for, plan_record
23
+ ```
24
+
25
+ Manager-specific applications retain inventory, target availability checks,
26
+ installation, verification, rollback guidance, and user-facing workflows.
27
+
28
+ Releases are published to PyPI from the `pypi` GitHub environment using
29
+ trusted publishing. Configure `tomck/macpkg-migrate-core` as a trusted
30
+ publisher on PyPI before publishing the first release.
31
+
32
+ ## First PyPI release
33
+
34
+ Publishing uses PyPI Trusted Publishing through GitHub Actions; no API token,
35
+ GPG key, or local signing setup is required. A repository administrator must
36
+ first create the project on PyPI (or configure its pending publisher) with:
37
+
38
+ - Owner: `tomck`
39
+ - Repository: `macpkg-migrate-core`
40
+ - Workflow: `publish.yml`
41
+ - GitHub environment: `pypi`
42
+
43
+ The workflow requests the required OIDC permission and publishes only when a
44
+ GitHub Release is marked published. After the PyPI publisher is saved, create
45
+ a GitHub Release for tag `v0.3.0`. The action builds and publishes the package
46
+ without storing a PyPI credential in GitHub.
47
+
48
+ After the workflow succeeds, verify the public installation in a clean
49
+ environment:
50
+
51
+ ```sh
52
+ python3 -m venv /tmp/macpkg-migrate-core-check
53
+ . /tmp/macpkg-migrate-core-check/bin/activate
54
+ python -m pip install --upgrade pip
55
+ python -m pip install macpkg-migrate-core==0.3.0
56
+ python -c 'import macpkg_migrate_core; print(macpkg_migrate_core.__version__)'
57
+ ```
58
+
59
+ Update consumer lock/checksum or Homebrew formula metadata only after this
60
+ clean installation succeeds.
@@ -0,0 +1,10 @@
1
+ README.md
2
+ __init__.py
3
+ core.py
4
+ pyproject.toml
5
+ ./__init__.py
6
+ ./core.py
7
+ macpkg_migrate_core.egg-info/PKG-INFO
8
+ macpkg_migrate_core.egg-info/SOURCES.txt
9
+ macpkg_migrate_core.egg-info/dependency_links.txt
10
+ macpkg_migrate_core.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ macpkg_migrate_core
@@ -0,0 +1,15 @@
1
+ [project]
2
+ name = "macpkg-migrate-core"
3
+ version = "0.3.0"
4
+ description = "Manager-neutral planning and safety primitives for Mac package migration"
5
+ readme = "README.md"
6
+ requires-python = ">=3.9"
7
+ license = {text = "MIT"}
8
+
9
+ [build-system]
10
+ requires = ["setuptools>=61"]
11
+ build-backend = "setuptools.build_meta"
12
+
13
+ [tool.setuptools]
14
+ packages = ["macpkg_migrate_core"]
15
+ package-dir = {"macpkg_migrate_core" = "."}
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+