Skip to main content

llusen for Python

Read and change the project open in llusen from Python: tables, formulas, filters, summary values, group tables, charts and notes; and export them as CSV, Excel or OpenDocument files.

import llusen

with llusen.connect() as project:
    orders = project.table("orders")
    orders.add_column("total", "price * qty")
    orders.set_format("total", 'currency("£", 2)')
    print(orders.summary())
    frame = orders.to_pandas()          # with pandas installed

Setting up

  • Python 3.14 or later. The package uses only the standard library; pandas is optional, for to_pandas and for giving DataFrames.

  • Install from PyPI: pip install llusen, or pip install "llusen[pandas]" with pandas. From this folder: pip install ..

  • In llusen, choose Tools > API Enabled. llusen then listens on this machine only, and writes its port and a token to api.json in its state folder:

    • Linux: ~/.local/state/llusen (or $XDG_STATE_HOME/llusen);
    • macOS: ~/Library/Application Support/llusen;
    • Windows: %LOCALAPPDATA%\llusen.

    llusen.connect() reads that file and gives the token. API Enabled is remembered, and the token is new each time it is turned on.

  • Which llusen: each project opens in a llusen of its own, with its own API, turned on in its own window. connect() reaches the llusen used last (the one whose window last had the keyboard), as api.json names it. To reach the llusen that has a given project open, name the project's file:

    with llusen.connect(project="~/reports/sales.llu") as project:
        ...
    

    If no llusen has it open, or the one that has has its API off, NotRunning says which. llusen.find_project(path) gives that llusen's port and token without connecting. (Each llusen lists what it has open in instances in the state folder.)

How it works

  • Changes go into the project as if made in llusen. Each is one step that Ctrl+Z takes back in that table, and the status bar says what the script did. A table a script makes opens in a tab.
  • Rows are counted in the order they were loaded or added, from 0, whatever the user's sort and filters show. Reading gives every row.
  • Reading waits until formulas are worked out, so values are never half done. llusen gives up on a call after five minutes of waiting.
  • A call llusen refuses raises LlusenError, with llusen's own message. For a formula, error.at is the (start, end) of the problem in it.
  • If llusen isn't running, or API Enabled is off, connect raises NotRunning (a ConnectionError).

Exporting

llusen writes the file, so the path is on the machine llusen runs on. A relative path, or one from ~, is made whole from the script's folder first. A file already there is replaced, through a temporary file, so a failed write leaves it as it was. Rows go in the order llusen shows them (sorted, every row, whatever the filters). llusen writes on a thread of its own and answers once the file is complete; the status bar says so. The path's extension decides the kind (.csv, .xlsx, .ods), unless format says; a path with no extension gets the format's.

Values

llusen Python pandas
int int int64 (Int64 if any are missing)
float float float64
bool bool bool (boolean if any are missing)
text str object
date datetime.date datetime64
datetime datetime.datetime (UTC, aware, if the column is UTC) datetime64
time datetime.time object (datetime.time)
duration datetime.timedelta timedelta64
null None missing
a fault such as #DIV0 llusen.Fault("#DIV0") missing

When giving values, NaN, NaT and pd.NA are null; numpy's numbers are numbers; a naive datetime is sent as it is, and an aware one in UTC; pandas' Timedelta and numpy's timedelta64 are durations (to the microsecond), and a time of day must have no time zone. A new column's type is worked out from its values (whole numbers make int, any decimal makes float), unless named in types.

Reference

llusen.connect(port=None, token=None, info_file=None, timeout=360, project=None) gives a Project: with project, the llusen that has that project file open. Use it in a with block, or call close(). One connection is for one thread at a time.

Project

  • describe(): every table as llusen describes it (columns, types, formulas, formats, filters, sort, summary values, groupings, charts, notes).
  • table_names(), table(name), chart(name).
  • new_table(name, data, types=None): data is a DataFrame, or a dict of column names to lists of values.
  • new_group_table(name, grouping), e.g. 'table("orders").group(r => ({ Item: r.item }))'.
  • new_chart(name, table, x, ys, kind=None): ys is a column or a list of them; kind is "bar", "line" or "doughnut" (a doughnut takes one column of values).
  • export(path, format=None, tables=None): every table (or those named) to an Excel (.xlsx) or OpenDocument (.ods) workbook, a sheet each, with summary values at the top and column formats; charts are left out. Gives {"path", "rows", "sheets"} once written.
  • call(method, **params): any API method, as llusen documents it.

