Skip to content

Commit 6e8ab6c

Browse files
committed
Add categories
1 parent c5c0788 commit 6e8ab6c

File tree

5 files changed

+158
-217
lines changed

5 files changed

+158
-217
lines changed

src/pyfirefly/models.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,39 @@ class TransactionResource(DataClassORJSONMixin):
173173
id: str
174174
attributes: TransactionAttributes
175175
links: dict[str, Any] | None = None
176+
177+
178+
@dataclass
179+
class CategoryAmount(DataClassORJSONMixin):
180+
"""Model for a category amount in Firefly."""
181+
182+
currency_id: str | None = None
183+
currency_code: str | None = None
184+
currency_symbol: str | None = None
185+
currency_decimal_places: int | None = None
186+
sum: str | None = None
187+
188+
189+
@dataclass
190+
class CategoryAttributes(DataClassORJSONMixin):
191+
"""Attributes of a Firefly category."""
192+
193+
created_at: str | None = None
194+
updated_at: str | None = None
195+
name: str | None = None
196+
notes: str | None = None
197+
native_currency_id: str | None = None
198+
native_currency_code: str | None = None
199+
native_currency_symbol: str | None = None
200+
native_currency_decimal_places: int | None = None
201+
spent: list[CategoryAmount] | None = None
202+
earned: list[CategoryAmount] | None = None
203+
204+
205+
@dataclass
206+
class Category(DataClassORJSONMixin):
207+
"""Model for a Firefly category."""
208+
209+
type: str
210+
id: str
211+
attributes: CategoryAttributes

src/pyfirefly/pyfirefly.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
FireflyNotFoundError,
2121
FireflyTimeoutError,
2222
)
23-
from pyfirefly.models import About, Account
23+
from pyfirefly.models import About, Account, Category, Transaction
2424

