Metadata-Version: 2.4
Name: arbolab
Version: 0.1.0
Summary: Arbolab - unified Lab (DuckDB/logging/config) and plugin SPI.
Author-email: Kyell Jensen <mail@kyelljensen.de>
License: MIT License
        
        Copyright (c) 2025 ArboLab
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS 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.
        
Project-URL: Homepage, https://github.com/arbolab/arbolab
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic>=2.6
Requires-Dist: pydantic-settings>=2.2
Requires-Dist: SQLAlchemy>=2.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: numpy>=1.26
Requires-Dist: pandas>=2.2
Requires-Dist: pyarrow>=16
Requires-Dist: duckdb>=1.0
Requires-Dist: duckdb-engine>=0.17
Requires-Dist: matplotlib>=3.8
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-cov>=4.1; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: black>=24.0; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: flake8>=7.0; extra == "dev"
Provides-Extra: plot
Requires-Dist: seaborn>=0.13; extra == "plot"
Requires-Dist: plotly>=5.20; extra == "plot"
Provides-Extra: ml
Requires-Dist: scipy>=1.13; extra == "ml"
Requires-Dist: scikit-learn>=1.4; extra == "ml"
Provides-Extra: latex
Requires-Dist: jinja2>=3.1; extra == "latex"
Requires-Dist: pylatexenc>=2.10; extra == "latex"
Provides-Extra: linescale
Requires-Dist: arbolab-linescale>=0.1; extra == "linescale"
Requires-Dist: pyserial>=3.5; extra == "linescale"
Provides-Extra: treeqinetic
Requires-Dist: arbolab-treeqinetic>=0.1; extra == "treeqinetic"
Requires-Dist: pyserial>=3.5; extra == "treeqinetic"
Provides-Extra: treecablecalc
Requires-Dist: arbolab-treecablecalc>=0.1; extra == "treecablecalc"
Requires-Dist: pyserial>=3.5; extra == "treecablecalc"
Provides-Extra: treemotion
Requires-Dist: arbolab-treemotion>=0.1; extra == "treemotion"
Requires-Dist: pyserial>=3.5; extra == "treemotion"
Provides-Extra: wind
Requires-Dist: arbolab-wind>=0.1; extra == "wind"
Requires-Dist: requests; extra == "wind"
Requires-Dist: beautifulsoup4; extra == "wind"
Requires-Dist: pyserial>=3.5; extra == "wind"
Provides-Extra: all
Requires-Dist: seaborn>=0.13; extra == "all"
Requires-Dist: plotly>=5.20; extra == "all"
Requires-Dist: scipy>=1.13; extra == "all"
Requires-Dist: scikit-learn>=1.4; extra == "all"
Requires-Dist: jinja2>=3.1; extra == "all"
Requires-Dist: pylatexenc>=2.10; extra == "all"
Requires-Dist: arbolab-linescale>=0.1; extra == "all"
Requires-Dist: arbolab-treeqinetic>=0.1; extra == "all"
Requires-Dist: arbolab-treecablecalc>=0.1; extra == "all"
Requires-Dist: arbolab-treemotion>=0.1; extra == "all"
Requires-Dist: arbolab-wind>=0.1; extra == "all"
Requires-Dist: requests; extra == "all"
Requires-Dist: beautifulsoup4; extra == "all"
Requires-Dist: pyserial>=3.5; extra == "all"
Dynamic: license-file

# arbolab

Core utilities for ArboLab providing configuration, logging, database management, plotting helpers and a small metadata layer.

## Installation
```bash
pip install -e .
```

### Extras
Optional dependencies can be installed via extras:

```bash
pip install -e ".[plot]"       # seaborn, plotly
pip install -e ".[ml]"         # scipy, scikit-learn
pip install -e ".[latex]"      # jinja2, pylatexenc
pip install -e ".[linescale]"  # arbolab-linescale, pyserial
pip install -e ".[treeqinetic]" # arbolab-treeqinetic, pyserial
pip install -e ".[treecablecalc]" # arbolab-treecablecalc, pyserial
pip install -e ".[treemotion]" # arbolab-treemotion, pyserial
pip install -e ".[wind]"       # arbolab-wind, requests, beautifulsoup4, pandas, numpy
pip install -e ".[all]"        # install every optional dependency
```

