sqlite-export-for-ynab 2.9.5__tar.gz → 2.9.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sqlite_export_for_ynab-2.9.5/sqlite_export_for_ynab.egg-info → sqlite_export_for_ynab-2.9.7}/PKG-INFO +35 -18
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/README.md +32 -16
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/pyproject.toml +11 -5
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab/_main.py +56 -46
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7/sqlite_export_for_ynab.egg-info}/PKG-INFO +35 -18
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab.egg-info/requires.txt +3 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/testing/fixtures.py +0 -1
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/LICENSE +0 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/setup.cfg +0 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab/__init__.py +0 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab/__main__.py +0 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab/ddl/__init__.py +0 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab/ddl/create-relations.sql +0 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab/ddl/drop-relations.sql +0 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab/py.typed +0 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab.egg-info/SOURCES.txt +0 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab.egg-info/dependency_links.txt +0 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab.egg-info/entry_points.txt +0 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab.egg-info/top_level.txt +0 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/testing/__init__.py +0 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/tests/__init__.py +0 -0
- {sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/tests/_main_test.py +9 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlite_export_for_ynab
|
|
3
|
-
Version: 2.9.
|
|
3
|
+
Version: 2.9.7
|
|
4
4
|
Summary: SQLite Export for YNAB - Export YNAB Data to SQLite
|
|
5
5
|
Author-email: Max R <mxr@users.noreply.github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -10,7 +10,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
11
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
12
12
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
13
|
-
Requires-Python: >=3.
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
16
|
Requires-Dist: aiohttp>=3
|
|
@@ -20,17 +20,20 @@ Requires-Dist: asyncio-for-ynab~=1.86.0
|
|
|
20
20
|
Requires-Dist: fasteners
|
|
21
21
|
Requires-Dist: rich>=10
|
|
22
22
|
Requires-Dist: tenacity
|
|
23
|
+
Requires-Dist: typing-extensions; python_version < "3.12"
|
|
23
24
|
Dynamic: license-file
|
|
24
25
|
|
|
25
26
|
# sqlite-export-for-ynab
|
|
26
27
|
|
|
27
|
-
[](https://results.pre-commit.ci/latest/github/mxr/sqlite-export-for-ynab/main)
|
|
28
|
+
[](https://results.pre-commit.ci/latest/github/mxr/sqlite-export-for-ynab/main)
|
|
29
|
+
[](https://codecov.io/github/mxr/sqlite-export-for-ynab)
|
|
28
30
|
|
|
29
31
|
SQLite Export for YNAB - Export YNAB Budget Data to SQLite
|
|
30
32
|
|
|
31
33
|
## What This Does
|
|
32
34
|
|
|
33
|
-
Export all your [YNAB](https://ynab.com/) plans to a local [SQLite](https://www.sqlite.org/) DB. Then you can query your
|
|
35
|
+
Export all your [YNAB](https://ynab.com/) plans to a local [SQLite](https://www.sqlite.org/) DB. Then you can query your
|
|
36
|
+
data with any tools compatible with SQLite.
|
|
34
37
|
|
|
35
38
|
## Installation
|
|
36
39
|
|
|
@@ -42,7 +45,8 @@ $ pip install sqlite-export-for-ynab
|
|
|
42
45
|
|
|
43
46
|
### CLI
|
|
44
47
|
|
|
45
|
-
Provision a [YNAB Personal Access Token](https://api.ynab.com/#personal-access-tokens) and save it as an environment
|
|
48
|
+
Provision a [YNAB Personal Access Token](https://api.ynab.com/#personal-access-tokens) and save it as an environment
|
|
49
|
+
variable.
|
|
46
50
|
|
|
47
51
|
```console
|
|
48
52
|
$ export YNAB_PERSONAL_ACCESS_TOKEN="..."
|
|
@@ -54,17 +58,22 @@ Run the tool from the terminal to download your plans:
|
|
|
54
58
|
$ sqlite-export-for-ynab
|
|
55
59
|
```
|
|
56
60
|
|
|
57
|
-
Running it again will pull only data that changed since the last pull (this is done with
|
|
58
|
-
|
|
61
|
+
Running it again will pull only data that changed since the last pull (this is done with
|
|
62
|
+
[Delta Requests](https://api.ynab.com/#deltas)). If you want to wipe the DB and pull all data again use the
|
|
63
|
+
`--full-refresh` flag. Pass `--quiet` to suppress all CLI output, including progress bars.
|
|
59
64
|
|
|
60
65
|
<a id="db-path"></a>You can specify the DB path with the following options
|
|
66
|
+
|
|
61
67
|
1. The `--db` flag.
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
2. The `XDG_DATA_HOME` variable (see the
|
|
69
|
+
[XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/latest/index.html)). In that
|
|
70
|
+
case the DB is saved in `"${XDG_DATA_HOME}"/sqlite-export-for-ynab/db.sqlite`.
|
|
71
|
+
3. If neither is set, the DB is saved in `~/.local/share/sqlite-export-for-ynab/db.sqlite`.
|
|
64
72
|
|
|
65
73
|
### Library
|
|
66
74
|
|
|
67
|
-
The library exposes the package `sqlite_export_for_ynab` and two functions - `default_db_path` and `sync`. You can use
|
|
75
|
+
The library exposes the package `sqlite_export_for_ynab` and two functions - `default_db_path` and `sync`. You can use
|
|
76
|
+
them as follows:
|
|
68
77
|
|
|
69
78
|
```python
|
|
70
79
|
import asyncio
|
|
@@ -82,11 +91,17 @@ asyncio.run(sync(token, db, full_refresh))
|
|
|
82
91
|
|
|
83
92
|
## Relations
|
|
84
93
|
|
|
85
|
-
The relations are defined in [create-relations.sql](sqlite_export_for_ynab/ddl/create-relations.sql). They are 1:1 with
|
|
94
|
+
The relations are defined in [create-relations.sql](sqlite_export_for_ynab/ddl/create-relations.sql). They are 1:1 with
|
|
95
|
+
[YNAB's OpenAPI Spec](https://api.ynab.com/papi/open_api_spec.yaml) (ex: transactions, accounts, etc) with some
|
|
96
|
+
additions:
|
|
86
97
|
|
|
87
|
-
1. Some objects are pulled out into their own tables so they can be more cleanly modeled in SQLite (ex: subtransactions,
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
1. Some objects are pulled out into their own tables so they can be more cleanly modeled in SQLite (ex: subtransactions,
|
|
99
|
+
loan account periodic values).
|
|
100
|
+
2. Foreign keys are added as needed (ex: plan ID, transaction ID) so data across plans remains separate.
|
|
101
|
+
3. Two new views called `flat_transactions` and `scheduled_flat_transactions`. These allow you to query split and
|
|
102
|
+
non-split transactions easily, without needing to also query `subtransactions` and `scheduled_subtransactions`
|
|
103
|
+
respectively. They also filter out deleted/unapproved transactions/subtransactions and project payee/category fields
|
|
104
|
+
to make querying more ergonomic.
|
|
90
105
|
|
|
91
106
|
## Querying
|
|
92
107
|
|
|
@@ -96,10 +111,10 @@ You can issue queries with typical SQLite tools. *`sqlite-export-for-ynab` delib
|
|
|
96
111
|
|
|
97
112
|
You can run the queries from this README using a tool like [`mdq`](https://github.com/yshavit/mdq). For example:
|
|
98
113
|
|
|
99
|
-
|
|
114
|
+
````console
|
|
100
115
|
$ mdq '```sql dupes' path/to/sqlite-export-for-ynab/README.md -o plain \
|
|
101
116
|
| sqlite3 path/to/sqlite-export-for-ynab/db.sqlite
|
|
102
|
-
|
|
117
|
+
````
|
|
103
118
|
|
|
104
119
|
The DB path is documented [above](#db-path).
|
|
105
120
|
|
|
@@ -233,7 +248,7 @@ FROM (
|
|
|
233
248
|
;
|
|
234
249
|
```
|
|
235
250
|
|
|
236
|
-
To estimate taxable interest for a given year[^1]:
|
|
251
|
+
To estimate taxable interest for a given year\[^1\]:
|
|
237
252
|
|
|
238
253
|
```sql
|
|
239
254
|
-- Parameters expected by this query:
|
|
@@ -591,4 +606,6 @@ WHERE
|
|
|
591
606
|
;
|
|
592
607
|
```
|
|
593
608
|
|
|
594
|
-
[^1]: This query is a rough estimate based on YNAB data and optional user inputs. It is not financial advice, tax
|
|
609
|
+
\[^1\]: This query is a rough estimate based on YNAB data and optional user inputs. It is not financial advice, tax
|
|
610
|
+
advice, or a substitute for Forms 1099-INT, brokerage statements, bank records, or guidance from a qualified
|
|
611
|
+
professional.
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# sqlite-export-for-ynab
|
|
2
2
|
|
|
3
|
-
[](https://results.pre-commit.ci/latest/github/mxr/sqlite-export-for-ynab/main)
|
|
3
|
+
[](https://results.pre-commit.ci/latest/github/mxr/sqlite-export-for-ynab/main)
|
|
4
|
+
[](https://codecov.io/github/mxr/sqlite-export-for-ynab)
|
|
4
5
|
|
|
5
6
|
SQLite Export for YNAB - Export YNAB Budget Data to SQLite
|
|
6
7
|
|
|
7
8
|
## What This Does
|
|
8
9
|
|
|
9
|
-
Export all your [YNAB](https://ynab.com/) plans to a local [SQLite](https://www.sqlite.org/) DB. Then you can query your
|
|
10
|
+
Export all your [YNAB](https://ynab.com/) plans to a local [SQLite](https://www.sqlite.org/) DB. Then you can query your
|
|
11
|
+
data with any tools compatible with SQLite.
|
|
10
12
|
|
|
11
13
|
## Installation
|
|
12
14
|
|
|
@@ -18,7 +20,8 @@ $ pip install sqlite-export-for-ynab
|
|
|
18
20
|
|
|
19
21
|
### CLI
|
|
20
22
|
|
|
21
|
-
Provision a [YNAB Personal Access Token](https://api.ynab.com/#personal-access-tokens) and save it as an environment
|
|
23
|
+
Provision a [YNAB Personal Access Token](https://api.ynab.com/#personal-access-tokens) and save it as an environment
|
|
24
|
+
variable.
|
|
22
25
|
|
|
23
26
|
```console
|
|
24
27
|
$ export YNAB_PERSONAL_ACCESS_TOKEN="..."
|
|
@@ -30,17 +33,22 @@ Run the tool from the terminal to download your plans:
|
|
|
30
33
|
$ sqlite-export-for-ynab
|
|
31
34
|
```
|
|
32
35
|
|
|
33
|
-
Running it again will pull only data that changed since the last pull (this is done with
|
|
34
|
-
|
|
36
|
+
Running it again will pull only data that changed since the last pull (this is done with
|
|
37
|
+
[Delta Requests](https://api.ynab.com/#deltas)). If you want to wipe the DB and pull all data again use the
|
|
38
|
+
`--full-refresh` flag. Pass `--quiet` to suppress all CLI output, including progress bars.
|
|
35
39
|
|
|
36
40
|
<a id="db-path"></a>You can specify the DB path with the following options
|
|
41
|
+
|
|
37
42
|
1. The `--db` flag.
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
2. The `XDG_DATA_HOME` variable (see the
|
|
44
|
+
[XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/latest/index.html)). In that
|
|
45
|
+
case the DB is saved in `"${XDG_DATA_HOME}"/sqlite-export-for-ynab/db.sqlite`.
|
|
46
|
+
3. If neither is set, the DB is saved in `~/.local/share/sqlite-export-for-ynab/db.sqlite`.
|
|
40
47
|
|
|
41
48
|
### Library
|
|
42
49
|
|
|
43
|
-
The library exposes the package `sqlite_export_for_ynab` and two functions - `default_db_path` and `sync`. You can use
|
|
50
|
+
The library exposes the package `sqlite_export_for_ynab` and two functions - `default_db_path` and `sync`. You can use
|
|
51
|
+
them as follows:
|
|
44
52
|
|
|
45
53
|
```python
|
|
46
54
|
import asyncio
|
|
@@ -58,11 +66,17 @@ asyncio.run(sync(token, db, full_refresh))
|
|
|
58
66
|
|
|
59
67
|
## Relations
|
|
60
68
|
|
|
61
|
-
The relations are defined in [create-relations.sql](sqlite_export_for_ynab/ddl/create-relations.sql). They are 1:1 with
|
|
69
|
+
The relations are defined in [create-relations.sql](sqlite_export_for_ynab/ddl/create-relations.sql). They are 1:1 with
|
|
70
|
+
[YNAB's OpenAPI Spec](https://api.ynab.com/papi/open_api_spec.yaml) (ex: transactions, accounts, etc) with some
|
|
71
|
+
additions:
|
|
62
72
|
|
|
63
|
-
1. Some objects are pulled out into their own tables so they can be more cleanly modeled in SQLite (ex: subtransactions,
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
1. Some objects are pulled out into their own tables so they can be more cleanly modeled in SQLite (ex: subtransactions,
|
|
74
|
+
loan account periodic values).
|
|
75
|
+
2. Foreign keys are added as needed (ex: plan ID, transaction ID) so data across plans remains separate.
|
|
76
|
+
3. Two new views called `flat_transactions` and `scheduled_flat_transactions`. These allow you to query split and
|
|
77
|
+
non-split transactions easily, without needing to also query `subtransactions` and `scheduled_subtransactions`
|
|
78
|
+
respectively. They also filter out deleted/unapproved transactions/subtransactions and project payee/category fields
|
|
79
|
+
to make querying more ergonomic.
|
|
66
80
|
|
|
67
81
|
## Querying
|
|
68
82
|
|
|
@@ -72,10 +86,10 @@ You can issue queries with typical SQLite tools. *`sqlite-export-for-ynab` delib
|
|
|
72
86
|
|
|
73
87
|
You can run the queries from this README using a tool like [`mdq`](https://github.com/yshavit/mdq). For example:
|
|
74
88
|
|
|
75
|
-
|
|
89
|
+
````console
|
|
76
90
|
$ mdq '```sql dupes' path/to/sqlite-export-for-ynab/README.md -o plain \
|
|
77
91
|
| sqlite3 path/to/sqlite-export-for-ynab/db.sqlite
|
|
78
|
-
|
|
92
|
+
````
|
|
79
93
|
|
|
80
94
|
The DB path is documented [above](#db-path).
|
|
81
95
|
|
|
@@ -209,7 +223,7 @@ FROM (
|
|
|
209
223
|
;
|
|
210
224
|
```
|
|
211
225
|
|
|
212
|
-
To estimate taxable interest for a given year[^1]:
|
|
226
|
+
To estimate taxable interest for a given year\[^1\]:
|
|
213
227
|
|
|
214
228
|
```sql
|
|
215
229
|
-- Parameters expected by this query:
|
|
@@ -567,4 +581,6 @@ WHERE
|
|
|
567
581
|
;
|
|
568
582
|
```
|
|
569
583
|
|
|
570
|
-
[^1]: This query is a rough estimate based on YNAB data and optional user inputs. It is not financial advice, tax
|
|
584
|
+
\[^1\]: This query is a rough estimate based on YNAB data and optional user inputs. It is not financial advice, tax
|
|
585
|
+
advice, or a substitute for Forms 1099-INT, brokerage statements, bank records, or guidance from a qualified
|
|
586
|
+
professional.
|
|
@@ -26,6 +26,7 @@ dependencies = [
|
|
|
26
26
|
"fasteners",
|
|
27
27
|
"rich>=10",
|
|
28
28
|
"tenacity",
|
|
29
|
+
"typing-extensions; python_version<'3.12'",
|
|
29
30
|
]
|
|
30
31
|
description = "SQLite Export for YNAB - Export YNAB Data to SQLite"
|
|
31
32
|
keywords = ["ynab", "sqlite", "sql", "budget", "plan", "cli"]
|
|
@@ -33,8 +34,8 @@ license = "MIT"
|
|
|
33
34
|
license-files = ["LICENSE"]
|
|
34
35
|
name = "sqlite_export_for_ynab"
|
|
35
36
|
readme = "README.md"
|
|
36
|
-
requires-python = ">=3.
|
|
37
|
-
version = "2.9.
|
|
37
|
+
requires-python = ">=3.11"
|
|
38
|
+
version = "2.9.7"
|
|
38
39
|
|
|
39
40
|
[project.scripts]
|
|
40
41
|
sqlite-export-for-ynab = "sqlite_export_for_ynab._main:main"
|
|
@@ -65,18 +66,23 @@ module = "tests.*"
|
|
|
65
66
|
asyncio_default_fixture_loop_scope = "function"
|
|
66
67
|
|
|
67
68
|
[tool.ruff]
|
|
68
|
-
target-version = "
|
|
69
|
+
target-version = "py311"
|
|
69
70
|
|
|
70
71
|
[tool.ruff.lint]
|
|
71
72
|
extend-select = [
|
|
72
|
-
"UP", # see pyupgrade
|
|
73
|
-
"B", # see flake8-bugbear
|
|
74
73
|
"A", # see flake8-builtins
|
|
74
|
+
"B", # see flake8-bugbear
|
|
75
75
|
"C4", # see flake8-comprehension
|
|
76
|
+
"I", # sort imports
|
|
76
77
|
"SIM", # see flake8-simplify
|
|
77
78
|
"TC", # see flake8-type-checking
|
|
79
|
+
"UP", # see pyupgrade
|
|
78
80
|
]
|
|
79
81
|
|
|
82
|
+
[tool.ruff.lint.isort]
|
|
83
|
+
force-single-line = true
|
|
84
|
+
required-imports = ["from __future__ import annotations"]
|
|
85
|
+
|
|
80
86
|
[tool.setuptools.package-data]
|
|
81
87
|
sqlite_export_for_ynab = ["py.typed"]
|
|
82
88
|
"sqlite_export_for_ynab.ddl" = ["*.sql"]
|
{sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab/_main.py
RENAMED
|
@@ -3,21 +3,24 @@ from __future__ import annotations
|
|
|
3
3
|
import argparse
|
|
4
4
|
import asyncio
|
|
5
5
|
import os
|
|
6
|
+
import sys
|
|
6
7
|
from contextlib import asynccontextmanager
|
|
7
8
|
from contextlib import contextmanager
|
|
8
9
|
from dataclasses import dataclass
|
|
9
10
|
from dataclasses import fields
|
|
10
11
|
from datetime import date
|
|
12
|
+
from datetime import datetime
|
|
11
13
|
from datetime import timedelta
|
|
12
14
|
from importlib import resources
|
|
13
15
|
from importlib.metadata import version
|
|
14
|
-
from itertools import batched
|
|
15
16
|
from pathlib import Path
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
16
18
|
from typing import Any
|
|
17
19
|
from typing import Literal
|
|
20
|
+
from typing import TypeVar
|
|
21
|
+
from typing import cast
|
|
22
|
+
from typing import get_args
|
|
18
23
|
from typing import overload
|
|
19
|
-
from typing import override
|
|
20
|
-
from typing import TYPE_CHECKING
|
|
21
24
|
|
|
22
25
|
import aiosqlite
|
|
23
26
|
import asyncio_for_ynab # noqa: F401
|
|
@@ -53,11 +56,30 @@ if TYPE_CHECKING:
|
|
|
53
56
|
from collections.abc import AsyncIterator
|
|
54
57
|
from collections.abc import Awaitable
|
|
55
58
|
from collections.abc import Callable
|
|
59
|
+
from collections.abc import Iterable
|
|
56
60
|
from collections.abc import Iterator
|
|
57
61
|
from collections.abc import Sequence
|
|
58
62
|
|
|
63
|
+
_T = TypeVar("_T")
|
|
64
|
+
|
|
65
|
+
if sys.version_info >= (3, 12): # pragma: >=3.12 cover
|
|
66
|
+
from itertools import batched
|
|
67
|
+
from typing import override
|
|
68
|
+
else: # pragma: <3.12 cover
|
|
69
|
+
from itertools import islice
|
|
70
|
+
|
|
71
|
+
from typing_extensions import override
|
|
72
|
+
|
|
73
|
+
def batched(iterable: Iterable[_T], n: int) -> Iterator[tuple[_T, ...]]:
|
|
74
|
+
it = iter(iterable)
|
|
75
|
+
while batch := tuple(islice(it, n)):
|
|
76
|
+
yield batch
|
|
77
|
+
|
|
78
|
+
|
|
59
79
|
try:
|
|
60
|
-
from rich.progress import
|
|
80
|
+
from rich.progress import (
|
|
81
|
+
MofNCompleteColumn, # pyright: ignore[reportAssignmentType]
|
|
82
|
+
)
|
|
61
83
|
# https://github.com/benleb/surepy/issues/240
|
|
62
84
|
except ImportError: # pragma: no cover
|
|
63
85
|
from rich.progress import ProgressColumn
|
|
@@ -84,28 +106,24 @@ except ImportError: # pragma: no cover
|
|
|
84
106
|
)
|
|
85
107
|
|
|
86
108
|
|
|
87
|
-
_EntryTable =
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
_Endpoint =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
| Literal["transactions"]
|
|
103
|
-
| Literal["scheduled_transactions"]
|
|
104
|
-
)
|
|
105
|
-
_ENDPOINTS = tuple(lit.__args__[0] for lit in _Endpoint.__args__)
|
|
109
|
+
_EntryTable = Literal[
|
|
110
|
+
"accounts",
|
|
111
|
+
"account_periodic_values",
|
|
112
|
+
"category_groups",
|
|
113
|
+
"categories",
|
|
114
|
+
"payees",
|
|
115
|
+
"transactions",
|
|
116
|
+
"subtransactions",
|
|
117
|
+
"scheduled_transactions",
|
|
118
|
+
"scheduled_subtransactions",
|
|
119
|
+
]
|
|
120
|
+
_Endpoint = Literal[
|
|
121
|
+
"accounts", "categories", "payees", "transactions", "scheduled_transactions"
|
|
122
|
+
]
|
|
123
|
+
_ENDPOINTS = get_args(_Endpoint)
|
|
106
124
|
_ALL_RELATIONS = frozenset(
|
|
107
125
|
("plans", "flat_transactions", "scheduled_flat_transactions")
|
|
108
|
-
+
|
|
126
|
+
+ get_args(_EntryTable)
|
|
109
127
|
)
|
|
110
128
|
|
|
111
129
|
_ENV_TOKEN = "YNAB_PERSONAL_ACCESS_TOKEN"
|
|
@@ -159,9 +177,9 @@ async def async_main(
|
|
|
159
177
|
)
|
|
160
178
|
|
|
161
179
|
args = parser.parse_args(argv)
|
|
162
|
-
db
|
|
163
|
-
full_refresh
|
|
164
|
-
quiet
|
|
180
|
+
db = cast("Path", args.db)
|
|
181
|
+
full_refresh = cast("bool", args.full_refresh)
|
|
182
|
+
quiet = cast("bool", args.quiet)
|
|
165
183
|
|
|
166
184
|
token = resolve_token(token_override)
|
|
167
185
|
|
|
@@ -556,28 +574,20 @@ async def insert_nested_entries(
|
|
|
556
574
|
context: _Context,
|
|
557
575
|
plan_id: str,
|
|
558
576
|
entries: list[dict[str, Any]],
|
|
559
|
-
desc: (
|
|
560
|
-
Literal["Accounts"]
|
|
561
|
-
| Literal["Categories"]
|
|
562
|
-
| Literal["Transactions"]
|
|
563
|
-
| Literal["Scheduled Transactions"]
|
|
564
|
-
),
|
|
577
|
+
desc: (Literal["Accounts", "Categories", "Transactions", "Scheduled Transactions"]),
|
|
565
578
|
entries_name: (
|
|
566
|
-
Literal["accounts"]
|
|
567
|
-
| Literal["category_groups"]
|
|
568
|
-
| Literal["transactions"]
|
|
569
|
-
| Literal["scheduled_transactions"]
|
|
579
|
+
Literal["accounts", "category_groups", "transactions", "scheduled_transactions"]
|
|
570
580
|
),
|
|
571
581
|
subentries_name: (
|
|
572
|
-
Literal["account_periodic_values"]
|
|
573
|
-
| Literal["categories"]
|
|
574
|
-
| Literal["subtransactions"]
|
|
582
|
+
Literal["account_periodic_values", "categories", "subtransactions"]
|
|
575
583
|
),
|
|
576
584
|
subentries_table_name: (
|
|
577
|
-
Literal[
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
585
|
+
Literal[
|
|
586
|
+
"account_periodic_values",
|
|
587
|
+
"categories",
|
|
588
|
+
"subtransactions",
|
|
589
|
+
"scheduled_subtransactions",
|
|
590
|
+
]
|
|
581
591
|
),
|
|
582
592
|
) -> None:
|
|
583
593
|
if not entries:
|
|
@@ -684,7 +694,7 @@ class _ProgressYnab:
|
|
|
684
694
|
task_id: TaskID
|
|
685
695
|
|
|
686
696
|
@retry(stop=stop_after_attempt(3))
|
|
687
|
-
async def get
|
|
697
|
+
async def get(self, endpoint: Callable[..., Awaitable[_T]]) -> _T:
|
|
688
698
|
try:
|
|
689
699
|
return await endpoint(
|
|
690
700
|
plan_id=self.plan_id,
|
|
@@ -713,7 +723,7 @@ class ChunkedTransactionsApi:
|
|
|
713
723
|
async def _get(
|
|
714
724
|
self, transactions_api: TransactionsApi, plan_id: str
|
|
715
725
|
) -> TransactionsResponse:
|
|
716
|
-
today =
|
|
726
|
+
today = datetime.now().astimezone().date()
|
|
717
727
|
responses = await asyncio.gather(
|
|
718
728
|
*(
|
|
719
729
|
self._chunk(transactions_api, plan_id, sd, ud)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlite_export_for_ynab
|
|
3
|
-
Version: 2.9.
|
|
3
|
+
Version: 2.9.7
|
|
4
4
|
Summary: SQLite Export for YNAB - Export YNAB Data to SQLite
|
|
5
5
|
Author-email: Max R <mxr@users.noreply.github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -10,7 +10,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
11
11
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
12
12
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
13
|
-
Requires-Python: >=3.
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
16
|
Requires-Dist: aiohttp>=3
|
|
@@ -20,17 +20,20 @@ Requires-Dist: asyncio-for-ynab~=1.86.0
|
|
|
20
20
|
Requires-Dist: fasteners
|
|
21
21
|
Requires-Dist: rich>=10
|
|
22
22
|
Requires-Dist: tenacity
|
|
23
|
+
Requires-Dist: typing-extensions; python_version < "3.12"
|
|
23
24
|
Dynamic: license-file
|
|
24
25
|
|
|
25
26
|
# sqlite-export-for-ynab
|
|
26
27
|
|
|
27
|
-
[](https://results.pre-commit.ci/latest/github/mxr/sqlite-export-for-ynab/main)
|
|
28
|
+
[](https://results.pre-commit.ci/latest/github/mxr/sqlite-export-for-ynab/main)
|
|
29
|
+
[](https://codecov.io/github/mxr/sqlite-export-for-ynab)
|
|
28
30
|
|
|
29
31
|
SQLite Export for YNAB - Export YNAB Budget Data to SQLite
|
|
30
32
|
|
|
31
33
|
## What This Does
|
|
32
34
|
|
|
33
|
-
Export all your [YNAB](https://ynab.com/) plans to a local [SQLite](https://www.sqlite.org/) DB. Then you can query your
|
|
35
|
+
Export all your [YNAB](https://ynab.com/) plans to a local [SQLite](https://www.sqlite.org/) DB. Then you can query your
|
|
36
|
+
data with any tools compatible with SQLite.
|
|
34
37
|
|
|
35
38
|
## Installation
|
|
36
39
|
|
|
@@ -42,7 +45,8 @@ $ pip install sqlite-export-for-ynab
|
|
|
42
45
|
|
|
43
46
|
### CLI
|
|
44
47
|
|
|
45
|
-
Provision a [YNAB Personal Access Token](https://api.ynab.com/#personal-access-tokens) and save it as an environment
|
|
48
|
+
Provision a [YNAB Personal Access Token](https://api.ynab.com/#personal-access-tokens) and save it as an environment
|
|
49
|
+
variable.
|
|
46
50
|
|
|
47
51
|
```console
|
|
48
52
|
$ export YNAB_PERSONAL_ACCESS_TOKEN="..."
|
|
@@ -54,17 +58,22 @@ Run the tool from the terminal to download your plans:
|
|
|
54
58
|
$ sqlite-export-for-ynab
|
|
55
59
|
```
|
|
56
60
|
|
|
57
|
-
Running it again will pull only data that changed since the last pull (this is done with
|
|
58
|
-
|
|
61
|
+
Running it again will pull only data that changed since the last pull (this is done with
|
|
62
|
+
[Delta Requests](https://api.ynab.com/#deltas)). If you want to wipe the DB and pull all data again use the
|
|
63
|
+
`--full-refresh` flag. Pass `--quiet` to suppress all CLI output, including progress bars.
|
|
59
64
|
|
|
60
65
|
<a id="db-path"></a>You can specify the DB path with the following options
|
|
66
|
+
|
|
61
67
|
1. The `--db` flag.
|
|
62
|
-
|
|
63
|
-
|
|
68
|
+
2. The `XDG_DATA_HOME` variable (see the
|
|
69
|
+
[XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/latest/index.html)). In that
|
|
70
|
+
case the DB is saved in `"${XDG_DATA_HOME}"/sqlite-export-for-ynab/db.sqlite`.
|
|
71
|
+
3. If neither is set, the DB is saved in `~/.local/share/sqlite-export-for-ynab/db.sqlite`.
|
|
64
72
|
|
|
65
73
|
### Library
|
|
66
74
|
|
|
67
|
-
The library exposes the package `sqlite_export_for_ynab` and two functions - `default_db_path` and `sync`. You can use
|
|
75
|
+
The library exposes the package `sqlite_export_for_ynab` and two functions - `default_db_path` and `sync`. You can use
|
|
76
|
+
them as follows:
|
|
68
77
|
|
|
69
78
|
```python
|
|
70
79
|
import asyncio
|
|
@@ -82,11 +91,17 @@ asyncio.run(sync(token, db, full_refresh))
|
|
|
82
91
|
|
|
83
92
|
## Relations
|
|
84
93
|
|
|
85
|
-
The relations are defined in [create-relations.sql](sqlite_export_for_ynab/ddl/create-relations.sql). They are 1:1 with
|
|
94
|
+
The relations are defined in [create-relations.sql](sqlite_export_for_ynab/ddl/create-relations.sql). They are 1:1 with
|
|
95
|
+
[YNAB's OpenAPI Spec](https://api.ynab.com/papi/open_api_spec.yaml) (ex: transactions, accounts, etc) with some
|
|
96
|
+
additions:
|
|
86
97
|
|
|
87
|
-
1. Some objects are pulled out into their own tables so they can be more cleanly modeled in SQLite (ex: subtransactions,
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
1. Some objects are pulled out into their own tables so they can be more cleanly modeled in SQLite (ex: subtransactions,
|
|
99
|
+
loan account periodic values).
|
|
100
|
+
2. Foreign keys are added as needed (ex: plan ID, transaction ID) so data across plans remains separate.
|
|
101
|
+
3. Two new views called `flat_transactions` and `scheduled_flat_transactions`. These allow you to query split and
|
|
102
|
+
non-split transactions easily, without needing to also query `subtransactions` and `scheduled_subtransactions`
|
|
103
|
+
respectively. They also filter out deleted/unapproved transactions/subtransactions and project payee/category fields
|
|
104
|
+
to make querying more ergonomic.
|
|
90
105
|
|
|
91
106
|
## Querying
|
|
92
107
|
|
|
@@ -96,10 +111,10 @@ You can issue queries with typical SQLite tools. *`sqlite-export-for-ynab` delib
|
|
|
96
111
|
|
|
97
112
|
You can run the queries from this README using a tool like [`mdq`](https://github.com/yshavit/mdq). For example:
|
|
98
113
|
|
|
99
|
-
|
|
114
|
+
````console
|
|
100
115
|
$ mdq '```sql dupes' path/to/sqlite-export-for-ynab/README.md -o plain \
|
|
101
116
|
| sqlite3 path/to/sqlite-export-for-ynab/db.sqlite
|
|
102
|
-
|
|
117
|
+
````
|
|
103
118
|
|
|
104
119
|
The DB path is documented [above](#db-path).
|
|
105
120
|
|
|
@@ -233,7 +248,7 @@ FROM (
|
|
|
233
248
|
;
|
|
234
249
|
```
|
|
235
250
|
|
|
236
|
-
To estimate taxable interest for a given year[^1]:
|
|
251
|
+
To estimate taxable interest for a given year\[^1\]:
|
|
237
252
|
|
|
238
253
|
```sql
|
|
239
254
|
-- Parameters expected by this query:
|
|
@@ -591,4 +606,6 @@ WHERE
|
|
|
591
606
|
;
|
|
592
607
|
```
|
|
593
608
|
|
|
594
|
-
[^1]: This query is a rough estimate based on YNAB data and optional user inputs. It is not financial advice, tax
|
|
609
|
+
\[^1\]: This query is a rough estimate based on YNAB data and optional user inputs. It is not financial advice, tax
|
|
610
|
+
advice, or a substitute for Forms 1099-INT, brokerage statements, bank records, or guidance from a qualified
|
|
611
|
+
professional.
|
|
File without changes
|
|
File without changes
|
{sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab/__init__.py
RENAMED
|
File without changes
|
{sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab/__main__.py
RENAMED
|
File without changes
|
{sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab/ddl/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sqlite_export_for_ynab-2.9.5 → sqlite_export_for_ynab-2.9.7}/sqlite_export_for_ynab/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -15,16 +15,16 @@ from rich.progress import Progress
|
|
|
15
15
|
|
|
16
16
|
from sqlite_export_for_ynab import default_db_path
|
|
17
17
|
from sqlite_export_for_ynab._main import _ALL_RELATIONS
|
|
18
|
-
from sqlite_export_for_ynab._main import _Context
|
|
19
|
-
from sqlite_export_for_ynab._main import _context
|
|
20
18
|
from sqlite_export_for_ynab._main import _ENV_TOKEN
|
|
21
|
-
from sqlite_export_for_ynab._main import _get_plan_summaries
|
|
22
19
|
from sqlite_export_for_ynab._main import _PACKAGE
|
|
23
20
|
from sqlite_export_for_ynab._main import _PROGRESS_COLUMNS
|
|
21
|
+
from sqlite_export_for_ynab._main import ChunkedTransactionsApi
|
|
22
|
+
from sqlite_export_for_ynab._main import _Context
|
|
23
|
+
from sqlite_export_for_ynab._main import _context
|
|
24
|
+
from sqlite_export_for_ynab._main import _get_plan_summaries
|
|
24
25
|
from sqlite_export_for_ynab._main import _quarterly
|
|
25
26
|
from sqlite_export_for_ynab._main import async_main
|
|
26
27
|
from sqlite_export_for_ynab._main import asyncio_for_ynab
|
|
27
|
-
from sqlite_export_for_ynab._main import ChunkedTransactionsApi
|
|
28
28
|
from sqlite_export_for_ynab._main import contents
|
|
29
29
|
from sqlite_export_for_ynab._main import get_last_knowledge_of_server
|
|
30
30
|
from sqlite_export_for_ynab._main import get_relations
|
|
@@ -41,8 +41,6 @@ from sqlite_export_for_ynab._main import sync
|
|
|
41
41
|
from testing.fixtures import ACCOUNT_ID_1
|
|
42
42
|
from testing.fixtures import ACCOUNT_ID_2
|
|
43
43
|
from testing.fixtures import ACCOUNTS
|
|
44
|
-
from testing.fixtures import accounts_response
|
|
45
|
-
from testing.fixtures import categories_response
|
|
46
44
|
from testing.fixtures import CATEGORY_GOAL_TARGET_DATE_1
|
|
47
45
|
from testing.fixtures import CATEGORY_GROUP_ID_1
|
|
48
46
|
from testing.fixtures import CATEGORY_GROUP_ID_2
|
|
@@ -61,10 +59,8 @@ from testing.fixtures import LKOS
|
|
|
61
59
|
from testing.fixtures import PAYEE_ID_1
|
|
62
60
|
from testing.fixtures import PAYEE_ID_2
|
|
63
61
|
from testing.fixtures import PAYEES
|
|
64
|
-
from testing.fixtures import payees_response
|
|
65
62
|
from testing.fixtures import PLAN_ID_1
|
|
66
63
|
from testing.fixtures import PLAN_ID_2
|
|
67
|
-
from testing.fixtures import plan_response
|
|
68
64
|
from testing.fixtures import PLANS
|
|
69
65
|
from testing.fixtures import SCHEDULED_SUBTRANSACTION_ID_1
|
|
70
66
|
from testing.fixtures import SCHEDULED_SUBTRANSACTION_ID_2
|
|
@@ -72,7 +68,6 @@ from testing.fixtures import SCHEDULED_TRANSACTION_ID_1
|
|
|
72
68
|
from testing.fixtures import SCHEDULED_TRANSACTION_ID_2
|
|
73
69
|
from testing.fixtures import SCHEDULED_TRANSACTION_ID_3
|
|
74
70
|
from testing.fixtures import SCHEDULED_TRANSACTIONS
|
|
75
|
-
from testing.fixtures import scheduled_transactions_response
|
|
76
71
|
from testing.fixtures import SERVER_KNOWLEDGE_1
|
|
77
72
|
from testing.fixtures import SERVER_KNOWLEDGE_2
|
|
78
73
|
from testing.fixtures import SUBTRANSACTION_ID_1
|
|
@@ -83,6 +78,11 @@ from testing.fixtures import TRANSACTION_ID_1
|
|
|
83
78
|
from testing.fixtures import TRANSACTION_ID_2
|
|
84
79
|
from testing.fixtures import TRANSACTION_ID_3
|
|
85
80
|
from testing.fixtures import TRANSACTIONS
|
|
81
|
+
from testing.fixtures import accounts_response
|
|
82
|
+
from testing.fixtures import categories_response
|
|
83
|
+
from testing.fixtures import payees_response
|
|
84
|
+
from testing.fixtures import plan_response
|
|
85
|
+
from testing.fixtures import scheduled_transactions_response
|
|
86
86
|
from testing.fixtures import transactions_response
|
|
87
87
|
|
|
88
88
|
|