signalk-cli 2.2.0__tar.gz → 3.0.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.
Files changed (32) hide show
  1. signalk_cli-2.2.0/README.md → signalk_cli-3.0.0/PKG-INFO +89 -11
  2. signalk_cli-2.2.0/PKG-INFO → signalk_cli-3.0.0/README.md +57 -39
  3. {signalk_cli-2.2.0 → signalk_cli-3.0.0}/pyproject.toml +45 -8
  4. {signalk_cli-2.2.0 → signalk_cli-3.0.0}/pyproject.toml.orig +43 -9
  5. signalk_cli-3.0.0/src/signalk_cli/__init__.py +33 -0
  6. signalk_cli-3.0.0/src/signalk_cli/_arrow.py +147 -0
  7. signalk_cli-3.0.0/src/signalk_cli/_cli.py +82 -0
  8. signalk_cli-3.0.0/src/signalk_cli/errors.py +37 -0
  9. signalk_cli-3.0.0/src/signalk_cli/history/__init__.py +25 -0
  10. signalk_cli-3.0.0/src/signalk_cli/history/_results.py +230 -0
  11. signalk_cli-3.0.0/src/signalk_cli/history/_time.py +177 -0
  12. signalk_cli-3.0.0/src/signalk_cli/history/api.py +515 -0
  13. signalk_cli-3.0.0/src/signalk_cli/history/cli.py +573 -0
  14. signalk_cli-3.0.0/src/signalk_cli/history/output.py +84 -0
  15. signalk_cli-3.0.0/src/signalk_cli/net.py +72 -0
  16. signalk_cli-3.0.0/src/signalk_cli/py.typed +0 -0
  17. signalk_cli-3.0.0/src/signalk_cli/stream/__init__.py +29 -0
  18. signalk_cli-3.0.0/src/signalk_cli/stream/api.py +401 -0
  19. {signalk_cli-2.2.0 → signalk_cli-3.0.0}/src/signalk_cli/stream/cli.py +46 -57
  20. signalk_cli-3.0.0/src/signalk_cli/stream/output.py +61 -0
  21. signalk_cli-2.2.0/src/signalk_cli/__init__.py +0 -1
  22. signalk_cli-2.2.0/src/signalk_cli/history/__init__.py +0 -33
  23. signalk_cli-2.2.0/src/signalk_cli/history/cli.py +0 -739
  24. signalk_cli-2.2.0/src/signalk_cli/history/history_api.py +0 -253
  25. signalk_cli-2.2.0/src/signalk_cli/history/output.py +0 -421
  26. signalk_cli-2.2.0/src/signalk_cli/net.py +0 -131
  27. signalk_cli-2.2.0/src/signalk_cli/stream/__init__.py +0 -21
  28. signalk_cli-2.2.0/src/signalk_cli/stream/output.py +0 -181
  29. signalk_cli-2.2.0/src/signalk_cli/stream/stream_api.py +0 -106
  30. {signalk_cli-2.2.0 → signalk_cli-3.0.0}/src/signalk_cli/__main__.py +0 -0
  31. {signalk_cli-2.2.0 → signalk_cli-3.0.0}/src/signalk_cli/history/__main__.py +0 -0
  32. {signalk_cli-2.2.0 → signalk_cli-3.0.0}/src/signalk_cli/stream/__main__.py +0 -0
@@ -1,21 +1,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: signalk-cli
3
+ Version: 3.0.0
4
+ Summary: Query SignalK v2 APIs and export data as CSV, JSON or Apache Arrow Feather from command line or as a Python API
5
+ Keywords: signalk,sailing,marine,boating,nmea,dataframe,cli,pyarrow,nanoarrow
6
+ Author: jey burrows
7
+ Author-email: jey burrows <jrb@rhizomatics.org.uk>
8
+ License-Expression: Apache-2.0
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Environment :: Console
11
+ Classifier: Natural Language :: English
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Scientific/Engineering
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Dist: click>=8.3.3
18
+ Requires-Dist: nanoarrow>=0.9.0,<1.0.0
19
+ Requires-Dist: niquests[ws]>=2.28
20
+ Requires-Dist: niquests[ws]>=3.21.0 ; sys_platform == 'emscripten'
21
+ Requires-Dist: zeroconf>=0.149.16,<0.152.0 ; sys_platform != 'emscripten'
22
+ Requires-Dist: pyarrow>=17.0 ; extra == 'arrow'
23
+ Requires-Dist: pyarrow>=17.0 ; extra == 'feather'
24
+ Requires-Python: >=3.13
25
+ Project-URL: Homepage, https://signalk-cli.rhizomatics.org.uk
26
+ Project-URL: Repository, https://github.com/rhizomatics/signalk-cli
27
+ Project-URL: Issues, https://github.com/rhizomatics/signalk-cli/issues
28
+ Project-URL: Changelog, https://github.com/rhizomatics/signalk-cli/blob/main/CHANGELOG.md
29
+ Provides-Extra: arrow
30
+ Provides-Extra: feather
31
+ Description-Content-Type: text/markdown
32
+
1
33
  # SignalK CLI
