Skip to content

Commit bc5bc3e

Browse files
committed
📝 plugin docs
1 parent aef778b commit bc5bc3e

File tree

7 files changed

+215
-11
lines changed

7 files changed

+215
-11
lines changed

docs/plugins/index.md

Lines changed: 199 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,204 @@
11
# Plugins
22

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

6-
### [PushLunch](pushlunch.md): Push Notifications via Pushover
6+
```shell
7+
pipx install "lunchable[plugins]"
8+
```
79

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

10-
### [PrimeLunch](primelunch.md): Amazon Transaction Updater
14+
- [PushLunch](pushlunch.md): Push Notifications via Pushover
15+
- [SplitLunch](splitlunch.md): Splitwise Integration
16+
- [PrimeLunch](primelunch.md): Amazon Transaction Updater
17+
18+
## LunchableApp
19+
20+
Lunchable provides a [LunchableApp](#lunchable.plugins.LunchableApp)
21+
class that can be used to easily build plugins, apps,
22+
and more. Notice a few of the main attributes / methods of the `LunchableApp` class:
23+
24+
attribute / method | description | type
25+
------------------------------------------------------------------------------|--------------------------------------------------------------------------------|-------------------------------------------------------
26+
**`lunch`** | The `LunchMoney` client | [LunchMoney](../interacting.md#lunchmoney)
27+
**`data`** ¹ | The `LunchableData` object | [LunchableData](#lunchable.plugins.app.LunchableData)
28+
[refresh_data](#lunchable.plugins.LunchableApp.refresh_data) | Refresh all data (besides Transactions) | `method`
29+
[refresh_transactions](#lunchable.plugins.LunchableApp.refresh_transactions) | Refresh transactions, takes same parameters as `LunchMoney.get_transactions()` | `method`
30+
[refresh](#lunchable.plugins.LunchableApp.refresh) ² | Refresh the data for one particular model, takes **kwargs | `method`
31+
[clear_transactions](#lunchable.plugins.LunchableApp.clear_transactions) ³ | Clear all transactions from the internal data | `method`
32+
33+
> ¹ This attribute contains all of the data that is loaded from LunchMoney. It has attributes
34+
> for `assets`, `categories`, `plaid_accounts`, `tags`, `transactions`, `crypto` and `user`.
35+
> These attributes (except for `user`) are `dict[int, LunchableModel]` objects, where the key is
36+
> the ID of the object and the value is the object itself.
37+
38+
> ² This method refreshes all of the data for one particular model. For example,
39+
> `refresh(AssetsObject)` will refresh the assets on the underling `data.assets`
40+
> attribute and return a `dict[int, AssetsObject]` object.
41+
42+
> ³ This the same as running `app.data.transactions.clear()`
43+
44+
### An Example App
45+
46+
```python
47+
from __future__ import annotations
48+
49+
from typing import Any
50+
51+
from lunchable.models import AssetsObject, TransactionUpdateObject
52+
from lunchable.plugins import LunchableApp
53+
54+
55+
class MyCustomApp(LunchableApp):
56+
"""
57+
My Custom App
58+
"""
59+
60+
def do_something_with_assets(self) -> None:
61+
"""
62+
Do something with the assets
63+
"""
64+
if not self.data.assets:
65+
# If the data hasn't been loaded yet, load it
66+
# The following method loads all of the data besides Transactions
67+
# (Assets, Categories, Plaid Accounts, Tags, Crypto, User)
68+
self.refresh_data()
69+
for asset_id, asset in self.data.assets.items():
70+
# Do something with the asset
71+
print(asset_id, asset)
72+
73+
def do_something_with_transactions(self) -> None:
74+
"""
75+
Do something with the transactions
76+
"""
77+
if not self.data.transactions:
78+
# If the transactions haven't been loaded yet, load them
79+
self.refresh_transactions(start_date="2021-01-01", end_date="2021-01-31")
80+
# Refresh the latest assets
81+
latest_assets: dict[int, AssetsObject] = self.refresh(model=AssetsObject)
82+
for transaction_id, transaction in self.data.transactions.items():
83+
if transaction.asset_id:
84+
asset = latest_assets[transaction.asset_id]
85+
print(transaction_id, transaction, asset)
86+
87+
def update_transaction(self, transaction_id: int, payee: str) -> dict[str, Any]:
88+
"""
89+
You can do anything you want with the `self
90+
"""
91+
update_transaction = TransactionUpdateObject(payee=payee)
92+
response = self.lunch.update_transaction(transaction_id=transaction_id,
93+
transaction=update_transaction)
94+
return response
95+
96+
97+
if __name__ == "__main__":
98+
app = MyCustomApp(access_token="xxxxxxxx")
99+
app.do_something_with_assets()
100+
app.do_something_with_transactions()
101+
app.update_transaction(transaction_id=12345, payee="New Payee")
102+
```
103+
104+
#### Choose a subset of data to load
105+
106+
If you don't want to load all of the data, you can specify which data you want to load by
107+
specifying the `lunchable_models` attribute of the `LunchableApp` class. The following example
108+
will only sync the `assets` and `plaid_accounts` data when the `refresh_data()` method is called:
109+
110+
```python
111+
from __future__ import annotations
112+
113+
from typing import ClassVar
114+
115+
from lunchable.models import AssetsObject, PlaidAccountObject, LunchableModel
116+
117+
from lunchable.plugins import LunchableApp
118+
119+
120+
class CustomApp(LunchableApp):
121+
"""
122+
Custom Lunchable App
123+
124+
This app syncs Plaid Accounts and Assets when its `refresh_data` method
125+
is called.
126+
"""
127+
128+
lunchable_models: ClassVar[list[type[LunchableModel]]] = [
129+
PlaidAccountObject,
130+
AssetsObject,
131+
]
132+
133+
def do_something_with_assets(self) -> None:
134+
"""
135+
Do something with the assets
136+
"""
137+
if not self.data.plaid_accounts:
138+
self.refresh_data()
139+
for plaid_account_id, plaid_account in self.data.plaid_accounts.items():
140+
print(plaid_account_id, plaid_account)
141+
```
142+
143+
## Building a Plugin
144+
145+
Plugins are built separate Python packages and are detected by lunchable via
146+
the `lunchable.cli` entrypoint, these are
147+
[click](https://github.com/pallets/click/) command line applications.
148+
To add your own plugin to lunchable you'll need to add a new entrypoint to
149+
your package. The below example shows how to do this with hatch, a modern,
150+
standards-based Python package manager:
151+
152+
```python
153+
import click
154+
155+
156+
@click.group
157+
def plugin_name():
158+
"""
159+
Plugin description
160+
"""
161+
pass
162+
163+
164+
@plugin_name.command
165+
def command():
166+
"""
167+
Plugin description
168+
"""
169+
pass
170+
```
171+
172+
```toml
173+
[project.entry-points."lunchable.cli"]
174+
your-package = "your_package.cli:plugin_name"
175+
```
176+
177+
The above example will add a new `command` / `group` to the lunchable `plugins` CLI. When
178+
your package is installed into the same environment as lunchable, your plugin will be
179+
accessible via the `lunchable plugins` command:
180+
181+
```shell
182+
lunchable plugins plugin-name command
183+
```
184+
185+
## API Documentation
186+
187+
::: lunchable.plugins.LunchableApp
188+
handler: python
189+
options:
190+
show_bases: false
191+
allow_inspection: true
192+
inherited_members: true
193+
group_by_category: true
194+
heading_level: 3
195+
show_source: false
196+
197+
::: lunchable.plugins.app.LunchableData
198+
handler: python
199+
options:
200+
show_bases: false
201+
allow_inspection: true
202+
group_by_category: true
203+
heading_level: 3
204+
show_source: false

docs/plugins/primelunch.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ lunchable plugins primelunch run \
9696
## Command Line Documentation
9797

9898
::: mkdocs-click
99-
:module: lunchable._cli
99+
:module: lunchable.plugins.primelunch.cli
100100
:command: run_primelunch
101101
:prog_name: lunchable plugins primelunch run
102102
:style: table

docs/plugins/pushlunch.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ docker run --rm \
4343
from lunchable.plugins.pushlunch import PushLunch
4444
```
4545

46-
::: lunchable.plugins.pushlunch.PushLunch
46+
::: lunchable.plugins.pushlunch.pushover.PushLunch
4747
handler: python
4848
options:
4949
show_bases: false

docs/plugins/splitlunch.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,10 @@ docker run \
103103
## Run via Python
104104

105105
```python
106-
from lunchable.plugins.splitlunch import SplitLunch
106+
from lunchable.plugins.splitlunch.lunchmoney_splitwise import SplitLunch
107107
```
108108

109-
::: lunchable.plugins.splitlunch.SplitLunch
109+
::: lunchable.plugins.splitlunch.lunchmoney_splitwise.SplitLunch
110110
handler: python
111111
options:
112112
show_bases: false

mkdocs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ nav:
55
- Home 🏠: index.md
66
- Usage 📖: usage.md
77
- LunchMoney 🍽️: interacting.md
8-
- Plugins 🧩:
8+
- Apps and Plugins 🧩:
99
- plugins/index.md
1010
- PushLunch 📲: plugins/pushlunch.md
1111
- SplitLunch 🍱: plugins/splitlunch.md

requirements.txt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#
22
# This file is autogenerated by hatch-pip-compile with Python 3.11
33
#
4+
# - click-plugins>=1.1.1
45
# - click>=8.0.1
56
# - httpx
7+
# - importlib-metadata>=3.6
68
# - pydantic<3,>=2
79
# - rich>=10.0.0
810
# - pandas
@@ -22,6 +24,10 @@ certifi==2023.11.17
2224
charset-normalizer==3.3.2
2325
# via requests
2426
click==8.1.7
27+
# via
28+
# hatch.envs.default
29+
# click-plugins
30+
click-plugins==1.1.1
2531
# via hatch.envs.default
2632
h11==0.14.0
2733
# via httpcore
@@ -34,6 +40,8 @@ idna==3.6
3440
# anyio
3541
# httpx
3642
# requests
43+
importlib-metadata==7.0.1
44+
# via hatch.envs.default
3745
markdown-it-py==3.0.0
3846
# via rich
3947
mdurl==0.1.2
@@ -80,3 +88,5 @@ tzdata==2023.3
8088
# via pandas
8189
urllib3==2.1.0
8290
# via requests
91+
zipp==3.17.0
92+
# via importlib-metadata

requirements/requirements-lint.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#
22
# This file is autogenerated by hatch-pip-compile with Python 3.11
33
#
4-
# [constraints] requirements.txt (SHA256: 1d7f43c7c1ecbc074ece84ddfddf590007473fce44e813c9dca4481292391f67)
4+
# [constraints] requirements.txt (SHA256: fab1a8fdd5601fab426b875234fb9b2fa894f0e59667df5182eb30bc9d261f69)
55
#
66
# - mypy>=1.6.1
77
# - ruff~=0.1.7

0 commit comments

Comments
 (0)