The wind extras can also be installed from PyPI:

```bash
pip install arbolab[wind]
```

## Usage
The package exposes a small user-facing API centered around the :class:`Lab` container. Import it from the top-level package and either create a new laboratory configuration with ``setup`` or load an existing one with ``load``:

```python
from arbolab import Lab

# create a new lab and persist its config/database
lab = Lab.setup(load_plugins=True)

# later, reload the same lab (defaults to a file named after the working directory)
lab = Lab.load("lab.duckdb")

# attached sensor adapters are available via lab.sensors
print(lab.sensors.keys())
```

Adapter plugins are discovered via the ``arbolab.adapters`` entry point group and loaded automatically when ``load_plugins`` is ``True``.

### Custom sensor packages
Third-party sensors integrate by implementing the :class:`~arbolab.adapter.Adapter` protocol and exposing an entry point:

```toml
[project.entry-points."arbolab.adapters"]
"mysensor" = "my_package:MyAdapter"
```

Installing such a package makes it discoverable by :meth:`Lab.setup` when ``load_plugins`` is ``True``.

Domain entities such as :class:`Project` or :class:`Series` live in ``arbolab.classes`` and operate on the active ``Lab`` instance. Measurements captured from sensors are represented by :class:`Measurement` objects. Each measurement stores additional metadata fields like ``unit`` (physical unit of the recorded values), ``sample_rate`` (in Hz) and ``sensor_type``. These fields provide defaults and are validated via Pydantic to ensure consistency. The database representation of a laboratory is called :class:`LabEntry` to avoid confusion with the user-facing container.

Entities deriving from :class:`BaseEntity` can store identifiers from external systems in a JSON mapping. Use :meth:`set_external_id` to register a value such as ``{"sensor_serial": "1234"}`` and :meth:`get_external_id` to retrieve it later.

## Configuration
Configuration values can be supplied via environment variables or a YAML file. The ``Config`` class and its nested sections are implemented using [Pydantic](https://docs.pydantic.dev/) models which provide validation and type coercion. The configuration layout looks as follows:

```yaml
# config.yaml
working_dir: /tmp/lab           # defaults to the current directory
db_url: duckdb:///lab.duckdb    # optional database URL (defaults to duckdb:///<working_dir_name>.duckdb)
logging:
  level: INFO                   # log level, use "NONE" to disable logging
  use_colors: true
  save_to_file: false
  tune_matplotlib: false
```

Load the configuration with:

```python
from arbolab import Config

cfg = Config.from_file("config.yaml")
```

The same fields can be provided via environment variables named ``ARBOLAB_WORKING_DIR``, ``ARBOLAB_DB_URL``, ``ARBOLAB_LOGGING__LEVEL``, ``ARBOLAB_LOGGING__USE_COLORS``, ``ARBOLAB_LOGGING__SAVE_TO_FILE`` and ``ARBOLAB_LOGGING__TUNE_MATPLOTLIB``.

### Database table prefix
To avoid name clashes between sensor packages, database tables can be prefixed via the ``ARBOLAB_DB__TABLE_PREFIX`` environment variable. For example, setting ``ARBOLAB_DB__TABLE_PREFIX=ls_`` will create tables like ``ls_measurements`` and ``ls_sensors``. All foreign keys and sequences use the same prefix.

### Updating configuration
Use ``Lab.update_config`` to adjust settings at runtime. It applies changes via Pydantic's ``model_copy`` and dispatches reconfiguration hooks based on the fields that changed so logging, database connections, plotting defaults and persistence refresh independently.

### Path parameters
Functions that accept file system paths support both strings and [`pathlib.Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path) instances. This applies to helpers such as :meth:`Config.from_file` and :meth:`Lab.load` as well as to :class:`Config` itself:

```python
from pathlib import Path
from arbolab import Config

Config(working_dir="/tmp/lab")
Config(working_dir=Path("/tmp/lab"))
```

## Development
```bash
python -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
pytest
```
