sam3-mlx 0.1.0__py3-none-any.whl
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.
- sam3_mlx/__init__.py +55 -0
- sam3_mlx/_device.py +2 -0
- sam3_mlx/_unsupported.py +363 -0
- sam3_mlx/assets/bpe_simple_vocab_16e6.txt.gz +0 -0
- sam3_mlx/convert.py +298 -0
- sam3_mlx/logger.py +67 -0
- sam3_mlx/mlx_runtime.py +85 -0
- sam3_mlx/model/__init__.py +1 -0
- sam3_mlx/model/act_ckpt_utils.py +36 -0
- sam3_mlx/model/box_ops.py +228 -0
- sam3_mlx/model/data_misc.py +362 -0
- sam3_mlx/model/decoder.py +1496 -0
- sam3_mlx/model/edt.py +138 -0
- sam3_mlx/model/encoder.py +506 -0
- sam3_mlx/model/geometry_encoders.py +830 -0
- sam3_mlx/model/grid_sample_mlx.py +198 -0
- sam3_mlx/model/io_utils.py +734 -0
- sam3_mlx/model/maskformer_segmentation.py +388 -0
- sam3_mlx/model/memory.py +222 -0
- sam3_mlx/model/model_misc.py +1162 -0
- sam3_mlx/model/multiplex_mask_decoder.py +512 -0
- sam3_mlx/model/multiplex_utils.py +459 -0
- sam3_mlx/model/necks.py +321 -0
- sam3_mlx/model/position_encoding.py +111 -0
- sam3_mlx/model/roi_align.py +198 -0
- sam3_mlx/model/sam1_task_predictor.py +441 -0
- sam3_mlx/model/sam3_base_predictor.py +270 -0
- sam3_mlx/model/sam3_image.py +777 -0
- sam3_mlx/model/sam3_image_processor.py +587 -0
- sam3_mlx/model/sam3_multiplex_base.py +4330 -0
- sam3_mlx/model/sam3_multiplex_detector.py +764 -0
- sam3_mlx/model/sam3_multiplex_detector_utils.py +641 -0
- sam3_mlx/model/sam3_multiplex_tracking.py +3067 -0
- sam3_mlx/model/sam3_multiplex_video_predictor.py +43 -0
- sam3_mlx/model/sam3_tracker_base.py +1012 -0
- sam3_mlx/model/sam3_tracker_utils.py +465 -0
- sam3_mlx/model/sam3_tracking_predictor.py +1461 -0
- sam3_mlx/model/sam3_video_base.py +565 -0
- sam3_mlx/model/sam3_video_inference.py +675 -0
- sam3_mlx/model/sam3_video_predictor.py +151 -0
- sam3_mlx/model/text_encoder_ve.py +324 -0
- sam3_mlx/model/tokenizer_ve.py +248 -0
- sam3_mlx/model/utils/__init__.py +43 -0
- sam3_mlx/model/utils/misc.py +116 -0
- sam3_mlx/model/utils/sam1_utils.py +94 -0
- sam3_mlx/model/utils/sam2_utils.py +240 -0
- sam3_mlx/model/video_tracking_multiplex.py +3176 -0
- sam3_mlx/model/video_tracking_multiplex_demo.py +1508 -0
- sam3_mlx/model/vitdet.py +823 -0
- sam3_mlx/model/vl_combiner.py +354 -0
- sam3_mlx/model_builder.py +2729 -0
- sam3_mlx/perflib/__init__.py +7 -0
- sam3_mlx/perflib/associate_det_trk.py +112 -0
- sam3_mlx/perflib/compile.py +107 -0
- sam3_mlx/perflib/connected_components.py +125 -0
- sam3_mlx/perflib/fa3.py +101 -0
- sam3_mlx/perflib/fused.py +19 -0
- sam3_mlx/perflib/iou.py +152 -0
- sam3_mlx/perflib/masks_ops.py +199 -0
- sam3_mlx/perflib/nms.py +160 -0
- sam3_mlx/rle.py +244 -0
- sam3_mlx/sam/__init__.py +51 -0
- sam3_mlx/sam/common.py +66 -0
- sam3_mlx/sam/mask_decoder.py +275 -0
- sam3_mlx/sam/prompt_encoder.py +159 -0
- sam3_mlx/sam/rope.py +165 -0
- sam3_mlx/sam/transformer.py +266 -0
- sam3_mlx/visualization_utils.py +636 -0
- sam3_mlx-0.1.0.dist-info/METADATA +192 -0
- sam3_mlx-0.1.0.dist-info/RECORD +73 -0
- sam3_mlx-0.1.0.dist-info/WHEEL +5 -0
- sam3_mlx-0.1.0.dist-info/licenses/LICENSE +61 -0
- sam3_mlx-0.1.0.dist-info/top_level.txt +1 -0
sam3_mlx/__init__.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"Sam3MlxUnsupportedError",
|
|
7
|
+
"build_tracker",
|
|
8
|
+
"build_sam3_image_model",
|
|
9
|
+
"build_sam3_multiplex_video_model",
|
|
10
|
+
"build_sam3_multiplex_video_predictor",
|
|
11
|
+
"build_sam3_predictor",
|
|
12
|
+
"build_sam3_video_model",
|
|
13
|
+
"build_sam3_video_predictor",
|
|
14
|
+
"download_ckpt_from_hf",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def __getattr__(name: str):
|
|
19
|
+
if name == "Sam3MlxUnsupportedError":
|
|
20
|
+
from sam3_mlx._unsupported import Sam3MlxUnsupportedError
|
|
21
|
+
|
|
22
|
+
return Sam3MlxUnsupportedError
|
|
23
|
+
if name == "build_tracker":
|
|
24
|
+
from sam3_mlx.model_builder import build_tracker
|
|
25
|
+
|
|
26
|
+
return build_tracker
|
|
27
|
+
if name == "build_sam3_image_model":
|
|
28
|
+
from sam3_mlx.model_builder import build_sam3_image_model
|
|
29
|
+
|
|
30
|
+
return build_sam3_image_model
|
|
31
|
+
if name == "build_sam3_multiplex_video_model":
|
|
32
|
+
from sam3_mlx.model_builder import build_sam3_multiplex_video_model
|
|
33
|
+
|
|
34
|
+
return build_sam3_multiplex_video_model
|
|
35
|
+
if name == "build_sam3_multiplex_video_predictor":
|
|
36
|
+
from sam3_mlx.model_builder import build_sam3_multiplex_video_predictor
|
|
37
|
+
|
|
38
|
+
return build_sam3_multiplex_video_predictor
|
|
39
|
+
if name == "build_sam3_predictor":
|
|
40
|
+
from sam3_mlx.model_builder import build_sam3_predictor
|
|
41
|
+
|
|
42
|
+
return build_sam3_predictor
|
|
43
|
+
if name == "build_sam3_video_model":
|
|
44
|
+
from sam3_mlx.model_builder import build_sam3_video_model
|
|
45
|
+
|
|
46
|
+
return build_sam3_video_model
|
|
47
|
+
if name == "build_sam3_video_predictor":
|
|
48
|
+
from sam3_mlx.model_builder import build_sam3_video_predictor
|
|
49
|
+
|
|
50
|
+
return build_sam3_video_predictor
|
|
51
|
+
if name == "download_ckpt_from_hf":
|
|
52
|
+
from sam3_mlx.model_builder import download_ckpt_from_hf
|
|
53
|
+
|
|
54
|
+
return download_ckpt_from_hf
|
|
55
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
sam3_mlx/_device.py
ADDED
sam3_mlx/_unsupported.py
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""Canonical fail-fast contract for unsupported SAM3 MLX features."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from functools import wraps
|
|
7
|
+
from typing import Any, NamedTuple, NoReturn, TypeVar, cast
|
|
8
|
+
|
|
9
|
+
UnsupportedReason = str
|
|
10
|
+
|
|
11
|
+
# Single source of truth for the upstream SAM3 commit every port was audited
|
|
12
|
+
# against. Pass upstream_commit=... explicitly only when a file is re-ported
|
|
13
|
+
# from a different commit.
|
|
14
|
+
UPSTREAM_COMMIT = "2814fa619404a722d03e9a012e083e4f293a4e53"
|
|
15
|
+
|
|
16
|
+
REASONS = frozenset(
|
|
17
|
+
{
|
|
18
|
+
"agent-llm",
|
|
19
|
+
"eval-stack",
|
|
20
|
+
"flash-attn-3",
|
|
21
|
+
"image-interactivity",
|
|
22
|
+
"optional-dependency",
|
|
23
|
+
"port-gap",
|
|
24
|
+
"torch-autograd",
|
|
25
|
+
"torch-compile",
|
|
26
|
+
"torch-distributed",
|
|
27
|
+
"torchcodec",
|
|
28
|
+
"training-loop",
|
|
29
|
+
"triton-kernel",
|
|
30
|
+
"unsupported-device",
|
|
31
|
+
"video-multi-gpu",
|
|
32
|
+
"video-multiplex",
|
|
33
|
+
"video-tracker",
|
|
34
|
+
"xformers",
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
UNSUPPORTED_METADATA_ATTR = "__sam3_mlx_unsupported__"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class UnsupportedFeatureInfo(NamedTuple):
|
|
42
|
+
feature: str
|
|
43
|
+
reason: UnsupportedReason
|
|
44
|
+
alternative: str | None
|
|
45
|
+
detail: str | None
|
|
46
|
+
upstream_commit: str | None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Sam3MlxUnsupportedError(NotImplementedError):
|
|
50
|
+
"""Raised when an upstream SAM3 feature has no MLX implementation."""
|
|
51
|
+
|
|
52
|
+
def __init__(
|
|
53
|
+
self,
|
|
54
|
+
feature: str,
|
|
55
|
+
*,
|
|
56
|
+
reason: UnsupportedReason,
|
|
57
|
+
alternative: str | None = None,
|
|
58
|
+
detail: str | None = None,
|
|
59
|
+
upstream_commit: str | None = UPSTREAM_COMMIT,
|
|
60
|
+
) -> None:
|
|
61
|
+
if reason not in REASONS:
|
|
62
|
+
known = ", ".join(sorted(REASONS))
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"Unknown unsupported reason {reason!r}; use one of: {known}."
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
self.feature = feature
|
|
68
|
+
self.reason = reason
|
|
69
|
+
self.alternative = alternative
|
|
70
|
+
self.detail = detail
|
|
71
|
+
self.upstream_commit = upstream_commit
|
|
72
|
+
|
|
73
|
+
message = f"{feature} is not supported in sam3_mlx ({reason})."
|
|
74
|
+
if detail:
|
|
75
|
+
message = f"{message} {detail}"
|
|
76
|
+
if alternative:
|
|
77
|
+
message = f"{message} Use {alternative} instead."
|
|
78
|
+
if upstream_commit:
|
|
79
|
+
message = f"{message} Upstream source commit: {upstream_commit}."
|
|
80
|
+
super().__init__(message)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def unsupported(
|
|
84
|
+
feature: str,
|
|
85
|
+
*,
|
|
86
|
+
reason: UnsupportedReason,
|
|
87
|
+
alternative: str | None = None,
|
|
88
|
+
detail: str | None = None,
|
|
89
|
+
upstream_commit: str | None = UPSTREAM_COMMIT,
|
|
90
|
+
) -> Sam3MlxUnsupportedError:
|
|
91
|
+
return Sam3MlxUnsupportedError(
|
|
92
|
+
feature,
|
|
93
|
+
reason=reason,
|
|
94
|
+
alternative=alternative,
|
|
95
|
+
detail=detail,
|
|
96
|
+
upstream_commit=upstream_commit,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def raise_unsupported(
|
|
101
|
+
feature: str,
|
|
102
|
+
*,
|
|
103
|
+
reason: UnsupportedReason,
|
|
104
|
+
alternative: str | None = None,
|
|
105
|
+
detail: str | None = None,
|
|
106
|
+
upstream_commit: str | None = UPSTREAM_COMMIT,
|
|
107
|
+
) -> NoReturn:
|
|
108
|
+
raise unsupported(
|
|
109
|
+
feature,
|
|
110
|
+
reason=reason,
|
|
111
|
+
alternative=alternative,
|
|
112
|
+
detail=detail,
|
|
113
|
+
upstream_commit=upstream_commit,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def unsupported_function(
|
|
121
|
+
feature: str,
|
|
122
|
+
*,
|
|
123
|
+
reason: UnsupportedReason,
|
|
124
|
+
alternative: str | None = None,
|
|
125
|
+
detail: str | None = None,
|
|
126
|
+
upstream_commit: str | None = UPSTREAM_COMMIT,
|
|
127
|
+
) -> Callable[[F], F]:
|
|
128
|
+
"""Decorate an import-compatible function that always fails fast."""
|
|
129
|
+
|
|
130
|
+
info = UnsupportedFeatureInfo(
|
|
131
|
+
feature=feature,
|
|
132
|
+
reason=reason,
|
|
133
|
+
alternative=alternative,
|
|
134
|
+
detail=detail,
|
|
135
|
+
upstream_commit=upstream_commit,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def decorator(fn: F) -> F:
|
|
139
|
+
@wraps(fn)
|
|
140
|
+
def wrapper(*args: Any, **kwargs: Any) -> NoReturn:
|
|
141
|
+
raise_unsupported(
|
|
142
|
+
feature,
|
|
143
|
+
reason=reason,
|
|
144
|
+
alternative=alternative,
|
|
145
|
+
detail=detail,
|
|
146
|
+
upstream_commit=upstream_commit,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
setattr(wrapper, UNSUPPORTED_METADATA_ATTR, info)
|
|
150
|
+
return cast(F, wrapper)
|
|
151
|
+
|
|
152
|
+
return decorator
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def reject_kwargs(
|
|
156
|
+
feature: str,
|
|
157
|
+
*,
|
|
158
|
+
reason: UnsupportedReason,
|
|
159
|
+
alternative: str | None = None,
|
|
160
|
+
detail: str | None = None,
|
|
161
|
+
upstream_commit: str | None = UPSTREAM_COMMIT,
|
|
162
|
+
**kwargs: Any,
|
|
163
|
+
) -> None:
|
|
164
|
+
"""Reject truthy unsupported keyword flags with the canonical error type."""
|
|
165
|
+
|
|
166
|
+
for name, value in kwargs.items():
|
|
167
|
+
if value:
|
|
168
|
+
raise_unsupported(
|
|
169
|
+
f"{feature}({name}={value!r})",
|
|
170
|
+
reason=reason,
|
|
171
|
+
alternative=alternative,
|
|
172
|
+
detail=detail,
|
|
173
|
+
upstream_commit=upstream_commit,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def get_unsupported_info(obj: Any) -> UnsupportedFeatureInfo | None:
|
|
178
|
+
"""Return the registered UnsupportedFeatureInfo for an object, if any."""
|
|
179
|
+
info = getattr(obj, UNSUPPORTED_METADATA_ATTR, None)
|
|
180
|
+
if isinstance(info, UnsupportedFeatureInfo):
|
|
181
|
+
return info
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def unsupported_features(
|
|
186
|
+
package: str = "sam3_mlx",
|
|
187
|
+
*,
|
|
188
|
+
on_import_error: Callable[[str, BaseException], None] | None = None,
|
|
189
|
+
) -> list[UnsupportedFeatureInfo]:
|
|
190
|
+
"""Walk `package` and collect every @unsupported_function entry.
|
|
191
|
+
|
|
192
|
+
Submodules that fail to import (e.g. optional heavy deps missing) are
|
|
193
|
+
skipped silently unless ``on_import_error`` is supplied.
|
|
194
|
+
"""
|
|
195
|
+
import importlib
|
|
196
|
+
import pkgutil
|
|
197
|
+
|
|
198
|
+
seen_ids: set[int] = set()
|
|
199
|
+
collected: list[UnsupportedFeatureInfo] = []
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
root = importlib.import_module(package)
|
|
203
|
+
except BaseException as exc:
|
|
204
|
+
if on_import_error is not None:
|
|
205
|
+
on_import_error(package, exc)
|
|
206
|
+
return collected
|
|
207
|
+
|
|
208
|
+
def _scan(module: Any) -> None:
|
|
209
|
+
for name in dir(module):
|
|
210
|
+
if name.startswith("__"):
|
|
211
|
+
continue
|
|
212
|
+
try:
|
|
213
|
+
obj = getattr(module, name)
|
|
214
|
+
except BaseException:
|
|
215
|
+
continue
|
|
216
|
+
info = get_unsupported_info(obj)
|
|
217
|
+
if info is None:
|
|
218
|
+
continue
|
|
219
|
+
ident = id(obj)
|
|
220
|
+
if ident in seen_ids:
|
|
221
|
+
continue
|
|
222
|
+
seen_ids.add(ident)
|
|
223
|
+
collected.append(info)
|
|
224
|
+
|
|
225
|
+
_scan(root)
|
|
226
|
+
if hasattr(root, "__path__"):
|
|
227
|
+
for module_info in pkgutil.walk_packages(
|
|
228
|
+
root.__path__, prefix=root.__name__ + "."
|
|
229
|
+
):
|
|
230
|
+
try:
|
|
231
|
+
submodule = importlib.import_module(module_info.name)
|
|
232
|
+
except BaseException as exc:
|
|
233
|
+
if on_import_error is not None:
|
|
234
|
+
on_import_error(module_info.name, exc)
|
|
235
|
+
continue
|
|
236
|
+
_scan(submodule)
|
|
237
|
+
|
|
238
|
+
collected.sort(key=lambda info: (info.reason, info.feature))
|
|
239
|
+
return collected
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
_UNSUPPORTED_DOCS_MARKER_BEGIN = "<!-- BEGIN AUTOGENERATED:UNSUPPORTED -->"
|
|
243
|
+
_UNSUPPORTED_DOCS_MARKER_END = "<!-- END AUTOGENERATED:UNSUPPORTED -->"
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def render_unsupported_markdown(
|
|
247
|
+
features: list[UnsupportedFeatureInfo] | None = None,
|
|
248
|
+
) -> str:
|
|
249
|
+
"""Render the registered unsupported features as a Markdown section.
|
|
250
|
+
|
|
251
|
+
Output is deterministic so the table can be checked into source and asserted
|
|
252
|
+
in CI. Pass ``features`` to override the registry walk; ``None`` uses
|
|
253
|
+
``unsupported_features()``.
|
|
254
|
+
"""
|
|
255
|
+
if features is None:
|
|
256
|
+
features = unsupported_features()
|
|
257
|
+
|
|
258
|
+
lines: list[str] = [
|
|
259
|
+
"## Unsupported upstream features",
|
|
260
|
+
"",
|
|
261
|
+
(
|
|
262
|
+
"Every entry below corresponds to an `@unsupported_function` stub. "
|
|
263
|
+
"Invoking one raises `sam3_mlx.Sam3MlxUnsupportedError` "
|
|
264
|
+
"(a subclass of `NotImplementedError`)."
|
|
265
|
+
),
|
|
266
|
+
"",
|
|
267
|
+
f"Total registered features: **{len(features)}**.",
|
|
268
|
+
"",
|
|
269
|
+
"| Reason | Feature | Alternative | Upstream commit |",
|
|
270
|
+
"| --- | --- | --- | --- |",
|
|
271
|
+
]
|
|
272
|
+
for info in features:
|
|
273
|
+
feature = info.feature.replace("|", r"\|")
|
|
274
|
+
alternative = (info.alternative or "").replace("|", r"\|")
|
|
275
|
+
commit = (info.upstream_commit or "")[:7]
|
|
276
|
+
lines.append(f"| `{info.reason}` | `{feature}` | {alternative} | {commit} |")
|
|
277
|
+
return "\n".join(lines) + "\n"
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def write_unsupported_section(path: Any) -> bool:
|
|
281
|
+
"""Update the auto-generated section in ``path`` with the latest render.
|
|
282
|
+
|
|
283
|
+
The file must contain the BEGIN/END HTML-comment markers. Returns True if
|
|
284
|
+
the file content changed.
|
|
285
|
+
"""
|
|
286
|
+
import pathlib
|
|
287
|
+
|
|
288
|
+
p = pathlib.Path(path)
|
|
289
|
+
original = p.read_text()
|
|
290
|
+
if (
|
|
291
|
+
_UNSUPPORTED_DOCS_MARKER_BEGIN not in original
|
|
292
|
+
or _UNSUPPORTED_DOCS_MARKER_END not in original
|
|
293
|
+
):
|
|
294
|
+
raise ValueError(
|
|
295
|
+
f"{p} is missing the autogenerated unsupported-features markers."
|
|
296
|
+
)
|
|
297
|
+
before, _, rest = original.partition(_UNSUPPORTED_DOCS_MARKER_BEGIN)
|
|
298
|
+
_, _, after = rest.partition(_UNSUPPORTED_DOCS_MARKER_END)
|
|
299
|
+
block = (
|
|
300
|
+
f"{_UNSUPPORTED_DOCS_MARKER_BEGIN}\n"
|
|
301
|
+
f"{render_unsupported_markdown()}"
|
|
302
|
+
f"{_UNSUPPORTED_DOCS_MARKER_END}"
|
|
303
|
+
)
|
|
304
|
+
updated = f"{before}{block}{after}"
|
|
305
|
+
if updated == original:
|
|
306
|
+
return False
|
|
307
|
+
p.write_text(updated)
|
|
308
|
+
return True
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _cli() -> int:
|
|
312
|
+
"""``python -m sam3_mlx._unsupported [--write PATH] [--check PATH]``."""
|
|
313
|
+
import argparse
|
|
314
|
+
import pathlib
|
|
315
|
+
import sys
|
|
316
|
+
|
|
317
|
+
parser = argparse.ArgumentParser(prog="sam3_mlx._unsupported")
|
|
318
|
+
parser.add_argument("--write", help="Path to update in place (e.g. UPSTREAM.md).")
|
|
319
|
+
parser.add_argument(
|
|
320
|
+
"--check",
|
|
321
|
+
help="Path to verify; exit 1 if the autogenerated section is stale.",
|
|
322
|
+
)
|
|
323
|
+
args = parser.parse_args()
|
|
324
|
+
|
|
325
|
+
if args.write:
|
|
326
|
+
changed = write_unsupported_section(args.write)
|
|
327
|
+
sys.stdout.write(f"{'updated' if changed else 'unchanged'}: {args.write}\n")
|
|
328
|
+
return 0
|
|
329
|
+
|
|
330
|
+
if args.check:
|
|
331
|
+
p = pathlib.Path(args.check)
|
|
332
|
+
original = p.read_text()
|
|
333
|
+
if _UNSUPPORTED_DOCS_MARKER_BEGIN not in original:
|
|
334
|
+
sys.stderr.write(f"{p}: missing autogenerated markers.\n")
|
|
335
|
+
return 1
|
|
336
|
+
before, _, rest = original.partition(_UNSUPPORTED_DOCS_MARKER_BEGIN)
|
|
337
|
+
_, _, after = rest.partition(_UNSUPPORTED_DOCS_MARKER_END)
|
|
338
|
+
block = (
|
|
339
|
+
f"{_UNSUPPORTED_DOCS_MARKER_BEGIN}\n"
|
|
340
|
+
f"{render_unsupported_markdown()}"
|
|
341
|
+
f"{_UNSUPPORTED_DOCS_MARKER_END}"
|
|
342
|
+
)
|
|
343
|
+
expected = f"{before}{block}{after}"
|
|
344
|
+
if expected != original:
|
|
345
|
+
sys.stderr.write(
|
|
346
|
+
f"{p}: autogenerated section is stale. "
|
|
347
|
+
f"Run `python -m sam3_mlx._unsupported --write {p}`.\n"
|
|
348
|
+
)
|
|
349
|
+
return 1
|
|
350
|
+
sys.stdout.write(f"{p}: up to date.\n")
|
|
351
|
+
return 0
|
|
352
|
+
|
|
353
|
+
sys.stdout.write(render_unsupported_markdown())
|
|
354
|
+
return 0
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
if __name__ == "__main__": # pragma: no cover - CLI entrypoint
|
|
358
|
+
# Re-import the package copy so the registry walker's isinstance check
|
|
359
|
+
# against UnsupportedFeatureInfo sees the same class as the decorated stubs
|
|
360
|
+
# (running `python -m sam3_mlx._unsupported` otherwise loads two copies).
|
|
361
|
+
import sam3_mlx._unsupported as _pkg
|
|
362
|
+
|
|
363
|
+
raise SystemExit(_pkg._cli())
|
|
Binary file
|