plot-io 0.12.0__py3-none-win_arm64.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.
Files changed (130) hide show
  1. plot_io/__init__.py +73 -0
  2. plot_io/_methods.py +1691 -0
  3. plot_io/_native/__init__.py +199 -0
  4. plot_io/_native/__main__.py +197 -0
  5. plot_io/_native/_api.py +457 -0
  6. plot_io/_native/bin/windows-arm64.dll +0 -0
  7. plot_io/_native/kernels.py +335 -0
  8. plot_io/_native/manifest.json +49 -0
  9. plot_io/axis.py +1347 -0
  10. plot_io/boxes.py +1156 -0
  11. plot_io/canvas.py +389 -0
  12. plot_io/color/__init__.py +3 -0
  13. plot_io/color/data.py +1646 -0
  14. plot_io/color/maps.py +922 -0
  15. plot_io/color/values.py +295 -0
  16. plot_io/color/variants.py +1246 -0
  17. plot_io/colorbar.py +1426 -0
  18. plot_io/columns.py +614 -0
  19. plot_io/decorations.py +920 -0
  20. plot_io/defaults.py +809 -0
  21. plot_io/density.py +113 -0
  22. plot_io/ext.py +68 -0
  23. plot_io/facets.py +240 -0
  24. plot_io/figure.py +788 -0
  25. plot_io/formatters.py +97 -0
  26. plot_io/genome/__init__.py +17 -0
  27. plot_io/genome/adapters.py +356 -0
  28. plot_io/genome/combine.py +123 -0
  29. plot_io/genome/contacts.py +508 -0
  30. plot_io/genome/intervals.py +750 -0
  31. plot_io/genome/lanes.py +191 -0
  32. plot_io/genome/layout.py +184 -0
  33. plot_io/genome/links.py +364 -0
  34. plot_io/genome/pack.py +27 -0
  35. plot_io/genome/readers.py +145 -0
  36. plot_io/genome/regions.py +228 -0
  37. plot_io/genome/scale.py +349 -0
  38. plot_io/genome/series.py +344 -0
  39. plot_io/genome/signal.py +621 -0
  40. plot_io/genome/sites.py +782 -0
  41. plot_io/genome/sources.py +459 -0
  42. plot_io/genome/testing.py +161 -0
  43. plot_io/geometry.py +289 -0
  44. plot_io/groups.py +308 -0
  45. plot_io/guides.py +503 -0
  46. plot_io/helpers/__init__.py +17 -0
  47. plot_io/helpers/clustermap.py +202 -0
  48. plot_io/helpers/dendrogram.py +172 -0
  49. plot_io/helpers/grids.py +304 -0
  50. plot_io/helpers/qq.py +334 -0
  51. plot_io/helpers/raincloud.py +249 -0
  52. plot_io/helpers/tree.py +214 -0
  53. plot_io/helpers/volcano.py +505 -0
  54. plot_io/hints.py +125 -0
  55. plot_io/kernels.py +740 -0
  56. plot_io/labels.py +566 -0
  57. plot_io/layers/__init__.py +100 -0
  58. plot_io/layers/base.py +630 -0
  59. plot_io/layers/context.py +87 -0
  60. plot_io/layers/core.py +446 -0
  61. plot_io/layers/geoms.py +1362 -0
  62. plot_io/layers/grammar.py +586 -0
  63. plot_io/layers/paint.py +614 -0
  64. plot_io/layers/pipeline.py +170 -0
  65. plot_io/layers/positions.py +431 -0
  66. plot_io/layers/stats.py +1088 -0
  67. plot_io/layout.py +1381 -0
  68. plot_io/legend.py +605 -0
  69. plot_io/levels.py +509 -0
  70. plot_io/limits.py +124 -0
  71. plot_io/lines.py +42 -0
  72. plot_io/mapped.py +522 -0
  73. plot_io/markers.py +110 -0
  74. plot_io/markup.py +128 -0
  75. plot_io/numbers.py +354 -0
  76. plot_io/occupancy.py +295 -0
  77. plot_io/plot.py +597 -0
  78. plot_io/presets/__init__.py +51 -0
  79. plot_io/presets/_util.py +153 -0
  80. plot_io/presets/area.py +145 -0
  81. plot_io/presets/bar.py +261 -0
  82. plot_io/presets/box.py +400 -0
  83. plot_io/presets/catpoints.py +177 -0
  84. plot_io/presets/contour.py +353 -0
  85. plot_io/presets/curves.py +30 -0
  86. plot_io/presets/dots.py +355 -0
  87. plot_io/presets/ecdf.py +97 -0
  88. plot_io/presets/errorbar.py +106 -0
  89. plot_io/presets/errors.py +296 -0
  90. plot_io/presets/fill.py +74 -0
  91. plot_io/presets/heatmap.py +322 -0
  92. plot_io/presets/hexbin.py +198 -0
  93. plot_io/presets/hist.py +391 -0
  94. plot_io/presets/hist2d.py +87 -0
  95. plot_io/presets/kde.py +162 -0
  96. plot_io/presets/kde2d.py +147 -0
  97. plot_io/presets/line.py +143 -0
  98. plot_io/presets/pie.py +502 -0
  99. plot_io/presets/regression.py +115 -0
  100. plot_io/presets/scatter.py +194 -0
  101. plot_io/presets/stem.py +100 -0
  102. plot_io/presets/violin.py +323 -0
  103. plot_io/py.typed +0 -0
  104. plot_io/registry.py +82 -0
  105. plot_io/render.py +480 -0
  106. plot_io/rows.py +491 -0
  107. plot_io/scales.py +103 -0
  108. plot_io/seams.py +138 -0
  109. plot_io/settings.py +807 -0
  110. plot_io/shapes.py +66 -0
  111. plot_io/sharing.py +232 -0
  112. plot_io/sizemaps.py +97 -0
  113. plot_io/spec.py +240 -0
  114. plot_io/states.py +216 -0
  115. plot_io/styles/__init__.py +307 -0
  116. plot_io/styles/library.py +397 -0
  117. plot_io/tables.py +138 -0
  118. plot_io/text.py +412 -0
  119. plot_io/ticks.py +400 -0
  120. plot_io/times.py +287 -0
  121. plot_io/titles.py +225 -0
  122. plot_io/transform.py +114 -0
  123. plot_io/types.py +13604 -0
  124. plot_io/units.py +768 -0
  125. plot_io/validators.py +1029 -0
  126. plot_io/values.py +190 -0
  127. plot_io-0.12.0.dist-info/METADATA +215 -0
  128. plot_io-0.12.0.dist-info/RECORD +130 -0
  129. plot_io-0.12.0.dist-info/WHEEL +4 -0
  130. plot_io-0.12.0.dist-info/licenses/LICENSE +28 -0
