briskapi 0.2.0__tar.gz → 0.2.2__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 (33) hide show
  1. {briskapi-0.2.0 → briskapi-0.2.2}/PKG-INFO +22 -10
  2. {briskapi-0.2.0 → briskapi-0.2.2}/README.md +8 -9
  3. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/__init__.py +1 -1
  4. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/_live.py +2 -0
  5. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/cli.py +27 -1
  6. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi.egg-info/PKG-INFO +22 -10
  7. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi.egg-info/SOURCES.txt +1 -0
  8. {briskapi-0.2.0 → briskapi-0.2.2}/pyproject.toml +16 -1
  9. {briskapi-0.2.0 → briskapi-0.2.2}/tests/test_archive.py +2 -0
  10. briskapi-0.2.2/tests/test_package.py +92 -0
  11. {briskapi-0.2.0 → briskapi-0.2.2}/LICENSE +0 -0
  12. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/LICENSE-pybrisk.txt +0 -0
  13. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/__main__.py +0 -0
  14. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/_archive.py +0 -0
  15. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/_market.py +0 -0
  16. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/_recording.py +0 -0
  17. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/archive.json +0 -0
  18. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/decoder/assets.json +0 -0
  19. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/decoder/decoder.cjs +0 -0
  20. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/decoder/sbi.cjs +0 -0
  21. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/decoder/web.cjs +0 -0
  22. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/references/historical_mock.json +0 -0
  23. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/sbi.py +0 -0
  24. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/schema.py +0 -0
  25. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi/timing.py +0 -0
  26. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi.egg-info/dependency_links.txt +0 -0
  27. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi.egg-info/entry_points.txt +0 -0
  28. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi.egg-info/requires.txt +0 -0
  29. {briskapi-0.2.0 → briskapi-0.2.2}/briskapi.egg-info/top_level.txt +0 -0
  30. {briskapi-0.2.0 → briskapi-0.2.2}/setup.cfg +0 -0
  31. {briskapi-0.2.0 → briskapi-0.2.2}/tests/test_api.py +0 -0
  32. {briskapi-0.2.0 → briskapi-0.2.2}/tests/test_sbi.py +0 -0
  33. {briskapi-0.2.0 → briskapi-0.2.2}/tests/test_timing.py +0 -0
@@ -1,9 +1,22 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: briskapi
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Unofficial Python API, live feed and shared archive for BRiSK auction data
5
5
  License-Expression: MIT
6
6
  Project-URL: Source, https://github.com/honvl/BRiSKapi
7
+ Project-URL: Issues, https://github.com/honvl/BRiSKapi/issues
8
+ Project-URL: Releases, https://github.com/honvl/BRiSKapi/releases
9
+ Project-URL: Privacy, https://github.com/honvl/BRiSKapi/blob/main/PRIVACY.md
10
+ Keywords: brisk,market-data,auction,tokyo-stock-exchange,japan,sbi
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Financial and Insurance Industry
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Office/Business :: Financial :: Investment
7
20
  Requires-Python: >=3.12
8
21
  Description-Content-Type: text/markdown
9
22
  License-File: LICENSE
@@ -14,7 +27,7 @@ Dynamic: license-file
14
27
 
15
28
  # briskapi
16
29
 
