quantpad-data 0.8.0__tar.gz → 0.8.1__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 (24) hide show
  1. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/PKG-INFO +3 -4
  2. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/README.md +2 -3
  3. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/pyproject.toml +1 -1
  4. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/quantpad_data/__init__.py +1 -1
  5. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/quantpad_data/client.py +1 -1
  6. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/quantpad_data/memory.py +1 -1
  7. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/quantpad_data/v2.py +14 -25
  8. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/tests/test_packaging.py +38 -0
  9. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/tests/test_v2.py +3 -3
  10. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/.gitignore +0 -0
  11. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/quantpad_data/cache.py +0 -0
  12. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/quantpad_data/errors.py +0 -0
  13. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/quantpad_data/lean.py +0 -0
  14. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/quantpad_data/py.typed +0 -0
  15. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/quantpad_data/rth.py +0 -0
  16. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/tests/conftest.py +0 -0
  17. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/tests/test_client.py +0 -0
  18. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/tests/test_lean.py +0 -0
  19. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/tests/test_message_encoding.py +0 -0
  20. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/tests/test_refusal_hint.py +0 -0
  21. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/tests/test_rth.py +0 -0
  22. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/tests/test_v2_cache.py +0 -0
  23. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/tests/test_v2_gate.py +0 -0
  24. {quantpad_data-0.8.0 → quantpad_data-0.8.1}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: quantpad-data
3
- Version: 0.8.0
3
+ Version: 0.8.1
4
4
  Summary: Official Python client for the QuantPad market-data API
5
5
  Project-URL: Documentation, https://api.quantpad.ai/external/docs
6
6
  Author: QuantPad
@@ -106,9 +106,8 @@ symbol it is. That is convenient until it is wrong, and when it is wrong it
106
106
  fails quietly — asking for a specific futures contract returns an empty frame
107
107
  that looks exactly like a coverage gap.
108
108
 
109
- `qpd.v2.Historical` removes the inference. It takes the same arguments as
110
- `databento.Historical`, so code written against Databento ports over by
111
- swapping the client:
109
+ `qpd.v2.Historical` removes the inference. Every call names its dataset and
110
+ symbology type explicitly, and every response reports the address it used:
112
111
 
113
112
  ```python
114
113
  import quantpad_data as qpd
@@ -84,9 +84,8 @@ symbol it is. That is convenient until it is wrong, and when it is wrong it
84
84
  fails quietly — asking for a specific futures contract returns an empty frame
85
85
  that looks exactly like a coverage gap.
86
86
 
87
- `qpd.v2.Historical` removes the inference. It takes the same arguments as
88
- `databento.Historical`, so code written against Databento ports over by
89
- swapping the client:
87
+ `qpd.v2.Historical` removes the inference. Every call names its dataset and
88
+ symbology type explicitly, and every response reports the address it used:
90
89
 
91
90
  ```python
92
91
  import quantpad_data as qpd
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "quantpad-data"
7
- version = "0.8.0"
7
+ version = "0.8.1"
8
8
  description = "Official Python client for the QuantPad market-data API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -198,7 +198,7 @@ def iter_l1_mbp1_df_chunks(symbol, start_ms, end_ms, chunksize=250_000,
198
198
 
199
199
  from . import cache # noqa: E402 (submodule: on-disk cache for settled /v2)
200
200
  from . import lean # noqa: E402 (submodule: LEAN backtesting data staging)
201
- from . import v2 # noqa: E402 (submodule: Databento-shaped explicit API)
201
+ from . import v2 # noqa: E402 (submodule: explicit-address API)
202
202
 
203
203
  # Exported at the root as well as on ``v2``: the sandbox module reaches it
204
204
  # as ``qpd.Historical()``, and one spelling across both surfaces means
@@ -84,7 +84,7 @@ V2_NOT_AVAILABLE = (
84
84
  "QuantPad support about /v2 access."
85
85
  )
86
86
 
87
- VERSION = "0.8.0"
87
+ VERSION = "0.8.1"
88
88
  USER_AGENT = f"quantpad-data-python/{VERSION}"
