llusen 0.1.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.
- llusen-0.1.0/LICENSE +21 -0
- llusen-0.1.0/PKG-INFO +198 -0
- llusen-0.1.0/README.md +186 -0
- llusen-0.1.0/llusen/__init__.py +40 -0
- llusen-0.1.0/llusen/_client.py +642 -0
- llusen-0.1.0/llusen/_frames.py +87 -0
- llusen-0.1.0/llusen/_values.py +202 -0
- llusen-0.1.0/llusen.egg-info/PKG-INFO +198 -0
- llusen-0.1.0/llusen.egg-info/SOURCES.txt +13 -0
- llusen-0.1.0/llusen.egg-info/dependency_links.txt +1 -0
- llusen-0.1.0/llusen.egg-info/requires.txt +3 -0
- llusen-0.1.0/llusen.egg-info/top_level.txt +1 -0
- llusen-0.1.0/pyproject.toml +19 -0
- llusen-0.1.0/setup.cfg +4 -0
- llusen-0.1.0/tests/test_client.py +635 -0
llusen-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anthony Blackshaw
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
llusen-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: llusen
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Read and change the project open in llusen from Python
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.14
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Provides-Extra: pandas
|
|
10
|
+
Requires-Dist: pandas>=2.2; extra == "pandas"
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
13
|
+
# llusen for Python
|
|
14
|
+
|
|
15
|
+
Read and change the project open in llusen from Python: tables, formulas,
|
|
16
|
+
filters, summary values, group tables, charts and notes; and export them
|
|
17
|
+
as CSV, Excel or OpenDocument files.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import llusen
|
|
21
|
+
|
|
22
|
+
with llusen.connect() as project:
|
|
23
|
+
orders = project.table("orders")
|
|
24
|
+
orders.add_column("total", "price * qty")
|
|
25
|
+
orders.set_format("total", 'currency("£", 2)')
|
|
26
|
+
print(orders.summary())
|
|
27
|
+
frame = orders.to_pandas() # with pandas installed
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Setting up
|
|
31
|
+
|
|
32
|
+
- **Python 3.14** or later. The package uses only the standard library;
|
|
33
|
+
pandas is optional, for `to_pandas` and for giving DataFrames.
|
|
34
|
+
- **Install** from PyPI: `pip install llusen`, or `pip install
|
|
35
|
+
"llusen[pandas]"` with pandas. From this folder: `pip install .`.
|
|
36
|
+
- **In llusen**, choose **Tools > API Enabled**. llusen then listens on
|
|
37
|
+
this machine only, and writes its port and a token to `api.json` in its
|
|
38
|
+
state folder:
|
|
39
|
+
- Linux: `~/.local/state/llusen` (or `$XDG_STATE_HOME/llusen`);
|
|
40
|
+
- macOS: `~/Library/Application Support/llusen`;
|
|
41
|
+
- Windows: `%LOCALAPPDATA%\llusen`.
|
|
42
|
+
|
|
43
|
+
`llusen.connect()` reads that file and gives the token. API Enabled is
|
|
44
|
+
remembered, and the token is new each time it is turned on.
|
|
45
|
+
|
|
46
|
+
- **Which llusen:** each project opens in a llusen of its own, with its
|
|
47
|
+
own API, turned on in its own window. `connect()` reaches the llusen
|
|
48
|
+
used last (the one whose window last had the keyboard), as `api.json`
|
|
49
|
+
names it. To reach the llusen that has a given project open, name the
|
|
50
|
+
project's file:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
with llusen.connect(project="~/reports/sales.llu") as project:
|
|
54
|
+
...
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If no llusen has it open, or the one that has has its API off,
|
|
58
|
+
`NotRunning` says which. `llusen.find_project(path)` gives that
|
|
59
|
+
llusen's port and token without connecting. (Each llusen lists what
|
|
60
|
+
it has open in `instances` in the state folder.)
|
|
61
|
+
|
|
62
|
+
## How it works
|
|
63
|
+
|
|
64
|
+
- Changes go into the project as if made in llusen. Each is one step
|
|
65
|
+
that Ctrl+Z takes back in that table, and the status bar says what the
|
|
66
|
+
script did. A table a script makes opens in a tab.
|
|
67
|
+
- Rows are counted in the order they were loaded or added, from 0,
|
|
68
|
+
whatever the user's sort and filters show. Reading gives every row.
|
|
69
|
+
- Reading waits until formulas are worked out, so values are never half
|
|
70
|
+
done. llusen gives up on a call after five minutes of waiting.
|
|
71
|
+
- A call llusen refuses raises `LlusenError`, with llusen's own message.
|
|
72
|
+
For a formula, `error.at` is the (start, end) of the problem in it.
|
|
73
|
+
- If llusen isn't running, or API Enabled is off, `connect` raises
|
|
74
|
+
`NotRunning` (a `ConnectionError`).
|
|
75
|
+
|
|
76
|
+
## Exporting
|
|
77
|
+
|
|
78
|
+
llusen writes the file, so the path is on the machine llusen runs on. A
|
|
79
|
+
relative path, or one from `~`, is made whole from the script's folder
|
|
80
|
+
first. A file already there is replaced, through a temporary file, so a
|
|
81
|
+
failed write leaves it as it was. Rows go in the order llusen shows them
|
|
82
|
+
(sorted, every row, whatever the filters). llusen writes on a thread of
|
|
83
|
+
its own and answers once the file is complete; the status bar says so.
|
|
84
|
+
The path's extension decides the kind (`.csv`, `.xlsx`, `.ods`), unless
|
|
85
|
+
`format` says; a path with no extension gets the format's.
|
|
86
|
+
|
|
87
|
+
## Values
|
|
88
|
+
|
|
89
|
+
| llusen | Python | pandas |
|
|
90
|
+
|----------|-------------------------------|---------------------------|
|
|
91
|
+
| int | `int` | `int64` (`Int64` if any are missing) |
|
|
92
|
+
| float | `float` | `float64` |
|
|
93
|
+
| bool | `bool` | `bool` (`boolean` if any are missing) |
|
|
94
|
+
| text | `str` | `object` |
|
|
95
|
+
| date | `datetime.date` | `datetime64` |
|
|
96
|
+
| datetime | `datetime.datetime` (UTC, aware, if the column is UTC) | `datetime64` |
|
|
97
|
+
| time | `datetime.time` | `object` (`datetime.time`) |
|
|
98
|
+
| duration | `datetime.timedelta` | `timedelta64` |
|
|
99
|
+
| null | `None` | missing |
|
|
100
|
+
| a fault such as `#DIV0` | `llusen.Fault("#DIV0")` | missing |
|
|
101
|
+
|
|
102
|
+
When giving values, NaN, `NaT` and `pd.NA` are null; numpy's numbers are
|
|
103
|
+
numbers; a naive datetime is sent as it is, and an aware one in UTC;
|
|
104
|
+
pandas' `Timedelta` and numpy's `timedelta64` are durations (to the
|
|
105
|
+
microsecond), and a time of day must have no time zone. A
|
|
106
|
+
new column's type is worked out from its values (whole numbers make
|
|
107
|
+
`int`, any decimal makes `float`), unless named in `types`.
|
|
108
|
+
|
|
109
|
+
## Reference
|
|
110
|
+
|
|
111
|
+
`llusen.connect(port=None, token=None, info_file=None, timeout=360,
|
|
112
|
+
project=None)` gives a `Project`: with `project`, the llusen that has
|
|
113
|
+
that project file open. Use it in a `with` block, or call `close()`. One
|
|
114
|
+
connection is for one thread at a time.
|
|
115
|
+
|
|
116
|
+
**Project**
|
|
117
|
+
- `describe()`: every table as llusen describes it (columns, types,
|
|
118
|
+
formulas, formats, filters, sort, summary values, groupings, charts,
|
|
119
|
+
notes).
|
|
120
|
+
- `table_names()`, `table(name)`, `chart(name)`.
|
|
121
|
+
- `new_table(name, data, types=None)`: `data` is a DataFrame, or a dict
|
|
122
|
+
of column names to lists of values.
|
|
123
|
+
- `new_group_table(name, grouping)`, e.g.
|
|
124
|
+
`'table("orders").group(r => ({ Item: r.item }))'`.
|
|
125
|
+
- `new_chart(name, table, x, ys, kind=None)`: `ys` is a column or a list
|
|
126
|
+
of them; `kind` is `"bar"`, `"line"` or `"doughnut"` (a doughnut
|
|
127
|
+
takes one column of values).
|
|
128
|
+
- `export(path, format=None, tables=None)`: every table (or those
|
|
129
|
+
named) to an Excel (`.xlsx`) or OpenDocument (`.ods`) workbook, a sheet
|
|
130
|
+
each, with summary values at the top and column formats; charts are
|
|
131
|
+
left out. Gives `{"path", "rows", "sheets"}` once written.
|
|
132
|
+
- `call(method, **params)`: any API method, as llusen documents it.
|
|
133
|
+
|
|
134
|
+
**Table**
|
|
135
|
+
- `read(columns=None, start=0, count=None)`: `{name: [values]}`, read in
|
|
136
|
+
pieces of 100,000 rows. `to_pandas(columns=None)`.
|
|
137
|
+
- `columns`, `len(table)`, `summary()` (values by name), `describe()`.
|
|
138
|
+
- `add_column(name, formula=None, values=None, type=None)`,
|
|
139
|
+
`set_formula`, `remove_formula`, `rename_column`,
|
|
140
|
+
`delete_columns(*names)`, `set_format(column, format=None)`.
|
|
141
|
+
- `append_rows(data)` gives the first new row; `set_cells(column,
|
|
142
|
+
values, start=0)`, all or none.
|
|
143
|
+
- `change_case(column, case)` puts every cell of a text column in
|
|
144
|
+
`"upper"`, `"lower"`, `"capitalize"` (`Hello world`) or `"title"`
|
|
145
|
+
(`Hello World`) case, and gives how many changed. The formula
|
|
146
|
+
functions of the same names do it in a new column instead.
|
|
147
|
+
- Live rows: `with table.locked(): table.insert_rows(data)`, or
|
|
148
|
+
`lock()`, `insert_rows(data)` and `unlock()`. While a script has a
|
|
149
|
+
table locked, rows go in at its end, many times a second if need be,
|
|
150
|
+
with no undo steps; llusen lifts any sort and filters, and no one else
|
|
151
|
+
can change the table (the user watches it fill, scrolled to the end).
|
|
152
|
+
`insert_rows` is answered at once, with the row its first will be at,
|
|
153
|
+
and its rows go in as soon as the table, and the tables reading it,
|
|
154
|
+
have worked out the last ones. `unlock` waits until all are in. The
|
|
155
|
+
lock goes if the script's connection closes. `is_locked` says whether
|
|
156
|
+
a script has it.
|
|
157
|
+
- `lock(clear=False, columns=None)`: `columns` are the rows' columns and
|
|
158
|
+
types, as `{"price": "float", "side": "text"}`. With `clear=True`, the
|
|
159
|
+
table is emptied for a new run and its plain columns made exactly
|
|
160
|
+
those, in that order (formula columns stay, after them), with no undo
|
|
161
|
+
step; it gives what changed. Without, they are checked against the
|
|
162
|
+
table's, and a difference refuses the lock. `locked()` takes the same.
|
|
163
|
+
- `set_filter(column, formula=None)` gives the rows shown; `value` is the
|
|
164
|
+
column's cell, as in `"value > 5"`. `clear_filters()`.
|
|
165
|
+
- `add_summary(formula, name=None, format=None)` gives its name;
|
|
166
|
+
`set_summary_formula`, `set_summary_format`, `delete_summary`.
|
|
167
|
+
- `set_grouping(grouping)` for a group table.
|
|
168
|
+
- `export(path, format=None)`: as CSV, or as a workbook of this table if
|
|
169
|
+
the path ends in `.xlsx` or `.ods` (or `format` says so).
|
|
170
|
+
- `rename(name)`, `delete()`.
|
|
171
|
+
|
|
172
|
+
**Chart**
|
|
173
|
+
- `notes()`, `add_note(text, anchors=())` gives its id. Each anchor is a
|
|
174
|
+
`(series, x)` pair, where `x` is a value of the column along the
|
|
175
|
+
bottom: a date, a time, a duration, a number, or text as llusen shows
|
|
176
|
+
it. A series of
|
|
177
|
+
`""` puts the note on every series at that place. A note's anchors
|
|
178
|
+
all share one series, or are all `""`.
|
|
179
|
+
- `set_settings(kind=None, **settings)`: the kind (`"bar"`, `"line"`,
|
|
180
|
+
`"doughnut"`) and the settings menu, as `smooth`, `line_fill`,
|
|
181
|
+
`line_from_zero`, `horizontal`, `bar_from_zero` and `pie` (each
|
|
182
|
+
`True` or `False`); what is left out stays. Gives whether it changed.
|
|
183
|
+
- `hidden_series()`, and `set_hidden_series(series)` to hide those
|
|
184
|
+
series from the key, as a click on it does, showing the rest. At
|
|
185
|
+
least one series stays shown.
|
|
186
|
+
- `rename(name)`, `delete()`.
|
|
187
|
+
|
|
188
|
+
## The protocol
|
|
189
|
+
|
|
190
|
+
Each message is one line of JSON-RPC 2.0 on a TCP connection to
|
|
191
|
+
`127.0.0.1`, with `authenticate {token}` first. See the module notes in
|
|
192
|
+
`llusen-core/src/api.rs` for every method and its params, if you want to
|
|
193
|
+
call llusen from another language.
|
|
194
|
+
|
|
195
|
+
## Tests
|
|
196
|
+
|
|
197
|
+
`python -m unittest discover -s tests` runs the tests against a stand-in
|
|
198
|
+
for llusen. `examples/orders.py` is a tour against the real thing.
|
llusen-0.1.0/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# llusen for Python
|
|
2
|
+
|
|
3
|
+
Read and change the project open in llusen from Python: tables, formulas,
|
|
4
|
+
filters, summary values, group tables, charts and notes; and export them
|
|
5
|
+
as CSV, Excel or OpenDocument files.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
import llusen
|
|
9
|
+
|
|
10
|
+
with llusen.connect() as project:
|
|
11
|
+
orders = project.table("orders")
|
|
12
|
+
orders.add_column("total", "price * qty")
|
|
13
|
+
orders.set_format("total", 'currency("£", 2)')
|
|
14
|
+
print(orders.summary())
|
|
15
|
+
frame = orders.to_pandas() # with pandas installed
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Setting up
|
|
19
|
+
|
|
20
|
+
- **Python 3.14** or later. The package uses only the standard library;
|
|
21
|
+
pandas is optional, for `to_pandas` and for giving DataFrames.
|
|
22
|
+
- **Install** from PyPI: `pip install llusen`, or `pip install
|
|
23
|
+
"llusen[pandas]"` with pandas. From this folder: `pip install .`.
|
|
24
|
+
- **In llusen**, choose **Tools > API Enabled**. llusen then listens on
|
|
25
|
+
this machine only, and writes its port and a token to `api.json` in its
|
|
26
|
+
state folder:
|
|
27
|
+
- Linux: `~/.local/state/llusen` (or `$XDG_STATE_HOME/llusen`);
|
|
28
|
+
- macOS: `~/Library/Application Support/llusen`;
|
|
29
|
+
- Windows: `%LOCALAPPDATA%\llusen`.
|
|
30
|
+
|
|
31
|
+
`llusen.connect()` reads that file and gives the token. API Enabled is
|
|
32
|
+
remembered, and the token is new each time it is turned on.
|
|
33
|
+
|
|
34
|
+
- **Which llusen:** each project opens in a llusen of its own, with its
|
|
35
|
+
own API, turned on in its own window. `connect()` reaches the llusen
|
|
36
|
+
used last (the one whose window last had the keyboard), as `api.json`
|
|
37
|
+
names it. To reach the llusen that has a given project open, name the
|
|
38
|
+
project's file:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
with llusen.connect(project="~/reports/sales.llu") as project:
|
|
42
|
+
...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If no llusen has it open, or the one that has has its API off,
|
|
46
|
+
`NotRunning` says which. `llusen.find_project(path)` gives that
|
|
47
|
+
llusen's port and token without connecting. (Each llusen lists what
|
|
48
|
+
it has open in `instances` in the state folder.)
|
|
49
|
+
|
|
50
|
+
## How it works
|
|
51
|
+
|
|
52
|
+
- Changes go into the project as if made in llusen. Each is one step
|
|
53
|
+
that Ctrl+Z takes back in that table, and the status bar says what the
|
|
54
|
+
script did. A table a script makes opens in a tab.
|
|
55
|
+
- Rows are counted in the order they were loaded or added, from 0,
|
|
56
|
+
whatever the user's sort and filters show. Reading gives every row.
|
|
57
|
+
- Reading waits until formulas are worked out, so values are never half
|
|
58
|
+
done. llusen gives up on a call after five minutes of waiting.
|
|
59
|
+
- A call llusen refuses raises `LlusenError`, with llusen's own message.
|
|
60
|
+
For a formula, `error.at` is the (start, end) of the problem in it.
|
|
61
|
+
- If llusen isn't running, or API Enabled is off, `connect` raises
|
|
62
|
+
`NotRunning` (a `ConnectionError`).
|
|
63
|
+
|
|
64
|
+
## Exporting
|
|
65
|
+
|
|
66
|
+
llusen writes the file, so the path is on the machine llusen runs on. A
|
|
67
|
+
relative path, or one from `~`, is made whole from the script's folder
|
|
68
|
+
first. A file already there is replaced, through a temporary file, so a
|
|
69
|
+
failed write leaves it as it was. Rows go in the order llusen shows them
|
|
70
|
+
(sorted, every row, whatever the filters). llusen writes on a thread of
|
|
71
|
+
its own and answers once the file is complete; the status bar says so.
|
|
72
|
+
The path's extension decides the kind (`.csv`, `.xlsx`, `.ods`), unless
|
|
73
|
+
`format` says; a path with no extension gets the format's.
|
|
74
|
+
|
|
75
|
+
## Values
|
|
76
|
+
|
|
77
|
+
| llusen | Python | pandas |
|
|
78
|
+
|----------|-------------------------------|---------------------------|
|
|
79
|
+
| int | `int` | `int64` (`Int64` if any are missing) |
|
|
80
|
+
| float | `float` | `float64` |
|
|
81
|
+
| bool | `bool` | `bool` (`boolean` if any are missing) |
|
|
82
|
+
| text | `str` | `object` |
|
|
83
|
+
| date | `datetime.date` | `datetime64` |
|
|
84
|
+
| datetime | `datetime.datetime` (UTC, aware, if the column is UTC) | `datetime64` |
|
|
85
|
+
| time | `datetime.time` | `object` (`datetime.time`) |
|
|
86
|
+
| duration | `datetime.timedelta` | `timedelta64` |
|
|
87
|
+
| null | `None` | missing |
|
|
88
|
+
| a fault such as `#DIV0` | `llusen.Fault("#DIV0")` | missing |
|
|
89
|
+
|
|
90
|
+
When giving values, NaN, `NaT` and `pd.NA` are null; numpy's numbers are
|
|
91
|
+
numbers; a naive datetime is sent as it is, and an aware one in UTC;
|
|
92
|
+
pandas' `Timedelta` and numpy's `timedelta64` are durations (to the
|
|
93
|
+
microsecond), and a time of day must have no time zone. A
|
|
94
|
+
new column's type is worked out from its values (whole numbers make
|
|
95
|
+
`int`, any decimal makes `float`), unless named in `types`.
|
|
96
|
+
|
|
97
|
+
## Reference
|
|
98
|
+
|
|
99
|
+
`llusen.connect(port=None, token=None, info_file=None, timeout=360,
|
|
100
|
+
project=None)` gives a `Project`: with `project`, the llusen that has
|
|
101
|
+
that project file open. Use it in a `with` block, or call `close()`. One
|
|
102
|
+
connection is for one thread at a time.
|
|
103
|
+
|
|
104
|
+
**Project**
|
|
105
|
+
- `describe()`: every table as llusen describes it (columns, types,
|
|
106
|
+
formulas, formats, filters, sort, summary values, groupings, charts,
|
|
107
|
+
notes).
|
|
108
|
+
- `table_names()`, `table(name)`, `chart(name)`.
|
|
109
|
+
- `new_table(name, data, types=None)`: `data` is a DataFrame, or a dict
|
|
110
|
+
of column names to lists of values.
|
|
111
|
+
- `new_group_table(name, grouping)`, e.g.
|
|
112
|
+
`'table("orders").group(r => ({ Item: r.item }))'`.
|
|
113
|
+
- `new_chart(name, table, x, ys, kind=None)`: `ys` is a column or a list
|
|
114
|
+
of them; `kind` is `"bar"`, `"line"` or `"doughnut"` (a doughnut
|
|
115
|
+
takes one column of values).
|
|
116
|
+
- `export(path, format=None, tables=None)`: every table (or those
|
|
117
|
+
named) to an Excel (`.xlsx`) or OpenDocument (`.ods`) workbook, a sheet
|
|
118
|
+
each, with summary values at the top and column formats; charts are
|
|
119
|
+
left out. Gives `{"path", "rows", "sheets"}` once written.
|
|
120
|
+
- `call(method, **params)`: any API method, as llusen documents it.
|
|
121
|
+
|
|
122
|
+
**Table**
|
|
123
|
+
- `read(columns=None, start=0, count=None)`: `{name: [values]}`, read in
|
|
124
|
+
pieces of 100,000 rows. `to_pandas(columns=None)`.
|
|
125
|
+
- `columns`, `len(table)`, `summary()` (values by name), `describe()`.
|
|
126
|
+
- `add_column(name, formula=None, values=None, type=None)`,
|
|
127
|
+
`set_formula`, `remove_formula`, `rename_column`,
|
|
128
|
+
`delete_columns(*names)`, `set_format(column, format=None)`.
|
|
129
|
+
- `append_rows(data)` gives the first new row; `set_cells(column,
|
|
130
|
+
values, start=0)`, all or none.
|
|
131
|
+
- `change_case(column, case)` puts every cell of a text column in
|
|
132
|
+
`"upper"`, `"lower"`, `"capitalize"` (`Hello world`) or `"title"`
|
|
133
|
+
(`Hello World`) case, and gives how many changed. The formula
|
|
134
|
+
functions of the same names do it in a new column instead.
|
|
135
|
+
- Live rows: `with table.locked(): table.insert_rows(data)`, or
|
|
136
|
+
`lock()`, `insert_rows(data)` and `unlock()`. While a script has a
|
|
137
|
+
table locked, rows go in at its end, many times a second if need be,
|
|
138
|
+
with no undo steps; llusen lifts any sort and filters, and no one else
|
|
139
|
+
can change the table (the user watches it fill, scrolled to the end).
|
|
140
|
+
`insert_rows` is answered at once, with the row its first will be at,
|
|
141
|
+
and its rows go in as soon as the table, and the tables reading it,
|
|
142
|
+
have worked out the last ones. `unlock` waits until all are in. The
|
|
143
|
+
lock goes if the script's connection closes. `is_locked` says whether
|
|
144
|
+
a script has it.
|
|
145
|
+
- `lock(clear=False, columns=None)`: `columns` are the rows' columns and
|
|
146
|
+
types, as `{"price": "float", "side": "text"}`. With `clear=True`, the
|
|
147
|
+
table is emptied for a new run and its plain columns made exactly
|
|
148
|
+
those, in that order (formula columns stay, after them), with no undo
|
|
149
|
+
step; it gives what changed. Without, they are checked against the
|
|
150
|
+
table's, and a difference refuses the lock. `locked()` takes the same.
|
|
151
|
+
- `set_filter(column, formula=None)` gives the rows shown; `value` is the
|
|
152
|
+
column's cell, as in `"value > 5"`. `clear_filters()`.
|
|
153
|
+
- `add_summary(formula, name=None, format=None)` gives its name;
|
|
154
|
+
`set_summary_formula`, `set_summary_format`, `delete_summary`.
|
|
155
|
+
- `set_grouping(grouping)` for a group table.
|
|
156
|
+
- `export(path, format=None)`: as CSV, or as a workbook of this table if
|
|
157
|
+
the path ends in `.xlsx` or `.ods` (or `format` says so).
|
|
158
|
+
- `rename(name)`, `delete()`.
|
|
159
|
+
|
|
160
|
+
**Chart**
|
|
161
|
+
- `notes()`, `add_note(text, anchors=())` gives its id. Each anchor is a
|
|
162
|
+
`(series, x)` pair, where `x` is a value of the column along the
|
|
163
|
+
bottom: a date, a time, a duration, a number, or text as llusen shows
|
|
164
|
+
it. A series of
|
|
165
|
+
`""` puts the note on every series at that place. A note's anchors
|
|
166
|
+
all share one series, or are all `""`.
|
|
167
|
+
- `set_settings(kind=None, **settings)`: the kind (`"bar"`, `"line"`,
|
|
168
|
+
`"doughnut"`) and the settings menu, as `smooth`, `line_fill`,
|
|
169
|
+
`line_from_zero`, `horizontal`, `bar_from_zero` and `pie` (each
|
|
170
|
+
`True` or `False`); what is left out stays. Gives whether it changed.
|
|
171
|
+
- `hidden_series()`, and `set_hidden_series(series)` to hide those
|
|
172
|
+
series from the key, as a click on it does, showing the rest. At
|
|
173
|
+
least one series stays shown.
|
|
174
|
+
- `rename(name)`, `delete()`.
|
|
175
|
+
|
|
176
|
+
## The protocol
|
|
177
|
+
|
|
178
|
+
Each message is one line of JSON-RPC 2.0 on a TCP connection to
|
|
179
|
+
`127.0.0.1`, with `authenticate {token}` first. See the module notes in
|
|
180
|
+
`llusen-core/src/api.rs` for every method and its params, if you want to
|
|
181
|
+
call llusen from another language.
|
|
182
|
+
|
|
183
|
+
## Tests
|
|
184
|
+
|
|
185
|
+
`python -m unittest discover -s tests` runs the tests against a stand-in
|
|
186
|
+
for llusen. `examples/orders.py` is a tour against the real thing.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Read and change the project open in llusen from Python.
|
|
2
|
+
|
|
3
|
+
import llusen
|
|
4
|
+
|
|
5
|
+
with llusen.connect() as project:
|
|
6
|
+
orders = project.table("orders")
|
|
7
|
+
frame = orders.to_pandas() # needs pandas
|
|
8
|
+
orders.add_column("total", "price * qty")
|
|
9
|
+
|
|
10
|
+
llusen must be running, with Tools > API Enabled on. The connection is
|
|
11
|
+
to this machine only, with the token llusen writes to its api.json: to
|
|
12
|
+
the llusen used last, or with `connect(project=...)` to the one that
|
|
13
|
+
has that project open.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from ._client import (
|
|
17
|
+
Chart,
|
|
18
|
+
LlusenError,
|
|
19
|
+
NotRunning,
|
|
20
|
+
Project,
|
|
21
|
+
Table,
|
|
22
|
+
connect,
|
|
23
|
+
find_project,
|
|
24
|
+
state_dir,
|
|
25
|
+
)
|
|
26
|
+
from ._values import TYPES, Fault
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"Chart",
|
|
30
|
+
"Fault",
|
|
31
|
+
"LlusenError",
|
|
32
|
+
"NotRunning",
|
|
33
|
+
"Project",
|
|
34
|
+
"TYPES",
|
|
35
|
+
"Table",
|
|
36
|
+
"connect",
|
|
37
|
+
"find_project",
|
|
38
|
+
"state_dir",
|
|
39
|
+
]
|
|
40
|
+
__version__ = "0.1.0"
|