submine 0.1.0__cp311-cp311-musllinux_1_2_x86_64.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 (49) hide show
  1. submine/__init__.py +37 -0
  2. submine/algorithms/__init__.py +23 -0
  3. submine/algorithms/base.py +143 -0
  4. submine/algorithms/gspan.py +156 -0
  5. submine/algorithms/gspan_cpp.cpython-311-x86_64-linux-musl.so +0 -0
  6. submine/algorithms/sopagrami.py +250 -0
  7. submine/algorithms/sopagrami_cpp.cpython-311-x86_64-linux-musl.so +0 -0
  8. submine/api.py +134 -0
  9. submine/backends/__init__.py +0 -0
  10. submine/backends/gspan/CMakeLists.txt +65 -0
  11. submine/backends/gspan/dfs.cpp +98 -0
  12. submine/backends/gspan/graph.cpp +165 -0
  13. submine/backends/gspan/gspan.cpp +776 -0
  14. submine/backends/gspan/gspan.h +296 -0
  15. submine/backends/gspan/ismin.cpp +124 -0
  16. submine/backends/gspan/main.cpp +106 -0
  17. submine/backends/gspan/misc.cpp +177 -0
  18. submine/backends/gspan/python_bindings.cpp +133 -0
  19. submine/backends/sopagrami/cpp/CMakeLists.txt +44 -0
  20. submine/backends/sopagrami/cpp/include/alg.hpp +150 -0
  21. submine/backends/sopagrami/cpp/include/common/timer.hpp +18 -0
  22. submine/backends/sopagrami/cpp/src/alg.cpp +805 -0
  23. submine/backends/sopagrami/cpp/src/dump.cpp +262 -0
  24. submine/backends/sopagrami/cpp/src/main.cpp +94 -0
  25. submine/backends/sopagrami/cpp/src/python_bindings.cpp +123 -0
  26. submine/cli/__init__.py +6 -0
  27. submine/cli/main.py +87 -0
  28. submine/core/__init__.py +12 -0
  29. submine/core/graph.py +179 -0
  30. submine/core/result.py +121 -0
  31. submine/datasets/__init__.py +11 -0
  32. submine/datasets/loaders.py +145 -0
  33. submine/errors.py +41 -0
  34. submine/io/__init__.py +30 -0
  35. submine/io/common.py +173 -0
  36. submine/io/gexf.py +88 -0
  37. submine/io/gspan.py +268 -0
  38. submine/io/sopagrami.py +143 -0
  39. submine/io/transcode.py +147 -0
  40. submine/registry.py +8 -0
  41. submine/utils/__init__.py +6 -0
  42. submine/utils/checks.py +115 -0
  43. submine/utils/logging.py +41 -0
  44. submine-0.1.0.dist-info/METADATA +178 -0
  45. submine-0.1.0.dist-info/RECORD +49 -0
  46. submine-0.1.0.dist-info/WHEEL +5 -0
  47. submine-0.1.0.dist-info/licenses/LICENSE +21 -0
  48. submine.libs/libgcc_s-2298274a.so.1 +0 -0
  49. submine.libs/libstdc++-08d5c7eb.so.6.0.33 +0 -0
