xpectral-utils 0.1.0__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.
@@ -0,0 +1,42 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ id-token: write
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v6
17
+
18
+ - uses: astral-sh/setup-uv@v5
19
+ with:
20
+ version: "latest"
21
+
22
+ - name: Build package
23
+ run: uv build
24
+
25
+ - uses: actions/upload-artifact@v4
26
+ with:
27
+ name: dist
28
+ path: dist/
29
+
30
+ publish:
31
+ needs: build
32
+ runs-on: ubuntu-latest
33
+ environment:
34
+ name: pypi
35
+ url: https://pypi.org/project/xpectral-utils/
36
+ steps:
37
+ - uses: actions/download-artifact@v4
38
+ with:
39
+ name: dist
40
+ path: dist/
41
+
42
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,125 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+ MANIFEST
27
+
28
+ # PyInstaller
29
+ *.manifest
30
+ *.spec
31
+
32
+ # Installer logs
33
+ pip-log.txt
34
+ pip-delete-this-directory.txt
35
+
36
+ # Unit test / coverage reports
37
+ htmlcov/
38
+ .tox/
39
+ .nox/
40
+ .coverage
41
+ .coverage.*
42
+ .cache
43
+ nosetests.xml
44
+ coverage.xml
45
+ *.cover
46
+ *.py,cover
47
+ .hypothesis/
48
+ .pytest_cache/
49
+
50
+ # Translations
51
+ *.mo
52
+ *.pot
53
+
54
+ # Django stuff:
55
+ *.log
56
+ local_settings.py
57
+ db.sqlite3
58
+ db.sqlite3-journal
59
+
60
+ # Flask stuff:
61
+ instance/
62
+ .webassets-cache
63
+
64
+ # Scrapy stuff:
65
+ .scrapy
66
+
67
+ # Sphinx documentation
68
+ docs/build/
69
+ docs/_build/
70
+
71
+ # PyBuilder
72
+ target/
73
+
74
+ # Jupyter Notebook
75
+ .ipynb_checkpoints
76
+
77
+ # IPython
78
+ profile_default/
79
+ ipython_config.py
80
+
81
+ # pyenv
82
+ .python-version
83
+
84
+ # pipenv
85
+ Pipfile.lock
86
+
87
+ # poetry
88
+ poetry.lock
89
+
90
+ # PEP 582
91
+ __pypackages__/
92
+
93
+ # Ruff cache
94
+ .ruff_cache/
95
+
96
+ # Claude Code
97
+ .claude/worktrees/
98
+
99
+ # VS Code
100
+ .vscode/
101
+
102
+ # PyCharm
103
+ .idea/
104
+
105
+ # Local virtual environments
106
+ venv/
107
+ .env
108
+ .env/
109
+ env/
110
+ .env.*
111
+ !.env.example
112
+ .venv/
113
+
114
+ # macOS
115
+ .DS_Store
116
+
117
+ # Windows
118
+ Thumbs.db
119
+
120
+ # Project-specific data files
121
+ *.parquet
122
+ *.csv
123
+ *.dbn
124
+ *.dbn.zst
125
+ logs/
@@ -0,0 +1,25 @@
1
+ repos:
2
+ - repo: local # Hooks defined in this project, not pulled from a remote repo
3
+ hooks:
4
+ # ---- Linter: finds issues and auto-fixes them (includes import sorting) ----
5
+ - id: ruff-check # Unique identifier for this hook
6
+ name: Lint with ruff # Human-readable label shown in pre-commit's output
7
+ entry: uv run ruff check --fix --exit-non-zero-on-fix
8
+ # Command pre-commit runs:
9
+ # uv run → run inside the project's uv environment
10
+ # ruff check → the linter
11
+ # --fix → automatically fix what it can
12
+ # --exit-non-zero-on-fix → fail the hook when a fix is applied,
13
+ # so the run also fails in CI, not just locally
14
+ language: system # Use the command as-is from the system/uv env (don't let pre-commit build its own)
15
+ types_or: [python, pyi, jupyter] # Run on .py files, .pyi stubs, AND .ipynb notebooks
16
+ require_serial: true # Don't run this hook in parallel with others (avoids fix/format conflicts)
17
+ stages: [pre-commit] # Run at commit time (i.e. when you `git commit`)
18
+
19
+ # ---- Formatter: reformats code style (runs after the linter) ----
20
+ - id: ruff-format # Unique identifier for this hook
21
+ name: Format with ruff # Human-readable label shown in pre-commit's output
22
+ entry: uv run ruff format # Command: run `ruff format` inside the uv environment
23
+ language: system # Use the command as-is from the system/uv env
24
+ types_or: [python, pyi, jupyter] # Format .py files, .pyi stubs, AND .ipynb notebooks
25
+ stages: [pre-commit] # Run at commit time
@@ -0,0 +1,10 @@
1
+ # Contributing
2
+
3
+ ## Setup
4
+
5
+ ```bash
6
+ uv sync --group dev
7
+ uv run pre-commit install
8
+ ```
9
+
10
+ `pre-commit install` registers the git hook that applies the repo's formatter locally before you commit.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 BayQuant
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.
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.5
2
+ Name: xpectral-utils
3
+ Version: 0.1.0
4
+ Summary: Shared utility functions for xpectral projects
5
+ Project-URL: Homepage, https://github.com/bayquant/xpectral-utils
6
+ Author: BayQuant
7
+ License: MIT License
8
+
9
+ Copyright (c) 2025 BayQuant
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+ License-File: LICENSE
29
+ Keywords: quant,utils
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Operating System :: OS Independent
32
+ Classifier: Programming Language :: Python :: 3
33
+ Classifier: Programming Language :: Python :: 3.12
34
+ Requires-Python: >=3.12
35
+ Provides-Extra: cache
36
+ Requires-Dist: diskcache>=5.6.3; extra == 'cache'
37
+ Provides-Extra: s3
38
+ Requires-Dist: boto3>=1.34; extra == 's3'
39
+ Description-Content-Type: text/markdown
40
+
41
+ # xpectral-utils
42
+
43
+ ![Spectral decomposition](https://raw.githubusercontent.com/bayquant/xpectral-utils/main/assets/xpectral_banner.gif)
44
+
45
+ Shared utility functions used across xpectral projects.
46
+
47
+ ## Install
48
+
49
+ ```bash
50
+ uv pip install xpectral-utils
51
+ ```
52
+
53
+ ## Development
54
+
55
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -0,0 +1,15 @@
1
+ # xpectral-utils
2
+
3
+ ![Spectral decomposition](https://raw.githubusercontent.com/bayquant/xpectral-utils/main/assets/xpectral_banner.gif)
4
+
5
+ Shared utility functions used across xpectral projects.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ uv pip install xpectral-utils
11
+ ```
12
+
13
+ ## Development
14
+
15
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -0,0 +1,247 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "3ae94973",
6
+ "metadata": {},
7
+ "source": "# ON-DISK FUNCTION CACHING WITH `DISK_CACHE`\n\n`xpectral_utils.cache.disk_cache` memoizes a function's return values **on disk**,\nkeyed by its arguments. Unlike `functools.lru_cache`, the results persist across\nprocesses and interpreter restarts, which is ideal for slow, repeatable work such\nas API data pulls.\n\nEach decorated function gets its own cache directory (no shared global instance),\nbacked by [`diskcache`](https://grantjenks.com/docs/diskcache/), which handles the\narg-hashing, atomic writes, TTL, size limits, and LRU eviction for us. Values are\npickled, so the function must take picklable/hashable arguments and return\npicklable objects."
8
+ },
9
+ {
10
+ "cell_type": "code",
11
+ "execution_count": null,
12
+ "id": "e3859601",
13
+ "metadata": {
14
+ "execution": {
15
+ "iopub.execute_input": "2026-07-05T07:59:03.978095Z",
16
+ "iopub.status.busy": "2026-07-05T07:59:03.977902Z",
17
+ "iopub.status.idle": "2026-07-05T07:59:03.993653Z",
18
+ "shell.execute_reply": "2026-07-05T07:59:03.992954Z"
19
+ }
20
+ },
21
+ "outputs": [],
22
+ "source": "%load_ext autoreload\n%autoreload 2\n\n# Standard library imports\nimport tempfile\nimport time\n\n# First-party imports\nfrom xpectral_utils.cache import disk_cache\n\n# Keep this example self-contained: cache into a throwaway temp directory\n# instead of the default ~/.cache/xpectral.\nCACHE_DIR = tempfile.mkdtemp(prefix=\"xpectral-cache-demo-\")\nCACHE_DIR"
23
+ },
24
+ {
25
+ "cell_type": "markdown",
26
+ "id": "00e28c86",
27
+ "metadata": {},
28
+ "source": [
29
+ "## A slow function\n",
30
+ "\n",
31
+ "We simulate expensive work with `time.sleep` and count how many times the body\n",
32
+ "actually runs, so we can prove the cache is doing its job."
33
+ ]
34
+ },
35
+ {
36
+ "cell_type": "code",
37
+ "execution_count": 2,
38
+ "id": "57933e19",
39
+ "metadata": {
40
+ "execution": {
41
+ "iopub.execute_input": "2026-07-05T07:59:03.995397Z",
42
+ "iopub.status.busy": "2026-07-05T07:59:03.995257Z",
43
+ "iopub.status.idle": "2026-07-05T07:59:04.003631Z",
44
+ "shell.execute_reply": "2026-07-05T07:59:04.003144Z"
45
+ }
46
+ },
47
+ "outputs": [],
48
+ "source": [
49
+ "calls = {\"n\": 0}\n",
50
+ "\n",
51
+ "\n",
52
+ "@disk_cache(\"demo\", cache_dir=CACHE_DIR)\n",
53
+ "def slow_add(a, b):\n",
54
+ " calls[\"n\"] += 1\n",
55
+ " time.sleep(0.5) # pretend this is a network / heavy compute call\n",
56
+ " return a + b"
57
+ ]
58
+ },
59
+ {
60
+ "cell_type": "markdown",
61
+ "id": "93cf0bd5",
62
+ "metadata": {},
63
+ "source": [
64
+ "## Miss vs. hit\n",
65
+ "\n",
66
+ "The first call runs the body (a *miss*); the second call with identical arguments\n",
67
+ "is served from disk (a *hit*) and skips the work entirely."
68
+ ]
69
+ },
70
+ {
71
+ "cell_type": "code",
72
+ "execution_count": 3,
73
+ "id": "278ccd10",
74
+ "metadata": {
75
+ "execution": {
76
+ "iopub.execute_input": "2026-07-05T07:59:04.005358Z",
77
+ "iopub.status.busy": "2026-07-05T07:59:04.005250Z",
78
+ "iopub.status.idle": "2026-07-05T07:59:04.513898Z",
79
+ "shell.execute_reply": "2026-07-05T07:59:04.513257Z"
80
+ }
81
+ },
82
+ "outputs": [
83
+ {
84
+ "name": "stdout",
85
+ "output_type": "stream",
86
+ "text": [
87
+ "result: 5 -> 5\n",
88
+ "miss took 0.505s\n",
89
+ "hit took 0.0002s\n",
90
+ "function body ran 1 time(s)\n"
91
+ ]
92
+ }
93
+ ],
94
+ "source": [
95
+ "t0 = time.perf_counter()\n",
96
+ "r1 = slow_add(2, 3) # miss\n",
97
+ "t1 = time.perf_counter()\n",
98
+ "r2 = slow_add(2, 3) # hit\n",
99
+ "t2 = time.perf_counter()\n",
100
+ "\n",
101
+ "print(f\"result: {r1} -> {r2}\")\n",
102
+ "print(f\"miss took {t1 - t0:.3f}s\")\n",
103
+ "print(f\"hit took {t2 - t1:.4f}s\")\n",
104
+ "print(f\"function body ran {calls['n']} time(s)\")\n",
105
+ "\n",
106
+ "assert r1 == r2\n",
107
+ "assert calls[\"n\"] == 1 # the body ran only for the miss"
108
+ ]
109
+ },
110
+ {
111
+ "cell_type": "markdown",
112
+ "id": "4d443128",
113
+ "metadata": {},
114
+ "source": [
115
+ "## Expiring stale entries (TTL)\n",
116
+ "\n",
117
+ "Pass `expire=<seconds>` to invalidate entries after a time-to-live. This matters\n",
118
+ "for data that goes stale (e.g. recent market bars). Here entries live for 1 second."
119
+ ]
120
+ },
121
+ {
122
+ "cell_type": "code",
123
+ "execution_count": 4,
124
+ "id": "9e86ad92",
125
+ "metadata": {
126
+ "execution": {
127
+ "iopub.execute_input": "2026-07-05T07:59:04.516530Z",
128
+ "iopub.status.busy": "2026-07-05T07:59:04.516301Z",
129
+ "iopub.status.idle": "2026-07-05T07:59:05.635004Z",
130
+ "shell.execute_reply": "2026-07-05T07:59:05.634097Z"
131
+ }
132
+ },
133
+ "outputs": [
134
+ {
135
+ "name": "stdout",
136
+ "output_type": "stream",
137
+ "text": [
138
+ "after two quick calls: body ran 1 time(s)\n"
139
+ ]
140
+ },
141
+ {
142
+ "name": "stdout",
143
+ "output_type": "stream",
144
+ "text": [
145
+ "after TTL expiry: body ran 2 time(s)\n"
146
+ ]
147
+ }
148
+ ],
149
+ "source": [
150
+ "ttl_calls = {\"n\": 0}\n",
151
+ "\n",
152
+ "\n",
153
+ "@disk_cache(\"demo_ttl\", expire=1, cache_dir=CACHE_DIR)\n",
154
+ "def slow_double(x):\n",
155
+ " ttl_calls[\"n\"] += 1\n",
156
+ " return x * 2\n",
157
+ "\n",
158
+ "\n",
159
+ "slow_double(21) # miss -> body runs\n",
160
+ "slow_double(21) # hit -> cached\n",
161
+ "print(f\"after two quick calls: body ran {ttl_calls['n']} time(s)\")\n",
162
+ "\n",
163
+ "time.sleep(1.1) # let the entry expire\n",
164
+ "slow_double(21) # miss again -> body re-runs\n",
165
+ "print(f\"after TTL expiry: body ran {ttl_calls['n']} time(s)\")\n",
166
+ "\n",
167
+ "assert ttl_calls[\"n\"] == 2"
168
+ ]
169
+ },
170
+ {
171
+ "cell_type": "markdown",
172
+ "id": "21afaea3",
173
+ "metadata": {},
174
+ "source": [
175
+ "## Inspecting and clearing\n",
176
+ "\n",
177
+ "The wrapped function exposes its backing store as `func.cache` (a\n",
178
+ "`diskcache.Cache`), so you can check how many entries it holds or clear them."
179
+ ]
180
+ },
181
+ {
182
+ "cell_type": "code",
183
+ "execution_count": 5,
184
+ "id": "573e6c6e",
185
+ "metadata": {
186
+ "execution": {
187
+ "iopub.execute_input": "2026-07-05T07:59:05.637120Z",
188
+ "iopub.status.busy": "2026-07-05T07:59:05.636935Z",
189
+ "iopub.status.idle": "2026-07-05T07:59:06.148140Z",
190
+ "shell.execute_reply": "2026-07-05T07:59:06.147301Z"
191
+ }
192
+ },
193
+ "outputs": [
194
+ {
195
+ "name": "stdout",
196
+ "output_type": "stream",
197
+ "text": [
198
+ "entries before clear: 1\n",
199
+ "entries after clear: 0\n"
200
+ ]
201
+ },
202
+ {
203
+ "name": "stdout",
204
+ "output_type": "stream",
205
+ "text": [
206
+ "body ran again after clear: True\n"
207
+ ]
208
+ }
209
+ ],
210
+ "source": [
211
+ "print(f\"entries before clear: {len(slow_add.cache)}\")\n",
212
+ "\n",
213
+ "slow_add.cache.clear()\n",
214
+ "print(f\"entries after clear: {len(slow_add.cache)}\")\n",
215
+ "\n",
216
+ "# With the cache emptied, the next call is a miss again and the body re-runs.\n",
217
+ "before = calls[\"n\"]\n",
218
+ "slow_add(2, 3)\n",
219
+ "print(f\"body ran again after clear: {calls['n'] == before + 1}\")\n",
220
+ "\n",
221
+ "assert len(slow_add.cache) == 1\n",
222
+ "assert calls[\"n\"] == before + 1"
223
+ ]
224
+ }
225
+ ],
226
+ "metadata": {
227
+ "kernelspec": {
228
+ "display_name": "xpectral-utils",
229
+ "language": "python",
230
+ "name": "python3"
231
+ },
232
+ "language_info": {
233
+ "codemirror_mode": {
234
+ "name": "ipython",
235
+ "version": 3
236
+ },
237
+ "file_extension": ".py",
238
+ "mimetype": "text/x-python",
239
+ "name": "python",
240
+ "nbconvert_exporter": "python",
241
+ "pygments_lexer": "ipython3",
242
+ "version": "3.12.11"
243
+ }
244
+ },
245
+ "nbformat": 4,
246
+ "nbformat_minor": 5
247
+ }
@@ -0,0 +1,87 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "a1b2c3d4",
6
+ "metadata": {},
7
+ "source": "# RATE LIMITER\n\nThrottle calls to an API with `RateLimiter`: at most `calls` acquisitions per `per_seconds` window."
8
+ },
9
+ {
10
+ "cell_type": "code",
11
+ "execution_count": 1,
12
+ "id": "b2c3d4e5",
13
+ "metadata": {},
14
+ "outputs": [],
15
+ "source": "%load_ext autoreload\n%autoreload 2"
16
+ },
17
+ {
18
+ "cell_type": "code",
19
+ "execution_count": 2,
20
+ "id": "c3d4e5f6",
21
+ "metadata": {},
22
+ "outputs": [],
23
+ "source": "# Standard library imports\nimport time\n\n# First-party imports\nfrom xpectral_utils.rate_limiter import RateLimiter\n\nCALLS = 3\nPER_SECONDS = 1"
24
+ },
25
+ {
26
+ "cell_type": "markdown",
27
+ "id": "d4e5f6a7",
28
+ "metadata": {},
29
+ "source": "## Throttle a loop\n\nCall `acquire()` before each request. The first `CALLS` go through immediately; the next one blocks until the oldest call leaves the window."
30
+ },
31
+ {
32
+ "cell_type": "code",
33
+ "execution_count": 3,
34
+ "id": "e5f6a7b8",
35
+ "metadata": {},
36
+ "outputs": [
37
+ {
38
+ "name": "stdout",
39
+ "output_type": "stream",
40
+ "text": "call 0 at 0.0s\ncall 1 at 0.0s\ncall 2 at 0.0s\ncall 3 at 1.0s\ncall 4 at 1.0s\ncall 5 at 1.0s\ncall 6 at 2.0s\ncall 7 at 2.0s\ncall 8 at 2.0s\n"
41
+ }
42
+ ],
43
+ "source": "limiter = RateLimiter(calls=CALLS, per_seconds=PER_SECONDS)\nstart = time.monotonic()\n\nfor i in range(9):\n limiter.acquire()\n print(f\"call {i} at {time.monotonic() - start:.1f}s\")"
44
+ },
45
+ {
46
+ "cell_type": "markdown",
47
+ "id": "f6a7b8c9",
48
+ "metadata": {},
49
+ "source": "## Invalid arguments\n\n`calls` and `per_seconds` must be positive."
50
+ },
51
+ {
52
+ "cell_type": "code",
53
+ "execution_count": 4,
54
+ "id": "a7b8c9d0",
55
+ "metadata": {},
56
+ "outputs": [
57
+ {
58
+ "name": "stdout",
59
+ "output_type": "stream",
60
+ "text": "calls and per_seconds must be positive\n"
61
+ }
62
+ ],
63
+ "source": "try:\n RateLimiter(calls=0, per_seconds=1)\nexcept ValueError as error:\n print(error)"
64
+ }
65
+ ],
66
+ "metadata": {
67
+ "kernelspec": {
68
+ "display_name": "xpectral-utils",
69
+ "language": "python",
70
+ "name": "python3"
71
+ },
72
+ "language_info": {
73
+ "codemirror_mode": {
74
+ "name": "ipython",
75
+ "version": 3
76
+ },
77
+ "file_extension": ".py",
78
+ "mimetype": "text/x-python",
79
+ "name": "python",
80
+ "nbconvert_exporter": "python",
81
+ "pygments_lexer": "ipython3",
82
+ "version": "3.13.7"
83
+ }
84
+ },
85
+ "nbformat": 4,
86
+ "nbformat_minor": 5
87
+ }
@@ -0,0 +1,57 @@
1
+ [project]
2
+ name = "xpectral-utils"
3
+ version = "0.1.0"
4
+ description = "Shared utility functions for xpectral projects"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ license = { file = "LICENSE" }
8
+ authors = [
9
+ { name = "BayQuant" }
10
+ ]
11
+ keywords = ["utils", "quant"]
12
+ classifiers = [
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.12",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Operating System :: OS Independent"
17
+ ]
18
+
19
+ dependencies = []
20
+
21
+ [project.optional-dependencies]
22
+ cache = ["diskcache>=5.6.3"] # xpectral_utils.cache
23
+ s3 = ["boto3>=1.34"] # xpectral_utils.s3
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/bayquant/xpectral-utils"
27
+
28
+ [build-system]
29
+ requires = ["hatchling"]
30
+ build-backend = "hatchling.build"
31
+
32
+ [dependency-groups]
33
+ dev = [
34
+ "ruff>=0.6", # code formatter and linter
35
+ "pre-commit>=4.4.0", # git hook manager
36
+ "pytest>=8.0.0", # test runner
37
+ "ipykernel>=7.0.1", # jupyter notebook support
38
+ "boto3>=1.34", # tests for xpectral_utils.s3 (the s3 extra at runtime)
39
+ "diskcache>=5.6.3", # tests for xpectral_utils.cache (the cache extra at runtime)
40
+ ]
41
+
42
+ [tool.hatch.build.targets.wheel]
43
+ packages = ["xpectral_utils"]
44
+
45
+ [tool.ruff]
46
+ line-length = 88
47
+ target-version = "py312"
48
+
49
+ [tool.ruff.lint]
50
+ select = ["E", "F", "W", "I"]
51
+ ignore = [
52
+ "E501", # Line too long: handled by formatter, not linter
53
+ ]
54
+
55
+ [tool.ruff.lint.isort]
56
+ known-first-party = ["xpectral_utils"]
57
+ force-single-line = true
File without changes