Table

  • read(columns=None, start=0, count=None): {name: [values]}, read in pieces of 100,000 rows. to_pandas(columns=None).
  • columns, len(table), summary() (values by name), describe().
  • add_column(name, formula=None, values=None, type=None), set_formula, remove_formula, rename_column, delete_columns(*names), set_format(column, format=None).
  • append_rows(data) gives the first new row; set_cells(column, values, start=0), all or none.
  • change_case(column, case) puts every cell of a text column in "upper", "lower", "capitalize" (Hello world) or "title" (Hello World) case, and gives how many changed. The formula functions of the same names do it in a new column instead.
  • Live rows: with table.locked(): table.insert_rows(data), or lock(), insert_rows(data) and unlock(). While a script has a table locked, rows go in at its end, many times a second if need be, with no undo steps; llusen lifts any sort and filters, and no one else can change the table (the user watches it fill, scrolled to the end). insert_rows is answered at once, with the row its first will be at, and its rows go in as soon as the table, and the tables reading it, have worked out the last ones. unlock waits until all are in. The lock goes if the script's connection closes. is_locked says whether a script has it.
  • lock(clear=False, columns=None): columns are the rows' columns and types, as {"price": "float", "side": "text"}. With clear=True, the table is emptied for a new run and its plain columns made exactly those, in that order (formula columns stay, after them), with no undo step; it gives what changed. Without, they are checked against the table's, and a difference refuses the lock. locked() takes the same.
  • set_filter(column, formula=None) gives the rows shown; value is the column's cell, as in "value > 5". clear_filters().
  • set_sort(column, direction="ascending") sorts the rows shown by a column, "ascending" or "descending"; clear_sort() puts them back as they were. Rows read are stored rows, whatever the sort.
  • add_summary(formula, name=None, format=None) gives its name; set_summary_formula, set_summary_format, delete_summary.
  • set_grouping(grouping) for a group table.
  • export(path, format=None): as CSV, or as a workbook of this table if the path ends in .xlsx or .ods (or format says so).
  • rename(name), delete().

Chart

  • notes(), add_note(text, anchors=()) gives its id. Each anchor is a (series, x) pair, where x is a value of the column along the bottom: a date, a time, a duration, a number, or text as llusen shows it. A series of "" puts the note on every series at that place. A note's anchors all share one series, or are all "".
  • set_settings(kind=None, **settings): the kind ("bar", "line", "doughnut") and the settings menu, as smooth, line_fill, line_from_zero, horizontal, bar_from_zero and pie (each True or False); what is left out stays. Gives whether it changed.
  • hidden_series(), and set_hidden_series(series) to hide those series from the key, as a click on it does, showing the rest. At least one series stays shown.
  • rename(name), delete().

The protocol

Each message is one line of JSON-RPC 2.0 on a TCP connection to 127.0.0.1, with authenticate {token} first. See the module notes in llusen-core/src/api.rs for every method and its params, if you want to call llusen from another language.

Tests

python -m unittest discover -s tests runs the tests against a stand-in for llusen. examples/orders.py is a tour against the real thing.

Metadata

Release files for llusen 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for llusen 0.1.2
File Size Uploaded Provenance Actions
llusen-0.1.2.tar.gz 25.2 kB Without provenance Details for llusen-0.1.2.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for llusen 0.1.2
File Interpreter ABI Platform Actions
llusen-0.1.2-py3-none-any.whl Python 3 none any Details for llusen-0.1.2-py3-none-any.whl

Total release size: 43.0 kB

Release files / llusen-0.1.2.tar.gz

Download URL llusen-0.1.2.tar.gz
Size 25.2 kB
Tags Source
SHA-256 checksum
How to use checksums
45f1e1de86d22f3b51d709648dcdd75088fd991eb857a2a505cc830e09647d70
BLAKE2b-256 checksum
How to use checksums
368ec2bcf4804164c1c67206c93f32aeb08780c87705f5de8e2dec5c56bf9a11
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.9

Release files / llusen-0.1.2-py3-none-any.whl

Download URL llusen-0.1.2-py3-none-any.whl
Size 17.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a6c694a75257b8ad65bfb688c7d774e52dad3fe301d1fee73fd09834a63c5435
BLAKE2b-256 checksum
How to use checksums
952787b5ca8fd83b0a739e50c3e819dc85624be267a26418abeb976dd1eb48c8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.9

Security for llusen 0.1.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page