17
- [English](README.md) | [日本語](README.ja.md)
30
+ [English](https://github.com/honvl/BRiSKapi/blob/main/README.md) | [日本語](https://github.com/honvl/BRiSKapi/blob/main/README.ja.md)
18
31
 
19
32
  An unofficial, pybrisk-style Python API and `brisk` command line for BRiSK auction
20
33
  data. Consume a live feed, query recordings at any point in time, pull shared
@@ -179,7 +192,7 @@ API never asks: until you decide, sessions stay on your computer.
179
192
  alias (random `anon-…` by default) and license. Your IP address is used only to
180
193
  rate limit uploads.
181
194
  - **Visibility:** published recordings are public and permanent, and you cannot
182
- delete them yourself. See [PRIVACY.md](PRIVACY.md).
195
+ delete them yourself. See [PRIVACY.md](https://github.com/honvl/BRiSKapi/blob/main/PRIVACY.md).
183
196
  - **Opting out:** `brisk consent --revoke`, `BRISK_CONTRIBUTE=0`, or `--no-upload`
184
197
  for one run.
185
198
  - **License:** accepting declares that you may redistribute the recordings under
@@ -190,12 +203,11 @@ API never asks: until you decide, sessions stay on your computer.
190
203
 
191
204
  ## More documentation
192
205
 
193
- - [ARCHITECTURE.md](ARCHITECTURE.md): how it works, data format, archive integrity and limits
194
- - [PRIVACY.md](PRIVACY.md): privacy policy
195
- - [CONTRIBUTING.md](CONTRIBUTING.md): development, tests and releases
196
- - [tools/brisk_mock/README.md](tools/brisk_mock/README.md): Rust collector, field definitions, timing and latency
197
- - [tools/brisk_mock/NAUTILUS_V2.md](tools/brisk_mock/NAUTILUS_V2.md): NautilusTrader v2 integration
198
- - [infra/README.md](infra/README.md): deploying your own archive
199
- - [THIRD_PARTY.md](THIRD_PARTY.md): decoder, data and pybrisk attribution
206
+ - [ARCHITECTURE.md](https://github.com/honvl/BRiSKapi/blob/main/ARCHITECTURE.md): how it works, data format, archive integrity and limits
207
+ - [PRIVACY.md](https://github.com/honvl/BRiSKapi/blob/main/PRIVACY.md): privacy policy
208
+ - [CONTRIBUTING.md](https://github.com/honvl/BRiSKapi/blob/main/CONTRIBUTING.md): development, tests and releases
209
+ - [tools/brisk_mock/README.md](https://github.com/honvl/BRiSKapi/blob/main/tools/brisk_mock/README.md): Rust collector, field definitions, timing and latency
210
+ - [infra/README.md](https://github.com/honvl/BRiSKapi/blob/main/infra/README.md): deploying your own archive
211
+ - [THIRD_PARTY.md](https://github.com/honvl/BRiSKapi/blob/main/THIRD_PARTY.md): decoder, data and pybrisk attribution
200
212
 
201
213
  Software is MIT licensed.
@@ -1,6 +1,6 @@
1
1
  # briskapi
2
2
 
3
- [English](README.md) | [日本語](README.ja.md)
3
+ [English](https://github.com/honvl/BRiSKapi/blob/main/README.md) | [日本語](https://github.com/honvl/BRiSKapi/blob/main/README.ja.md)
4
4
 
5
5
  An unofficial, pybrisk-style Python API and `brisk` command line for BRiSK auction
6
6
  data. Consume a live feed, query recordings at any point in time, pull shared
@@ -165,7 +165,7 @@ API never asks: until you decide, sessions stay on your computer.
165
165
  alias (random `anon-…` by default) and license. Your IP address is used only to
166
166
  rate limit uploads.
167
167
  - **Visibility:** published recordings are public and permanent, and you cannot
168
- delete them yourself. See [PRIVACY.md](PRIVACY.md).
168
+ delete them yourself. See [PRIVACY.md](https://github.com/honvl/BRiSKapi/blob/main/PRIVACY.md).
169
169
  - **Opting out:** `brisk consent --revoke`, `BRISK_CONTRIBUTE=0`, or `--no-upload`
170
170
  for one run.
171
171
  - **License:** accepting declares that you may redistribute the recordings under
@@ -176,12 +176,11 @@ API never asks: until you decide, sessions stay on your computer.
176
176
 
177
177
  ## More documentation
178
178
 
179
- - [ARCHITECTURE.md](ARCHITECTURE.md): how it works, data format, archive integrity and limits
180
- - [PRIVACY.md](PRIVACY.md): privacy policy
181
- - [CONTRIBUTING.md](CONTRIBUTING.md): development, tests and releases
182
- - [tools/brisk_mock/README.md](tools/brisk_mock/README.md): Rust collector, field definitions, timing and latency
183
- - [tools/brisk_mock/NAUTILUS_V2.md](tools/brisk_mock/NAUTILUS_V2.md): NautilusTrader v2 integration
184
- - [infra/README.md](infra/README.md): deploying your own archive
185
- - [THIRD_PARTY.md](THIRD_PARTY.md): decoder, data and pybrisk attribution
179
+ - [ARCHITECTURE.md](https://github.com/honvl/BRiSKapi/blob/main/ARCHITECTURE.md): how it works, data format, archive integrity and limits
180
+ - [PRIVACY.md](https://github.com/honvl/BRiSKapi/blob/main/PRIVACY.md): privacy policy
181
+ - [CONTRIBUTING.md](https://github.com/honvl/BRiSKapi/blob/main/CONTRIBUTING.md): development, tests and releases
182
+ - [tools/brisk_mock/README.md](https://github.com/honvl/BRiSKapi/blob/main/tools/brisk_mock/README.md): Rust collector, field definitions, timing and latency
183
+ - [infra/README.md](https://github.com/honvl/BRiSKapi/blob/main/infra/README.md): deploying your own archive
184
+ - [THIRD_PARTY.md](https://github.com/honvl/BRiSKapi/blob/main/THIRD_PARTY.md): decoder, data and pybrisk attribution
186
185
 
187
186
  Software is MIT licensed.
@@ -18,7 +18,7 @@ from briskapi._live import Feed, connect as _connect, record as _record, stream
18
18
  from briskapi._market import Market, Ticker
19
19
  from briskapi._recording import JST, BriskError, NotFoundError, Recording, Table
20
20
 
21
- __version__ = '0.2.0'
21
+ __version__ = '0.2.2'
22
22
  __all__ = ['JST', 'Archive', 'BriskError', 'Feed', 'Market', 'NotFoundError', 'Recording', 'Table', 'Ticker',
23
23
  'connect', 'consent', 'current', 'load', 'pull', 'record', 'recordings', 'stream']
24
24
 
@@ -45,6 +45,7 @@ def _stop(process):
45
45
 
46
46
  def stream(web=False, cache=None, codes=None, speed=1, limit_frames=None, node='node') -> Iterator[dict]:
47
47
  """Yield raw decoded batches as each frame replays (bootstrap, quote deltas, end)."""
48
+ cli.check_node(node)
48
49
  process = _decoder(web, cache, codes, speed, limit_frames, node)
49
50
  try:
50
51
  for line in process.stdout:
@@ -90,6 +91,7 @@ class Feed:
90
91
  history=False, node='node', *, command=None, env=None, timing=None):
91
92
  # `command` runs another decoder host (SBI live). Its market data is never
92
93
  # contributed; with `timing` set, a timing-only summary may be.
94
+ cli.check_node(command[0] if command else node)
93
95
  if command is None:
94
96
  choice, upload = _consent(contribute, limit_frames)
95
97
  self._choice, self._timing = (choice if upload else None), None
@@ -20,12 +20,14 @@ import warnings
20
20
  import boto3
21
21
  from botocore import UNSIGNED
22
22
  from botocore.config import Config
23
+ from briskapi._recording import BriskError
23
24
  from briskapi.schema import (SCHEMA, MAX_TIMING_BYTES, canonical_lines, compress, digest, inspect_package, validate_manifest,
24
25
  validate_stream, validate_timing, require)
25
26
 
26
27
  PACKAGE = Path(__file__).resolve().parent
27
28
  # BRiSK's own WASM decoder runs under Node; the package ships only this host and SHA-256 pins.
28
29
  DECODER = PACKAGE / 'decoder' / 'decoder.cjs'
30
+ MIN_NODE = 22
29
31
  LICENSES = ('CC0-1.0', 'CC-BY-4.0')
30
32
  # Bump with any PRIVACY.md change to what is collected; saved choices then lapse.
31
33
  POLICY_VERSION = 2
@@ -46,6 +48,20 @@ Policy: https://github.com/honvl/BRiSKapi/blob/main/PRIVACY.md
46
48
  Opt out at any time: `brisk consent --revoke` or BRISK_CONTRIBUTE=0.
47
49
  '''
48
50
 
51
+ def check_node(node='node'):
52
+ """Fail early, with the fix, when Node is missing or older than the decoder hosts support."""
53
+ advice = f'Install Node.js {MIN_NODE} or newer from https://nodejs.org, or pass the path to it.'
54
+ try:
55
+ out = subprocess.run([node, '--version'], capture_output=True, text=True, timeout=30).stdout
56
+ except FileNotFoundError:
57
+ raise BriskError(f'Node.js was not found (looked for {str(node)!r}). BRiSK\'s decoder runs under Node. {advice}') from None
58
+ except (OSError, subprocess.SubprocessError) as error:
59
+ raise BriskError(f'Could not run {str(node)!r} --version: {error}. {advice}') from error
60
+ found = re.match(r'v(\d+)\.', out.strip())
61
+ if found and int(found[1]) < MIN_NODE:
62
+ raise BriskError(f'Node.js {out.strip()} is too old; the decoder hosts need Node {MIN_NODE}+. {advice}')
63
+
64
+
49
65
  def settings(path=None):
50
66
  return json.loads((path or PACKAGE / 'archive.json').read_text())
51
67
 
@@ -211,6 +227,7 @@ def record_events(events, web=False, cache=None, codes=None, limit_frames=None,
211
227
  Needs only Node. A Rust collector binary (`brisk_quote_ingest`) is optional; it
212
228
  records the same batches and adds its own state validation and latency display.
213
229
  """
230
+ check_node(node)
214
231
  options = ['--web'] if web else ['--cache', str(cache)]
215
232
  options += ['--speed', str(speed)]
216
233
  if codes:
@@ -250,7 +267,16 @@ def live(args):
250
267
  print(json.dumps({'contribution': feed.contribution}), file=sys.stderr)
251
268
 
252
269
  def main(argv=None):
253
- parser = argparse.ArgumentParser(description=__doc__)
270
+ try:
271
+ _main(argv)
272
+ except BriskError as error:
273
+ sys.exit(f'brisk: error: {error}')
274
+
275
+
276
+ def _main(argv):
277
+ from briskapi import __version__
278
+ parser = argparse.ArgumentParser(prog='brisk', description=__doc__)
279
+ parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
254
280
  parser.add_argument('--config', type=Path, default=PACKAGE / 'archive.json')
255
281
  sub = parser.add_subparsers(dest='command', required=True)
256
282
  for name in ('record', 'package'):
@@ -1,9 +1,22 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: briskapi
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Unofficial Python API, live feed and shared archive for BRiSK auction data
5
5
  License-Expression: MIT
6
6
  Project-URL: Source, https://github.com/honvl/BRiSKapi
7
+ Project-URL: Issues, https://github.com/honvl/BRiSKapi/issues
8
+ Project-URL: Releases, https://github.com/honvl/BRiSKapi/releases
9
+ Project-URL: Privacy, https://github.com/honvl/BRiSKapi/blob/main/PRIVACY.md
10
+ Keywords: brisk,market-data,auction,tokyo-stock-exchange,japan,sbi
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Financial and Insurance Industry
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Office/Business :: Financial :: Investment
7
20
  Requires-Python: >=3.12
8
21
  Description-Content-Type: text/markdown
9
22
  License-File: LICENSE
@@ -14,7 +27,7 @@ Dynamic: license-file
14
27
 
15
28
  # briskapi
16
29
 
17
- [English](README.md) | [日本語](README.ja.md)
30
+ [English](https://github.com/honvl/BRiSKapi/blob/main/README.md) | [日本語](https://github.com/honvl/BRiSKapi/blob/main/README.ja.md)
18
31
 
19
32
  An unofficial, pybrisk-style Python API and `brisk` command line for BRiSK auction
20
33
  data. Consume a live feed, query recordings at any point in time, pull shared
@@ -179,7 +192,7 @@ API never asks: until you decide, sessions stay on your computer.
179
192
  alias (random `anon-…` by default) and license. Your IP address is used only to
180
193
  rate limit uploads.
181
194
  - **Visibility:** published recordings are public and permanent, and you cannot
182
- delete them yourself. See [PRIVACY.md](PRIVACY.md).
195
+ delete them yourself. See [PRIVACY.md](https://github.com/honvl/BRiSKapi/blob/main/PRIVACY.md).
183
196
  - **Opting out:** `brisk consent --revoke`, `BRISK_CONTRIBUTE=0`, or `--no-upload`
184
197
  for one run.
185
198
  - **License:** accepting declares that you may redistribute the recordings under
@@ -190,12 +203,11 @@ API never asks: until you decide, sessions stay on your computer.
190
203
 
191
204
  ## More documentation
192
205
 
193
- - [ARCHITECTURE.md](ARCHITECTURE.md): how it works, data format, archive integrity and limits
194
- - [PRIVACY.md](PRIVACY.md): privacy policy
195
- - [CONTRIBUTING.md](CONTRIBUTING.md): development, tests and releases
196
- - [tools/brisk_mock/README.md](tools/brisk_mock/README.md): Rust collector, field definitions, timing and latency
197
- - [tools/brisk_mock/NAUTILUS_V2.md](tools/brisk_mock/NAUTILUS_V2.md): NautilusTrader v2 integration
198
- - [infra/README.md](infra/README.md): deploying your own archive
199
- - [THIRD_PARTY.md](THIRD_PARTY.md): decoder, data and pybrisk attribution
206
+ - [ARCHITECTURE.md](https://github.com/honvl/BRiSKapi/blob/main/ARCHITECTURE.md): how it works, data format, archive integrity and limits
207
+ - [PRIVACY.md](https://github.com/honvl/BRiSKapi/blob/main/PRIVACY.md): privacy policy
208
+ - [CONTRIBUTING.md](https://github.com/honvl/BRiSKapi/blob/main/CONTRIBUTING.md): development, tests and releases
209
+ - [tools/brisk_mock/README.md](https://github.com/honvl/BRiSKapi/blob/main/tools/brisk_mock/README.md): Rust collector, field definitions, timing and latency
210
+ - [infra/README.md](https://github.com/honvl/BRiSKapi/blob/main/infra/README.md): deploying your own archive
211
+ - [THIRD_PARTY.md](https://github.com/honvl/BRiSKapi/blob/main/THIRD_PARTY.md): decoder, data and pybrisk attribution
200
212
 
201
213
  Software is MIT licensed.
@@ -26,5 +26,6 @@ briskapi/decoder/web.cjs
26
26
  briskapi/references/historical_mock.json
27
27
  tests/test_api.py
28
28
  tests/test_archive.py
29
+ tests/test_package.py
29
30
  tests/test_sbi.py
30
31
  tests/test_timing.py
@@ -4,18 +4,33 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "briskapi"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Unofficial Python API, live feed and shared archive for BRiSK auction data"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
11
  requires-python = ">=3.12"
12
12
  dependencies = ["boto3>=1.41,<2"]
13
+ keywords = ["brisk", "market-data", "auction", "tokyo-stock-exchange", "japan", "sbi"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "Intended Audience :: Financial and Insurance Industry",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3 :: Only",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Topic :: Office/Business :: Financial :: Investment",
24
+ ]
13
25
 
14
26
  [project.optional-dependencies]
15
27
  pandas = ["pandas>=2"]
16
28
 
17
29
  [project.urls]
18
30
  Source = "https://github.com/honvl/BRiSKapi"
31
+ Issues = "https://github.com/honvl/BRiSKapi/issues"
32
+ Releases = "https://github.com/honvl/BRiSKapi/releases"
33
+ Privacy = "https://github.com/honvl/BRiSKapi/blob/main/PRIVACY.md"
19
34
 
20
35
  [project.scripts]
21
36
  brisk = "briskapi.cli:main"
@@ -448,6 +448,7 @@ def harness(tmp_path, monkeypatch):
448
448
  else:
449
449
  Path(cmd[cmd.index('--events') + 1]).write_bytes(raw(batches()))
450
450
  monkeypatch.setattr(cli.subprocess, 'run', run)
451
+ monkeypatch.setattr(cli, 'check_node', lambda node='node': None)
451
452
  (tmp_path / 'source.jsonl').write_bytes(raw(batches()))
452
453
  def main(*args):
453
454
  cli.main(['--config', str(config), *args])
@@ -529,6 +530,7 @@ def test_consent_controls(tmp_path, monkeypatch, harness, capsys):
529
530
  def test_record_events_command(monkeypatch, tmp_path):
530
531
  calls = []
531
532
  monkeypatch.setattr(cli.subprocess, 'run', lambda cmd, check, stdout=None: calls.append((cmd, stdout)))
533
+ monkeypatch.setattr(cli, 'check_node', lambda node='node': None)
532
534
  cli.record_events(tmp_path / 'e.jsonl', cache=tmp_path, codes=['7203', '6758'], limit_frames=5, speed=0)
533
535
  cmd, stdout = calls[0]
534
536
  assert cmd[:2] == ['node', str(cli.DECODER)] and stdout is not None and cli.DECODER.exists()
@@ -0,0 +1,92 @@
1
+ """Install-time behavior: the Node preflight, --version and the PyPI package description."""
2
+ import re
3
+ import subprocess
4
+ import tomllib
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ import briskapi
10
+ import briskapi.cli as cli
11
+ from briskapi import _live, sbi
12
+
13
+ ROOT = Path(__file__).resolve().parent.parent
14
+
15
+
16
+ @pytest.fixture(autouse=True)
17
+ def isolated(tmp_path, monkeypatch):
18
+ monkeypatch.setenv('XDG_CONFIG_HOME', str(tmp_path / 'config'))
19
+ monkeypatch.delenv('BRISK_CONTRIBUTE', raising=False)
20
+ monkeypatch.setattr(briskapi, '_current', None)
21
+
22
+
23
+ def fake_node(tmp_path, output, executable=True):
24
+ script = tmp_path / 'fake-node'
25
+ script.write_text(f'#!/bin/sh\necho {output}\n')
26
+ script.chmod(0o755 if executable else 0o644)
27
+ return str(script)
28
+
29
+
30
+ def test_check_node_accepts_supported_versions(tmp_path):
31
+ for output in ('v22.0.0', 'v25.2.1', 'unrecognised output'):
32
+ cli.check_node(fake_node(tmp_path, output))
33
+ cli.check_node() # The real node on PATH, as the other tests need.
34
+
35
+
36
+ def test_check_node_explains_how_to_fix_it(tmp_path):
37
+ with pytest.raises(briskapi.BriskError, match=r'Node\.js was not found.*nodejs\.org'):
38
+ cli.check_node(str(tmp_path / 'missing'))
39
+ with pytest.raises(briskapi.BriskError, match=r'v20\.11\.0 is too old.*Node 22\+.*nodejs\.org'):
40
+ cli.check_node(fake_node(tmp_path, 'v20.11.0'))
41
+ with pytest.raises(briskapi.BriskError, match=r'Could not run.*nodejs\.org'):
42
+ cli.check_node(fake_node(tmp_path, 'v22.0.0', executable=False))
43
+
44
+
45
+ def test_check_node_reports_a_hung_node(monkeypatch):
46
+ def hang(*args, **kwargs):
47
+ raise subprocess.TimeoutExpired('node', 30)
48
+
49
+ monkeypatch.setattr(cli.subprocess, 'run', hang)
50
+ with pytest.raises(briskapi.BriskError, match='Could not run'):
51
+ cli.check_node()
52
+
53
+
54
+ def test_every_entry_point_checks_node_first(tmp_path):
55
+ missing = str(tmp_path / 'missing')
56
+ with pytest.raises(briskapi.BriskError, match='not found'):
57
+ next(_live.stream(cache=tmp_path, node=missing))
58
+ with pytest.raises(briskapi.BriskError, match='not found'):
59
+ briskapi.Feed(cache=tmp_path, node=missing)
60
+ with pytest.raises(briskapi.BriskError, match='not found'):
61
+ briskapi.connect(cache=tmp_path, node=missing)
62
+ with pytest.raises(briskapi.BriskError, match='not found'):
63
+ cli.record_events(tmp_path / 'events.jsonl', cache=tmp_path, node=missing)
64
+ sbi.login({'session_bfaf77a2': 'v'})
65
+ with pytest.raises(briskapi.BriskError, match='not found'):
66
+ sbi.connect(node=missing)
67
+ assert briskapi._current is None
68
+
69
+
70
+ def test_cli_reports_errors_without_a_traceback(tmp_path, monkeypatch):
71
+ monkeypatch.setenv('PATH', str(tmp_path))
72
+ with pytest.raises(SystemExit) as stopped:
73
+ cli.main(['live', '--cache', str(tmp_path)])
74
+ assert str(stopped.value.code).startswith('brisk: error: Node.js was not found')
75
+
76
+
77
+ def test_cli_version(capsys):
78
+ with pytest.raises(SystemExit) as stopped:
79
+ cli.main(['--version'])
80
+ assert stopped.value.code == 0
81
+ assert capsys.readouterr().out.strip() == f'brisk {briskapi.__version__}'
82
+
83
+
84
+ def test_version_matches_pyproject():
85
+ project = tomllib.loads((ROOT / 'pyproject.toml').read_text())['project']
86
+ assert project['version'] == briskapi.__version__
87
+
88
+
89
+ def test_readme_links_work_on_pypi():
90
+ # README.md is the PyPI description, where relative links do not resolve.
91
+ readme = (ROOT / 'README.md').read_text()
92
+ assert not re.findall(r'\]\((?!https?://|#|mailto:)[^)]+\)', readme)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes