Skip to content

Commit

Permalink
✨ plugin separation
Browse files Browse the repository at this point in the history
  • Loading branch information
juftin committed Jan 25, 2024
1 parent dcaff05 commit cfd44a0
Show file tree
Hide file tree
Showing 31 changed files with 1,038 additions and 953 deletions.
202 changes: 197 additions & 5 deletions docs/plugins/index.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,202 @@
# Plugins

lunchable supports plugins with other, external, services. See below for what's been built already.
If you can't find what you're looking for, consider building it yourself and opening a pull-request.
To install all the known plugins, and their dependencies, install
lunchable with the `plugins` extra:

### [PushLunch](pushlunch.md): Push Notifications via Pushover
```shell
pipx install "lunchable[plugins]"
```

### [SplitLunch](splitlunch.md): Splitwise Integration
lunchable supports CLI plugins with other external packages. See below for what's been built already.
If you can't find what you're looking for, consider building it yourself and opening a pull-request
to add it to the list below:

### [PrimeLunch](primelunch.md): Amazon Transaction Updater
- [PushLunch](pushlunch.md): Push Notifications via Pushover
- [SplitLunch](splitlunch.md): Splitwise Integration
- [PrimeLunch](primelunch.md): Amazon Transaction Updater

## LunchableApp

Lunchable provides a [LunchableApp](#lunchable.plugins.LunchableApp)
class that can be used to easily build plugins, apps,
and more. Notice a few of the main attributes / methods of the `LunchableApp` class:

attribute / method | description | type
------------------------------------------------------------------------------|--------------------------------------------------------------------------------|-------------------------------------------------------
**`lunch`** | The `LunchMoney` client | [LunchMoney](../interacting.md#lunchmoney)
**`data`** ¹ | The `LunchableData` object | [LunchableData](#lunchable.plugins.app.LunchableData)
[refresh_data](#lunchable.plugins.LunchableApp.refresh_data) | Refresh all data (besides Transactions) | `method`
[refresh_transactions](#lunchable.plugins.LunchableApp.refresh_transactions) | Refresh transactions, takes same parameters as `LunchMoney.get_transactions()` | `method`
[refresh](#lunchable.plugins.LunchableApp.refresh) ² | Refresh the data for one particular model, takes **kwargs | `method`
[clear_transactions](#lunchable.plugins.LunchableApp.clear_transactions) | Clear all transactions from the internal data | `method`

> ¹ This attribute contains all of the data that is loaded from LunchMoney. It has attributes
> for `assets`, `categories`, `plaid_accounts`, `tags`, `transactions`, `crypto` and `user`.
> These attributes (except for `user`) are `dict[int, LunchableModel]` objects, where the key is
> the ID of the object and the value is the object itself.
> ² This method refreshes all of the data for one particular model. For example,
> `refresh(AssetsObject)` will refresh the assets on the underling `data.assets`
> attribute and return a `dict[int, AssetsObject]` object.
### An Example App

```python
from __future__ import annotations

from typing import Any

from lunchable.models import AssetsObject, TransactionUpdateObject
from lunchable.plugins import LunchableApp


class MyCustomApp(LunchableApp):
"""
My Custom App
"""

def do_something_with_assets(self) -> None:
"""
Do something with the assets
"""
if not self.data.assets:
# If the data hasn't been loaded yet, load it
# The following method loads all of the data besides Transactions
# (Assets, Categories, Plaid Accounts, Tags, Crypto, User)
self.refresh_data()
for asset_id, asset in self.data.assets.items():
# Do something with the asset
print(asset_id, asset)

def do_something_with_transactions(self) -> None:
"""
Do something with the transactions
"""
if not self.data.transactions:
# If the transactions haven't been loaded yet, load them
self.refresh_transactions(start_date="2021-01-01", end_date="2021-01-31")
# Refresh the latest assets
latest_assets: dict[int, AssetsObject] = self.refresh(model=AssetsObject)
for transaction_id, transaction in self.data.transactions.items():
if transaction.asset_id:
asset = latest_assets[transaction.asset_id]
print(transaction_id, transaction, asset)

def update_transaction(self, transaction_id: int, payee: str) -> dict[str, Any]:
"""
You can do anything you want with the `self
"""
update_transaction = TransactionUpdateObject(payee=payee)
response = self.lunch.update_transaction(transaction_id=transaction_id,
transaction=update_transaction)
return response


if __name__ == "__main__":
app = MyCustomApp(access_token="xxxxxxxx")
app.do_something_with_assets()
app.do_something_with_transactions()
app.update_transaction(transaction_id=12345, payee="New Payee")
```

#### Choose a subset of data to load

If you don't want to load all of the data, you can specify which data you want to load by
specifying the `lunchable_models` attribute of the `LunchableApp` class. The following example
will only sync the `assets` and `plaid_accounts` data when the `refresh_data()` method is called:

```python
from __future__ import annotations

from typing import ClassVar

from lunchable.models import AssetsObject, PlaidAccountObject, LunchableModel

from lunchable.plugins import LunchableApp


class CustomApp(LunchableApp):
"""
Custom Lunchable App
This app syncs Plaid Accounts and Assets when its `refresh_data` method
is called.
"""

lunchable_models: ClassVar[list[type[LunchableModel]]] = [
PlaidAccountObject,
AssetsObject,
]

def do_something_with_assets(self) -> None:
"""
Do something with the assets
"""
if not self.data.plaid_accounts:
self.refresh_data()
for plaid_account_id, plaid_account in self.data.plaid_accounts.items():
print(plaid_account_id, plaid_account)
```

## Building a Plugin

Plugins are built separate Python packages and are detected by lunchable via
the `lunchable.cli` entrypoint, these are
[click](https://github.com/pallets/click/) command line applications.
To add your own plugin to lunchable you'll need to add a new entrypoint to
your package. The below example shows how to do this with hatch, a modern,
standards-based Python package manager:

```python
import click


@click.group
def plugin_name():
"""
Plugin description
"""
pass


@plugin_name.command
def command():
"""
Plugin description
"""
pass
```

```toml
[project.entry-points."lunchable.cli"]
your-package = "your_package.cli:plugin_name"
```

The above example will add a new `command` / `group` to the lunchable `plugins` CLI. When
your package is installed into the same environment as lunchable, your plugin will be
accessible via the `lunchable plugins` command:

```shell
lunchable plugins plugin-name command
```

## API Documentation

::: lunchable.plugins.LunchableApp
handler: python
options:
show_bases: false
allow_inspection: true
inherited_members: true
group_by_category: true
heading_level: 3
show_source: false

::: lunchable.plugins.app.LunchableData
handler: python
options:
show_bases: false
allow_inspection: true
group_by_category: true
heading_level: 3
show_source: false
2 changes: 1 addition & 1 deletion docs/plugins/primelunch.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ lunchable plugins primelunch run \
## Command Line Documentation

::: mkdocs-click
:module: lunchable._cli
:module: lunchable.plugins.primelunch.cli
:command: run_primelunch
:prog_name: lunchable plugins primelunch run
:style: table
Expand Down
2 changes: 1 addition & 1 deletion docs/plugins/pushlunch.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ docker run --rm \
from lunchable.plugins.pushlunch import PushLunch
```

::: lunchable.plugins.pushlunch.PushLunch
::: lunchable.plugins.pushlunch.pushover.PushLunch
handler: python
options:
show_bases: false
Expand Down
4 changes: 2 additions & 2 deletions docs/plugins/splitlunch.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@ docker run \
## Run via Python

```python
from lunchable.plugins.splitlunch import SplitLunch
from lunchable.plugins.splitlunch.lunchmoney_splitwise import SplitLunch
```

::: lunchable.plugins.splitlunch.SplitLunch
::: lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch
handler: python
options:
show_bases: false
Expand Down
Loading

0 comments on commit cfd44a0

Please sign in to comment.