release: v1.5.1 — bind guard failed open, [full] would not install, DIMSCALE did nothing

Three defects found after 1.5.0 reached PyPI, two of them by the adversarial
pass over the rewritten README.

The HTTP bind guard failed open on the launch path it was written for.
`_guarded_run_async` read the bind address from the `host` kwarg and treated an
absent one as loopback. It is not: `fastmcp run` forwards that kwarg only when
`--host` was passed, and fastmcp otherwise resolves the host from
`fastmcp.settings.host`, which is environment-backed. So

    FASTMCP_HOST=0.0.0.0 fastmcp run server.py:mcp --transport http

bound every interface -- 149 tools including file open/save and AutoCAD command
execution, no token -- while the guard inspected the literal "127.0.0.1" and
waved it through. v1.4.0's changelog claimed this guard "fires on every launch
path"; it fired, and looked at the wrong host. It now resolves the effective
host, and warns rather than staying silent if it cannot.

`[com]` and `[full]` could not be installed off Windows. Both listed
`pywin32>=306` with no environment marker, and pywin32 publishes Windows wheels
and no sdist, so `pip install autocad-mcp-pro[full]` died at resolution on
Linux and macOS -- and `[full]` is the extra the README recommended to
contributors. CI never caught it because every Linux lane installs `[pdf]` or
syncs from uv.lock, whose resolution markers hide the gap. Both entries carry
`; sys_platform == "win32"` now, with a release-consistency test to keep them.

`drawing_settings({"dimscale": ...})` reported success and changed nothing
headlessly. It writes `$DIMSCALE`, which is what AutoCAD reads when it draws a
dimension, so the call worked on COM; ezdxf renders from the dimstyle table
entry and never consults the header. One call, one reported success, two
different drawings. The header value now folds into the per-dimension override
on all five dimension tools -- including `dimension_aligned` and
`dimension_angular`, which do not share the tolerance-override path and the
obvious fix would have missed. DIMSCALE 1.0 writes nothing.

README: every engine-conditional family in the feature table now says which
engine (boundary tracing, WIPEOUT/REVCLOUD/MTEXT masks, CHSPACE, typed hatch
edge paths, headless undo), and the checkout install line stopped recommending
an extra that could not resolve on the reader's platform.

