makea-cli 0.2.28__tar.gz → 0.2.29__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.
- {makea_cli-0.2.28 → makea_cli-0.2.29}/CHANGELOG.md +12 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/PKG-INFO +1 -1
- makea_cli-0.2.29/makea_cli/__init__.py +1 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/file_reader.py +121 -10
- {makea_cli-0.2.28 → makea_cli-0.2.29}/pyproject.toml +1 -1
- {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_file_reader.py +48 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/uv.lock +1 -1
- makea_cli-0.2.28/makea_cli/__init__.py +0 -1
- {makea_cli-0.2.28 → makea_cli-0.2.29}/.claude/skills/create-supplier-profile/SKILL.md +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/.claude/skills/lark-cli-setup/SKILL.md +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/.claude/skills/link-supplier-user/SKILL.md +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/.claude/skills/makea-cli-setup/SKILL.md +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/.claude/skills/payment-revenue-report/SKILL.md +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/.claude/skills/supplier-user-id-migration/SKILL.md +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/.gitignore +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/README.md +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea-collection-upload/SKILL.md +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea-collection-upload/scripts/create_products.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/__main__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/api_client.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/auth_pkce.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/broker.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/_util.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/about/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/about/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/analytics/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/analytics/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/auth/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/auth/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/brand/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/brand/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/designer/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/designer/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/directory/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/directory/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/document/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/document/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/email/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/email/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/factory/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/factory/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/files/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/files/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/flags/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/flags/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/library/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/library/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/metrics/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/metrics/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/orders/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/orders/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/playbook/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/playbook/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/product/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/product/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/production/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/production/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/production/pricing_spec.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/shipping/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/shipping/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/supplier/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/supplier/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/tasks/__init__.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/commands/tasks/cmd.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/companion.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/config.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/errors.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/identity.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/main.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/output.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/posthog_client.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/makea_cli/registry.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/npm/makea-cli/README.md +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/npm/makea-cli/makea-cli.mjs +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/npm/makea-cli/package.json +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/golden_manifest.json +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_broker.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_companion.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_currency_conversion.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_output_contract.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_persona_commands.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_pm_reads.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_registry.py +0 -0
- {makea_cli-0.2.28 → makea_cli-0.2.29}/tests/test_supplier_scorecard.py +0 -0
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.29
|
|
4
|
+
|
|
5
|
+
The file reader stays inside a web worker's memory (TECH-145).
|
|
6
|
+
|
|
7
|
+
- It no longer raises Pillow's global decompression limit, and decodes no
|
|
8
|
+
image over 120 MP (the same bound as the backend); JPEGs decode at reduced
|
|
9
|
+
scale. A DXF sheet draws at most 40 pieces. Pictures shrink before they are
|
|
10
|
+
flattened, so a large PSD is not copied at full size several times.
|
|
11
|
+
- `python -m makea_cli.file_reader <name>` reads the file on stdin and prints
|
|
12
|
+
the reading as JSON; `read_in_child()` runs it that way, so a long-lived
|
|
13
|
+
worker gets its memory back when the child exits.
|
|
14
|
+
|
|
3
15
|
## 0.2.28
|
|
4
16
|
|
|
5
17
|
Read the files brands send that no model reads (TECH-143).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.29"
|
|
@@ -33,6 +33,26 @@ _MAX_MEMBERS = 40
|
|
|
33
33
|
_MAX_PICTURES = 8
|
|
34
34
|
_PICTURE_SIDE = 1800
|
|
35
35
|
|
|
36
|
+
# Never decode an image bigger than this. A decoded image costs w x h x 4
|
|
37
|
+
# bytes whatever the file size, and this runs in web workers (previews) as
|
|
38
|
+
# well as Celery: a 410 MB decode killed one and 502'd the site (TECH-145).
|
|
39
|
+
# Same bound as garmentby_service/common/image_limits.MAX_DECODE_PIXELS.
|
|
40
|
+
MAX_DECODE_PIXELS = 120_000_000
|
|
41
|
+
_MAX_PIECES_DRAWN = 40
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _open_bounded(data: bytes):
|
|
45
|
+
"""Open an image and decode it only when it fits MAX_DECODE_PIXELS; JPEGs
|
|
46
|
+
are decoded at reduced scale. Raises ValueError when it is too big."""
|
|
47
|
+
Image = _pil()
|
|
48
|
+
im = Image.open(io.BytesIO(data))
|
|
49
|
+
if im.width * im.height > MAX_DECODE_PIXELS:
|
|
50
|
+
raise ValueError(f"{im.width} x {im.height} px is too large to draw here")
|
|
51
|
+
if im.format == "JPEG":
|
|
52
|
+
im.draft("RGB", (_PICTURE_SIDE, _PICTURE_SIDE))
|
|
53
|
+
im.load()
|
|
54
|
+
return im
|
|
55
|
+
|
|
36
56
|
|
|
37
57
|
@dataclass
|
|
38
58
|
class Picture:
|
|
@@ -109,20 +129,24 @@ def _pil():
|
|
|
109
129
|
Image,
|
|
110
130
|
) # noqa: PLC0415 - optional at import time, required to draw
|
|
111
131
|
|
|
112
|
-
Image.MAX_IMAGE_PIXELS = 400_000_000
|
|
113
132
|
return Image
|
|
114
133
|
except ImportError: # pragma: no cover - Pillow is a dependency
|
|
115
134
|
return None
|
|
116
135
|
|
|
117
136
|
|
|
118
137
|
def _png(im) -> bytes:
|
|
138
|
+
"""Shrink first, then flatten: every conversion of the full-size image is
|
|
139
|
+
another full-size copy in memory."""
|
|
119
140
|
Image = _pil()
|
|
141
|
+
if max(im.size) > _PICTURE_SIDE:
|
|
142
|
+
if im.mode in ("P", "PA", "1"):
|
|
143
|
+
im = im.convert("RGBA") # palette images cannot be resampled
|
|
144
|
+
im.thumbnail((_PICTURE_SIDE, _PICTURE_SIDE))
|
|
120
145
|
if im.mode not in ("RGB", "L"):
|
|
121
146
|
rgba = im.convert("RGBA")
|
|
122
147
|
bg = Image.new("RGB", rgba.size, "white")
|
|
123
148
|
bg.paste(rgba, mask=rgba.split()[-1])
|
|
124
149
|
im = bg
|
|
125
|
-
im.thumbnail((_PICTURE_SIDE, _PICTURE_SIDE))
|
|
126
150
|
buf = io.BytesIO()
|
|
127
151
|
im.save(buf, "PNG", optimize=True)
|
|
128
152
|
return buf.getvalue()
|
|
@@ -499,8 +523,7 @@ def _read_eps(data: bytes, name: str, depth: int) -> Reading:
|
|
|
499
523
|
Image = _pil()
|
|
500
524
|
if tif_len and Image:
|
|
501
525
|
try:
|
|
502
|
-
im =
|
|
503
|
-
im.load()
|
|
526
|
+
im = _open_bounded(_fix_preview_tiff(data[tif_off : tif_off + tif_len]))
|
|
504
527
|
pictures.append(Picture("preview", _png(im)))
|
|
505
528
|
except Exception: # noqa: BLE001 - no preview is still a reading
|
|
506
529
|
pass
|
|
@@ -574,8 +597,11 @@ def _read_psd(data: bytes, name: str, depth: int) -> Reading:
|
|
|
574
597
|
layers = [layer[0] for layer in (getattr(im, "layers", None) or []) if layer[0]]
|
|
575
598
|
if layers:
|
|
576
599
|
lines.append(f"{len(layers)} layer(s): " + "; ".join(layers[:30]) + ".")
|
|
577
|
-
|
|
578
|
-
|
|
600
|
+
if width * height > MAX_DECODE_PIXELS:
|
|
601
|
+
lines.append("Too large to draw here.")
|
|
602
|
+
else:
|
|
603
|
+
im.load()
|
|
604
|
+
pictures.append(Picture("flattened image", _png(im)))
|
|
579
605
|
except Exception: # noqa: BLE001
|
|
580
606
|
lines.append("The flattened image could not be decoded.")
|
|
581
607
|
return Reading("psd", "PSD", " ".join(lines), pictures, readable=bool(pictures))
|
|
@@ -711,8 +737,7 @@ def _read_wilcom(data: bytes, name: str, depth: int) -> Reading:
|
|
|
711
737
|
if Image and ole.exists(stream):
|
|
712
738
|
raw = ole.openstream(stream).read()
|
|
713
739
|
try:
|
|
714
|
-
im =
|
|
715
|
-
im.load()
|
|
740
|
+
im = _open_bounded(zlib.decompress(raw[4:]))
|
|
716
741
|
pictures.append(Picture("stitch-out preview", _png(im)))
|
|
717
742
|
break
|
|
718
743
|
except Exception: # noqa: BLE001
|
|
@@ -974,10 +999,12 @@ def _seam_allowance(b: _Block, cut: Tuple[float, float, float, float]) -> str:
|
|
|
974
999
|
|
|
975
1000
|
|
|
976
1001
|
def _draw_dxf(items: List[Tuple[str, _Block]]) -> List[Picture]:
|
|
977
|
-
"""One sheet with every piece of the base size, numbered
|
|
1002
|
+
"""One sheet with every piece of the base size, numbered (the first
|
|
1003
|
+
_MAX_PIECES_DRAWN, so the sheet stays a bounded size)."""
|
|
978
1004
|
Image = _pil()
|
|
979
1005
|
if not Image or not items:
|
|
980
1006
|
return []
|
|
1007
|
+
items = items[:_MAX_PIECES_DRAWN]
|
|
981
1008
|
from PIL import ImageDraw # noqa: PLC0415
|
|
982
1009
|
|
|
983
1010
|
shapes = []
|
|
@@ -1171,8 +1198,17 @@ def _read_image(data: bytes, name: str, depth: int) -> Reading:
|
|
|
1171
1198
|
ext = os.path.splitext(name.lower())[1]
|
|
1172
1199
|
im = Image.open(io.BytesIO(data))
|
|
1173
1200
|
frames = getattr(im, "n_frames", 1)
|
|
1174
|
-
im.load()
|
|
1175
1201
|
text = f"{im.format or _format_label(ext)} image, {im.width} x {im.height} px"
|
|
1202
|
+
if im.width * im.height > MAX_DECODE_PIXELS:
|
|
1203
|
+
return Reading(
|
|
1204
|
+
"image",
|
|
1205
|
+
im.format or _format_label(ext),
|
|
1206
|
+
text + ". Too large to draw here.",
|
|
1207
|
+
readable=False,
|
|
1208
|
+
)
|
|
1209
|
+
if im.format == "JPEG":
|
|
1210
|
+
im.draft("RGB", (_PICTURE_SIDE, _PICTURE_SIDE))
|
|
1211
|
+
im.load()
|
|
1176
1212
|
dpi = im.info.get("dpi")
|
|
1177
1213
|
if dpi and dpi[0]:
|
|
1178
1214
|
text += f", {dpi[0]:.0f} dpi ({im.width / dpi[0] * 25.4:.0f} x {im.height / dpi[1] * 25.4:.0f} mm)"
|
|
@@ -1234,3 +1270,78 @@ _READERS: List[Tuple[Callable[[bytes, str], bool], Callable[[bytes, str, int], R
|
|
|
1234
1270
|
(_is_svg, _read_svg),
|
|
1235
1271
|
(_is_image, _read_image),
|
|
1236
1272
|
]
|
|
1273
|
+
|
|
1274
|
+
|
|
1275
|
+
# ── Run in a child process ───────────────────────────────────────────────
|
|
1276
|
+
#
|
|
1277
|
+
# A long-lived worker never gives decoded-image memory back to the system, so
|
|
1278
|
+
# the backend reads through `python -m makea_cli.file_reader <name>` (file on
|
|
1279
|
+
# stdin, JSON on stdout): whatever the read costs is returned when the child
|
|
1280
|
+
# exits (TECH-145).
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
def to_json(reading: Reading) -> dict:
|
|
1284
|
+
import base64 # noqa: PLC0415
|
|
1285
|
+
|
|
1286
|
+
return {
|
|
1287
|
+
"kind": reading.kind,
|
|
1288
|
+
"format": reading.format,
|
|
1289
|
+
"text": reading.text,
|
|
1290
|
+
"readable": reading.readable,
|
|
1291
|
+
"pictures": [
|
|
1292
|
+
{"label": p.label, "png": base64.b64encode(p.png).decode("ascii")}
|
|
1293
|
+
for p in reading.pictures
|
|
1294
|
+
],
|
|
1295
|
+
"members": [[name, to_json(sub)] for name, sub in reading.members],
|
|
1296
|
+
"raw": base64.b64encode(reading.raw).decode("ascii") if reading.raw is not None else None,
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
|
|
1300
|
+
def from_json(data: dict) -> Reading:
|
|
1301
|
+
import base64 # noqa: PLC0415
|
|
1302
|
+
|
|
1303
|
+
return Reading(
|
|
1304
|
+
kind=data["kind"],
|
|
1305
|
+
format=data["format"],
|
|
1306
|
+
text=data["text"],
|
|
1307
|
+
readable=data["readable"],
|
|
1308
|
+
pictures=[Picture(p["label"], base64.b64decode(p["png"])) for p in data["pictures"]],
|
|
1309
|
+
members=[(name, from_json(sub)) for name, sub in data["members"]],
|
|
1310
|
+
raw=base64.b64decode(data["raw"]) if data.get("raw") is not None else None,
|
|
1311
|
+
)
|
|
1312
|
+
|
|
1313
|
+
|
|
1314
|
+
def read_in_child(data: bytes, name: str, *, timeout: float = 90.0) -> Reading:
|
|
1315
|
+
"""read_bytes in a child Python process; a failure or a timeout is a
|
|
1316
|
+
reading that says so, never an exception."""
|
|
1317
|
+
import json # noqa: PLC0415
|
|
1318
|
+
import subprocess # noqa: PLC0415
|
|
1319
|
+
import sys # noqa: PLC0415
|
|
1320
|
+
|
|
1321
|
+
try:
|
|
1322
|
+
done = subprocess.run(
|
|
1323
|
+
[sys.executable, "-m", "makea_cli.file_reader", name],
|
|
1324
|
+
input=data,
|
|
1325
|
+
capture_output=True,
|
|
1326
|
+
timeout=timeout,
|
|
1327
|
+
check=True,
|
|
1328
|
+
)
|
|
1329
|
+
return from_json(json.loads(done.stdout))
|
|
1330
|
+
except Exception as e: # noqa: BLE001 - the file stays uploaded either way
|
|
1331
|
+
detail = (
|
|
1332
|
+
"it took too long" if isinstance(e, subprocess.TimeoutExpired) else type(e).__name__
|
|
1333
|
+
)
|
|
1334
|
+
ext = os.path.splitext(name.lower())[1]
|
|
1335
|
+
return Reading(
|
|
1336
|
+
"unknown",
|
|
1337
|
+
_format_label(ext),
|
|
1338
|
+
f"{_format_label(ext)} file; it could not be read here ({detail}).",
|
|
1339
|
+
readable=False,
|
|
1340
|
+
)
|
|
1341
|
+
|
|
1342
|
+
|
|
1343
|
+
if __name__ == "__main__":
|
|
1344
|
+
import json
|
|
1345
|
+
import sys
|
|
1346
|
+
|
|
1347
|
+
sys.stdout.write(json.dumps(to_json(read_bytes(sys.stdin.buffer.read(), sys.argv[1]))))
|
|
@@ -223,3 +223,51 @@ class IllustratorNativeTest(unittest.TestCase):
|
|
|
223
223
|
|
|
224
224
|
def test_ai_with_real_pdf_content_stays_a_pdf(self):
|
|
225
225
|
self.assertEqual(read_bytes(_blank_pdf(), "logo.ai").kind, "pdf")
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class DecodeBoundTest(unittest.TestCase):
|
|
229
|
+
"""Nothing here decodes an image too big for a web worker (TECH-145)."""
|
|
230
|
+
|
|
231
|
+
def test_pillow_global_limit_is_left_alone(self):
|
|
232
|
+
before = Image.MAX_IMAGE_PIXELS
|
|
233
|
+
read_bytes(b"x", "a.dxf")
|
|
234
|
+
self.assertEqual(Image.MAX_IMAGE_PIXELS, before)
|
|
235
|
+
|
|
236
|
+
def test_an_image_over_the_bound_is_described_not_drawn(self):
|
|
237
|
+
from unittest import mock
|
|
238
|
+
|
|
239
|
+
from makea_cli import file_reader
|
|
240
|
+
|
|
241
|
+
buf = io.BytesIO()
|
|
242
|
+
Image.new("RGB", (400, 300), "white").save(buf, "TIFF")
|
|
243
|
+
with mock.patch.object(file_reader, "MAX_DECODE_PIXELS", 1000):
|
|
244
|
+
r = read_bytes(buf.getvalue(), "scan.tif")
|
|
245
|
+
self.assertEqual(r.pictures, [])
|
|
246
|
+
self.assertIn("400 x 300 px", r.text)
|
|
247
|
+
self.assertIn("Too large", r.text)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class ChildProcessTest(unittest.TestCase):
|
|
251
|
+
def test_a_reading_comes_back_whole_from_the_child(self):
|
|
252
|
+
from makea_cli.file_reader import read_in_child
|
|
253
|
+
|
|
254
|
+
buf = io.BytesIO()
|
|
255
|
+
with zipfile.ZipFile(buf, "w") as z:
|
|
256
|
+
z.writestr("pattern.dxf", _dxf())
|
|
257
|
+
z.writestr("spec.docx", b"PK\x03\x04 docx")
|
|
258
|
+
r = read_in_child(buf.getvalue(), "pack.zip")
|
|
259
|
+
self.assertEqual(r.kind, "archive")
|
|
260
|
+
kinds = {n: m.kind for n, m in r.members}
|
|
261
|
+
self.assertEqual(kinds, {"pattern.dxf": "pattern", "spec.docx": "document"})
|
|
262
|
+
self.assertEqual(dict(r.members)["spec.docx"].raw, b"PK\x03\x04 docx")
|
|
263
|
+
self.assertTrue(dict(r.members)["pattern.dxf"].pictures[0].png.startswith(b"\x89PNG"))
|
|
264
|
+
|
|
265
|
+
def test_a_child_that_fails_is_a_reading(self):
|
|
266
|
+
from unittest import mock
|
|
267
|
+
|
|
268
|
+
from makea_cli import file_reader
|
|
269
|
+
|
|
270
|
+
with mock.patch("subprocess.run", side_effect=OSError("no python")):
|
|
271
|
+
r = file_reader.read_in_child(b"x", "a.dxf")
|
|
272
|
+
self.assertFalse(r.readable)
|
|
273
|
+
self.assertIn("could not be read", r.text)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.28"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|