89
89
  TICK_SCHEMAS = (
90
90
  "trades", "mbp-1", "mbp-10", "cmbp-1", "tcbbo", "cbbo-1s",
@@ -15,7 +15,7 @@ before fetching it, which is why it takes both.
15
15
 
16
16
  THE UNITS ARE NOT THE SAME, AND THAT IS THE WHOLE DIFFICULTY
17
17
  The service prices in DBN bytes: record count times record width, which is
18
- Databento's own billing arithmetic. This process pays for the compressed wire
18
+ upstream's own billing arithmetic. This process pays for the compressed wire
19
19
  bytes, an Arrow table decoded from them, and a pandas frame copied out of that
20
20
  table -- partly all three at once. Measured with
21
21
  scripts/measure_client_memory.py against production, peak RSS per priced byte
@@ -1,4 +1,4 @@
1
- """Databento-shaped access to QuantPad's cache.
1
+ """Explicit-address access to QuantPad's cache.
2
2
 
3
3
  The v1 helpers take QuantPad's own arguments and infer the rest: which
4
4
  dataset a symbol lives in, what kind of symbol it is, how far back to
@@ -7,18 +7,7 @@ it is wrong it fails quietly — a specific futures contract returns an
7
7
  empty frame that looks exactly like a coverage gap.
8
8
 
9
9
  This module removes the inference. Every call names its dataset and its
10
- symbology type, exactly as ``databento.Historical`` requires, and every
11
- response reports the address it actually used. Code written against the
12
- Databento client transfers over with the client swapped:
13
-
14
- import databento as db
15
- client = db.Historical(key=...)
16
- df = client.timeseries.get_range(
17
- dataset="GLBX.MDP3", symbols="ESZ6", schema="ohlcv-1m",
18
- start="2026-01-05", end="2026-01-06",
19
- ).to_df()
20
-
21
- becomes
10
+ symbology type, and every response reports the address it actually used:
22
11
 
23
12
  import quantpad_data as qpd
24
13
  client = qpd.v2.Historical()
@@ -27,9 +16,9 @@ becomes
27
16
  start="2026-01-05", end="2026-01-06",
28
17
  )
29
18
 
30
- The differences are deliberate and few: a DataFrame comes back directly
31
- rather than a DBNStore, and there is no cost or billing surface, because
32
- data is included in the QuantPad plan rather than metered per request.
19
+ A DataFrame comes back directly, and there is no cost or billing surface,
20
+ because data is included in the QuantPad plan rather than metered per
21
+ request.
33
22
  """
34
23
 
35
24
  from __future__ import annotations
@@ -255,7 +244,7 @@ class _Namespace:
255
244
 
256
245
 
257
246
  class Timeseries(_Namespace):
258
- """Mirrors ``databento.Historical.timeseries``."""
247
+ """Ranges of bars, ticks and book depth for an explicit address."""
259
248
 
260
249
  def get_range(
261
250
  self,
@@ -763,12 +752,12 @@ class Timeseries(_Namespace):
763
752
 
764
753
 
765
754
  class Symbology(_Namespace):
766
- """Mirrors ``databento.Historical.symbology``, plus a way in.
755
+ """Symbol resolution, plus a way in.
767
756
 
768
757
  Every other call here demands an explicit ``dataset``, which is the
769
758
  point and also a dead end for someone holding the string "AAPL".
770
- ``search`` is the one endpoint that answers that, and it has no
771
- upstream counterpart because Databento assumes you already know.
759
+ ``search`` is the one endpoint that answers that; the rest of this
760
+ surface assumes you already know.
772
761
  """
773
762
 