1257 passed, 1 xfailed. 1.5.0 is to be yanked: its diameter and radius callouts
put wrong numbers on drawings at default settings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Umut CAN
2026-08-06 14:28:35 +03:00
co-authored by Claude Opus 5
parent 776fe5bcf1
commit 25f8737132
11 changed files with 291 additions and 19 deletions
+47
View File
@@ -5,6 +5,53 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.5.1] — 2026-08-06
A patch for three defects found *after* 1.5.0 went to PyPI, two of them by the
adversarial pass over the rewritten README. 1.5.0 is yanked: its diameter and
radius callouts put wrong numbers on drawings at default settings, and that is
not something to leave installable.
### Fixed
- **The HTTP bind guard failed open on the launch path it was written for.**
`_guarded_run_async` read the bind address from the `host` kwarg and treated
an absent one as loopback. It is not: `fastmcp run` forwards that kwarg only
when `--host` was passed, and otherwise fastmcp resolves the host from
`fastmcp.settings.host`, which is environment-backed. So
`FASTMCP_HOST=0.0.0.0 fastmcp run server.py:mcp --transport http` bound every
interface — 149 tools including file open/save and AutoCAD command execution,
with no token — while the guard inspected the literal string `"127.0.0.1"`
and waved it through. v1.4.0's changelog claimed the guard "now fires on
every launch path"; it fired, and then looked at the wrong host. It now
resolves the effective host, and says so in a warning if it cannot.
- **`[com]` and `[full]` could not be installed off Windows.** Both extras
listed `pywin32>=306` with no environment marker, and pywin32 publishes
Windows wheels and *no sdist* — so `pip install autocad-mcp-pro[full]` died
at resolution on Linux and macOS, and `[full]` is the extra this project's
own README recommended to contributors. CI never caught it because every
Linux lane installs `[pdf]` or syncs from `uv.lock`, whose resolution markers
hide the gap. Both entries now carry `; sys_platform == "win32"`, and a
release-consistency test fails if a Windows-only distribution loses its
marker again.
- **`drawing_settings({"dimscale": ...})` reported success and changed nothing
headlessly.** It writes the `$DIMSCALE` header variable, which is what
AutoCAD consults when it draws a dimension — so the call worked on the live
backend. ezdxf renders from the dimstyle table entry and never reads the
header, so the same call produced two different drawings on the two engines
while both said `ok`. The header value is now folded into the per-dimension
override on all five dimension tools, including `dimension_aligned` and
`dimension_angular`, which do not share the tolerance-override path and were
missed by the obvious fix. DIMSCALE 1.0 writes no override.
### Changed
- README: the "What you get" table qualified every engine-conditional family
(boundary tracing, WIPEOUT/REVCLOUD/MTEXT masks, CHSPACE, typed hatch edge
paths, headless undo) instead of listing them as if both engines had them,
and the checkout install line no longer recommends an extra that cannot
resolve on the reader's platform.
## [1.5.0] — 2026-08-06
### Added
+7 -7
View File
@@ -29,7 +29,7 @@ Live through COM on Windows, or headless through ezdxf on any platform — one t
---
> **v1.5 release snapshot:** 154 tools · 6 resources · 5 prompt templates · 1250 collected tests.
> **v1.5 release snapshot:** 154 tools · 6 resources · 5 prompt templates · 1258 collected tests.
> 154 is the **registered** count. A default install **advertises 149** over `tools/list`, because
> `ENABLE_3D` is unset and the five `solid_*` tools stay hidden. `system_about` is the runtime authority.
@@ -74,7 +74,7 @@ AUTOCAD_MCP_BACKEND=com autocad-mcp # live AutoCAD (pip install "autocad-mc
> [!NOTE]
> The bare install **cannot draw pixels**. `ezdxf.addons.drawing.frontend` does an unconditional top-level `import PIL.Image`, so Pillow is required by *every* ezdxf render path — PNG screenshot and PDF export alike — not just by the COM window capture it is listed under. Add `[pdf]` (or `[full]`) if you want images on Linux or macOS.
From a checkout: `pip install -e ".[full]"` then `python server.py`.
From a checkout: `pip install -e ".[full]"` on Windows, or `pip install -e ".[pdf]"` elsewhere, then `python server.py`. (`[com]` and `[full]` carry a `sys_platform == "win32"` marker on `pywin32`, so they resolve everywhere — they simply install nothing Windows-specific off Windows.)
### Wire it to a client
@@ -100,18 +100,18 @@ Works with Claude Desktop, Cursor, or any stdio MCP host. For HTTP: `autocad-mcp
<table>
<tr><th align="left">Area</th><th align="left">What it does</th></tr>
<tr><td><b>Drawing lifecycle</b></td><td>create, open, save, export DXF/PDF, audit <i>(now repairs)</i>, purge, undo/redo</td></tr>
<tr><td><b>Drawing lifecycle</b></td><td>create, open, save, export DXF/PDF, audit <i>(now repairs)</i>, purge; undo/redo live on COM and opt-in headless via <code>EZDXF_UNDO_DEPTH</code></td></tr>
<tr><td><b>Geometry</b></td><td>lines, arcs, polylines, splines, hatches, trim/extend/fillet/chamfer, handle-preserving edits</td></tr>
<tr><td><b>Annotation</b></td><td>ISO 129 toleranced dimensions, ISO 286 fits (<code>fit="H7"</code>), TABLE, MLEADER, GD&amp;T frames and datums (ISO 1101)</td></tr>
<tr><td><b>Engineering generators</b></td><td>involute gears (front + section A-A), DIN 6885 keyed bores, ISO A3 title block</td></tr>
<tr><td><b>Discovery</b> ✨</td><td><code>search_tools</code> ranked over an AutoCAD command and synonym corpus — <code>FILLET</code>, <code>BPOLY</code>, <code>QSELECT</code>, <code>WBLOCK</code>, <code>OVERKILL</code>, <code>CHSPACE</code> each rank <b>#1</b> of the 149-tool advertised catalog</td></tr>
<tr><td><b>Batching</b> ✨</td><td><code>cad_batch</code> runs a whole step list in one round trip; <code>fields=</code> projects the response on 11 result-heavy tools (a closed, gated list)</td></tr>
<tr><td><b>Paper space</b> ✨</td><td>tab lifecycle, viewports, <code>entity_change_space</code> (CHSPACE), <code>drawing_export_pdf(layout=...)</code></td></tr>
<tr><td><b>Paper space</b> ✨</td><td>tab lifecycle, viewports and <code>drawing_export_pdf(layout=...)</code> on both engines; <code>entity_change_space</code> (CHSPACE) headless only</td></tr>
<tr><td><b>Selection</b> ✨</td><td>window vs crossing stated back to the caller; a polygon tested against its own shape, not its bounding box</td></tr>
<tr><td><b>Boundaries</b> ✨</td><td><code>boundary_trace</code> (BOUNDARY/BPOLY) chains loose edges into one closed polyline, arcs kept as bulges</td></tr>
<tr><td><b>Boundaries</b> ✨</td><td><code>boundary_trace</code> (BOUNDARY/BPOLY) chains loose edges into one closed polyline, arcs kept as bulges — <i>headless engine only</i></td></tr>
<tr><td><b>Measurement</b> ✨</td><td><code>analysis_measure_entity</code> measures what is <i>in</i> the drawing, by handle</td></tr>
<tr><td><b>Hatch depth</b> ✨</td><td>gradients, in-place edits, typed edge boundaries, island styles</td></tr>
<tr><td><b>Annotation objects</b> ✨</td><td>WIPEOUT, REVCLOUD, MTEXT background masks, text find/replace</td></tr>
<tr><td><b>Hatch depth</b> ✨</td><td>gradients, in-place edits and island styles on both; typed edge boundaries headless only</td></tr>
<tr><td><b>Annotation objects</b> ✨</td><td>WIPEOUT, REVCLOUD, MTEXT background masks — <i>headless only, ActiveX exposes no member</i> — plus text find/replace on both</td></tr>
<tr><td><b>3D solids</b></td><td><code>solid_box/cylinder/extrude/revolve/boolean</code> on live AutoCAD (<code>ENABLE_3D=true</code>)</td></tr>
<tr><td><b>Quality loop</b></td><td><code>drawing_preflight</code> → <code>drawing_plan</code> → <code>drawing_critique</code> → <code>drawing_refine</code> → <code>drawing_finalize</code> (0–100 score)</td></tr>
<tr><td><b>Delivery</b></td><td><code>drawing_deliver</code>: DXF/PDF/PNG + SHA-256 manifest + reopen-parity checks</td></tr>
+29
View File
@@ -443,6 +443,30 @@ def _measure_hatch(ent, tolerance: float, result: dict) -> dict:
return _round_measure(result)
def _with_header_dimscale(doc, override: dict | None) -> dict | None:
"""Carry `$DIMSCALE` onto the dimension being created.
`drawing_settings({"dimscale": ...})` writes the header variable, which is
what AutoCAD consults when it draws a dimension — so the call worked on the
live backend. ezdxf renders from the dimstyle table entry and never reads
the header, so headlessly the same call reported success and changed
nothing. Folding it into the per-dimension override makes one call mean one
thing on both engines.
1.0 is the default and is deliberately not written, so an untouched drawing
does not gain an override on every dimension.
"""
try:
scale = float(doc.header.get("$DIMSCALE", 1.0) or 1.0)
except (AttributeError, TypeError, ValueError):
return override
if abs(scale - 1.0) < 1e-9:
return override
merged = dict(override or {})
merged.setdefault("dimscale", scale)
return merged
def _round_measure(result: dict) -> dict:
"""Six decimal places, matching analysis_measure_distance."""
result["area"] = round(float(result["area"]), 6)
@@ -2870,6 +2894,7 @@ class EzdxfBackend(AutoCADBackend):
from engineering.tolerances import build_dim_override
override, text = build_dim_override(tol_upper, tol_lower, tol_mode, text_override)
override = _with_header_dimscale(self._require_doc(), override)
msp = self._msp()
dim = msp.add_linear_dim(
base=(float(dim_x), float(dim_y)),
@@ -2906,6 +2931,7 @@ class EzdxfBackend(AutoCADBackend):
distance=math.sqrt(
(float(dim_x) - float(x1)) ** 2 + (float(dim_y) - float(y1)) ** 2
),
override=_with_header_dimscale(self._require_doc(), None),
)
dim.render()
ent = dim.dimension
@@ -2936,6 +2962,7 @@ class EzdxfBackend(AutoCADBackend):
line1=((vxf, vyf), (float(x1), float(y1))),
line2=((vxf, vyf), (float(x2), float(y2))),
location=(float(tx), float(ty)),
override=_with_header_dimscale(self._require_doc(), None),
)
dim.render()
ent = dim.dimension
@@ -2963,6 +2990,7 @@ class EzdxfBackend(AutoCADBackend):
from engineering.tolerances import build_dim_override
override, text = build_dim_override(tol_upper, tol_lower, tol_mode, text_override)
override = _with_header_dimscale(self._require_doc(), override)
msp = self._msp()
cxf, cyf = float(cx), float(cy)
radius = math.sqrt((chord_x - cxf) ** 2 + (chord_y - cyf) ** 2)
@@ -3006,6 +3034,7 @@ class EzdxfBackend(AutoCADBackend):
from engineering.tolerances import build_dim_override
override, text = build_dim_override(tol_upper, tol_lower, tol_mode, text_override)
override = _with_header_dimscale(self._require_doc(), override)
msp = self._msp()
x1f, y1f, x2f, y2f = float(x1), float(y1), float(x2), float(y2)
cx = (x1f + x2f) / 2
+1 -1
View File
@@ -1,7 +1,7 @@
{
"_comment": "GENERATED FILE - do not edit by hand. Derived from the live FastMCP component registry; regenerate with `python scripts/generate_tool_inventory.py`.",
"schema_version": 1,
"package_version": "1.5.0",
"package_version": "1.5.1",
"totals": {
"tools": 154,
"resources": 6,
+12 -3
View File
@@ -1,6 +1,6 @@
[project]
name = "autocad-mcp-pro"
version = "1.5.0"
version = "1.5.1"
description = "Production-grade AutoCAD MCP server — COM API + ezdxf dual engine"
readme = "README.md"
requires-python = ">=3.11"
@@ -74,7 +74,13 @@ Changelog = "https://github.com/U-C4N/Autocad-MCP/blob/main/CHANGELOG.md"
# renders nothing. Install `[pdf]` (or `[full]`) for rendering on any platform:
# Pillow comes in transitively as a matplotlib dependency.
com = [
"pywin32>=306",
# pywin32 publishes Windows wheels and no sdist, so without this marker
# the extra cannot be resolved at all on Linux or macOS -- pip dies at
# "no matching distribution" before installing anything. The marker
# makes `pip install autocad-mcp-pro[com]` a no-op there instead of a
# hard failure, which is the honest outcome: the COM backend needs
# Windows, and the server already falls back to ezdxf.
'pywin32>=306; sys_platform == "win32"',
# Win32 window capture for the live-AutoCAD screenshot path.
"Pillow>=10.0",
]
@@ -83,7 +89,10 @@ pdf = [
"matplotlib>=3.8",
]
full = [
"pywin32>=306",
# Same marker as `com` above, and for the same reason: `[full]` is the
# extra this project recommends to contributors, and it was
# uninstallable on every non-Windows machine.
'pywin32>=306; sys_platform == "win32"',
"Pillow>=10.0",
"matplotlib>=3.8",
]
+2 -2
View File
@@ -7,12 +7,12 @@
"url": "https://github.com/U-C4N/Autocad-MCP",
"source": "github"
},
"version": "1.5.0",
"version": "1.5.1",
"packages": [
{
"registryType": "pypi",
"identifier": "autocad-mcp-pro",
"version": "1.5.0",
"version": "1.5.1",
"transport": {
"type": "stdio"
},
+39 -1
View File
@@ -6599,9 +6599,47 @@ _HTTP_TRANSPORTS = {"http", "sse", "streamable-http"}
_orig_run_async = mcp.run_async
def _effective_http_host(kwargs: dict) -> str:
"""The address that will actually be bound — not the one we hope for.
This used to read ``kwargs.get("host", "127.0.0.1")`` and treat an absent
kwarg as loopback. It is not: ``fastmcp run`` forwards ``host`` only when
``--host`` was given, and otherwise fastmcp resolves it from
``fastmcp.settings.host``, which is environment-backed. So
``FASTMCP_HOST=0.0.0.0`` bound every interface while this guard inspected
the literal string ``"127.0.0.1"`` and let it through — the check failed
*open*, without a token, on the exact launch path it was written for.
If the setting cannot be resolved we fall back to loopback rather than
refusing to start, but we say so: a guard that cannot see the host is not
enforcing anything, and silence is how the original bug survived.
"""
host = kwargs.get("host")
if host:
return str(host)
try:
import fastmcp
resolved = getattr(fastmcp.settings, "host", None)
if resolved:
return str(resolved)
except Exception as exc: # pragma: no cover - defensive
log.warning(
"Could not resolve the effective HTTP host from fastmcp settings (%s); "
"assuming loopback. Pass --host explicitly to be sure of the bind guard.",
exc,
)
return "127.0.0.1"
log.warning(
"fastmcp exposes no resolvable host setting; assuming loopback. Pass "
"--host explicitly to be sure of the bind guard."
)
return "127.0.0.1"
async def _guarded_run_async(transport=None, *args, **kwargs):
if (transport in _HTTP_TRANSPORTS) or (kwargs.get("transport") in _HTTP_TRANSPORTS):
_validate_http_bind(kwargs.get("host", "127.0.0.1"))
_validate_http_bind(_effective_http_host(kwargs))
return await _orig_run_async(transport, *args, **kwargs)
+59
View File
@@ -78,3 +78,62 @@ async def test_server_tool_reads_and_writes(backend):
assert write["ok"] is True
read = await server.drawing_settings(ctx=ctx)
assert read["settings"]["units"]["name"] == "mm"
# ── DIMSCALE has to reach the dimension, not just the header ────────────────
def _dim_text_heights(backend, handle) -> list[float]:
"""The text heights actually rendered into the dimension's geometry block.
The override lands in the dimension's XDATA, not on `dim.dxf`, so the only
honest check is the drawing it produces.
"""
dim = backend._doc.entitydb.get(handle)
block = backend._doc.blocks.get(dim.dxf.geometry)
heights = []
for entity in block:
if entity.dxftype() == "MTEXT":
heights.append(round(float(entity.dxf.char_height), 3))
elif entity.dxftype() == "TEXT":
heights.append(round(float(entity.dxf.height), 3))
return sorted(set(heights))
async def test_dimscale_changes_the_dimension_it_is_set_before(backend):
"""`drawing_settings({"dimscale": ...})` reported success and did nothing.
It writes the `$DIMSCALE` header variable, which is what AutoCAD reads when
it creates a dimension — so on the live backend the call worked. ezdxf
renders dimensions from the dimstyle table entry and never consults the
header, so headlessly the text stayed the same size however large the
number was: one call, one reported success, two different drawings.
"""
plain = await backend.dimension_linear(0, 0, 100, 0, 50, -30)
await backend.drawing_settings({"dimscale": 4.0})
scaled = await backend.dimension_linear(0, 0, 100, 0, 50, -60)
before = _dim_text_heights(backend, plain.handle)
after = _dim_text_heights(backend, scaled.handle)
assert before and after
assert after == pytest.approx([h * 4.0 for h in before]), (
f"DIMSCALE=4 must scale the dimension text: {before} -> {after}"
)
async def test_the_default_dimscale_adds_no_override(backend):
"""DIMSCALE 1.0 is the default; it must not litter every dimension."""
plain = await backend.dimension_linear(0, 0, 100, 0, 50, -30)
dim = backend._doc.entitydb.get(plain.handle)
assert not dim.dxf.hasattr("dimscale") or dim.dxf.dimscale == pytest.approx(1.0)
async def test_dimscale_reaches_aligned_and_angular_too(backend):
"""Those two do not go through `build_dim_override`, so they were missed."""
await backend.drawing_settings({"dimscale": 3.0})
aligned = await backend.dimension_aligned(0, 0, 60, 60, 40, 70)
angular = await backend.dimension_angular(0, 0, 50, 0, 0, 50, 30, 30)
for handle in (aligned.handle, angular.handle):
assert _dim_text_heights(backend, handle) == pytest.approx([3.0])
+67
View File
@@ -179,3 +179,70 @@ def test_validate_http_bind_remote_with_token_logs_warning(caplog):
finally:
cfg.settings.allow_remote_http = old_remote
cfg.settings.mcp_auth_token = old_token
# ── the guard must read the host that will actually be bound ────────────────
#
# `_guarded_run_async` exists to cover the `fastmcp run server.py:mcp
# --transport http` path, which the module docstring and CLAUDE.md both
# advertise. It inferred the bind address from the `host` kwarg and treated an
# absent one as loopback -- but fastmcp only forwards that kwarg when `--host`
# was passed, and otherwise resolves the host from `fastmcp.settings.host`,
# which is environment-backed (`FASTMCP_HOST`). So `FASTMCP_HOST=0.0.0.0`
# bound every interface while the guard inspected the string "127.0.0.1" and
# waved it through: the check failed OPEN, with no token required, on the one
# path it was written for.
def _guard_probe(monkeypatch, *, fastmcp_host, allow_remote=False, token=""):
"""Drive `mcp.run_async` without starting a server."""
sys.modules.pop("server", None)
import fastmcp
import config as cfg
import server as srv
monkeypatch.setattr(fastmcp.settings, "host", fastmcp_host, raising=False)
monkeypatch.setattr(cfg.settings, "allow_remote_http", allow_remote, raising=False)
monkeypatch.setattr(cfg.settings, "mcp_auth_token", token, raising=False)
async def _never_runs(*args, **kwargs):
raise AssertionError("the server started despite the guard")
monkeypatch.setattr(srv, "_orig_run_async", _never_runs, raising=False)
return srv
@pytest.mark.asyncio
async def test_an_omitted_host_is_resolved_not_assumed_to_be_loopback(monkeypatch):
"""FASTMCP_HOST=0.0.0.0 with no --host must still be refused."""
srv = _guard_probe(monkeypatch, fastmcp_host="0.0.0.0")
with pytest.raises(SystemExit, match="Refusing"):
await srv.mcp.run_async(transport="http")
@pytest.mark.asyncio
async def test_an_omitted_host_on_a_loopback_default_still_starts(monkeypatch):
"""The fix must not refuse the ordinary case."""
srv = _guard_probe(monkeypatch, fastmcp_host="127.0.0.1")
with pytest.raises(AssertionError, match="the server started"):
await srv.mcp.run_async(transport="http")
@pytest.mark.asyncio
async def test_an_explicit_host_still_wins_over_the_setting(monkeypatch):
"""An explicit loopback --host must not be overridden by the env default."""
srv = _guard_probe(monkeypatch, fastmcp_host="0.0.0.0")
with pytest.raises(AssertionError, match="the server started"):
await srv.mcp.run_async(transport="http", host="127.0.0.1")
@pytest.mark.asyncio
async def test_stdio_never_consults_the_http_guard(monkeypatch):
srv = _guard_probe(monkeypatch, fastmcp_host="0.0.0.0")
with pytest.raises(AssertionError, match="the server started"):
await srv.mcp.run_async(transport="stdio")
+23
View File
@@ -245,3 +245,26 @@ def test_dockerfile_copies_every_wheel_include() -> None:
includes = _pyproject()["tool"]["hatch"]["build"]["targets"]["wheel"]["only-include"]
missing = [name for name in includes if name not in copy_lines]
assert not missing, f"Dockerfile COPY misses wheel-shipped paths: {missing}"
def test_windows_only_dependencies_carry_a_platform_marker() -> None:
"""`pip install autocad-mcp-pro[full]` must resolve on Linux and macOS.
pywin32 publishes Windows wheels and **no sdist**, so an unmarked
`pywin32>=306` in an extra makes that extra uninstallable everywhere else:
pip fails at resolution with "no matching distribution" before it reaches
anything else. `[full]` is the extra this project's own README recommends
to contributors, and CI never caught it because every Linux lane installs
`[pdf]` or uses uv.lock, whose resolution markers paper over the gap.
"""
extras = _pyproject()["project"]["optional-dependencies"]
unmarked: list[str] = []
for extra, requirements in extras.items():
for requirement in requirements:
name = re.split(r"[<>=!~\[; ]", requirement, maxsplit=1)[0].strip().lower()
if name in {"pywin32"} and "sys_platform" not in requirement:
unmarked.append(f"{extra}: {requirement}")
assert not unmarked, (
'Windows-only distributions need `; sys_platform == "win32"` or the '
f"extra cannot be installed off Windows: {unmarked}"
)
Generated
+5 -5
View File
@@ -67,7 +67,7 @@ wheels = [
[[package]]
name = "autocad-mcp-pro"
version = "1.5.0"
version = "1.5.1"
source = { editable = "." }
dependencies = [
{ name = "ezdxf" },
@@ -78,12 +78,12 @@ dependencies = [
[package.optional-dependencies]
com = [
{ name = "pillow" },
{ name = "pywin32" },
{ name = "pywin32", marker = "sys_platform == 'win32'" },
]
full = [
{ name = "matplotlib" },
{ name = "pillow" },
{ name = "pywin32" },
{ name = "pywin32", marker = "sys_platform == 'win32'" },
]
pdf = [
{ name = "matplotlib" },
@@ -106,8 +106,8 @@ requires-dist = [
{ name = "pillow", marker = "extra == 'com'", specifier = ">=10.0" },
{ name = "pillow", marker = "extra == 'full'", specifier = ">=10.0" },
{ name = "pydantic", specifier = ">=2.0,<3" },
{ name = "pywin32", marker = "extra == 'com'", specifier = ">=306" },
{ name = "pywin32", marker = "extra == 'full'", specifier = ">=306" },
{ name = "pywin32", marker = "sys_platform == 'win32' and extra == 'com'", specifier = ">=306" },
{ name = "pywin32", marker = "sys_platform == 'win32' and extra == 'full'", specifier = ">=306" },
]
provides-extras = ["com", "pdf", "full"]