Metadata-Version: 2.5
Name: datamuo
Version: 1.1.24
Summary: Data tensors, interactive (pivot) tables.
License: Copyright (c) 2025 Andrea Muolo
        
        This software is licensed under the terms of this agreement. By using this software, you agree to the following terms:
        
        1. Ownership: The software and all associated intellectual property rights are owned by Andrea Muolo. 
        You are granted a non-exclusive, non-transferable, revocable license to use the software.
        
        2. Restrictions: You may not:
           a. Reverse engineer, decompile, or disassemble the software.
           b. Modify, adapt, or create derivative works based on the software.
           c. Redistribute, sublicense, or transfer the software to any third party.
        
        3. Termination: This license will terminate automatically if you fail to comply with any of the terms. 
        Upon termination, you must cease all use of the software and destroy all copies.
        
        4. The software is provided "as is" without warranty of any kind, express or implied, 
        including but not limited to the warranties of mechantability, fitness for a particular purpose
        and noninfringment. In no event shall the owner or the author or the copyright holder be liable for any claim,
        damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection
        with the software or the use or other dealings in the software. 
        
        
License-File: LICENSE.txt
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: pythonnet<4,>=3.0.3
Description-Content-Type: text/markdown

# datamuo

Data tensors from plain records, interactive tables and pivot tables as HTML, and comments on data points.

## Install

Requires Python 3.12+ and the ASP.NET Core Runtime 10 
(`dotnet --list-runtimes` lists `Microsoft.AspNetCore.App 10.x`; the SDK is not needed). 
The package bundles the [Muoland.Reporting](https://www.nuget.org/packages/Muoland.Reporting) 
assemblies of its version.

```bash
pip install datamuo
```

## Usage

```python
from datamuo import Tensor

systems = Tensor("System", records)  # records: mappings; dimensions default to ("id",), values to the other properties
roadmap = Tensor("RoadmapItem", items, dimensions=["id", "status"], aggregations={"end_date": max})

# options: order, hidden, page_size, height, width, cell_width, table_style, theme, centered
table = systems.table(order=["name", "owner"], page_size=50, height="65vh")
pivot = roadmap.pivot(rows=["id"], columns=["status"], theme="minimalist")

fragments = table.render()        # Fragments(main, filter, comments, dialogs, view, slice) as HTML
fragments = table.render(action)  # after a user interaction, see below

systems.add_comment(record, "Owner missing", author="validator", priority=1)  # lower priorities are more critical
systems.comments(record)          # history, oldest first: [Comment(tag, author, text, priority, created, view)]
```

`None`, NaN and infinite values become `""`, and engine failures raise `ReportingError`. Tensors are thread-safe and shared by all users, while a report keeps its user's UI state (paging, filters, open dialogs), so web apps keep one per session, stored as they see fit:

```python
from datamuo import Assets, Fragments, Report, assets

reports: dict[tuple[str, str], Report] = {}

@app.post("/reports/{name}")
def render(name: str, session: str, action: str = "") -> Fragments:
    if (session, name) not in reports:
        reports[session, name] = tensors[name].table()
    return reports[session, name].render(action)

@app.get("/reports/assets")
def report_assets() -> Assets:
    return assets()
```

### Data entry, custom views and persistence

```python
from datamuo import Comment, DimensionMember, Record, Report, Tensor, UiTreeBuilder, View, deserialize

def badge(ui: UiTreeBuilder, status: str) -> None:  # draws each cell of a property; markup is inserted as is
    ui.open_element("b")
    ui.add_attribute("class", "badge")
    ui.add_markup_content(html.escape(status))
    ui.close_element()

table = systems.table(
    editable=["name", "owner"],                                             # all properties by default
    validate=lambda report, record: [] if record["name"] else ["Name required"],  # errors reject the edit
    on_submit=lambda report, record: save(record),                          # each valid edit
    on_row_click=lambda report, record: select(record),                     # replaces the record dialog
    custom_views={"status": badge},
)

systems = Tensor(
    "System", records,
    dimension_data={"status": [DimensionMember("live", "Live"), DimensionMember("eol", "End of life", parent="live")]},
    comments=load_comments(), on_comment=save_comment,  # list[Comment], (Comment) -> None
    views=load_views(), on_view=save_view,              # list[View], (View) -> None
)

deserialize(json_lines)  # JSON records as dicts sharing all keys, the missing ones None
```

Callbacks get the report and plain Python records. `on_comment` and `on_view` also receive the comments and views users add in reports, on another thread.

## Frontend

Load `assets().css` and run `assets().js` once per page, then inject the fragments' HTML. Each user interaction with it dispatches a bubbling `mlu-action` event: send its `detail.action` to `Report.render` and apply the returned HTML unless `detail.silent`. Process a report's actions one at a time, in order.

```js
container.addEventListener("mlu-action", ({ detail: { action, silent } }) => queue(action, silent));
```

## Development

Copy the `lib/net10.0/*.dll` of a `dotnet pack` output into `src/datamuo/bin/`, then:

```bash
pip install -e python pytest playwright
python -m playwright install chromium  # or set PLAYWRIGHT_CHANNEL=msedge to use an installed Edge
pytest python/tests                    # browser tests of actions.js skip without Playwright
```