@@ -0,0 +1,199 @@
1
+ """plot_io's bundled C library ``plio``: sources (``src/``), built libraries (``bin/``),
2
+ ``manifest.json`` and the bindings generated from ``src/plio_api.h`` (``_api.py``).
3
+ Building is described in ``README.md``; ``python -m plot_io._native verify`` checks the
4
+ installed library.
5
+
6
+ The library is loaded on first use, from the first of: ``PLOT_IO_NATIVE_LIB`` (a sanitizer
7
+ build, any other build), ``bin/<platform tag>.<ext>`` (a release binary), ``bin/hosted.<ext>``
8
+ (built with the local C compiler when no release binary fits). At load the library's
9
+ signature table (``plio_signatures``) must equal the bindings': names, argument kinds, and
10
+ the sizes and field offsets of the parameter structures. Calls release the GIL; memory
11
+ comes from Python's raw allocator, passed in a per-thread context that also holds a buffer
12
+ for error messages.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import ctypes
18
+ import os
19
+ import platform
20
+ import sys
21
+ import sysconfig
22
+ import threading
23
+ from typing import Any
24
+
25
+ from . import _api
26
+
27
+ __all__ = ["NativeLibraryError", "lib", "ctx", "platform_tag", "library_path", "check", "take",
28
+ "ptr", "api"]
29
+
30
+ api = _api
31
+
32
+
33
+ class NativeLibraryError(RuntimeError):
34
+ """The bundled C library is not available on this platform or failed to load."""
35
+
36
+
37
+ def platform_tag() -> str | None:
38
+ """``<os>-<arch>`` of the bundled library for this interpreter, or ``None``."""
39
+ if sys.maxsize <= 2 ** 32:
40
+ return None
41
+ machine = platform.machine().lower()
42
+ if sys.platform.startswith("win"):
43
+ plat = sysconfig.get_platform()
44
+ return {"win-amd64": "windows-x86_64", "win-arm64": "windows-arm64"}.get(plat)
45
+ if sys.platform == "darwin":
46
+ return "macos-arm64" if machine in ("arm64", "aarch64") else None
47
+ if sys.platform.startswith("linux"):
48
+ arch = {"x86_64": "x86_64", "amd64": "x86_64", "aarch64": "aarch64",
49
+ "arm64": "aarch64"}.get(machine)
50
+ return f"linux-{arch}" if arch else None
51
+ return None
52
+
53
+
54
+ _EXT = {"linux": ".so", "windows": ".dll", "macos": ".dylib"}
55
+
56
+
57
+ def _hosted_ext() -> str:
58
+ return ".dll" if sys.platform.startswith("win") else ".dylib" if sys.platform == "darwin" else ".so"
59
+
60
+
61
+ def library_path() -> str | None:
62
+ """Path of the library that would be loaded: ``PLOT_IO_NATIVE_LIB``, else the platform's
63
+ binary in ``bin/``, else ``bin/hosted.<ext>``; ``None`` when there is none."""
64
+ env = os.environ.get("PLOT_IO_NATIVE_LIB")
65
+ if env:
66
+ return env
67
+ bin_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "bin")
68
+ tag = platform_tag()
69
+ if tag is not None:
70
+ path = os.path.join(bin_dir, tag + _EXT[tag.split("-")[0]])
71
+ if os.path.exists(path):
72
+ return path
73
+ hosted = os.path.join(bin_dir, "hosted" + _hosted_ext())
74
+ return hosted if os.path.exists(hosted) else None
75
+
76
+
77
+ _lock = threading.Lock()
78
+ _lib: Any = None
79
+ _error: str | None = None
80
+ _local = threading.local()
81
+ _MESSAGE_SIZE = 256
82
+
83
+
84
+ def signature_problems(handle: Any) -> list[str]:
85
+ """How the library's signature table differs from the bindings (empty: it matches)."""
86
+ text, numbers = ctypes.c_void_p(), ctypes.c_void_p()
87
+ n = handle.plio_signatures(None, ctypes.byref(text), ctypes.byref(numbers))
88
+ if not text.value or not numbers.value:
89
+ return ["no signature table"]
90
+ got = ctypes.string_at(text.value).decode("ascii").splitlines()
91
+ want = _api.SIGNATURE_TEXT.splitlines()
92
+ out = [f"library has {line!r}" for line in got if line not in want]
93
+ out += [f"library lacks {line!r}" for line in want if line not in got]
94
+ if out or n != len(got):
95
+ return out or [f"{n} signature lines for {len(got)} names"]
96
+ values = (ctypes.c_int64 * n).from_address(numbers.value)
97
+ for line, value in zip(got, values):
98
+ kind, name = line.split()[:2]
99
+ if kind == "s":
100
+ expected = ctypes.sizeof(_api.STRUCTS[name])
101
+ elif kind == "m":
102
+ struct, field = name.split(".")
103
+ expected = getattr(_api.STRUCTS[struct], field).offset
104
+ else:
105
+ continue
106
+ if value != expected:
107
+ out.append(f"{name}: {value} in the library, {expected} in the bindings")
108
+ return out
109
+
110
+
111
+ def _load() -> None:
112
+ global _lib, _error
113
+ path = library_path()
114
+ if path is None:
115
+ _error = (f"plot_io's C library is not installed for this platform "
116
+ f"({sys.platform}, {platform.machine()}): install plot_io from the sdist "
117
+ f"with a C compiler (pip install --no-binary plot_io plot_io), or set "
118
+ f"PLOT_IO_NATIVE_LIB to a build of it")
119
+ return
120
+ try:
121
+ handle = ctypes.CDLL(path)
122
+ except OSError as exc:
123
+ _error = f"cannot load plot_io's C library {path}: {exc}"
124
+ return
125
+ try:
126
+ for name, (restype, argtypes) in _api.FUNCTIONS.items():
127
+ fn = getattr(handle, name)
128
+ fn.restype = restype
129
+ fn.argtypes = argtypes
130
+ except AttributeError as exc:
131
+ _error = f"plot_io's C library {path} is incomplete: {exc}"
132
+ return
133
+ abi = handle.plio_abi_version()
134
+ if abi != _api.ABI_VERSION:
135
+ _error = f"plot_io's C library {path} has ABI {abi}, {_api.ABI_VERSION} expected"
136
+ return
137
+ problems = signature_problems(handle)
138
+ if problems:
139
+ _error = (f"plot_io's C library {path} does not match its bindings: "
140
+ + "; ".join(problems[:5]))
141
+ return
142
+ _lib = handle
143
+
144
+
145
+ def lib() -> Any:
146
+ """The loaded library; raises :class:`NativeLibraryError` when it is unavailable."""
147
+ if _lib is None:
148
+ with _lock:
149
+ if _lib is None and _error is None:
150
+ _load()
151
+ if _lib is None:
152
+ raise NativeLibraryError(_error)
153
+ return _lib
154
+
155
+
156
+ def ctx() -> Any:
157
+ """The calling thread's context (pointer), the first argument of every function."""
158
+ c = getattr(_local, "ctx", None)
159
+ if c is None:
160
+ lib()
161
+ pyapi = ctypes.pythonapi
162
+ buf = ctypes.create_string_buffer(_MESSAGE_SIZE)
163
+ c = ctypes.pointer(_api.Ctx(
164
+ malloc=ctypes.cast(pyapi.PyMem_RawMalloc, ctypes.c_void_p).value,
165
+ realloc=ctypes.cast(pyapi.PyMem_RawRealloc, ctypes.c_void_p).value,
166
+ free=ctypes.cast(pyapi.PyMem_RawFree, ctypes.c_void_p).value,
167
+ message=ctypes.addressof(buf), message_size=_MESSAGE_SIZE))
168
+ _local.ctx, _local.buf = c, buf
169
+ else:
170
+ _local.buf[0] = 0
171
+ return c
172
+
173
+
174
+ def check(rc: int, what: str = "plot_io native call", exc: type[Exception] = ValueError) -> int:
175
+ """``rc`` when it is not negative; otherwise raise ``exc`` (MemoryError when out of
176
+ memory) with the library's message, else the code's."""
177
+ if rc >= 0:
178
+ return rc
179
+ buf = getattr(_local, "buf", None)
180
+ detail = buf.value.decode("utf-8", "replace") if buf is not None and buf.value else ""
181
+ text = detail or _api.ERRORS.get(rc, f"error {rc}")
182
+ if rc == _api.E_NOMEM:
183
+ raise MemoryError(f"{what}: out of memory")
184
+ raise exc(f"{what}: {text}")
185
+
186
+
187
+ def take(p: Any, n: int) -> bytes:
188
+ """Copy ``n`` bytes of a library-allocated result and release it."""
189
+ if not p:
190
+ return b""
191
+ try:
192
+ return ctypes.string_at(p, n)
193
+ finally:
194
+ _lib.plio_free(ctx(), p)
195
+
196
+
197
+ def ptr(a: Any) -> int | None:
198
+ """Address of an ``array.array``'s data (``None`` when empty or ``None``)."""
199
+ return a.buffer_info()[0] if a is not None and len(a) else None
@@ -0,0 +1,197 @@
1
+ """``python -m plot_io._native verify``: check the C library this installation loads.
2
+
3
+ Prints the platform, the file loaded, its SHA-256 and the manifest's ABI and source hash,
4
+ then runs three steps:
5
+
6
+ 1. **loads**: the loader's checks (ABI version, the library's signature table against the
7
+ bindings);
8
+ 2. **manifest**: the file's SHA-256 is the manifest's entry of its name (a release binary,
9
+ or a file named like one through ``PLOT_IO_NATIVE_LIB``); a hosted build or another
10
+ override has no reference hash, which is not a failure;
11
+ 3. **self-test**: a density, a group summary, a sort, a colour map lookup and the library's
12
+ math at 16 points, each output's SHA-256 compared with the one the release binaries give
13
+ (every target computes identical results).
14
+
15
+ Exit status 0 when every step passes, 1 otherwise (the failing steps are named).
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import array
21
+ import hashlib
22
+ import json
23
+ import os
24
+ import platform
25
+ import sys
26
+ from typing import Callable
27
+
28
+ # SHA-256 of each self-test's output, from the release binaries
29
+ EXPECTED = {
30
+ "density": "c5d3b904c127c54a6ed045c800f14f6c6809bf66d0a392f8706865ad211c56db",
31
+ "group summary": "695de6b532cd9a9b58927bb5fb4168e9e4abd0e8e0fdc948ba4a8c27b211bc5c",
32
+ "sort": "c198461a79f1cd31a257c64cc9a19817a110d53c6308b655465915c6841f858e",
33
+ "colour map": "f39c20a0456f7c9cacefd4c632e1d9107c53b8b8ee3d4d7c8d949fa78d1d7404",
34
+ "math": "ada4f741f1482fe9e39f5364e179f8000b4bcc4b5c2ca25862c3167181cffbe8",
35
+ "linkage": "60d86a1bd7b6a55b5f82f8461a806782b3e2a8da825ed9221f16e2446c55f524",
36
+ }
37
+
38
+
39
+ def _values(n: int) -> array.array:
40
+ """Fixed values: a few clusters, ties, a NaN."""
41
+ out = array.array("d")
42
+ for i in range(n):
43
+ out.append(((i * 37) % 23) * 0.25 + (i % 5) * 1.5 - 3.0 + (2.75 if i % 7 == 0 else 0.0))
44
+ out[n // 2] = float("nan")
45
+ return out
46
+
47
+
48
+ def _density() -> bytes:
49
+ from . import kernels
50
+
51
+ v = array.array("d", [x for x in _values(64) if x == x])
52
+ w = array.array("d", [1.0 + (i % 3) * 0.5 for i in range(len(v))])
53
+ return (kernels.kde(v, None, -4.0, 6.0, 32, 0.6, False).tobytes()
54
+ + kernels.kde(v, w, -4.0, 6.0, 32, 0.45, True).tobytes())
55
+
56
+
57
+ def _group_summary() -> bytes:
58
+ from . import kernels
59
+
60
+ v = _values(48) # 12 rows x 4 columns
61
+ groups = array.array("q", [i % 3 - (i == 5) for i in range(12)])
62
+ return b"".join(a.tobytes() for a in kernels.group_summary(v, 12, 4, groups, 3, 0.5))
63
+
64
+
65
+ def _sort() -> bytes:
66
+ from . import kernels
67
+
68
+ v = _values(200)
69
+ v[3], v[17] = float("inf"), -0.0
70
+ return kernels.argsort(v, False).tobytes() + kernels.argsort(v, True).tobytes()
71
+
72
+
73
+ def _colour_map() -> bytes:
74
+ from . import kernels
75
+
76
+ stops = [(0.0, (0.1, 0.2, 0.6, 1.0)), (0.4, (0.9, 0.9, 0.3, 0.8)), (1.0, (0.7, 0.1, 0.1, 1.0))]
77
+ ts = array.array("d", [i / 15 for i in range(16)])
78
+ out = b""
79
+ for mode in ("linear", "smooth", "spline"):
80
+ for space in ("srgb", "oklab"):
81
+ out += kernels.colormap_eval(stops, mode, space, ts).tobytes()
82
+ table = array.array("d", [c / 7 for c in range(4 * 8)])
83
+ vals = array.array("d", [-0.5, 0.0, 0.1, 0.33, 0.5, 0.77, 1.0, 1.5, float("nan")])
84
+ return out + kernels.colors_at(vals, table, None, (0, 0, 0, 1), (1, 1, 1, 1),
85
+ (0.5, 0.5, 0.5, 0)).tobytes()
86
+
87
+
88
+ def _math() -> bytes:
89
+ import ctypes
90
+
91
+ from . import _api, ctx, lib, ptr
92
+
93
+ xs = array.array("d", [-7.5, -2.25, -1.0, -0.5, -1e-3, 0.0, 1e-3, 0.3, 0.5, 0.75, 1.0,
94
+ 1.5, 2.5, 3.14159, 10.0, 123.456])
95
+ ys = array.array("d", [0.5 + i * 0.37 for i in range(16)])
96
+ positive = (_api.MATH_SQRT, _api.MATH_LOG, _api.MATH_POW)
97
+ out = b""
98
+ for fn in range(_api.MATH_COS + 1):
99
+ res = array.array("d", bytes(8 * len(xs)))
100
+ # sqrt, log and pow on positive arguments: a NaN's sign bit is the compiler's
101
+ x = xs if fn not in positive else array.array("d", [abs(v) + 0.25 for v in xs])
102
+ lib().plio_math_eval(ctx(), fn, ptr(x), ptr(ys), ctypes.c_void_p(ptr(res)), len(xs))
103
+ out += res.tobytes()
104
+ return out
105
+
106
+
107
+ def _linkage() -> bytes:
108
+ from . import kernels
109
+
110
+ v = array.array("d", [x for x in _values(61) if x == x]) # 20 rows x 3
111
+ out = b""
112
+ for method, metric in (("single", "cityblock"), ("average", "correlation"),
113
+ ("ward", "euclidean"), ("centroid", "euclidean")):
114
+ out += kernels.linkage(v, 20, 3, method, metric).tobytes()
115
+ return out
116
+
117
+
118
+ TESTS: dict[str, Callable[[], bytes]] = {
119
+ "density": _density, "group summary": _group_summary, "sort": _sort,
120
+ "colour map": _colour_map, "math": _math, "linkage": _linkage,
121
+ }
122
+
123
+
124
+ def self_test() -> dict[str, str]:
125
+ """The SHA-256 of each self-test's output (an exception's text when one fails)."""
126
+ out = {}
127
+ for name, fn in TESTS.items():
128
+ try:
129
+ out[name] = hashlib.sha256(fn()).hexdigest()
130
+ except Exception as exc: # a failing call is a failed step
131
+ out[name] = f"{type(exc).__name__}: {exc}"
132
+ return out
133
+
134
+
135
+ def _sha256(path: str) -> str:
136
+ with open(path, "rb") as f:
137
+ return hashlib.sha256(f.read()).hexdigest()
138
+
139
+
140
+ def verify() -> int:
141
+ from . import NativeLibraryError, library_path, lib, platform_tag
142
+
143
+ here = os.path.dirname(os.path.abspath(__file__))
144
+ try:
145
+ with open(os.path.join(here, "manifest.json")) as f:
146
+ manifest = json.load(f)
147
+ except OSError:
148
+ manifest = {}
149
+ path = library_path()
150
+ print(f"platform {platform_tag() or 'none'} ({sys.platform}, {platform.machine()})")
151
+ print(f"file {path or 'none'}")
152
+ digest = _sha256(path) if path and os.path.isfile(path) else None
153
+ print(f"sha256 {digest or '-'}")
154
+ print(f"manifest ABI {manifest.get('abi', '-')}, sources {manifest.get('source_sha256', '-')}")
155
+ failed = []
156
+ try:
157
+ lib()
158
+ print("loads ok")
159
+ except NativeLibraryError as exc:
160
+ print(f"loads FAIL: {exc}")
161
+ failed.append("loads")
162
+ entry = manifest.get("files", {}).get(os.path.basename(path or ""))
163
+ if digest is None:
164
+ print("manifest FAIL: no library file")
165
+ failed.append("manifest")
166
+ elif entry is None:
167
+ print("manifest no reference hash (not a release binary)")
168
+ elif entry.get("sha256") == digest:
169
+ print("manifest ok")
170
+ else:
171
+ print(f"manifest FAIL: the manifest says {entry.get('sha256')}")
172
+ failed.append("manifest")
173
+ if "loads" not in failed:
174
+ results = self_test()
175
+ bad = [n for n, h in results.items() if h != EXPECTED[n]]
176
+ for n in bad:
177
+ print(f"self-test FAIL: {n}: {results[n]}")
178
+ if bad:
179
+ failed.append("self-test")
180
+ else:
181
+ print(f"self-test ok ({len(results)} checks)")
182
+ if failed:
183
+ print(f"failed: {', '.join(failed)}")
184
+ return 1
185
+ return 0
186
+
187
+
188
+ def main(argv: list[str] | None = None) -> int:
189
+ args = sys.argv[1:] if argv is None else argv
190
+ if args != ["verify"]:
191
+ print("usage: python -m plot_io._native verify", file=sys.stderr)
192
+ return 2
193
+ return verify()
194
+
195
+
196
+ if __name__ == "__main__":
197
+ sys.exit(main())