774
763
  def search(
@@ -830,7 +819,7 @@ class Symbology(_Namespace):
830
819
 
831
820
 
832
821
  class Metadata(_Namespace):
833
- """Mirrors ``databento.Historical.metadata``.
822
+ """Datasets, schemas, fields, coverage and request sizing.
834
823
 
835
824
  The cost and unit-price endpoints are deliberately absent: QuantPad
836
825
  data is included in the plan rather than metered, so a per-request
@@ -969,8 +958,8 @@ def _chain_frame(payload: dict) -> pd.DataFrame:
969
958
  class Options(_Namespace):
970
959
  """Options-chain enumeration.
971
960
 
972
- Not part of Databento's client surface — upstream expects you to pull
973
- the whole ``definition`` schema and filter it yourself. This does
961
+ Not part of the usual historical-client surface, which expects you to
962
+ pull the whole ``definition`` schema and filter it yourself. This does
974
963
  that server-side, because "download every instrument definition for
975
964
  the day" is a poor answer to "what strikes exist".
976
965
 
@@ -1008,7 +997,7 @@ class Options(_Namespace):
1008
997
 
1009
998
 
1010
999
  class Batch(_Namespace):
1011
- """Mirrors ``databento.Historical.batch``, scoped to your own jobs.
1000
+ """Batch extracts, scoped to your own jobs.
1012
1001
 
1013
1002
  Useful when a range is too large to stream in one sitting: submit,
1014
1003
  walk away, then hydrate the result into the cache so ordinary
@@ -1059,7 +1048,7 @@ class Batch(_Namespace):
1059
1048
 
1060
1049
 
1061
1050
  class Historical:
1062
- """A ``databento.Historical``-shaped view of QuantPad's cache.
1051
+ """An explicit-address view of QuantPad's cache.
1063
1052
 
1064
1053
  Takes the same credentials as the v1 ``Client`` — an API key, or the
1065
1054
  injected token when running inside a QuantPad sandbox.
@@ -21,6 +21,7 @@ from __future__ import annotations
21
21
 
22
22
  import ast
23
23
  import os
24
+ import re
24
25
  import subprocess
25
26
  import sys
26
27
  import tarfile
@@ -53,6 +54,7 @@ def built(tmp_path_factory):
53
54
  # ./dist would make it skip in CI, where nothing has built there yet --
54
55
  # a test that skips exactly where it is needed.
55
56
  "wheel_path": wheel,
57
+ "sdist_path": sdist,
56
58
  }
57
59
 
58
60
 
@@ -182,3 +184,39 @@ class TestAFreshInstallExposesTheApi:
182
184
  f"a fresh install cannot answer for its own API:\n"
183
185
  f"{done.stdout}\n{done.stderr[-1500:]}"
184
186
  )
187
+
188
+
189
+ class TestNothingShippedNamesTheDataVendor:
190
+ """QuantPad presents its market data as its own, and a published package is
191
+ as public as a response: PyPI serves every file in both artifacts, the
192
+ README is the project page, and ``help()`` in a user's sandbox prints the
193
+ docstrings. So nothing shipped may name the vendor -- read out of the
194
+ artifacts, for the same reason as everything else in this file."""
195
+
196
+ VENDOR = re.compile(r"data[\s_-]?bento", re.IGNORECASE)
197
+
198
+ @staticmethod
199
+ def _texts(built, kind):
200
+ if kind == "wheel":
201
+ with zipfile.ZipFile(built["wheel_path"]) as archive:
202
+ for name in archive.namelist():
203
+ yield name, archive.read(name)
204
+ else:
205
+ with tarfile.open(built["sdist_path"]) as archive:
206
+ for member in archive.getmembers():
207
+ if member.isfile():
208
+ yield member.name, archive.extractfile(member).read()
209
+
210
+ @pytest.mark.parametrize("kind", ["wheel", "sdist"])
211
+ def test_no_shipped_file_names_the_vendor(self, built, kind):
212
+ offenders = [
213
+ name
214
+ for name, data in self._texts(built, kind)
215
+ if self.VENDOR.search(data.decode("utf-8", errors="replace"))
216
+ ]
217
+ assert not offenders, f"the {kind} names the data vendor in {offenders}"
218
+
219
+ def test_the_readme_is_in_what_was_checked(self, built):
220
+ """Guards the test above: the wheel's METADATA carries the README."""
221
+ names = [name for name, _ in self._texts(built, "wheel")]
222
+ assert any(name.endswith("METADATA") for name in names)
@@ -1,4 +1,4 @@
1
- """The Databento-shaped surface.
1
+ """The explicit-address surface.
2
2
 
3
3
  Two things are being checked. First that the wire calls carry exactly
4
4
  what the caller said and nothing the SDK invented — the whole reason
@@ -95,7 +95,7 @@ class TestNothingIsInferred:
95
95
  assert kwargs["json"]["symbols"] == ["ESZ6"]
96
96
 
97
97
  def test_a_single_symbol_string_is_sent_as_a_list(self):
98
- """Databento accepts either; normalising here keeps the server
98
+ """Upstream accepts either; normalising here keeps the server
99
99
  contract one shape instead of two."""
100
100
  api, session = client_with([response(200, arrow_bytes(bars_table()))])
101
101
  api.timeseries.get_range(
@@ -104,7 +104,7 @@ class TestNothingIsInferred:
104
104
  )
105
105
  assert session.calls[0][2]["json"]["symbols"] == ["ESZ6"]
106
106
 
107
- def test_the_default_stype_matches_databento_not_our_own_convention(self):
107
+ def test_the_default_stype_matches_upstream_not_our_own_convention(self):
108
108
  api, session = client_with([response(200, arrow_bytes(bars_table()))])
109
109
  api.timeseries.get_range(
110
110
  dataset="GLBX.MDP3", symbols="ESZ6", schema="ohlcv-1m",
File without changes