@@ -0,0 +1,115 @@
1
+ """Environment and safety checks for submine.
2
+
3
+ This module centralizes lightweight validation utilities that protect the
4
+ library from common failure modes (corrupt inputs, missing binaries) and
5
+ from avoidable abuse (e.g., attempting to load arbitrarily large files into
6
+ memory, unsafe subprocess execution defaults).
7
+
8
+ These are not meant to be a sandbox. They are meant to provide sensible,
9
+ defensive defaults for a publishable OSS library.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ import shutil
16
+ from pathlib import Path
17
+ from typing import Optional
18
+
19
+
20
+ DEFAULT_MAX_INPUT_MB = int(os.getenv("SUBMINE_MAX_INPUT_MB", "128"))
21
+ DEFAULT_MAX_INPUT_BYTES = DEFAULT_MAX_INPUT_MB * 1024 * 1024
22
+
23
+
24
+ DEFAULT_MAX_LINES = int(os.getenv("SUBMINE_MAX_LINES", "5000000")) # 5M lines
25
+ DEFAULT_MAX_LINE_BYTES = int(os.getenv("SUBMINE_MAX_LINE_BYTES", "1048576")) # 1 MiB per line
26
+
27
+
28
+ def iter_text_lines(
29
+ path: str | Path,
30
+ *,
31
+ encoding: str = "utf-8",
32
+ max_lines: int | None = None,
33
+ max_line_bytes: int | None = None,
34
+ ):
35
+ """Yield decoded lines from *path* with hard limits.
36
+
37
+ This is intended for parsers that stream large graph files. It protects
38
+ against:
39
+ - extremely long lines (often accidental corruption or malicious inputs)
40
+ - unbounded files that could consume excessive CPU time
41
+
42
+ Notes:
43
+ - Lines are decoded with ``errors='replace'`` to avoid UnicodeDecodeError.
44
+ - The returned lines are stripped of trailing ``\n``.
45
+ """
46
+ # Resolve limits at call time so test suites (and embedding apps) can
47
+ # override them via environment variables without requiring a reload.
48
+ if max_lines is None:
49
+ max_lines = int(os.getenv("SUBMINE_MAX_LINES", str(DEFAULT_MAX_LINES)))
50
+ if max_line_bytes is None:
51
+ max_line_bytes = int(os.getenv("SUBMINE_MAX_LINE_BYTES", str(DEFAULT_MAX_LINE_BYTES)))
52
+
53
+ p = assert_regular_file(path)
54
+ count = 0
55
+ with p.open("rb") as f:
56
+ for raw in f:
57
+ count += 1
58
+ if count > max_lines:
59
+ from ..errors import ResourceLimitError
60
+ raise ResourceLimitError(
61
+ f"Refusing to parse {p}: exceeds max line count limit ({max_lines}). "
62
+ "Set SUBMINE_MAX_LINES to increase the limit if you trust this input."
63
+ )
64
+ if len(raw) > max_line_bytes:
65
+ from ..errors import ResourceLimitError
66
+ raise ResourceLimitError(
67
+ f"Refusing to parse {p}: line {count} exceeds max line length ({max_line_bytes} bytes). "
68
+ "Set SUBMINE_MAX_LINE_BYTES to increase the limit if you trust this input."
69
+ )
70
+ yield raw.decode(encoding, errors="replace").rstrip("\n")
71
+
72
+ def is_tool_available(name: str) -> bool:
73
+ """Return True if a given executable exists on the system PATH."""
74
+ return shutil.which(name) is not None
75
+
76
+
77
+ def assert_regular_file(path: str | Path, *, must_exist: bool = True) -> Path:
78
+ """Validate that *path* points to a regular file.
79
+
80
+ We reject directories and (by default) require existence. We also resolve
81
+ symlinks to avoid surprises.
82
+ """
83
+ p = Path(path).expanduser()
84
+ if must_exist and not p.exists():
85
+ raise FileNotFoundError(f"File does not exist: {p}")
86
+ if must_exist and not p.is_file():
87
+ raise ValueError(f"Expected a regular file path, got: {p}")
88
+ # Resolve to eliminate '..' segments and follow symlinks.
89
+ return p.resolve()
90
+
91
+
92
+ def assert_file_size_under(path: str | Path, *, max_bytes: int = DEFAULT_MAX_INPUT_BYTES) -> None:
93
+ """Raise if the file exceeds *max_bytes*.
94
+
95
+ This is primarily used to protect code paths that necessarily read the full
96
+ file into memory (e.g., certain bindings).
97
+ """
98
+ p = Path(path)
99
+ try:
100
+ size = p.stat().st_size
101
+ except OSError as e:
102
+ raise OSError(f"Unable to stat file: {p}") from e
103
+ if size > max_bytes:
104
+ from ..errors import ResourceLimitError
105
+ raise ResourceLimitError(
106
+ f"Refusing to load {p} ({size} bytes): exceeds configured limit of {max_bytes} bytes. "
107
+ "Set SUBMINE_MAX_INPUT_MB to increase the limit if you trust this input."
108
+ )
109
+
110
+
111
+ def safe_read_text(path: str | Path, *, encoding: str = "utf-8", max_bytes: int = DEFAULT_MAX_INPUT_BYTES) -> str:
112
+ """Read a text file with a hard cap on bytes."""
113
+ p = assert_regular_file(path)
114
+ assert_file_size_under(p, max_bytes=max_bytes)
115
+ return p.read_text(encoding=encoding, errors="replace")
@@ -0,0 +1,41 @@
1
+ """Logging utilities for submine.
2
+
3
+ This module configures a simple hierarchical logging setup that
4
+ suppresses log output by default. Users can enable verbose logging
5
+ per algorithm by passing ``verbose=True`` to the constructor. All
6
+ modules within submine should obtain their logger via
7
+ :func:`get_logger` rather than calling :func:`logging.getLogger`
8
+ directly.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ from typing import Optional
15
+
16
+
17
+ def get_logger(name: Optional[str] = None) -> logging.Logger:
18
+ """Return a logger with a default configuration.
19
+
20
+ If called for the first time this function sets up a root logger
21
+ with a basic configuration that logs messages with level WARNING and
22
+ above to stderr. Subsequent calls return child loggers that
23
+ propagate messages to the root.
24
+
25
+ Parameters
26
+ ----------
27
+ name: str, optional
28
+ Name of the logger. If omitted, a root logger is returned.
29
+
30
+ Returns
31
+ -------
32
+ logging.Logger
33
+ Configured logger instance.
34
+ """
35
+ if not logging.getLogger().handlers:
36
+ # Configure root logger once
37
+ logging.basicConfig(
38
+ level=logging.WARNING,
39
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
40
+ )
41
+ return logging.getLogger(name)
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.2
2
+ Name: submine
3
+ Version: 0.1.0
4
+ Summary: Modular subgraph mining library with unified API
5
+ Keywords: graph-mining,subgraph-mining,gspan,frequent-subgraph-mining
6
+ Author: Ridwan Amure
7
+ License: MIT
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Project-URL: Homepage, https://github.com/instabaines/submine
13
+ Project-URL: Repository, https://github.com/instabaines/submine
14
+ Project-URL: Issues, https://github.com/instabaines/submine/issues
15
+ Requires-Python: >=3.9
16
+ Requires-Dist: networkx>=2.8
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=7; extra == "dev"
19
+ Requires-Dist: pytest-cov>=4; extra == "dev"
20
+ Requires-Dist: build>=1; extra == "dev"
21
+ Requires-Dist: twine>=5; extra == "dev"
22
+ Requires-Dist: ruff>=0.5; extra == "dev"
23
+ Description-Content-Type: text/markdown
24
+
25
+ # submine
26
+
27
+ *submine* is a modular Python library for frequent subgraph mining that provides a unified, safe, and extensible interface over heterogeneous mining algorithms implemented in Python, C/C++, and Java.
28
+
29
+ The library is designed to support research-grade reproducibility and production-safe execution, while remaining lightweight and backend-agnostic.
30
+
31
+ ## Core Design Principles
32
+
33
+ * Algorithm-agnostic API
34
+ Users select an algorithm; submine handles format adaptation and execution.
35
+
36
+ * No redundant graph rewrites
37
+ Input graphs are converted directly into the format required by the selected algorithm.
38
+
39
+ * Strict input validation & safety
40
+ Resource limits, parameter validation, and hardened subprocess execution are enforced by default.
41
+
42
+ * Clean extensibility model
43
+ New algorithms can be plugged in without modifying core logic.
44
+
45
+ ## Package Layout
46
+ ```
47
+ submine/
48
+ ├── api.py # Public API entrypoint
49
+ ├── errors.py # Structured exception hierarchy
50
+ ├── core/
51
+ │ ├── graph.py # Canonical graph representation
52
+ │ └── result.py # Mining result containers
53
+ ├── algorithms/
54
+ │ ├── base.py # Base miner abstraction
55
+ │ ├── gspan.py # gSpan wrapper / implementation
56
+ │ ├── sopagrami.py # SoPaGraMi backend wrapper
57
+ │ └── ...
58
+ ├── io/
59
+ │ ├── common.py # Shared readers / writers
60
+ │ ├── transcode.py # Format detection & conversion
61
+ │ ├── gspan.py # gSpan I/O
62
+ │ ├── sopagrami.py # .lg I/O
63
+ │ └── ...
64
+ ├── utils/
65
+ │ └── checks.py # Input validation & resource limits
66
+ └── tests/
67
+ ├── unit/
68
+ └── functional/
69
+ ```
70
+
71
+ ## Supported Algorithms
72
+
73
+ | Algorithm | Graph Type | Backend | Notes |
74
+ | --------- | ------------------ | ------------ | ------------------------ |
75
+ | gSpan | Multiple graphs | Python / C++ | Frequent subgraph mining |
76
+ | SoPaGraMi | Single large graph | C++ | Social pattern mining |
77
+
78
+ Each algorithm declares:
79
+
80
+ * required input format,
81
+ * parameter schema,
82
+ * execution strategy (in-process vs subprocess).
83
+
84
+ ## Supported Input Formats
85
+
86
+ submine accepts graphs in multiple formats and converts them directly into the format required by the selected algorithm:
87
+
88
+ * Edge list (.txt, .edgelist)
89
+
90
+ * gSpan datasets (.data, .data.x, .data.N)
91
+
92
+ * SoPaGraMi .lg
93
+
94
+ * GEXF (.gexf)
95
+
96
+ * Format detection is automatic and deterministic.
97
+
98
+ ## Installation
99
+ ### Runtime installation
100
+
101
+
102
+ ```bash
103
+ pip install submine
104
+
105
+ ```
106
+ ### Dev installation
107
+
108
+ ```bash
109
+ pip install -e ".[dev]"
110
+ ```
111
+
112
+ ## Basic Usage
113
+
114
+ ```python
115
+ from submine.api import mine_subgraphs
116
+
117
+ results = mine_subgraphs(
118
+ data="graph.data",
119
+ algorithm="gspan",
120
+ min_support=5
121
+ )
122
+
123
+ ```
124
+
125
+ For SoPaGraMi:
126
+ ```python
127
+ results = mine_subgraphs("citeseer.lg", algorithm="sopagrami",
128
+ min_support=100,
129
+ sorted_seeds=4,
130
+ dump_images_csv=True, #if you want to dump the images
131
+ dump_sample_embeddings=True, #if you want to dump the embeddings, not implemented yet
132
+ out_dir= "." # /path/to/dir to save the images and embeddings default is ./sopagrami_result
133
+ )
134
+ ```
135
+
136
+ ## TODO
137
+ * Include more algorithms
138
+ * Added more utils for cross-usage between other graph/network libraries
139
+
140
+ # Citation
141
+
142
+ If you use gspan kindly cite the paper
143
+
144
+ ```
145
+ @inproceedings{yan2002gspan,
146
+ title={gspan: Graph-based substructure pattern mining},
147
+ author={Yan, Xifeng and Han, Jiawei},
148
+ booktitle={2002 IEEE International Conference on Data Mining, 2002. Proceedings.},
149
+ pages={721--724},
150
+ year={2002},
151
+ organization={IEEE}
152
+ }
153
+ ```
154
+
155
+ if you use sopagrami, kindly cite the paper
156
+
157
+ ```
158
+ @article{nguyen2020fast,
159
+ title={Fast and scalable algorithms for mining subgraphs in a single large graph},
160
+ author={Nguyen, Lam BQ and Vo, Bay and Le, Ngoc-Thao and Snasel, Vaclav and Zelinka, Ivan},
161
+ journal={Engineering Applications of Artificial Intelligence},
162
+ volume={90},
163
+ pages={103539},
164
+ year={2020},
165
+ publisher={Elsevier}
166
+ }
167
+ ```
168
+
169
+ You can cite this implementation as well
170
+
171
+ ```
172
+ @misc{amure_submine,
173
+ title = {submine: A Subgraph Mining Library},
174
+ author = {Amure, Ridwan},
175
+ year = {2025},
176
+ url = {https://github.com/instabaines/submine}
177
+ }
178
+ ```
@@ -0,0 +1,49 @@
1
+ submine/__init__.py,sha256=tnuVK76tHfOrZPntQLD-iUugR5PyyGGaE0kyrY2VRjI,899
2
+ submine/api.py,sha256=mGy1ynLFyWnj4yv1piDbBkebNE6GEw4FztwhrKsp4Ao,4369
3
+ submine/errors.py,sha256=YaEtNKkN4qpyrbhSLaN3Vkcot6sfG-9atiYPo8HWds8,1112
4
+ submine/registry.py,sha256=Jr8tbfbU_j5L5waH7gl4dqtrNB0X_boHnu3A0bVMmuo,254
5
+ submine/algorithms/__init__.py,sha256=2OvRa_DdtsGbE-Yf_FF0Q6XadS9wTSh2mlYfrc6Yl9s,953
6
+ submine/algorithms/base.py,sha256=YW2X021ZfHIhnggwMkzklh5KMdnN1HoQ1XB7MTfwmRk,5381
7
+ submine/algorithms/gspan.py,sha256=wa3kyEkNGN1GaEq8z4SglL4FUbXvYGkc1CwF96fmugo,5616
8
+ submine/algorithms/gspan_cpp.cpython-311-x86_64-linux-musl.so,sha256=Nb4MC28OKw0wusW4HO63Qv3U583mRyUvNoqY9plMYTk,228185
9
+ submine/algorithms/sopagrami.py,sha256=GDqPmZ-XRjxEAlzYDpRJTqpxRrNN1ccpP_0_PLPDLX4,9157
10
+ submine/algorithms/sopagrami_cpp.cpython-311-x86_64-linux-musl.so,sha256=OugFgAEZqb8BhV2JYNJzJ6HvAJIKasw82xTkCzixxo0,345897
11
+ submine/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ submine/backends/gspan/CMakeLists.txt,sha256=etRcErf0JK9w7xj_OghoFGszpiQooETHPZE4UIu3RvA,1586
13
+ submine/backends/gspan/dfs.cpp,sha256=sBsixi3CzhmnClbzmFDj9DOYQ-eaeplL3zVIO7q1lhI,2593
14
+ submine/backends/gspan/graph.cpp,sha256=zhtw3vXcOIFk1sZpoIOQUFIey6teLmPvCeQ_2LiD98w,4265
15
+ submine/backends/gspan/gspan.cpp,sha256=9iud5jf23oyUyjNWe_z_r94f48tnqK5EWQceWUfGIgk,21101
16
+ submine/backends/gspan/gspan.h,sha256=w9rPfrOB1JIi1M1AlD02PKsrELkRwC3uvcFc0jrLIz4,8247
17
+ submine/backends/gspan/ismin.cpp,sha256=MKYMYgnMIMaiAKZLIdNjEe7eUQ2E_mGzo6phOcDmiuM,4083
18
+ submine/backends/gspan/main.cpp,sha256=ipp3kPAssz-BBu_7rDi6GfPWwKjJfkJYIv74Rl5DH_A,3414
19
+ submine/backends/gspan/misc.cpp,sha256=w5Z2XvogkSNqskZezxauvii-1H0lqZ_RWIpEWfQK5LI,4961
20
+ submine/backends/gspan/python_bindings.cpp,sha256=IrOhBJiYjMOa90GEzncxeBZ1VkZ5-wkJHZB-cqNHZsI,3912
21
+ submine/backends/sopagrami/cpp/CMakeLists.txt,sha256=WXOoHkclzxmcBCyRBe_D-wV1bXM48CQeKioE-ZvR0Rk,1263
22
+ submine/backends/sopagrami/cpp/include/alg.hpp,sha256=D8krOTYxX47DS3ur_2fwyYYROmv1nXuM6rDiMx5spw8,5284
23
+ submine/backends/sopagrami/cpp/include/common/timer.hpp,sha256=h3wUdSJU_tpoXf5NVtmcUj92wBcNgUPUKEuLcCGiz3Q,360
24
+ submine/backends/sopagrami/cpp/src/alg.cpp,sha256=wd8CTdfUS7iemG6OCWn3mNtujJNplaf561Q0SCzynGo,28710
25
+ submine/backends/sopagrami/cpp/src/dump.cpp,sha256=0knEeEzPQXVOCJhvU3i4ELIfXdeJsTQUG1OTHxVXtN4,8942
26
+ submine/backends/sopagrami/cpp/src/main.cpp,sha256=oiI4I73bKqj_IbuNjEI4WHZL8M9pvIVlJwhcGgSGbHA,3197
27
+ submine/backends/sopagrami/cpp/src/python_bindings.cpp,sha256=e9OsCPhaFdkBmqTYSVMXLwFTrg2ig0nebw5dKUxtBqM,3356
28
+ submine/cli/__init__.py,sha256=k3MAv7_6UTA7O0Kp1FaMBd0veB0I3MNHdOsHql2oYG8,246
29
+ submine/cli/main.py,sha256=VkdlRALYUzSrgMOL3mBpNsrrfgluajvNKbPsScNL-lQ,2555
30
+ submine/core/__init__.py,sha256=prISqLxgIC4zUO39LDC69AhSrK4tGl1iC0BmKzuxL_A,443
31
+ submine/core/graph.py,sha256=7yzUfEyuRuOmGPACFg3AR51UoHkvgiGkE43ceXY5HLA,6739
32
+ submine/core/result.py,sha256=t9D1sYYWr-vdVJGw5AX-CSbiuSioVrrmbY3EeT54mX8,3653
33
+ submine/datasets/__init__.py,sha256=JggD0frQ9zZh9iXINpNMGvTkFac1DZa3U-Gf8umvnmE,384
34
+ submine/datasets/loaders.py,sha256=USiGS45-5DwshmJsx0f788Pb-471xcB7np31OUzkm5E,4952
35
+ submine/io/__init__.py,sha256=VAuJCGXE29KGGSq3jHGOV0mtYLS_yMcg_v-7ESHQze4,697
36
+ submine/io/common.py,sha256=afDqYSBco4DH8vuhRIZd0SKy_AqFPrrSOWNja5Of7lU,4905
37
+ submine/io/gexf.py,sha256=jdJYTgf3WyLIhIP-8m4b9JJbGGo2_PvAQ1ddwVGKBLU,2695
38
+ submine/io/gspan.py,sha256=B49RPwsSurN-rbPJUZppzJ_5hFzGgwBF9UYsTv3h_wI,8499
39
+ submine/io/sopagrami.py,sha256=qYqZvKpUmSxj8HDzn7IxNnhu11LRFcMX4NCKro4NIpw,4950
40
+ submine/io/transcode.py,sha256=9lj5QOZVz1Q6SQTCWGWlxEzjOfVTqFNzFQ026hVotG4,4428
41
+ submine/utils/__init__.py,sha256=pF9-ISTFv1XH4g7RiVz2Dapt1EdJBUNleJ7mhpELlGA,196
42
+ submine/utils/checks.py,sha256=fFlxFyPNRfhztgoYXzvfaoELzHN8qQOCmuHUaUbLX2E,4519
43
+ submine/utils/logging.py,sha256=zcx-noJ20Cl2sbaH1HZkKSZZyOXMfHB9JRbbVACJpIM,1275
44
+ submine.libs/libgcc_s-2298274a.so.1,sha256=V2IJwfUuwSSQhJqB29JtMx8hH79dCRjAvTQIGWShH-s,181737
45
+ submine.libs/libstdc++-08d5c7eb.so.6.0.33,sha256=k0S_imrCh_IE6WpvxrLoVPYiRyMVf15RDWRiXPIywV8,3562401
46
+ submine-0.1.0.dist-info/METADATA,sha256=Jq56qn6BxgKiVzGlwPtGUmNuLungSuByqgmP9WsGaxM,5321
47
+ submine-0.1.0.dist-info/WHEEL,sha256=Y3GjFou-TcvYG8r2iXbNPPSJEiD3r1Fl3pPy2E65s_w,117
48
+ submine-0.1.0.dist-info/RECORD,,
49
+ submine-0.1.0.dist-info/licenses/LICENSE,sha256=KNAADYhXKAIGySYjfCVq6P4ZDhIUFfHxeZFYa3-32ec,1055
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: scikit-build-core 0.11.6
3
+ Root-Is-Purelib: false
4
+ Tag: cp311-cp311-musllinux_1_2_x86_64
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
Binary file