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.
- signalk_cli-2.2.0/README.md → signalk_cli-3.0.0/PKG-INFO +89 -11
- signalk_cli-2.2.0/PKG-INFO → signalk_cli-3.0.0/README.md +57 -39
- {signalk_cli-2.2.0 → signalk_cli-3.0.0}/pyproject.toml +45 -8
- {signalk_cli-2.2.0 → signalk_cli-3.0.0}/pyproject.toml.orig +43 -9
- signalk_cli-3.0.0/src/signalk_cli/__init__.py +33 -0
- signalk_cli-3.0.0/src/signalk_cli/_arrow.py +147 -0
- signalk_cli-3.0.0/src/signalk_cli/_cli.py +82 -0
- signalk_cli-3.0.0/src/signalk_cli/errors.py +37 -0
- signalk_cli-3.0.0/src/signalk_cli/history/__init__.py +25 -0
- signalk_cli-3.0.0/src/signalk_cli/history/_results.py +230 -0
- signalk_cli-3.0.0/src/signalk_cli/history/_time.py +177 -0
- signalk_cli-3.0.0/src/signalk_cli/history/api.py +515 -0
- signalk_cli-3.0.0/src/signalk_cli/history/cli.py +573 -0
- signalk_cli-3.0.0/src/signalk_cli/history/output.py +84 -0
- signalk_cli-3.0.0/src/signalk_cli/net.py +72 -0
- signalk_cli-3.0.0/src/signalk_cli/py.typed +0 -0
- signalk_cli-3.0.0/src/signalk_cli/stream/__init__.py +29 -0
- signalk_cli-3.0.0/src/signalk_cli/stream/api.py +401 -0
- {signalk_cli-2.2.0 → signalk_cli-3.0.0}/src/signalk_cli/stream/cli.py +46 -57
- signalk_cli-3.0.0/src/signalk_cli/stream/output.py +61 -0
- signalk_cli-2.2.0/src/signalk_cli/__init__.py +0 -1
- signalk_cli-2.2.0/src/signalk_cli/history/__init__.py +0 -33
- signalk_cli-2.2.0/src/signalk_cli/history/cli.py +0 -739
- signalk_cli-2.2.0/src/signalk_cli/history/history_api.py +0 -253
- signalk_cli-2.2.0/src/signalk_cli/history/output.py +0 -421
- signalk_cli-2.2.0/src/signalk_cli/net.py +0 -131
- signalk_cli-2.2.0/src/signalk_cli/stream/__init__.py +0 -21
- signalk_cli-2.2.0/src/signalk_cli/stream/output.py +0 -181
- signalk_cli-2.2.0/src/signalk_cli/stream/stream_api.py +0 -106
- {signalk_cli-2.2.0 → signalk_cli-3.0.0}/src/signalk_cli/__main__.py +0 -0
- {signalk_cli-2.2.0 → signalk_cli-3.0.0}/src/signalk_cli/history/__main__.py +0 -0
- {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
|
-
|
|
35
|
+
[](https://github.com/rhizomatics)
|
|
36
|
+
|
|
37
|
+
[](https://pypi.org/project/signalk-cli/)
|
|
38
|
+
[](https://github.com/rhizomatics/signalk-cli)
|
|
39
|
+
[](https://signalk-cli.rhizomatics.org.uk/developer/coverage/)
|
|
40
|
+

|
|
41
|
+
[](https://results.pre-commit.ci/latest/github/rhizomatics/signalk-cli/main)
|
|
42
|
+
[](https://github.com/rhizomatics/signalk-cli/actions/workflows/pypi-publish.yml)
|
|
43
|
+
[](https://github.com/rhizomatics/signalk-cli/actions/workflows/python-package.yml)
|
|
44
|
+
[](https://github.com/rhizomatics/signalk-cli/actions/workflows/github-code-scanning/codeql)
|
|
45
|
+
[](https://github.com/rhizomatics/signalk-cli/actions/workflows/dependabot/dependabot-updates)
|
|
46
|
+
[](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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3
|
+
[](https://github.com/rhizomatics)
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/signalk-cli/)
|
|
6
|
+
[](https://github.com/rhizomatics/signalk-cli)
|
|
7
|
+
[](https://signalk-cli.rhizomatics.org.uk/developer/coverage/)
|
|
8
|
+

|
|
9
|
+
[](https://results.pre-commit.ci/latest/github/rhizomatics/signalk-cli/main)
|
|
10
|
+
[](https://github.com/rhizomatics/signalk-cli/actions/workflows/pypi-publish.yml)
|
|
11
|
+
[](https://github.com/rhizomatics/signalk-cli/actions/workflows/python-package.yml)
|
|
12
|
+
[](https://github.com/rhizomatics/signalk-cli/actions/workflows/github-code-scanning/codeql)
|
|
13
|
+
[](https://github.com/rhizomatics/signalk-cli/actions/workflows/dependabot/dependabot-updates)
|
|
14
|
+
[](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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
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
|
|
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 = "
|
|
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 ::
|
|
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
|
-
"
|
|
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://
|
|
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 =
|
|
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 = "
|
|
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", "
|
|
12
|
+
keywords = ["signalk", "sailing", "marine", "boating",
|
|
13
|
+
"nmea",
|
|
14
|
+
"dataframe","cli","pyarrow","nanoarrow"]
|
|
13
15
|
classifiers = [
|
|
14
|
-
"Development Status ::
|
|
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
|
-
|
|
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://
|
|
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=
|
|
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)
|