2525
try:
2626
VERSION = metadata.version(__package__)
@@ -187,7 +187,7 @@ async def get_transactions(
187187
account_id: int | None = None,
188188
start: str | None = None,
189189
end: str | None = None,
190-
) -> list[dict[str, Any]]:
190+
) -> list[Transaction]:
191191
"""Get transactions for a specific account. Else, return all transactions.
192192
193193
Args:
@@ -229,7 +229,28 @@ async def get_transactions(
229229

230230
next_page = current_page + 1 if current_page < total_pages else None
231231

232-
return transactions
232+
return [Transaction.from_dict(tx) for tx in transactions]
233+
234+
async def get_categories(self, category_id: int, start: str | None = None, end: str | None = None) -> Category:
235+
"""Get a specific category by its ID.
236+
237+
Args:
238+
category_id: The ID of the category to retrieve.
239+
start: The start date for the category, to show spent and earned info.
240+
end: The end date for the category, to show spent and earned info.
241+
242+
Returns:
243+
A Category object containing the category information.
244+
245+
"""
246+
params: dict[str, str] = {}
247+
if start:
248+
params["start"] = start
249+
if end:
250+
params["end"] = end
251+
252+
category = await self._request(uri=f"categories/{category_id}", params=params)
253+
return Category.from_dict(category["data"])
233254

234255
async def close(self) -> None:
235256
"""Close open client session."""

tests/__snapshots__/test_models.ambr

Lines changed: 12 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -4,219 +4,12 @@
44
# ---
55
# name: test_account_transactions_model
66
list([
7-
dict({
8-
'attributes': dict({
9-
'created_at': '2018-09-17T12:46:47+01:00',
10-
'group_title': 'Split transaction title.',
11-
'transactions': list([
12-
dict({
13-
'amount': '123.45',
14-
'bill_id': '111',
15-
'bill_name': 'Monthly rent',
16-
'book_date': '2025-06-16T19:01:38.730Z',
17-
'budget_id': '4',
18-
'budget_name': 'Groceries',
19-
'bunq_payment_id': 'string',
20-
'category_id': '43',
21-
'category_name': 'Groceries',
22-
'currency_code': 'EUR',
23-
'currency_decimal_places': 2,
24-
'currency_id': '12',
25-
'currency_name': 'Euro',
26-
'currency_symbol': '$',
27-
'date': '2018-09-17T12:46:47+01:00',
28-
'description': 'Vegetables',
29-
'destination_iban': 'NL02ABNA0123456789',
30-
'destination_id': '2',
31-
'destination_name': 'Buy and Large',
32-
'destination_type': 'Asset account',
33-
'due_date': '2025-06-16T19:01:38.730Z',
34-
'external_id': 'string',
35-
'external_url': 'string',
36-
'foreign_amount': '123.45',
37-
'foreign_currency_code': 'USD',
38-
'foreign_currency_decimal_places': 2,
39-
'foreign_currency_id': '17',
40-
'foreign_currency_symbol': '$',
41-
'has_attachments': False,
42-
'import_hash_v2': 'string',
43-
'interest_date': '2025-06-16T19:01:38.730Z',
44-
'internal_reference': 'string',
45-
'invoice_date': '2025-06-16T19:01:38.730Z',
46-
'latitude': 51.983333,
47-
'longitude': 5.916667,
48-
'notes': 'Some example notes',
49-
'order': 0,
50-
'original_source': 'string',
51-
'payment_date': '2025-06-16T19:01:38.730Z',
52-
'process_date': '2025-06-16T19:01:38.730Z',
53-
'reconciled': False,
54-
'recurrence_count': 12,
55-
'recurrence_id': 'string',
56-
'recurrence_total': 0,
57-
'sepa_batch_id': 'string',
58-
'sepa_cc': 'string',
59-
'sepa_ci': 'string',
60-
'sepa_country': 'string',
61-
'sepa_ct_id': 'string',
62-
'sepa_ct_op': 'string',
63-
'sepa_db': 'string',
64-
'sepa_ep': 'string',
65-
'source_iban': 'NL02ABNA0123456789',
66-
'source_id': '2',
67-
'source_name': 'Checking account',
68-
'source_type': 'Asset account',
69-
'tags': None,
70-
'transaction_journal_id': '10421',
71-
'type': 'withdrawal',
72-
'user': '3',
73-
'zoom_level': 6,
74-
}),
75-
dict({
76-
'amount': '200.00',
77-
'bill_id': '112',
78-
'bill_name': 'Monthly salary',
79-
'book_date': '2019-01-01T10:00:00+01:00',
80-
'budget_id': '5',
81-
'budget_name': 'Income',
82-
'bunq_payment_id': 'bunq-jan-2019',
83-
'category_id': '44',
84-
'category_name': 'Salary',
85-
'currency_code': 'USD',
86-
'currency_decimal_places': 2,
87-
'currency_id': '13',
88-
'currency_name': 'US Dollar',
89-
'currency_symbol': '$',
90-
'date': '2019-01-01T10:00:00+01:00',
91-
'description': 'Salary',
92-
'destination_iban': 'NL02ABNA0123456789',
93-
'destination_id': '2',
94-
'destination_name': 'Checking account',
95-
'destination_type': 'Asset account',
96-
'due_date': '2019-01-01T10:00:00+01:00',
97-
'external_id': 'ext-jan-2019',
98-
'external_url': 'https://employer.com/payroll',
99-
'foreign_amount': '180.00',
100-
'foreign_currency_code': 'EUR',
101-
'foreign_currency_decimal_places': 2,
102-
'foreign_currency_id': '18',
103-
'foreign_currency_symbol': '€',
104-
'has_attachments': True,
105-
'import_hash_v2': 'hash-jan-2019',
106-
'interest_date': '2019-01-01T10:00:00+01:00',
107-
'internal_reference': 'ref-jan-2019',
108-
'invoice_date': '2019-01-01T10:00:00+01:00',
109-
'latitude': 40.7128,
110-
'longitude': -74.006,
111-
'notes': 'Salary for January',
112-
'order': 1,
113-
'original_source': 'payroll',
114-
'payment_date': '2019-01-01T10:00:00+01:00',
115-
'process_date': '2019-01-01T10:00:00+01:00',
116-
'reconciled': True,
117-
'recurrence_count': 1,
118-
'recurrence_id': 'rec-jan-2019',
119-
'recurrence_total': 1,
120-
'sepa_batch_id': 'sepa-batch-jan-2019',
121-
'sepa_cc': 'sepa-cc-jan-2019',
122-
'sepa_ci': 'sepa-ci-jan-2019',
123-
'sepa_country': 'US',
124-
'sepa_ct_id': 'sepa-id-jan-2019',
125-
'sepa_ct_op': 'sepa-op-jan-2019',
126-
'sepa_db': 'sepa-db-jan-2019',
127-
'sepa_ep': 'sepa-ep-jan-2019',
128-
'source_iban': 'US12345678901234567890',
129-
'source_id': '3',
130-
'source_name': 'Employer',
131-
'source_type': 'Income account',
132-
'tags': list([
133-
'income',
134-
'salary',
135-
]),
136-
'transaction_journal_id': '10422',
137-
'type': 'deposit',
138-
'user': '4',
139-
'zoom_level': 8,
140-
}),
141-
dict({
142-
'amount': '75.00',
143-
'bill_id': '113',
144-
'bill_name': 'Monthly transfer',
145-
'book_date': '2020-05-15T15:30:00+01:00',
146-
'budget_id': '6',
147-
'budget_name': 'Savings',
148-
'bunq_payment_id': 'bunq-may-2020',
149-
'category_id': '45',
150-
'category_name': 'Transfers',
151-
'currency_code': 'GBP',
152-
'currency_decimal_places': 2,
153-
'currency_id': '14',
154-
'currency_name': 'Pound Sterling',
155-
'currency_symbol': '£',
156-
'date': '2020-05-15T15:30:00+01:00',
157-
'description': 'Transfer to savings',
158-
'destination_iban': 'GB29NWBK60161331926819',
159-
'destination_id': '4',
160-
'destination_name': 'Savings account',
161-
'destination_type': 'Asset account',
162-
'due_date': '2020-05-15T15:30:00+01:00',
163-
'external_id': 'ext-may-2020',
164-
'external_url': 'https://bank.com/transfer',
165-
'foreign_amount': '100.00',
166-
'foreign_currency_code': 'USD',
167-
'foreign_currency_decimal_places': 2,
168-
'foreign_currency_id': '19',
169-
'foreign_currency_symbol': '$',
170-
'has_attachments': False,
171-
'import_hash_v2': 'hash-may-2020',
172-
'interest_date': '2020-05-15T15:30:00+01:00',
173-
'internal_reference': 'ref-may-2020',
174-
'invoice_date': '2020-05-15T15:30:00+01:00',
175-
'latitude': 51.5074,
176-
'longitude': 0.1278,
177-
'notes': 'Transfer to savings account',
178-
'order': 2,
179-
'original_source': 'bank',
180-
'payment_date': '2020-05-15T15:30:00+01:00',
181-
'process_date': '2020-05-15T15:30:00+01:00',
182-
'reconciled': False,
183-
'recurrence_count': 1,
184-
'recurrence_id': 'rec-may-2020',
185-
'recurrence_total': 1,
186-
'sepa_batch_id': 'sepa-batch-may-2020',
187-
'sepa_cc': 'sepa-cc-may-2020',
188-
'sepa_ci': 'sepa-ci-may-2020',
189-
'sepa_country': 'GB',
190-
'sepa_ct_id': 'sepa-id-may-2020',
191-
'sepa_ct_op': 'sepa-op-may-2020',
192-
'sepa_db': 'sepa-db-may-2020',
193-
'sepa_ep': 'sepa-ep-may-2020',
194-
'source_iban': 'GB29NWBK60161331926819',
195-
'source_id': '2',
196-
'source_name': 'Checking account',
197-
'source_type': 'Asset account',
198-
'tags': list([
199-
'transfer',
200-
]),
201-
'transaction_journal_id': '10423',
202-
'type': 'transfer',
203-
'user': '5',
204-
'zoom_level': 10,
205-
}),
206-
]),
207-
'updated_at': '2018-09-17T12:46:47+01:00',
208-
'user': '3',
209-
}),
210-
'id': '2',
211-
'links': dict({
212-
'0': dict({
213-
'rel': 'self',
214-
'uri': '/OBJECTS/1',
215-
}),
216-
'self': 'https://demo.firefly-iii.org/api/v1/OBJECTS/1',
217-
}),
218-
'type': 'transactions',
219-
}),
7+
Transaction(user=None, transaction_journal_id=None, type='transactions', date=None, order=None, currency_id=None, currency_code=None, currency_symbol=None, currency_name=None, currency_decimal_places=None, foreign_currency_id=None, foreign_currency_code=None, foreign_currency_symbol=None, foreign_currency_decimal_places=None, amount=None, foreign_amount=None, description=None, source_id=None, source_name=None, source_iban=None, source_type=None, destination_id=None, destination_name=None, destination_iban=None, destination_type=None, budget_id=None, budget_name=None, category_id=None, category_name=None, bill_id=None, bill_name=None, reconciled=None, notes=None, tags=None, internal_reference=None, external_id=None, external_url=None, original_source=None, recurrence_id=None, recurrence_total=None, recurrence_count=None, bunq_payment_id=None, import_hash_v2=None, sepa_cc=None, sepa_ct_op=None, sepa_ct_id=None, sepa_db=None, sepa_country=None, sepa_ep=None, sepa_ci=None, sepa_batch_id=None, interest_date=None, book_date=None, process_date=None, due_date=None, payment_date=None, invoice_date=None, latitude=None, longitude=None, zoom_level=None, has_attachments=None),
8+
])
9+
# ---
10+
# name: test_account_transactions_model.1
11+
list([
12+
Transaction(user=None, transaction_journal_id=None, type='transactions', date=None, order=None, currency_id=None, currency_code=None, currency_symbol=None, currency_name=None, currency_decimal_places=None, foreign_currency_id=None, foreign_currency_code=None, foreign_currency_symbol=None, foreign_currency_decimal_places=None, amount=None, foreign_amount=None, description=None, source_id=None, source_name=None, source_iban=None, source_type=None, destination_id=None, destination_name=None, destination_iban=None, destination_type=None, budget_id=None, budget_name=None, category_id=None, category_name=None, bill_id=None, bill_name=None, reconciled=None, notes=None, tags=None, internal_reference=None, external_id=None, external_url=None, original_source=None, recurrence_id=None, recurrence_total=None, recurrence_count=None, bunq_payment_id=None, import_hash_v2=None, sepa_cc=None, sepa_ct_op=None, sepa_ct_id=None, sepa_db=None, sepa_country=None, sepa_ep=None, sepa_ci=None, sepa_batch_id=None, interest_date=None, book_date=None, process_date=None, due_date=None, payment_date=None, invoice_date=None, latitude=None, longitude=None, zoom_level=None, has_attachments=None),
22013
])
22114
# ---
22215
# name: test_accounts_model
@@ -226,3 +19,9 @@
22619
Account(type='accounts', id='4', attributes=AccountAttributes(created_at='2021-05-10T09:30:00+01:00', updated_at='2022-05-10T09:30:00+01:00', active=True, name='Credit Card', type='liability', account_role='creditCard', currency_id='14', currency_code='GBP', currency_symbol='£', currency_decimal_places=2, native_currency_id='14', native_currency_code='GBP', native_currency_symbol='£', native_currency_decimal_places=2, current_balance='-250.00', native_current_balance='-250.00', current_balance_date='2022-05-10T09:30:00+01:00', order=3, notes='Credit card account', monthly_payment_date='2022-05-15T09:30:00+01:00', credit_card_type='monthlyFull', account_number='9876543210', iban='GB29NWBK60161331926819', bic='NWBKGB2L', virtual_balance='0.00', native_virtual_balance='0.00', opening_balance='0.00', native_opening_balance='0.00', opening_balance_date='2021-05-10T09:30:00+01:00', liability_type='credit', liability_direction='debit', interest='19.99', interest_period='monthly', current_debt='250.00', include_net_worth=False, longitude=0.1278, latitude=51.5074, zoom_level=10, last_activity=None)),
22720
])
22821
# ---
22+
# name: test_category_model
23+
Category(type='categories', id='2', attributes=CategoryAttributes(created_at='2018-09-17T12:46:47+01:00', updated_at='2018-09-17T12:46:47+01:00', name='Lunch', notes='Some example notes', native_currency_id='5', native_currency_code='EUR', native_currency_symbol='$', native_currency_decimal_places=2, spent=[CategoryAmount(currency_id='5', currency_code='USD', currency_symbol='$', currency_decimal_places=2, sum='-12423.45')], earned=[CategoryAmount(currency_id='5', currency_code='USD', currency_symbol='$', currency_decimal_places=2, sum='123.45')]))
24+
# ---
25+
# name: test_category_model.1
26+
Category(type='categories', id='2', attributes=CategoryAttributes(created_at='2018-09-17T12:46:47+01:00', updated_at='2018-09-17T12:46:47+01:00', name='Lunch', notes='Some example notes', native_currency_id='5', native_currency_code='EUR', native_currency_symbol='$', native_currency_decimal_places=2, spent=[CategoryAmount(currency_id='5', currency_code='USD', currency_symbol='$', currency_decimal_places=2, sum='-12423.45')], earned=[CategoryAmount(currency_id='5', currency_code='USD', currency_symbol='$', currency_decimal_places=2, sum='123.45')]))
27+
# ---

tests/fixtures/category.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"data": {
3+
"type": "categories",
4+
"id": "2",
5+
"attributes": {
6+
"created_at": "2018-09-17T12:46:47+01:00",
7+
"updated_at": "2018-09-17T12:46:47+01:00",
8+
"name": "Lunch",
9+
"notes": "Some example notes",
10+
"native_currency_id": "5",
11+
"native_currency_code": "EUR",
12+
"native_currency_symbol": "$",
13+
"native_currency_decimal_places": 2,
14+
"spent": [
15+
{
16+
"currency_id": "5",
17+
"currency_code": "USD",
18+
"currency_symbol": "$",
19+
"currency_decimal_places": 2,
20+
"sum": "-12423.45"
21+
}
22+
],
23+
"earned": [
24+
{
25+
"currency_id": "5",
26+
"currency_code": "USD",
27+
"currency_symbol": "$",
28+
"currency_decimal_places": 2,
29+
"sum": "123.45"
30+
}
31+
]
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)