arcobi-sdk 0.1.0b1__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.
Files changed (79) hide show
  1. arcobi_sdk-0.1.0b1/.gitignore +12 -0
  2. arcobi_sdk-0.1.0b1/LICENSE +37 -0
  3. arcobi_sdk-0.1.0b1/PKG-INFO +164 -0
  4. arcobi_sdk-0.1.0b1/README.md +123 -0
  5. arcobi_sdk-0.1.0b1/deploy/pool-server/Dockerfile +30 -0
  6. arcobi_sdk-0.1.0b1/deploy/pool-server/README.md +108 -0
  7. arcobi_sdk-0.1.0b1/deploy/pool-server/credentials_pool.example.yaml +23 -0
  8. arcobi_sdk-0.1.0b1/deploy/pool-server/docker-compose.yml +53 -0
  9. arcobi_sdk-0.1.0b1/docs/CREDENTIALS.md +98 -0
  10. arcobi_sdk-0.1.0b1/docs/EXCEL.md +154 -0
  11. arcobi_sdk-0.1.0b1/pyproject.toml +107 -0
  12. arcobi_sdk-0.1.0b1/src/arcobi_sdk/__init__.py +88 -0
  13. arcobi_sdk-0.1.0b1/src/arcobi_sdk/cli.py +395 -0
  14. arcobi_sdk-0.1.0b1/src/arcobi_sdk/config.py +78 -0
  15. arcobi_sdk-0.1.0b1/src/arcobi_sdk/datahub/__init__.py +63 -0
  16. arcobi_sdk-0.1.0b1/src/arcobi_sdk/datahub/client.py +525 -0
  17. arcobi_sdk-0.1.0b1/src/arcobi_sdk/datahub/extract.py +588 -0
  18. arcobi_sdk-0.1.0b1/src/arcobi_sdk/datahub/specs.py +247 -0
  19. arcobi_sdk-0.1.0b1/src/arcobi_sdk/errors.py +68 -0
  20. arcobi_sdk-0.1.0b1/src/arcobi_sdk/excel/__init__.py +58 -0
  21. arcobi_sdk-0.1.0b1/src/arcobi_sdk/excel/convert.py +361 -0
  22. arcobi_sdk-0.1.0b1/src/arcobi_sdk/excel/udf.py +369 -0
  23. arcobi_sdk-0.1.0b1/src/arcobi_sdk/excel/vba/ArcobiSdk.bas +327 -0
  24. arcobi_sdk-0.1.0b1/src/arcobi_sdk/excel/vba/README.md +157 -0
  25. arcobi_sdk-0.1.0b1/src/arcobi_sdk/io.py +234 -0
  26. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/__init__.py +35 -0
  27. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/client.py +704 -0
  28. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/priority.py +105 -0
  29. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/protocol.py +143 -0
  30. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/__init__.py +12 -0
  31. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/__main__.py +55 -0
  32. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/api/__init__.py +1 -0
  33. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/api/deps.py +55 -0
  34. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/api/routes.py +113 -0
  35. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/api/websocket.py +141 -0
  36. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/app.py +111 -0
  37. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/config.py +88 -0
  38. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/core/__init__.py +1 -0
  39. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/core/credentials.py +146 -0
  40. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/core/lease_manager.py +639 -0
  41. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/core/metrics.py +85 -0
  42. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/core/queue.py +179 -0
  43. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/core/store.py +973 -0
  44. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/providers/__init__.py +80 -0
  45. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/providers/base.py +68 -0
  46. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/providers/datahub.py +182 -0
  47. arcobi_sdk-0.1.0b1/src/arcobi_sdk/pool/server/providers/static.py +57 -0
  48. arcobi_sdk-0.1.0b1/src/arcobi_sdk/timeutils.py +76 -0
  49. arcobi_sdk-0.1.0b1/tests/__init__.py +0 -0
  50. arcobi_sdk-0.1.0b1/tests/datahub/__init__.py +0 -0
  51. arcobi_sdk-0.1.0b1/tests/datahub/test_client.py +383 -0
  52. arcobi_sdk-0.1.0b1/tests/datahub/test_extract.py +469 -0
  53. arcobi_sdk-0.1.0b1/tests/datahub/test_specs.py +126 -0
  54. arcobi_sdk-0.1.0b1/tests/excel/__init__.py +0 -0
  55. arcobi_sdk-0.1.0b1/tests/excel/test_convert.py +239 -0
  56. arcobi_sdk-0.1.0b1/tests/excel/test_udf.py +308 -0
  57. arcobi_sdk-0.1.0b1/tests/live/__init__.py +0 -0
  58. arcobi_sdk-0.1.0b1/tests/live/conftest.py +43 -0
  59. arcobi_sdk-0.1.0b1/tests/live/test_datahub_concurrency_live.py +72 -0
  60. arcobi_sdk-0.1.0b1/tests/live/test_datahub_live.py +56 -0
  61. arcobi_sdk-0.1.0b1/tests/pool/__init__.py +1 -0
  62. arcobi_sdk-0.1.0b1/tests/pool/server/__init__.py +1 -0
  63. arcobi_sdk-0.1.0b1/tests/pool/server/conftest.py +87 -0
  64. arcobi_sdk-0.1.0b1/tests/pool/server/test_api.py +159 -0
  65. arcobi_sdk-0.1.0b1/tests/pool/server/test_lease_manager.py +214 -0
  66. arcobi_sdk-0.1.0b1/tests/pool/server/test_main.py +79 -0
  67. arcobi_sdk-0.1.0b1/tests/pool/server/test_postgres_mappers.py +51 -0
  68. arcobi_sdk-0.1.0b1/tests/pool/server/test_postgres_store.py +383 -0
  69. arcobi_sdk-0.1.0b1/tests/pool/server/test_scheduling.py +77 -0
  70. arcobi_sdk-0.1.0b1/tests/pool/server/test_units.py +309 -0
  71. arcobi_sdk-0.1.0b1/tests/pool/server/test_websocket.py +286 -0
  72. arcobi_sdk-0.1.0b1/tests/pool/test_client.py +496 -0
  73. arcobi_sdk-0.1.0b1/tests/test_cli.py +298 -0
  74. arcobi_sdk-0.1.0b1/tests/test_config.py +51 -0
  75. arcobi_sdk-0.1.0b1/tests/test_io.py +131 -0
  76. arcobi_sdk-0.1.0b1/tests/test_priority.py +60 -0
  77. arcobi_sdk-0.1.0b1/tests/test_protocol.py +52 -0
  78. arcobi_sdk-0.1.0b1/tests/test_timeutils.py +56 -0
  79. arcobi_sdk-0.1.0b1/tests/test_version.py +13 -0
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ build/
6
+ *.egg-info/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .coverage
10
+ htmlcov/
11
+ .env
12
+ deploy/pool-server/credentials_pool.yaml
@@ -0,0 +1,37 @@
1
+ Arcobi SDK Licence
2
+
3
+ Copyright (c) 2026 Arcobi. All rights reserved.
4
+
5
+ This software and its accompanying documentation (the "Software") are the
6
+ proprietary property of Arcobi. The Software is licensed, not sold.
7
+
8
+ 1. Grant. Subject to a current written agreement with Arcobi covering access to
9
+ the Arcobi data API, Arcobi grants you a non-exclusive, non-transferable,
10
+ revocable licence to install and use the Software, in unmodified form, solely
11
+ to access that API for your own internal business purposes.
12
+
13
+ 2. Restrictions. Except as that agreement or applicable mandatory law permits,
14
+ you may not: copy the Software other than as reasonably required for backup
15
+ or internal deployment; modify it or create derivative works from it;
16
+ distribute, sublicense, sell, rent, lease or otherwise make it available to
17
+ any third party; remove or alter any proprietary notice in it; or reverse
18
+ engineer, decompile or disassemble it.
19
+
20
+ 3. Reservation of rights. Arcobi retains all right, title and interest in and to
21
+ the Software. No rights are granted other than those expressly stated here.
22
+
23
+ 4. No warranty. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
26
+
27
+ 5. Limitation of liability. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW,
28
+ ARCOBI SHALL NOT BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
29
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
30
+ CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER DEALINGS IN THE SOFTWARE.
31
+
32
+ 6. Termination. This licence terminates automatically if you breach it, or when
33
+ your agreement with Arcobi ends. On termination you must stop using the
34
+ Software and delete all copies in your possession.
35
+
36
+ 7. Third-party components. The Software depends on separately distributed
37
+ third-party packages, which remain governed by their own licences.
@@ -0,0 +1,164 @@
1
+ Metadata-Version: 2.5
2
+ Name: arcobi-sdk
3
+ Version: 0.1.0b1
4
+ Summary: Fetch, align and export energy-market time-series data (Datahub), with an optional shared credential pool.
5
+ Project-URL: Documentation, https://sdk.arcobi.com
6
+ Author: Arcobi
7
+ License-Expression: LicenseRef-Proprietary
8
+ License-File: LICENSE
9
+ Keywords: aeso,data,datahub,energy,ercot,ieso,time-series
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Scientific/Engineering
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: httpx>=0.25
19
+ Requires-Dist: numpy>=1.24
20
+ Requires-Dist: pandas<4,>=2.2
21
+ Requires-Dist: pydantic>=2.0
22
+ Requires-Dist: websockets>=12
23
+ Provides-Extra: all
24
+ Requires-Dist: fastapi>=0.110; extra == 'all'
25
+ Requires-Dist: psycopg[binary,pool]>=3.1; extra == 'all'
26
+ Requires-Dist: pyarrow>=14; extra == 'all'
27
+ Requires-Dist: pydantic-settings>=2.0; extra == 'all'
28
+ Requires-Dist: pyyaml>=6.0; extra == 'all'
29
+ Requires-Dist: uvicorn[standard]>=0.27; extra == 'all'
30
+ Provides-Extra: excel
31
+ Requires-Dist: xlwings>=0.30; extra == 'excel'
32
+ Provides-Extra: parquet
33
+ Requires-Dist: pyarrow>=14; extra == 'parquet'
34
+ Provides-Extra: pool-server
35
+ Requires-Dist: fastapi>=0.110; extra == 'pool-server'
36
+ Requires-Dist: psycopg[binary,pool]>=3.1; extra == 'pool-server'
37
+ Requires-Dist: pydantic-settings>=2.0; extra == 'pool-server'
38
+ Requires-Dist: pyyaml>=6.0; extra == 'pool-server'
39
+ Requires-Dist: uvicorn[standard]>=0.27; extra == 'pool-server'
40
+ Description-Content-Type: text/markdown
41
+
42
+ # arcobi-sdk
43
+
44
+ Load energy-market time-series data from Datahub, align it on a time grid, and export it to a
45
+ DataFrame, CSV, Parquet or JSON. Optionally share a pool of vendor credentials across many jobs
46
+ through a small lease service. It takes you from the API to a clean, aligned frame or file,
47
+ ready for the features and models you build on top.
48
+
49
+ ```bash
50
+ pip install arcobi-sdk # extraction + export + pool client
51
+ pip install "arcobi-sdk[parquet]" # + Parquet export
52
+ pip install "arcobi-sdk[pool-server]" # + the credential-pool service
53
+ ```
54
+
55
+ ## Quick start
56
+
57
+ ```python
58
+ from datetime import datetime, timedelta
59
+
60
+ from arcobi_sdk import DatahubClient, fetch_streams, to_csv
61
+
62
+ end = datetime.now()
63
+ start = end - timedelta(days=7)
64
+
65
+ with DatahubClient(username="...", password="...") as client:
66
+ df = fetch_streams(
67
+ client, streams=[1001, 1002], start=start, end=end, names=["demand", "price"]
68
+ )
69
+
70
+ to_csv(df, "week.csv")
71
+ ```
72
+
73
+ `fetch_streams` fetches the streams one after another. To fetch in parallel, share the same
74
+ client across threads: it is thread-safe and holds the account's single token. Never open a
75
+ second client with the same account, Datahub allows one session per account (see the
76
+ [credentials guide](https://sdk.arcobi.com/credentials)).
77
+
78
+ Credentials can also come from the environment (`DATAHUB_USERNAME` / `DATAHUB_PASSWORD`,
79
+ or a pre-issued `DATAHUB_BEARER_TOKEN`), or from a local `.env` file loaded with
80
+ `arcobi_sdk.load_env()`. The [credentials guide](https://sdk.arcobi.com/credentials) covers every
81
+ option and the live smoke test.
82
+
83
+ ## Sharing credentials across jobs
84
+
85
+ If several processes share a handful of vendor accounts, run the pool service once and let each
86
+ job lease a token. Leases carry a generic **priority level**: `0` is served first, then `1`, then
87
+ `2`; the default names are `high`, `normal`, `low` and deployments may rename them.
88
+
89
+ ```python
90
+ from arcobi_sdk import DatahubClient, fetch_streams
91
+ from arcobi_sdk.pool import PoolClient
92
+
93
+ pool = PoolClient("http://localhost:8002", api_key="...")
94
+ with pool.lease(holder_id="nightly-report", priority="high") as lease:
95
+ with DatahubClient(token_provider=lease.token_provider) as client:
96
+ df = fetch_streams(client, [1001], start, end)
97
+ ```
98
+
99
+ The token is refreshed by the service before it expires and the client picks the new one up
100
+ mid-extraction. Running the service is described in the
101
+ [credential-pool guide](https://sdk.arcobi.com/examples/credential-pool).
102
+
103
+ ## Command line and Excel
104
+
105
+ Every install also provides a small CLI, handy for scripts, schedulers and spreadsheet macros:
106
+
107
+ ```bash
108
+ arcobi-sdk check # authenticate, print token expiry, release
109
+ arcobi-sdk fetch --streams 1001,1002 --names demand,price \
110
+ --start 2024-01-01 --end 2024-01-08 --out week.csv
111
+ ```
112
+
113
+ Exit codes are stable (0 ok, 2 bad arguments or config, 3 vendor or auth error, 4 no data) and
114
+ errors are one `error: ...` line on stderr, so a VBA `Shell` call can act on them. For Excel,
115
+ `ArcobiSdk.bas` is a ready-made macro module that runs the CLI and imports the CSV onto a
116
+ sheet. It ships inside the installed package:
117
+
118
+ ```bash
119
+ python -c "import importlib.resources as r; print(r.files('arcobi_sdk.excel') / 'vba')"
120
+ ```
121
+
122
+ `pip install "arcobi-sdk[excel]"` additionally provides xlwings-based functions (`datahub_fetch`,
123
+ `datahub_fetch_to_sheet`, `datahub_check`) for spill formulas and `RunPython`. The
124
+ [Excel guide](https://sdk.arcobi.com/excel) walks through both paths.
125
+
126
+ ## What is in the box
127
+
128
+ | Module | Purpose |
129
+ |---|---|
130
+ | `arcobi_sdk.datahub` | `DatahubClient`, `fetch_streams`, extraction planning, time-grid alignment, gap interpolation |
131
+ | `arcobi_sdk.timeutils` | local-to-UTC conversion, timestamp naming |
132
+ | `arcobi_sdk.io` | `to_csv`, `to_parquet`, `to_json`, timestamped output folders |
133
+ | `arcobi_sdk.pool` | credential-pool client, priorities, wire protocol; server behind an extra |
134
+ | `arcobi_sdk.cli` | `arcobi-sdk fetch / check / version` command line |
135
+ | `arcobi_sdk.excel` | Excel helpers: value conversion, xlwings UDFs (extra `excel`), VBA module |
136
+
137
+ ## Documentation
138
+
139
+ **<https://sdk.arcobi.com>** -- the guides referred to above, plus the API reference generated
140
+ from these docstrings:
141
+
142
+ | Guide | |
143
+ |---|---|
144
+ | [Installation](https://sdk.arcobi.com/installation) | extras, supported versions |
145
+ | [Quick start](https://sdk.arcobi.com/quickstart) | first extraction end to end |
146
+ | [Credentials](https://sdk.arcobi.com/credentials) | every way to authenticate, and the live smoke test |
147
+ | [Credential pool](https://sdk.arcobi.com/examples/credential-pool) | running and deploying the pool service |
148
+ | [Excel](https://sdk.arcobi.com/excel) | the VBA module and the xlwings UDFs |
149
+
150
+ The same material ships in the source distribution on PyPI, under `docs/` and
151
+ `deploy/pool-server/`. A wheel install carries the package only.
152
+
153
+ ## Development
154
+
155
+ ```bash
156
+ uv sync --all-extras --dev
157
+ uv run pytest
158
+ uv run ruff check . && uv run ruff format --check .
159
+ ```
160
+
161
+ Tests do not need a database or network access. Tests marked `postgres` run only when
162
+ `POOL_TEST_DATABASE_URL` points at a reachable PostgreSQL. Tests marked `live` call the real
163
+ Datahub API and run only with `DATAHUB_LIVE=1`, credentials, and `DATAHUB_TEST_STREAM_ID`
164
+ set (`uv run pytest -m live`).
@@ -0,0 +1,123 @@
1
+ # arcobi-sdk
2
+
3
+ Load energy-market time-series data from Datahub, align it on a time grid, and export it to a
4
+ DataFrame, CSV, Parquet or JSON. Optionally share a pool of vendor credentials across many jobs
5
+ through a small lease service. It takes you from the API to a clean, aligned frame or file,
6
+ ready for the features and models you build on top.
7
+
8
+ ```bash
9
+ pip install arcobi-sdk # extraction + export + pool client
10
+ pip install "arcobi-sdk[parquet]" # + Parquet export
11
+ pip install "arcobi-sdk[pool-server]" # + the credential-pool service
12
+ ```
13
+
14
+ ## Quick start
15
+
16
+ ```python
17
+ from datetime import datetime, timedelta
18
+
19
+ from arcobi_sdk import DatahubClient, fetch_streams, to_csv
20
+
21
+ end = datetime.now()
22
+ start = end - timedelta(days=7)
23
+
24
+ with DatahubClient(username="...", password="...") as client:
25
+ df = fetch_streams(
26
+ client, streams=[1001, 1002], start=start, end=end, names=["demand", "price"]
27
+ )
28
+
29
+ to_csv(df, "week.csv")
30
+ ```
31
+
32
+ `fetch_streams` fetches the streams one after another. To fetch in parallel, share the same
33
+ client across threads: it is thread-safe and holds the account's single token. Never open a
34
+ second client with the same account, Datahub allows one session per account (see the
35
+ [credentials guide](https://sdk.arcobi.com/credentials)).
36
+
37
+ Credentials can also come from the environment (`DATAHUB_USERNAME` / `DATAHUB_PASSWORD`,
38
+ or a pre-issued `DATAHUB_BEARER_TOKEN`), or from a local `.env` file loaded with
39
+ `arcobi_sdk.load_env()`. The [credentials guide](https://sdk.arcobi.com/credentials) covers every
40
+ option and the live smoke test.
41
+
42
+ ## Sharing credentials across jobs
43
+
44
+ If several processes share a handful of vendor accounts, run the pool service once and let each
45
+ job lease a token. Leases carry a generic **priority level**: `0` is served first, then `1`, then
46
+ `2`; the default names are `high`, `normal`, `low` and deployments may rename them.
47
+
48
+ ```python
49
+ from arcobi_sdk import DatahubClient, fetch_streams
50
+ from arcobi_sdk.pool import PoolClient
51
+
52
+ pool = PoolClient("http://localhost:8002", api_key="...")
53
+ with pool.lease(holder_id="nightly-report", priority="high") as lease:
54
+ with DatahubClient(token_provider=lease.token_provider) as client:
55
+ df = fetch_streams(client, [1001], start, end)
56
+ ```
57
+
58
+ The token is refreshed by the service before it expires and the client picks the new one up
59
+ mid-extraction. Running the service is described in the
60
+ [credential-pool guide](https://sdk.arcobi.com/examples/credential-pool).
61
+
62
+ ## Command line and Excel
63
+
64
+ Every install also provides a small CLI, handy for scripts, schedulers and spreadsheet macros:
65
+
66
+ ```bash
67
+ arcobi-sdk check # authenticate, print token expiry, release
68
+ arcobi-sdk fetch --streams 1001,1002 --names demand,price \
69
+ --start 2024-01-01 --end 2024-01-08 --out week.csv
70
+ ```
71
+
72
+ Exit codes are stable (0 ok, 2 bad arguments or config, 3 vendor or auth error, 4 no data) and
73
+ errors are one `error: ...` line on stderr, so a VBA `Shell` call can act on them. For Excel,
74
+ `ArcobiSdk.bas` is a ready-made macro module that runs the CLI and imports the CSV onto a
75
+ sheet. It ships inside the installed package:
76
+
77
+ ```bash
78
+ python -c "import importlib.resources as r; print(r.files('arcobi_sdk.excel') / 'vba')"
79
+ ```
80
+
81
+ `pip install "arcobi-sdk[excel]"` additionally provides xlwings-based functions (`datahub_fetch`,
82
+ `datahub_fetch_to_sheet`, `datahub_check`) for spill formulas and `RunPython`. The
83
+ [Excel guide](https://sdk.arcobi.com/excel) walks through both paths.
84
+
85
+ ## What is in the box
86
+
87
+ | Module | Purpose |
88
+ |---|---|
89
+ | `arcobi_sdk.datahub` | `DatahubClient`, `fetch_streams`, extraction planning, time-grid alignment, gap interpolation |
90
+ | `arcobi_sdk.timeutils` | local-to-UTC conversion, timestamp naming |
91
+ | `arcobi_sdk.io` | `to_csv`, `to_parquet`, `to_json`, timestamped output folders |
92
+ | `arcobi_sdk.pool` | credential-pool client, priorities, wire protocol; server behind an extra |
93
+ | `arcobi_sdk.cli` | `arcobi-sdk fetch / check / version` command line |
94
+ | `arcobi_sdk.excel` | Excel helpers: value conversion, xlwings UDFs (extra `excel`), VBA module |
95
+
96
+ ## Documentation
97
+
98
+ **<https://sdk.arcobi.com>** -- the guides referred to above, plus the API reference generated
99
+ from these docstrings:
100
+
101
+ | Guide | |
102
+ |---|---|
103
+ | [Installation](https://sdk.arcobi.com/installation) | extras, supported versions |
104
+ | [Quick start](https://sdk.arcobi.com/quickstart) | first extraction end to end |
105
+ | [Credentials](https://sdk.arcobi.com/credentials) | every way to authenticate, and the live smoke test |
106
+ | [Credential pool](https://sdk.arcobi.com/examples/credential-pool) | running and deploying the pool service |
107
+ | [Excel](https://sdk.arcobi.com/excel) | the VBA module and the xlwings UDFs |
108
+
109
+ The same material ships in the source distribution on PyPI, under `docs/` and
110
+ `deploy/pool-server/`. A wheel install carries the package only.
111
+
112
+ ## Development
113
+
114
+ ```bash
115
+ uv sync --all-extras --dev
116
+ uv run pytest
117
+ uv run ruff check . && uv run ruff format --check .
118
+ ```
119
+
120
+ Tests do not need a database or network access. Tests marked `postgres` run only when
121
+ `POOL_TEST_DATABASE_URL` points at a reachable PostgreSQL. Tests marked `live` call the real
122
+ Datahub API and run only with `DATAHUB_LIVE=1`, credentials, and `DATAHUB_TEST_STREAM_ID`
123
+ set (`uv run pytest -m live`).
@@ -0,0 +1,30 @@
1
+ # Credential-pool server: arcobi-sdk with the [pool-server] extra.
2
+ # Build from the repository root: docker build -f deploy/pool-server/Dockerfile -t arcobi-pool-server .
3
+ FROM python:3.12-slim
4
+
5
+ ENV PYTHONDONTWRITEBYTECODE=1 \
6
+ PYTHONUNBUFFERED=1 \
7
+ PIP_NO_CACHE_DIR=1 \
8
+ POOL_HOST=0.0.0.0 \
9
+ POOL_PORT=8002
10
+
11
+ RUN apt-get update \
12
+ && apt-get install -y --no-install-recommends curl \
13
+ && rm -rf /var/lib/apt/lists/* \
14
+ && useradd --create-home --uid 10001 pool
15
+
16
+ WORKDIR /build
17
+
18
+ # Copy the sources first so the install sees the whole package.
19
+ COPY pyproject.toml README.md ./
20
+ COPY src ./src
21
+ RUN pip install --upgrade pip && pip install "./[pool-server]" && rm -rf /build
22
+
23
+ WORKDIR /app
24
+ USER pool
25
+
26
+ HEALTHCHECK --interval=15s --timeout=5s --retries=5 \
27
+ CMD curl -fsS "http://127.0.0.1:${POOL_PORT}/health" || exit 1
28
+
29
+ EXPOSE 8002
30
+ CMD ["python", "-m", "arcobi_sdk.pool.server"]
@@ -0,0 +1,108 @@
1
+ # Credential-pool server
2
+
3
+ A small FastAPI service that leases short-lived vendor tokens from a shared pool
4
+ of accounts so several jobs can share a handful of credentials fairly. It ships
5
+ in `arcobi-sdk` behind the `pool-server` extra; the client is in the default
6
+ install.
7
+
8
+ ```bash
9
+ pip install "arcobi-sdk[pool-server]"
10
+ export POOL_DATABASE_URL=postgresql://pool:secret@localhost:5432/pool
11
+ export POOL_API_KEY=$(openssl rand -hex 24)
12
+ export POOL_CREDENTIALS_PATH=./credentials_pool.yaml
13
+ python -m arcobi_sdk.pool.server
14
+ ```
15
+
16
+ Or with Docker, from the repository root:
17
+
18
+ ```bash
19
+ export POOL_API_KEY=$(openssl rand -hex 24) POOL_DB_PASSWORD=$(openssl rand -hex 16)
20
+ cp deploy/pool-server/credentials_pool.example.yaml deploy/pool-server/credentials_pool.yaml
21
+ docker compose -f deploy/pool-server/docker-compose.yml up --build
22
+ ```
23
+
24
+ ## Configuration
25
+
26
+ All settings are environment variables prefixed `POOL_`.
27
+
28
+ | Variable | Default | Meaning |
29
+ |---|---|---|
30
+ | `POOL_DATABASE_URL` | required | PostgreSQL DSN. Schema is created/migrated on start. |
31
+ | `POOL_API_KEY` | required | Shared secret; clients send it as `X-API-Key`. |
32
+ | `POOL_HOST` / `POOL_PORT` | `0.0.0.0` / `8002` | Listener. |
33
+ | `POOL_PROVIDER` | `datahub` | Credential provider (`datahub`, `static`, or an entry point). |
34
+ | `POOL_PROVIDER_OPTIONS` | `{}` | JSON keyword options for the provider, e.g. `{"timeout_seconds": 10}`. |
35
+ | `POOL_CREDENTIALS_PATH` | unset | YAML file upserted on start (see `credentials_pool.example.yaml`). |
36
+ | `POOL_REFRESH_BUFFER_SECONDS` | `90` | Refresh tokens this long before they expire. |
37
+ | `POOL_REFRESH_RETRY_SECONDS` | `30` | Delay between failed refresh attempts. |
38
+ | `POOL_HEARTBEAT_TIMEOUT_SECONDS` | `60` | Reclaim a lease whose last `ping` is older than this. |
39
+ | `POOL_HANDOFF_GRACE_SECONDS` | `15` | Wait for `token_ack` this long before revoking the old token. |
40
+ | `POOL_MAX_QUEUE_DEPTH` | `50` | Queued requests before `POST /leases` answers 503. |
41
+ | `POOL_PRIORITY_LEVELS` | `high,normal,low` | Level names, most urgent first (level 0). |
42
+ | `POOL_PRIORITY_DEFAULT` | `normal` | Level used when a request has no priority. |
43
+ | `POOL_PRIORITY_ALIASES` | unset | Extra names, e.g. `urgent:0,batch:2`. |
44
+ | `POOL_ALLOW_WS_QUERY_API_KEY` | `false` | Also accept `?api_key=` on the WebSocket URL (legacy clients). |
45
+ | `POOL_METRICS` | `noop` | `noop` or `logging`; other sinks via `create_app(metrics=...)`. |
46
+ | `POOL_LOG_LEVEL` | `INFO` | Python log level. |
47
+
48
+ ## Priorities
49
+
50
+ A priority is an integer level; `0` is served first and requests within a level
51
+ are first-in-first-out. Clients may send the integer or a level name
52
+ (`"high"`), and a newcomer never overtakes queued requests of equal or higher
53
+ urgency. Rename or add levels per deployment with the `POOL_PRIORITY_*`
54
+ settings; the SDK ships no domain vocabulary.
55
+
56
+ ## Endpoints
57
+
58
+ All REST endpoints except `/health` require the `X-API-Key` header.
59
+
60
+ | Method | Path | Result |
61
+ |---|---|---|
62
+ | `POST` | `/leases` | `201` `status="granted"` with `token`, `expires_at`, `token_version`, `ws_url`; or `202` `status="queued"` with a real `lease_id`, `queue_position`, `estimated_wait_seconds`. `409` if the holder is already active or queued, `422` for an unknown priority, `503` if the queue is full or the vendor is down. |
63
+ | `GET` | `/leases/{lease_id}` | Current state including the token once granted (lets a client that connects late pick it up). |
64
+ | `DELETE` | `/leases/{lease_id}` | Release; `204` even when already finished, `404` if unknown. |
65
+ | `GET` | `/status` | Pool size, active/available credentials, queue depth per level name. |
66
+ | `GET` | `/health` | `status`, `db`, real `uptime_seconds`, `timestamp`. |
67
+
68
+ WebSocket `/ws/{lease_id}` (API key in the handshake header):
69
+
70
+ * server to client: `lease_granted`, `token_refreshed` (both carry `token`,
71
+ `token_type`, `expires_at`, `version`), `token_refresh_failed`,
72
+ `queue_position`, `lease_reclaimed`, `service_shutdown`, `pong`;
73
+ * client to server: `ping` (persists the heartbeat, answered with `pong`),
74
+ `token_ack` (`version`), `checkin` (release and close).
75
+
76
+ Lifecycle: a free credential is leased immediately; otherwise stale leases
77
+ (no heartbeat for `POOL_HEARTBEAT_TIMEOUT_SECONDS`) are reclaimed and retried,
78
+ and failing that the request is persisted as `queued`. A dispatcher grants
79
+ queued requests in priority order whenever a lease is released or reclaimed and
80
+ pushes `lease_granted`. Each active lease is refreshed before expiry; the old
81
+ token is revoked after the client's `token_ack` or the grace period.
82
+
83
+ ## Client
84
+
85
+ ```python
86
+ from arcobi_sdk.pool import PoolClient
87
+
88
+ pool = PoolClient(
89
+ "http://localhost:8002", api_key="..."
90
+ ) # or ARCOBI_POOL_URL / ARCOBI_POOL_API_KEY
91
+ with pool.lease("job-42", priority="high", max_wait_seconds=300) as lease:
92
+ headers = lease.auth_header() # always the latest token; refreshes arrive over the WebSocket
93
+ ...
94
+ # leaving the block releases the lease (DELETE /leases/{id})
95
+ ```
96
+
97
+ ## Custom providers
98
+
99
+ Implement `arcobi_sdk.pool.server.providers.base.CredentialProvider` and register
100
+ it under the `arcobi_sdk.pool.providers` entry-point group; then set
101
+ `POOL_PROVIDER` to its name. The `static` provider issues fake tokens and is
102
+ meant for tests and local development only.
103
+
104
+ ## Tests
105
+
106
+ The default suite runs on an in-memory store with the static provider. Store
107
+ tests against PostgreSQL are marked `postgres` and run only when
108
+ `POOL_TEST_DATABASE_URL` points at a reachable database.
@@ -0,0 +1,23 @@
1
+ # Credentials the pool server leases out.
2
+ #
3
+ # Copy to credentials_pool.yaml, fill in your vendor accounts and point
4
+ # POOL_CREDENTIALS_PATH at it. The file is re-read on every start with upsert
5
+ # semantics: existing accounts keep their id and lease history, changed
6
+ # passwords are updated in place, and accounts removed from the file are
7
+ # marked inactive (never deleted).
8
+ #
9
+ # Every key other than `username`, `provider` and `active` becomes part of the
10
+ # credential's secret. The datahub provider needs `password`.
11
+
12
+ credentials:
13
+ - username: "user_1"
14
+ password: "change-me"
15
+
16
+ - username: "user_2"
17
+ password: "change-me"
18
+
19
+ # Optional fields:
20
+ # - username: "user_3"
21
+ # password: "change-me"
22
+ # provider: datahub # defaults to POOL_PROVIDER; other providers' entries are skipped
23
+ # active: false # keep the row but never lease it
@@ -0,0 +1,53 @@
1
+ # Local stack: PostgreSQL + the pool server.
2
+ #
3
+ # export POOL_API_KEY=$(openssl rand -hex 24)
4
+ # export POOL_DB_PASSWORD=$(openssl rand -hex 16)
5
+ # cp credentials_pool.example.yaml credentials_pool.yaml # then fill in real accounts
6
+ # docker compose -f deploy/pool-server/docker-compose.yml up --build
7
+ #
8
+ # No secret has a default: compose refuses to start until POOL_API_KEY and
9
+ # POOL_DB_PASSWORD are set in the environment or an .env file next to this file.
10
+
11
+ services:
12
+ postgres:
13
+ image: postgres:16-alpine
14
+ environment:
15
+ POSTGRES_USER: pool
16
+ POSTGRES_PASSWORD: ${POOL_DB_PASSWORD:?required}
17
+ POSTGRES_DB: pool
18
+ volumes:
19
+ - pgdata:/var/lib/postgresql/data
20
+ healthcheck:
21
+ test: ["CMD-SHELL", "pg_isready -U pool -d pool"]
22
+ interval: 5s
23
+ timeout: 3s
24
+ retries: 10
25
+ restart: unless-stopped
26
+
27
+ pool-server:
28
+ build:
29
+ context: ../..
30
+ dockerfile: deploy/pool-server/Dockerfile
31
+ ports:
32
+ - "${POOL_PORT:-8002}:8002"
33
+ environment:
34
+ POOL_DATABASE_URL: postgresql://pool:${POOL_DB_PASSWORD:?required}@postgres:5432/pool
35
+ POOL_API_KEY: ${POOL_API_KEY:?required}
36
+ POOL_PORT: "8002"
37
+ POOL_PROVIDER: ${POOL_PROVIDER:-datahub}
38
+ POOL_CREDENTIALS_PATH: /app/credentials_pool.yaml
39
+ POOL_REFRESH_BUFFER_SECONDS: ${POOL_REFRESH_BUFFER_SECONDS:-90}
40
+ POOL_HEARTBEAT_TIMEOUT_SECONDS: ${POOL_HEARTBEAT_TIMEOUT_SECONDS:-60}
41
+ POOL_HANDOFF_GRACE_SECONDS: ${POOL_HANDOFF_GRACE_SECONDS:-15}
42
+ POOL_MAX_QUEUE_DEPTH: ${POOL_MAX_QUEUE_DEPTH:-50}
43
+ POOL_LOG_LEVEL: ${POOL_LOG_LEVEL:-INFO}
44
+ POOL_METRICS: ${POOL_METRICS:-noop}
45
+ volumes:
46
+ - ./credentials_pool.yaml:/app/credentials_pool.yaml:ro
47
+ depends_on:
48
+ postgres:
49
+ condition: service_healthy
50
+ restart: unless-stopped
51
+
52
+ volumes:
53
+ pgdata:
@@ -0,0 +1,98 @@
1
+ # Configuring Datahub credentials
2
+
3
+ The SDK never stores credentials itself. It looks for them in this order:
4
+
5
+ 1. **Constructor arguments** on `DatahubClient`:
6
+ `DatahubClient(username="...", password="...")` or `DatahubClient(bearer_token="...")`.
7
+ 2. **Environment variables**: `DATAHUB_USERNAME` + `DATAHUB_PASSWORD`, or `DATAHUB_BEARER_TOKEN`.
8
+ 3. **A leased token** from the credential pool: `DatahubClient(token_provider=lease.token_provider)`
9
+ (see the README section on sharing credentials).
10
+
11
+ If none is found, constructing a client raises `ConfigurationError`.
12
+
13
+ ## Environment variables
14
+
15
+ ```bash
16
+ # bash / zsh
17
+ export DATAHUB_USERNAME="your-user"
18
+ export DATAHUB_PASSWORD="your-password"
19
+ ```
20
+
21
+ ```powershell
22
+ # PowerShell
23
+ $env:DATAHUB_USERNAME = "your-user"
24
+ $env:DATAHUB_PASSWORD = "your-password"
25
+ ```
26
+
27
+ ## A `.env` file for local work
28
+
29
+ Create a `.env` file next to your script and keep it out of source control:
30
+
31
+ ```ini
32
+ DATAHUB_USERNAME=
33
+ DATAHUB_PASSWORD=
34
+ # Alternatively a pre-issued token:
35
+ # DATAHUB_BEARER_TOKEN=
36
+ ```
37
+
38
+ Load it one of two ways:
39
+
40
+ ```python
41
+ from arcobi_sdk import load_env, DatahubClient
42
+
43
+ load_env() # reads ./.env; never overrides variables already set
44
+ client = DatahubClient()
45
+ ```
46
+
47
+ ```bash
48
+ uv run --env-file .env python my_script.py
49
+ ```
50
+
51
+ `load_env` is dependency-free and accepts `export KEY=VALUE`, quoted values and `#` comments.
52
+ Pass `override=True` to let the file replace variables that are already set.
53
+
54
+ ## One session per account (concurrency)
55
+
56
+ Datahub allows **one active session per account**. A second login with the same credentials
57
+ is rejected with "Another user is currently using the API" (the SDK raises `DatahubAuthError`).
58
+ Consequences:
59
+
60
+ - To fetch several streams in parallel, share **one** `DatahubClient` across threads. The
61
+ client is thread-safe: token acquisition, refresh and release are serialised, so all threads
62
+ reuse the single token. Verified live: three streams fetched by three threads through one
63
+ client return exactly the same data as a sequential fetch.
64
+ - Do not create one client per thread or per process with the same account. For many parallel
65
+ jobs use several accounts through the credential pool, where each job leases its own account.
66
+ - Always release the token when done (`with DatahubClient() as client:` does this), otherwise
67
+ the account stays busy until the vendor-side session expires.
68
+
69
+ ## Pool client
70
+
71
+ | Variable | Purpose |
72
+ |---|---|
73
+ | `ARCOBI_POOL_URL` | Base URL of the credential-pool service |
74
+ | `ARCOBI_POOL_API_KEY` | API key sent in the `X-API-Key` header |
75
+
76
+ Both can also be passed to `PoolClient(service_url, api_key=...)`.
77
+
78
+ ## Running the live test
79
+
80
+ The default test suite never touches the network. A live smoke test exists and runs only when
81
+ you opt in:
82
+
83
+ ```bash
84
+ DATAHUB_LIVE=1 DATAHUB_TEST_STREAM_ID=<id> DATAHUB_TEST_STREAM_IDS=<id1>,<id2>,<id3> uv run pytest -m live
85
+ ```
86
+
87
+ `DATAHUB_TEST_STREAM_ID` drives the single-stream smoke test; `DATAHUB_TEST_STREAM_IDS`
88
+ (three ids) drives the concurrency test, which also asserts the one-session-per-account rule.
89
+
90
+ With a `.env` file in the repo root the credentials are picked up automatically; the stream id
91
+ can also go in that file. The test authenticates, fetches the last two days of the stream and
92
+ writes a CSV to a temporary directory.
93
+
94
+ ## Good practice
95
+
96
+ - Keep credentials out of source control; never commit a filled-in `.env`.
97
+ - Prefer one account per job, or the credential pool, over sharing one login across processes.
98
+ - Rotate a password that has been pasted into a chat, ticket or log.