2
34
 
3
- Query and explore NMEA and other boat data from SignalK APIs using the command line, and export data as CSV, Apache Arrow Feather, or JSON.
35
+ [![Rhizomatics Open Source](https://img.shields.io/badge/rhizomatics%20open%20source-lightseagreen)](https://github.com/rhizomatics)
36
+
37
+ [![PyPI - Version](https://img.shields.io/pypi/v/signalk-cli)](https://pypi.org/project/signalk-cli/)
38
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/rhizomatics/signalk-cli)
39
+ [![Coverage](https://raw.githubusercontent.com/rhizomatics/signalk-cli/refs/heads/badges/badges/coverage.svg)](https://signalk-cli.rhizomatics.org.uk/developer/coverage/)
40
+ ![Tests](https://raw.githubusercontent.com/rhizomatics/signalk-cli/refs/heads/badges/badges/tests.svg)
41
+ [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/rhizomatics/signalk-cli/main.svg)](https://results.pre-commit.ci/latest/github/rhizomatics/signalk-cli/main)
42
+ [![Publish Python 🐍 distribution 📦 to PyPI and TestPyPI](https://github.com/rhizomatics/signalk-cli/actions/workflows/pypi-publish.yml/badge.svg)](https://github.com/rhizomatics/signalk-cli/actions/workflows/pypi-publish.yml)
43
+ [![Github Deploy](https://github.com/rhizomatics/signalk-cli/actions/workflows/python-package.yml/badge.svg?branch=main)](https://github.com/rhizomatics/signalk-cli/actions/workflows/python-package.yml)
44
+ [![CodeQL](https://github.com/rhizomatics/signalk-cli/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/rhizomatics/signalk-cli/actions/workflows/github-code-scanning/codeql)
45
+ [![Dependabot Updates](https://github.com/rhizomatics/signalk-cli/actions/workflows/dependabot/dependabot-updates/badge.svg)](https://github.com/rhizomatics/signalk-cli/actions/workflows/dependabot/dependabot-updates)
46
+ [![boat tech directory](https://boat-tech-directory.rhizomatics.org.uk/images/badge.svg)](https://boat-tech-directory.rhizomatics.org.uk)
47
+
48
+ Query and explore NMEA and other boat data from SignalK APIs using the command line or the Python API. Export data as CSV, Apache Arrow Feather, or JSON or use as dataframes in scripts or data notebooks.
4
49
 
5
50
  APIs supported:
6
51
 
7
- * [SignalK v2 History API](https://signalk.org/https://demo.signalk.org/documentation/Developing/REST_APIs/History_API.html). Commands available:
8
- - list-paths
9
- - list-providers
10
- - list-contexts
11
- - query
12
- - cardinality
13
- * [SignalK v1 Streaming API](https://signalk.org/specification/1.8.2/doc/streaming_api.html). Commands available:
14
- - deltas
52
+ ### [SignalK v2 History API](https://demo.signalk.org/documentation/Developing/REST_APIs/History_API.html).
53
+
54
+ - Commands available:
55
+ - `list-paths`
56
+ - `list-providers`
57
+ - `list-contexts`
58
+ - `query`
59
+ - `cardinality`
60
+
61
+ ### [SignalK v1 Streaming API](https://signalk.org/specification/1.8.2/doc/streaming_api.html).
62
+
63
+ - Commands available:
64
+ - `deltas`
15
65
 
16
66
  ## Installation
17
67
 
18
- `signalk-cli` is published to PyPi at https://pypi.org/project/signalk-cli/
68
+ `signalk-cli` is published to [PyPi](https://pypi.org/project/signalk-cli/)
19
69
 
20
70
  Python is required to run this, version 3.13 or above. [uv](https://docs.astral.sh/uv/) is the recommended way to install the package ( and can install Python ) but is not required.
21
71
 
@@ -25,6 +75,19 @@ Python is required to run this, version 3.13 or above. [uv](https://docs.astral.
25
75
 
26
76
  For Apache Arrow Feather export, use the optional dependency: ```pip install 'signalk-cli[feather]'```
27
77
 
78
+ ### Pyodide / WebAssembly
79
+
80
+ The same wheel installs in Pyodide (e.g. marimo in the browser). Platform markers skip `zeroconf`, which can't run there, and require `niquests>=3.21.0`:
81
+
82
+ ```python
83
+ import micropip
84
+ await micropip.install("signalk-cli")
85
+ ```
86
+
87
+ Without `zeroconf` there's no mDNS discovery, so the host must be given with `--host` or `SIGNALK_HOST` (or come from the cache).
88
+
89
+ See [signalk-datalab-plugin](https://github.com/rhizomatics/signalk-datalab-plugin) for a working example of this.
90
+
28
91
  ### Local Copy
29
92
 
30
93
  Requires Python 3.13+ and [uv](https://docs.astral.sh/uv/).
@@ -47,6 +110,20 @@ uv run --with signalk-cli signalk_cli.history list-providers
47
110
  uv run --with signalk-cli signalk_cli.stream deltas navigation.position --follow
48
111
  ```
49
112
 
113
+ ## Python library
114
+
115
+ Everything the CLI does is also available from Python, returning tables that load straight into polars, pandas, pyarrow or DuckDB:
116
+
117
+ ```python
118
+ from signalk_cli import HistoryClient, TimeRange
119
+ import polars as pl
120
+
121
+ with HistoryClient("http://boat.local:3000") as client:
122
+ df = pl.DataFrame(client.query(["navigation.*"], TimeRange(duration="P1D")))
123
+ ```
124
+
125
+ See the [Python library guide](https://signalk-cli.rhizomatics.org.uk/library/) and API reference.
126
+
50
127
  ## Running
51
128
 
52
129
  Run via `python -m signalk_cli.history <command>` or `python -m signalk_cli.stream <command>` or without installing the module with `uv run --with signalk-cli signalk_cli.history <command>`.
@@ -90,8 +167,9 @@ python -m signalk_cli.history query [OPTIONS] PATH...
90
167
  ```
91
168
 
92
169
  **PATH** arguments may be:
170
+
93
171
  - **Literal paths** — e.g. `navigation.speedOverGround`
94
- - **Regex / glob patterns** — any argument containing metacharacters (`*`, `.`, `[`, `(`, etc.) is matched against the server's `/paths` endpoint. Bare `*` is treated as a glob wildcard.
172
+ - **Regex / glob patterns** — any argument containing a pattern character (`*`, `?`, `+`, `[`, `(`, `{`, `|`, `^`, `$` or `\`) is matched against the server's `/paths` endpoint. A `.` on its own doesn't count, so a plain dotted path is always literal. Bare `*` is treated as a glob wildcard.
95
173
  - **Inline path specs** — `path:method` or `path:method:param`, e.g. `navigation.speedOverGround:sma:5`. These pass through to the server unchanged.
96
174
 
97
175
  #### Options
@@ -1,49 +1,39 @@
1
- Metadata-Version: 2.4
2
- Name: signalk-cli
3
- Version: 2.2.0
4
- Summary: Query SignalK v2 APIs and export data as CSV, JSON or Apache Arrow Feather
5
- Keywords: signalk,sailing,marine,nmea,boating
6
- Author: jey burrows
7
- Author-email: jey burrows <jrb@rhizomatics.org.uk>
8
- License-Expression: Apache-2.0
9
- Classifier: Development Status :: 4 - Beta
10
- Classifier: Environment :: Console
11
- Classifier: Natural Language :: English
12
- Classifier: Intended Audience :: Developers
13
- Classifier: Topic :: Scientific/Engineering
14
- Classifier: Programming Language :: Python
15
- Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Programming Language :: Python :: 3.14
17
- Requires-Dist: click>=8.3.3
18
- Requires-Dist: niquests[ws]>=2.28
19
- Requires-Dist: zeroconf>=0.149.16,<0.150.0
20
- Requires-Dist: pyarrow>=17.0 ; extra == 'feather'
21
- Requires-Python: >=3.13
22
- Project-URL: Homepage, https://github.com/rhizomatics/signalk-cli
23
- Project-URL: Repository, https://github.com/rhizomatics/signalk-cli
24
- Project-URL: Issues, https://github.com/rhizomatics/signalk-cli/issues
25
- Project-URL: Changelog, https://github.com/rhizomatics/signalk-cli/blob/main/CHANGELOG.md
26
- Provides-Extra: feather
27
- Description-Content-Type: text/markdown
28
-
29
1
  # SignalK CLI
30
2
 
31
- Query and explore NMEA and other boat data from SignalK APIs using the command line, and export data as CSV, Apache Arrow Feather, or JSON.
3
+ [![Rhizomatics Open Source](https://img.shields.io/badge/rhizomatics%20open%20source-lightseagreen)](https://github.com/rhizomatics)
4
+
5
+ [![PyPI - Version](https://img.shields.io/pypi/v/signalk-cli)](https://pypi.org/project/signalk-cli/)
6
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/rhizomatics/signalk-cli)
7
+ [![Coverage](https://raw.githubusercontent.com/rhizomatics/signalk-cli/refs/heads/badges/badges/coverage.svg)](https://signalk-cli.rhizomatics.org.uk/developer/coverage/)
8
+ ![Tests](https://raw.githubusercontent.com/rhizomatics/signalk-cli/refs/heads/badges/badges/tests.svg)
9
+ [![pre-commit.ci status](https://results.pre-commit.ci/badge/github/rhizomatics/signalk-cli/main.svg)](https://results.pre-commit.ci/latest/github/rhizomatics/signalk-cli/main)
10
+ [![Publish Python 🐍 distribution 📦 to PyPI and TestPyPI](https://github.com/rhizomatics/signalk-cli/actions/workflows/pypi-publish.yml/badge.svg)](https://github.com/rhizomatics/signalk-cli/actions/workflows/pypi-publish.yml)
11
+ [![Github Deploy](https://github.com/rhizomatics/signalk-cli/actions/workflows/python-package.yml/badge.svg?branch=main)](https://github.com/rhizomatics/signalk-cli/actions/workflows/python-package.yml)
12
+ [![CodeQL](https://github.com/rhizomatics/signalk-cli/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/rhizomatics/signalk-cli/actions/workflows/github-code-scanning/codeql)
13
+ [![Dependabot Updates](https://github.com/rhizomatics/signalk-cli/actions/workflows/dependabot/dependabot-updates/badge.svg)](https://github.com/rhizomatics/signalk-cli/actions/workflows/dependabot/dependabot-updates)
14
+ [![boat tech directory](https://boat-tech-directory.rhizomatics.org.uk/images/badge.svg)](https://boat-tech-directory.rhizomatics.org.uk)
15
+
16
+ Query and explore NMEA and other boat data from SignalK APIs using the command line or the Python API. Export data as CSV, Apache Arrow Feather, or JSON or use as dataframes in scripts or data notebooks.
32
17
 
33
18
  APIs supported:
34
19
 
35
- * [SignalK v2 History API](https://signalk.org/https://demo.signalk.org/documentation/Developing/REST_APIs/History_API.html). Commands available:
36
- - list-paths
37
- - list-providers
38
- - list-contexts
39
- - query
40
- - cardinality
41
- * [SignalK v1 Streaming API](https://signalk.org/specification/1.8.2/doc/streaming_api.html). Commands available:
42
- - deltas
20
+ ### [SignalK v2 History API](https://demo.signalk.org/documentation/Developing/REST_APIs/History_API.html).
21
+
22
+ - Commands available:
23
+ - `list-paths`
24
+ - `list-providers`
25
+ - `list-contexts`
26
+ - `query`
27
+ - `cardinality`
28
+
29
+ ### [SignalK v1 Streaming API](https://signalk.org/specification/1.8.2/doc/streaming_api.html).
30
+
31
+ - Commands available:
32
+ - `deltas`
43
33
 
44
34
  ## Installation
45
35
 
46
- `signalk-cli` is published to PyPi at https://pypi.org/project/signalk-cli/
36
+ `signalk-cli` is published to [PyPi](https://pypi.org/project/signalk-cli/)
47
37
 
48
38
  Python is required to run this, version 3.13 or above. [uv](https://docs.astral.sh/uv/) is the recommended way to install the package ( and can install Python ) but is not required.
49
39
 
@@ -53,6 +43,19 @@ Python is required to run this, version 3.13 or above. [uv](https://docs.astral.
53
43
 
54
44
  For Apache Arrow Feather export, use the optional dependency: ```pip install 'signalk-cli[feather]'```
55
45
 
46
+ ### Pyodide / WebAssembly
47
+
48
+ The same wheel installs in Pyodide (e.g. marimo in the browser). Platform markers skip `zeroconf`, which can't run there, and require `niquests>=3.21.0`:
49
+
50
+ ```python
51
+ import micropip
52
+ await micropip.install("signalk-cli")
53
+ ```
54
+
55
+ Without `zeroconf` there's no mDNS discovery, so the host must be given with `--host` or `SIGNALK_HOST` (or come from the cache).
56
+
57
+ See [signalk-datalab-plugin](https://github.com/rhizomatics/signalk-datalab-plugin) for a working example of this.
58
+
56
59
  ### Local Copy
57
60
 
58
61
  Requires Python 3.13+ and [uv](https://docs.astral.sh/uv/).
@@ -75,6 +78,20 @@ uv run --with signalk-cli signalk_cli.history list-providers
75
78
  uv run --with signalk-cli signalk_cli.stream deltas navigation.position --follow
76
79
  ```
77
80
 
81
+ ## Python library
82
+
83
+ Everything the CLI does is also available from Python, returning tables that load straight into polars, pandas, pyarrow or DuckDB:
84
+
85
+ ```python
86
+ from signalk_cli import HistoryClient, TimeRange
87
+ import polars as pl
88
+
89
+ with HistoryClient("http://boat.local:3000") as client:
90
+ df = pl.DataFrame(client.query(["navigation.*"], TimeRange(duration="P1D")))
91
+ ```
92
+
93
+ See the [Python library guide](https://signalk-cli.rhizomatics.org.uk/library/) and API reference.
94
+
78
95
  ## Running
79
96
 
80
97
  Run via `python -m signalk_cli.history <command>` or `python -m signalk_cli.stream <command>` or without installing the module with `uv run --with signalk-cli signalk_cli.history <command>`.
@@ -118,8 +135,9 @@ python -m signalk_cli.history query [OPTIONS] PATH...
118
135
  ```
119
136
 
120
137
  **PATH** arguments may be:
138
+
121
139
  - **Literal paths** — e.g. `navigation.speedOverGround`
122
- - **Regex / glob patterns** — any argument containing metacharacters (`*`, `.`, `[`, `(`, etc.) is matched against the server's `/paths` endpoint. Bare `*` is treated as a glob wildcard.
140
+ - **Regex / glob patterns** — any argument containing a pattern character (`*`, `?`, `+`, `[`, `(`, `{`, `|`, `^`, `$` or `\`) is matched against the server's `/paths` endpoint. A `.` on its own doesn't count, so a plain dotted path is always literal. Bare `*` is treated as a glob wildcard.
123
141
  - **Inline path specs** — `path:method` or `path:method:param`, e.g. `navigation.speedOverGround:sma:5`. These pass through to the server unchanged.
124
142
 
125
143
  #### Options
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "signalk-cli"
3
- version = "2.2.0"
4
- description = "Query SignalK v2 APIs and export data as CSV, JSON or Apache Arrow Feather"
3
+ version = "3.0.0"
4
+ description = "Query SignalK v2 APIs and export data as CSV, JSON or Apache Arrow Feather from command line or as a Python API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
7
7
  license = "Apache-2.0"
@@ -9,11 +9,15 @@ keywords = [
9
9
  "signalk",
10
10
  "sailing",
11
11
  "marine",
12
- "nmea",
13
12
  "boating",
13
+ "nmea",
14
+ "dataframe",
15
+ "cli",
16
+ "pyarrow",
17
+ "nanoarrow",
14
18
  ]
15
19
  classifiers = [
16
- "Development Status :: 4 - Beta",
20
+ "Development Status :: 5 - Production/Stable",
17
21
  "Environment :: Console",
18
22
  "Natural Language :: English",
19
23
  "Intended Audience :: Developers",
@@ -24,8 +28,10 @@ classifiers = [
24
28
  ]
25
29
  dependencies = [
26
30
  "click>=8.3.3",
31
+ "nanoarrow>=0.9.0,<1.0.0",
27
32
  "niquests[ws]>=2.28",
28
- "zeroconf>=0.149.16,<0.150.0",
33
+ "niquests[ws]>=3.21.0; sys_platform == 'emscripten'",
34
+ "zeroconf>=0.149.16,<0.152.0; sys_platform != 'emscripten'",
29
35
  ]
30
36
 
31
37
  [[project.authors]]
@@ -34,9 +40,10 @@ email = "jrb@rhizomatics.org.uk"
34
40
 
35
41
  [project.optional-dependencies]
36
42
  feather = ["pyarrow>=17.0"]
43
+ arrow = ["pyarrow>=17.0"]
37
44
 
38
45
  [project.urls]
39
- Homepage = "https://github.com/rhizomatics/signalk-cli"
46
+ Homepage = "https://signalk-cli.rhizomatics.org.uk"
40
47
  Repository = "https://github.com/rhizomatics/signalk-cli"
41
48
  Issues = "https://github.com/rhizomatics/signalk-cli/issues"
42
49
  Changelog = "https://github.com/rhizomatics/signalk-cli/blob/main/CHANGELOG.md"
@@ -55,8 +62,26 @@ dev = [
55
62
  "pytest-xdist",
56
63
  "mypy",
57
64
  "coverage",
65
+ "genbadge[all]",
58
66
  "icdiff",
59
67
  "codespell",
68
+ "polars>=1.44.2,<2.0.0",
69
+ "duckdb>=1.5.5,<2.0.0",
70
+ "pandas>=3.0.6,<4.0.0",
71
+ ]
72
+ docs = [
73
+ "properdocs",
74
+ "mkdocs-materialx[imaging]",
75
+ "mkdocs-minify-plugin",
76
+ "mkdocs-autorefs",
77
+ "mkdocs-pagetree-plugin",
78
+ "mkdocs-coverage",
79
+ "pymdown-extensions",
80
+ "mkdocs-meta-descriptions-plugin",
81
+ "pngquant",
82
+ "mkdocs-htmlproofer-plugin",
83
+ "mkdocs-llmstxt>=0.5.0,<0.6.0",
84
+ "mkdocstrings[python]>=1.0.6,<2.0.0",
60
85
  ]
61
86
 
62
87
  [tool.bandit]
@@ -73,7 +98,6 @@ pythonpath = [
73
98
  "src",
74
99
  ".",
75
100
  ]
76
- asyncio_mode = "auto"
77
101
  testpaths = ["tests"]
78
102
  norecursedirs = [".git"]
79
103
 
@@ -82,7 +106,7 @@ branch = true
82
106
  source = ["src"]
83
107
 
84
108
  [tool.coverage.report]
85
- fail_under = 0
109
+ fail_under = 90
86
110
 
87
111
  [tool.coverage.xml]
88
112
  output = "cov.xml"
@@ -99,6 +123,19 @@ ignore-words-list = "hass,referer,uint"
99
123
  [tool.mypy]
100
124
  mypy_path = ["./src"]
101
125
 
126
+ [[tool.mypy.overrides]]
127
+ module = [
128
+ "pyarrow.*",
129
+ "nanoarrow.*",
130
+ ]
131
+ ignore_missing_imports = true
132
+
133
+ [tool.ty.analysis]
134
+ allowed-unresolved-imports = [
135
+ "pyarrow",
136
+ "pyarrow.**",
137
+ ]
138
+
102
139
  [tool.uv]
103
140
  compile-bytecode = true
104
141
  managed = true
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "signalk-cli"
3
- version = "2.2.0"
4
- description = "Query SignalK v2 APIs and export data as CSV, JSON or Apache Arrow Feather"
3
+ version = "3.0.0"
4
+ description = "Query SignalK v2 APIs and export data as CSV, JSON or Apache Arrow Feather from command line or as a Python API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
7
7
  license="Apache-2.0"
@@ -9,9 +9,11 @@ authors = [
9
9
  { name = "jey burrows", email = "jrb@rhizomatics.org.uk" }
10
10
  ]
11
11
 
12
- keywords = ["signalk", "sailing", "marine", "nmea","boating"]
12
+ keywords = ["signalk", "sailing", "marine", "boating",
13
+ "nmea",
14
+ "dataframe","cli","pyarrow","nanoarrow"]
13
15
  classifiers = [
14
- "Development Status :: 4 - Beta",
16
+ "Development Status :: 5 - Production/Stable",
15
17
  "Environment :: Console",
16
18
  "Natural Language :: English",
17
19
  "Intended Audience :: Developers",
@@ -23,12 +25,18 @@ classifiers = [
23
25
 
24
26
  dependencies = [
25
27
  "click>=8.3.3",
28
+ "nanoarrow>=0.9.0,<1.0.0",
26
29
  "niquests[ws]>=2.28",
27
- "zeroconf>=0.149.16,<0.150.0",
30
+ # Pyodide/WASM: niquests needs >=3.21 there, and zeroconf can't run (no raw sockets)
31
+ "niquests[ws]>=3.21.0; sys_platform == 'emscripten'",
32
+ "zeroconf>=0.149.16,<0.152.0; sys_platform != 'emscripten'",
28
33
  ]
29
34
 
30
35
  [project.optional-dependencies]
36
+ # pyarrow, only needed to write Feather files from the CLI
31
37
  feather = ["pyarrow>=17.0"]
38
+ # alias of feather
39
+ arrow = ["pyarrow>=17.0"]
32
40
  [dependency-groups]
33
41
  dev = [
34
42
  "ruff>=0.16.0",
@@ -39,12 +47,31 @@ dev = [
39
47
  "pytest-xdist",
40
48
  "mypy",
41
49
  "coverage",
50
+ "genbadge[all]",
42
51
  "icdiff",
43
- "codespell"
52
+ "codespell",
53
+ "polars>=1.44.2,<2.0.0",
54
+ "duckdb>=1.5.5,<2.0.0",
55
+ "pandas>=3.0.6,<4.0.0",
56
+ ]
57
+
58
+ docs=[
59
+ "properdocs",
60
+ "mkdocs-materialx[imaging]",
61
+ "mkdocs-minify-plugin",
62
+ "mkdocs-autorefs",
63
+ "mkdocs-pagetree-plugin",
64
+ "mkdocs-coverage",
65
+ "pymdown-extensions",
66
+ "mkdocs-meta-descriptions-plugin",
67
+ "pngquant",
68
+ "mkdocs-htmlproofer-plugin",
69
+ "mkdocs-llmstxt>=0.5.0,<0.6.0",
70
+ "mkdocstrings[python]>=1.0.6,<2.0.0",
44
71
  ]
45
72
 
46
73
  [project.urls]
47
- Homepage = "https://github.com/rhizomatics/signalk-cli"
74
+ Homepage = "https://signalk-cli.rhizomatics.org.uk"
48
75
  Repository = "https://github.com/rhizomatics/signalk-cli"
49
76
  Issues="https://github.com/rhizomatics/signalk-cli/issues"
50
77
  Changelog="https://github.com/rhizomatics/signalk-cli/blob/main/CHANGELOG.md"
@@ -67,7 +94,6 @@ line-ending = "auto"
67
94
 
68
95
  [tool.pytest.ini_options]
69
96
  pythonpath = ["src", "."]
70
- asyncio_mode = "auto"
71
97
  testpaths = [
72
98
  "tests",
73
99
  ]
@@ -81,7 +107,7 @@ branch = true
81
107
  source = ["src"]
82
108
 
83
109
  [tool.coverage.report]
84
- fail_under=0
110
+ fail_under=90
85
111
 
86
112
  [tool.coverage.xml]
87
113
  output="cov.xml"
@@ -98,6 +124,14 @@ ignore-words-list="hass,referer,uint"
98
124
  [tool.mypy]
99
125
  mypy_path=["./src"]
100
126
 
127
+ [[tool.mypy.overrides]]
128
+ module = ["pyarrow.*", "nanoarrow.*"]
129
+ ignore_missing_imports = true
130
+
131
+ [tool.ty.analysis]
132
+ # optional `feather` extra
133
+ allowed-unresolved-imports = ["pyarrow", "pyarrow.**"]
134
+
101
135
  [tool.uv]
102
136
  compile-bytecode = true
103
137
  managed = true
@@ -0,0 +1,33 @@
1
+ """signalk_cli — Python clients and CLI for SignalK APIs.
2
+
3
+ The clients are importable from here or from their packages:
4
+
5
+ ```python
6
+ from signalk_cli import HistoryClient, StreamClient, TimeRange
7
+ ```
8
+ """
9
+
10
+ import logging
11
+
12
+ from ._arrow import ArrowTable
13
+ from .errors import SignalKError
14
+ from .history import HistoryClient, HistoryResult, TimeRange
15
+ from .net import discover_host
16
+ from .stream import DeltaMessage, DeltaRow, DeltaStream, StreamClient
17
+
18
+ # Library convention: stay silent unless the application configures logging.
19
+ # The CLI attaches its own stderr handler per command.
20
+ logging.getLogger("signalk_cli").addHandler(logging.NullHandler())
21
+
22
+ __all__ = [
23
+ "ArrowTable",
24
+ "DeltaMessage",
25
+ "DeltaRow",
26
+ "DeltaStream",
27
+ "HistoryClient",
28
+ "HistoryResult",
29
+ "SignalKError",
30
+ "StreamClient",
31
+ "TimeRange",
32
+ "discover_host",
33
+ ]
@@ -0,0 +1,147 @@
1
+ """A small Arrow table built with nanoarrow, handed to dataframe libraries via the PyCapsule interface."""
2
+
3
+ import json
4
+ from collections.abc import Mapping, Sequence
5
+ from datetime import datetime
6
+ from typing import Any
7
+
8
+ import nanoarrow as na
9
+
10
+ _TIMESTAMP = na.timestamp("us", timezone="UTC")
11
+
12
+ FEATHER_EXTENSIONS = {".feather", ".arrow", ".fea"}
13
+
14
+
15
+ def _is_number(v: object) -> bool:
16
+ return isinstance(v, (int, float)) and not isinstance(v, bool)
17
+
18
+
19
+ def as_text(v: object) -> str | None:
20
+ if v is None or isinstance(v, str):
21
+ return v
22
+ if isinstance(v, (dict, list)):
23
+ return json.dumps(v)
24
+ return str(v)
25
+
26
+
27
+ def _epoch_us(ts: str | datetime | None) -> int | None:
28
+ if ts is None:
29
+ return None
30
+ dt = datetime.fromisoformat(ts) if isinstance(ts, str) else ts
31
+ return round(dt.timestamp() * 1_000_000)
32
+
33
+
34
+ def infer_column(values: Sequence[Any]) -> tuple[list, Any]:
35
+ """Pick one Arrow type for a column of JSON values: float64, bool, or string.
36
+
37
+ Numbers (and nulls) become float64, booleans stay bool, and anything else
38
+ — strings, objects, arrays, or a mix — becomes a string column, with
39
+ objects and arrays JSON-encoded.
40
+ """
41
+ present = [v for v in values if v is not None]
42
+ if all(_is_number(v) for v in present):
43
+ return [None if v is None else float(v) for v in values], na.float64()
44
+ if all(isinstance(v, bool) for v in present):
45
+ return list(values), na.bool_()
46
+ return [as_text(v) for v in values], na.string()
47
+
48
+
49
+ class ArrowTable:
50
+ """A table of query results, ready to load into any Arrow-aware dataframe library.
51
+
52
+ It implements the [Arrow PyCapsule interface](https://arrow.apache.org/docs/format/CDataInterface/PyCapsuleInterface.html),
53
+ so it can be passed straight to polars, pandas, pyarrow, DuckDB and others
54
+ without signalk-cli depending on any of them:
55
+
56
+ Examples:
57
+ ```python
58
+ import polars as pl
59
+ df = pl.DataFrame(table)
60
+ import pandas as pd
61
+ df = pd.DataFrame.from_arrow(table) # pandas >= 3.0
62
+ import pyarrow as pa
63
+ t = pa.table(table)
64
+ ```
65
+ """
66
+
67
+ def __init__(self, columns: dict[str, tuple[list, Any]]) -> None:
68
+ self._columns = {name: values for name, (values, _) in columns.items()}
69
+ self._types = {name: t for name, (_, t) in columns.items()}
70
+ lengths = {len(v) for v in self._columns.values()}
71
+ if len(lengths) > 1:
72
+ raise ValueError(f"Columns have different lengths: {sorted(lengths)}")
73
+ self._num_rows = lengths.pop() if lengths else 0
74
+
75
+ @classmethod
76
+ def from_rows(
77
+ cls,
78
+ timestamps: Sequence[str | datetime | None],
79
+ columns: Mapping[str, Sequence[Any]],
80
+ *,
81
+ text_columns: Sequence[str] = (),
82
+ timestamp_name: str = "timestamp",
83
+ ) -> "ArrowTable":
84
+ """Build a table with a UTC timestamp column followed by the given columns.
85
+
86
+ Column types are inferred from the values (see `infer_column`),
87
+ except `text_columns`, which are always strings so an empty result
88
+ keeps the same schema.
89
+ """
90
+ built: dict[str, tuple[list, Any]] = {
91
+ timestamp_name: ([_epoch_us(t) for t in timestamps], _TIMESTAMP)
92
+ }
93
+ for name, values in columns.items():
94
+ if name in text_columns:
95
+ built[name] = ([as_text(v) for v in values], na.string())
96
+ else:
97
+ built[name] = infer_column(values)
98
+ return cls(built)
99
+
100
+ @property
101
+ def num_rows(self) -> int:
102
+ return self._num_rows
103
+
104
+ @property
105
+ def column_names(self) -> list[str]:
106
+ return list(self._columns)
107
+
108
+ def __len__(self) -> int:
109
+ return self._num_rows
110
+
111
+ def __repr__(self) -> str:
112
+ cols = ", ".join(f"{n}: {self._types[n].type.name}" for n in self._columns)
113
+ return f"ArrowTable({self._num_rows} rows; {cols})"
114
+
115
+ def to_pydict(self) -> dict[str, list]:
116
+ """Return the columns as plain Python lists (timestamps as epoch microseconds)."""
117
+ return {name: list(values) for name, values in self._columns.items()}
118
+
119
+ def _batch(self):
120
+ children = [
121
+ na.c_array(values, self._types[name])
122
+ for name, values in self._columns.items()
123
+ ]
124
+ schema = na.struct({name: self._types[name] for name in self._columns})
125
+ return na.c_array_from_buffers(
126
+ schema, self._num_rows, [None], children=children
127
+ )
128
+
129
+ def __arrow_c_schema__(self):
130
+ return na.c_schema(
131
+ na.struct({name: self._types[name] for name in self._columns})
132
+ ).__arrow_c_schema__()
133
+
134
+ def __arrow_c_stream__(self, requested_schema=None):
135
+ return na.ArrayStream(self._batch()).__arrow_c_stream__(requested_schema)
136
+
137
+
138
+ def write_feather(table: ArrowTable, output: str) -> None:
139
+ """Write a table to a Feather (Arrow IPC file) — needs pyarrow."""
140
+ try:
141
+ import pyarrow as pa
142
+ from pyarrow import feather
143
+ except ImportError:
144
+ raise ImportError(
145
+ "pyarrow is required for Feather output: pip install 'signalk-cli[feather]'"
146
+ ) from None
147
+ feather.write_feather(pa.table(table), output)