actualpy


Nameactualpy JSON
Version 0.15.0 PyPI version JSON
download
home_pageNone
SummaryImplementation of the Actual API to interact with Actual over Python.
upload_time2025-08-22 17:22:36
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9.0
licenseNone
keywords actual actualbudget api client
VCS
bugtrack_url
requirements requests sqlmodel pydantic sqlalchemy proto-plus protobuf cryptography python-dateutil rich typer pyyaml
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # actualpy

|                      | **[Documentation](https://actualpy.readthedocs.io/en/latest/)** · **[Examples](https://actualpy.readthedocs.io/en/latest/examples)** · **[Releases](https://github.com/bvanelli/actualpy/releases)**                                                                                                                                                                                                                                                                                                                                                                                                                  |
|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Open Source** | [![MIT License](https://img.shields.io/github/license/bvanelli/actualpy)](https://github.com/bvanelli/actualpy/blob/main/LICENSE)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| **Community**        | [![Discussions](https://img.shields.io/github/discussions/bvanelli/actualpy)](https://github.com/bvanelli/actualpy/discussions/new/choose) ![GitHub contributors](https://img.shields.io/github/contributors/bvanelli/actualpy)                                                                                                                                                                                                                                                                                                                                                                                       |
| **CI/CD**            | [![github-actions](https://github.com/bvanelli/actualpy/workflows/Tests/badge.svg)](https://github.com/bvanelli/actualpy/actions) [![docs](https://readthedocs.org/projects/actualpy/badge/?version=latest)](https://actualpy.readthedocs.io/)                                                                                                                                                                                                                                                                                                                                                                        |
| **Code**             | [![!pypi](https://img.shields.io/pypi/v/actualpy?color=orange)](https://pypi.org/project/actualpy/) [![codecov](https://codecov.io/github/bvanelli/actualpy/graph/badge.svg?token=N6V05MY70U)](https://codecov.io/github/bvanelli/actualpy) [![!python-versions](https://img.shields.io/pypi/pyversions/actualpy)](https://www.python.org/) [![ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)  [![codestyle](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black) |
| **Downloads**        | ![PyPI - Downloads](https://img.shields.io/pypi/dw/actualpy) ![PyPI - Downloads](https://img.shields.io/pypi/dm/actualpy) [![Downloads](https://img.shields.io/pepy/dt/actualpy?label=cumulative%20(pypi))](https://pepy.tech/project/actualpy)                                                                                                                                                                                                                                                                                                                                                                       |

Python API implementation for Actual server.

[Actual Budget](https://actualbudget.org/) is a superfast and privacy-focused app for managing your finances.

This library is a re-implementation for the Node.js version of the npm package
[@actual-app/api](https://actualbudget.org/docs/api/).
It implements a different approach, offering a more pythonic way to deal with the database objects using the SQLAlchemy
ORM. This means that you can use the full power of SQLAlchemy to query the database and build your own queries. This
is useful if you want to build a custom tool to manage your budget, or to export your data to another format. All the
useful relationships between the objects are also available to facilitate handling the dtata.

If you find any issue with the library, please open an issue on the
[GitHub repository](https://github.com/bvanelli/actualpy/issues).

If you have a question, you can also open a new discussion on the
[GitHub repository](https://github.com/bvanelli/actualpy/discussions/new/choose).

# Installation

Install it via Pip:

```bash
pip install actualpy
```

If you want to have the latest git version, you can also install using the repository url:

```bash
pip install git+https://github.com/bvanelli/actualpy.git
```

For querying basic information, you additionally install the CLI, checkout the
[basic documentation](https://actualpy.readthedocs.io/en/latest/command-line-interface/)

# Basic usage

The most common usage would be downloading a budget to more easily build queries. This would you could handle the
Actual database using SQLAlchemy instead of having to retrieve the data via the export. The following script will print
every single transaction registered on the Actual budget file:

```python
from actual import Actual
from actual.queries import get_transactions

with Actual(
        base_url="http://localhost:5006",  # Url of the Actual Server
        password="<your_password>",  # Password for authentication
        encryption_password=None,  # Optional: Password for the file encryption. Will not use it if set to None.
        # Set the file to work with. Can be either the file id or file name, if name is unique
        file="<file_id_or_name>",
        # Optional: Directory to store downloaded files. Will use a temporary if not provided
        data_dir="<path_to_data_directory>",
        # Optional: Path to the certificate file to use for the connection, can also be set as False to disable SSL verification
        cert="<path_to_cert_file>"
) as actual:
    transactions = get_transactions(actual.session)
    for t in transactions:
        account_name = t.account.name if t.account else None
        category = t.category.name if t.category else None
        print(t.date, account_name, t.notes, t.amount, category)
```

The `file` will be matched to either one of the following:

- The name of the budget, found top the top left cornet
- The ID of the budget, a UUID that is only available if you inspect the result of the method `list_user_files`
- The Sync ID of the budget, a UUID available on the frontend on the "Advanced options"
- If none of those options work for you, you can search for the file manually with `list_user_files` and provide the
  object directly:

```python
from actual import Actual

with Actual("http://localhost:5006", password="mypass") as actual:
    actual.set_file(actual.list_user_files().data[0])
    actual.download_budget()
```

Checkout [the full documentation](https://actualpy.readthedocs.io) for more examples.

# Understanding how Actual handles changes

The Actual budget is stored in a sqlite database hosted on the user's browser. This means all your data is fully local
and can be encrypted with a local key, so that not even the server can read your statements.

The Actual Server is a way of only hosting files and changes. Since re-uploading the full database on every single
change is too heavy, Actual only stores one state of the "base database" and everything added by the user via frontend
or via the APIs are individual changes applied on top. This means that on every change, done locally, the frontend
does a SYNC request with a list of the following string parameters:

- `dataset`: the name of the table where the change happened.
- `row`: the row identifier for the entry that was added/update. This would be the primary key of the row (a uuid value)
- `column`: the column that had the value changed
- `value`: the new value. Since it's a string, the values are either prefixed by `S:` to denote a string, `N:` to denote
  a numeric value and `0:` to denote a null value.

All individual column changes are computed for an insert or update, serialized with protobuf and sent to the server to
be stored. Null values and server defaults are not required to be present in the SYNC message, unless a column is
changed to null. If the file is encrypted, the protobuf content will also be encrypted, so that the server does not know
what was changed.

New clients can use this individual changes to then update their local copies. Whenever a SYNC request is done, the
response will also contain changes that might have been done in other browsers, so that the user is informated about
the latest information.

But this also means that new users need to download a long list of changes, possibly making the initialization slow.
Thankfully, the user is also allowed to reset the sync. When doing a reset of the file via frontend, the browser is then
resetting the file completely and clearing the list of changes. This would make sure all changes are actually stored in
the "base database". This is done on the frontend under *Settings > Reset sync*, and causes the current file to be
reset (removed from the server) and re-uploaded again, with all changes already in place.

This means that, when using this library to operate changes on the database, you have to make sure that either:

- do a sync request is made using the `actual.commit()` method. This only handles pending operations that haven't yet
  been committed, generates a change list with them and posts them on the sync endpoint.
- do a full re-upload of the database is done.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "actualpy",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9.0",
    "maintainer_email": null,
    "keywords": "actual, actualbudget, api, client",
    "author": null,
    "author_email": "Brunno Vanelli <brunnovanelli@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/f3/9c/d181c586db8dd300867b5ac9b297f80b297333ada00240485d3e870767cb/actualpy-0.15.0.tar.gz",
    "platform": null,
    "description": "# actualpy\n\n|                      | **[Documentation](https://actualpy.readthedocs.io/en/latest/)** \u00b7 **[Examples](https://actualpy.readthedocs.io/en/latest/examples)** \u00b7 **[Releases](https://github.com/bvanelli/actualpy/releases)**                                                                                                                                                                                                                                                                                                                                                                                                                  |\n|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| **Open&#160;Source** | [![MIT License](https://img.shields.io/github/license/bvanelli/actualpy)](https://github.com/bvanelli/actualpy/blob/main/LICENSE)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |\n| **Community**        | [![Discussions](https://img.shields.io/github/discussions/bvanelli/actualpy)](https://github.com/bvanelli/actualpy/discussions/new/choose) ![GitHub contributors](https://img.shields.io/github/contributors/bvanelli/actualpy)                                                                                                                                                                                                                                                                                                                                                                                       |\n| **CI/CD**            | [![github-actions](https://github.com/bvanelli/actualpy/workflows/Tests/badge.svg)](https://github.com/bvanelli/actualpy/actions) [![docs](https://readthedocs.org/projects/actualpy/badge/?version=latest)](https://actualpy.readthedocs.io/)                                                                                                                                                                                                                                                                                                                                                                        |\n| **Code**             | [![!pypi](https://img.shields.io/pypi/v/actualpy?color=orange)](https://pypi.org/project/actualpy/) [![codecov](https://codecov.io/github/bvanelli/actualpy/graph/badge.svg?token=N6V05MY70U)](https://codecov.io/github/bvanelli/actualpy) [![!python-versions](https://img.shields.io/pypi/pyversions/actualpy)](https://www.python.org/) [![ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)  [![codestyle](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black) |\n| **Downloads**        | ![PyPI - Downloads](https://img.shields.io/pypi/dw/actualpy) ![PyPI - Downloads](https://img.shields.io/pypi/dm/actualpy) [![Downloads](https://img.shields.io/pepy/dt/actualpy?label=cumulative%20(pypi))](https://pepy.tech/project/actualpy)                                                                                                                                                                                                                                                                                                                                                                       |\n\nPython API implementation for Actual server.\n\n[Actual Budget](https://actualbudget.org/) is a superfast and privacy-focused app for managing your finances.\n\nThis library is a re-implementation for the Node.js version of the npm package\n[@actual-app/api](https://actualbudget.org/docs/api/).\nIt implements a different approach, offering a more pythonic way to deal with the database objects using the SQLAlchemy\nORM. This means that you can use the full power of SQLAlchemy to query the database and build your own queries. This\nis useful if you want to build a custom tool to manage your budget, or to export your data to another format. All the\nuseful relationships between the objects are also available to facilitate handling the dtata.\n\nIf you find any issue with the library, please open an issue on the\n[GitHub repository](https://github.com/bvanelli/actualpy/issues).\n\nIf you have a question, you can also open a new discussion on the\n[GitHub repository](https://github.com/bvanelli/actualpy/discussions/new/choose).\n\n# Installation\n\nInstall it via Pip:\n\n```bash\npip install actualpy\n```\n\nIf you want to have the latest git version, you can also install using the repository url:\n\n```bash\npip install git+https://github.com/bvanelli/actualpy.git\n```\n\nFor querying basic information, you additionally install the CLI, checkout the\n[basic documentation](https://actualpy.readthedocs.io/en/latest/command-line-interface/)\n\n# Basic usage\n\nThe most common usage would be downloading a budget to more easily build queries. This would you could handle the\nActual database using SQLAlchemy instead of having to retrieve the data via the export. The following script will print\nevery single transaction registered on the Actual budget file:\n\n```python\nfrom actual import Actual\nfrom actual.queries import get_transactions\n\nwith Actual(\n        base_url=\"http://localhost:5006\",  # Url of the Actual Server\n        password=\"<your_password>\",  # Password for authentication\n        encryption_password=None,  # Optional: Password for the file encryption. Will not use it if set to None.\n        # Set the file to work with. Can be either the file id or file name, if name is unique\n        file=\"<file_id_or_name>\",\n        # Optional: Directory to store downloaded files. Will use a temporary if not provided\n        data_dir=\"<path_to_data_directory>\",\n        # Optional: Path to the certificate file to use for the connection, can also be set as False to disable SSL verification\n        cert=\"<path_to_cert_file>\"\n) as actual:\n    transactions = get_transactions(actual.session)\n    for t in transactions:\n        account_name = t.account.name if t.account else None\n        category = t.category.name if t.category else None\n        print(t.date, account_name, t.notes, t.amount, category)\n```\n\nThe `file` will be matched to either one of the following:\n\n- The name of the budget, found top the top left cornet\n- The ID of the budget, a UUID that is only available if you inspect the result of the method `list_user_files`\n- The Sync ID of the budget, a UUID available on the frontend on the \"Advanced options\"\n- If none of those options work for you, you can search for the file manually with `list_user_files` and provide the\n  object directly:\n\n```python\nfrom actual import Actual\n\nwith Actual(\"http://localhost:5006\", password=\"mypass\") as actual:\n    actual.set_file(actual.list_user_files().data[0])\n    actual.download_budget()\n```\n\nCheckout [the full documentation](https://actualpy.readthedocs.io) for more examples.\n\n# Understanding how Actual handles changes\n\nThe Actual budget is stored in a sqlite database hosted on the user's browser. This means all your data is fully local\nand can be encrypted with a local key, so that not even the server can read your statements.\n\nThe Actual Server is a way of only hosting files and changes. Since re-uploading the full database on every single\nchange is too heavy, Actual only stores one state of the \"base database\" and everything added by the user via frontend\nor via the APIs are individual changes applied on top. This means that on every change, done locally, the frontend\ndoes a SYNC request with a list of the following string parameters:\n\n- `dataset`: the name of the table where the change happened.\n- `row`: the row identifier for the entry that was added/update. This would be the primary key of the row (a uuid value)\n- `column`: the column that had the value changed\n- `value`: the new value. Since it's a string, the values are either prefixed by `S:` to denote a string, `N:` to denote\n  a numeric value and `0:` to denote a null value.\n\nAll individual column changes are computed for an insert or update, serialized with protobuf and sent to the server to\nbe stored. Null values and server defaults are not required to be present in the SYNC message, unless a column is\nchanged to null. If the file is encrypted, the protobuf content will also be encrypted, so that the server does not know\nwhat was changed.\n\nNew clients can use this individual changes to then update their local copies. Whenever a SYNC request is done, the\nresponse will also contain changes that might have been done in other browsers, so that the user is informated about\nthe latest information.\n\nBut this also means that new users need to download a long list of changes, possibly making the initialization slow.\nThankfully, the user is also allowed to reset the sync. When doing a reset of the file via frontend, the browser is then\nresetting the file completely and clearing the list of changes. This would make sure all changes are actually stored in\nthe \"base database\". This is done on the frontend under *Settings > Reset sync*, and causes the current file to be\nreset (removed from the server) and re-uploaded again, with all changes already in place.\n\nThis means that, when using this library to operate changes on the database, you have to make sure that either:\n\n- do a sync request is made using the `actual.commit()` method. This only handles pending operations that haven't yet\n  been committed, generates a change list with them and posts them on the sync endpoint.\n- do a full re-upload of the database is done.\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Implementation of the Actual API to interact with Actual over Python.",
    "version": "0.15.0",
    "project_urls": {
        "Bug Tracker": "https://github.com/bvanelli/actualpy/issues",
        "Documentation": "https://actualpy.readthedocs.io/",
        "Homepage": "https://github.com/bvanelli/actualpy",
        "Repository": "https://github.com/bvanelli/actualpy.git"
    },
    "split_keywords": [
        "actual",
        " actualbudget",
        " api",
        " client"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d452709490bcda74c9d5036395e80a8a4b06656dc50a3b966bb069e8042c689a",
                "md5": "861c7e4a7fb37605ae3686676872fab4",
                "sha256": "7a04af0773d2e236adf64e96437d78d1a332933aa9dfb82c0e2a1e7b322ae2de"
            },
            "downloads": -1,
            "filename": "actualpy-0.15.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "861c7e4a7fb37605ae3686676872fab4",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9.0",
            "size": 77105,
            "upload_time": "2025-08-22T17:22:34",
            "upload_time_iso_8601": "2025-08-22T17:22:34.721024Z",
            "url": "https://files.pythonhosted.org/packages/d4/52/709490bcda74c9d5036395e80a8a4b06656dc50a3b966bb069e8042c689a/actualpy-0.15.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "f39cd181c586db8dd300867b5ac9b297f80b297333ada00240485d3e870767cb",
                "md5": "86157fa133b4d02bad484899cd77eb63",
                "sha256": "3598da21957c3153f8deff709e6a911b5eda5391278491209a54c516d5e871e8"
            },
            "downloads": -1,
            "filename": "actualpy-0.15.0.tar.gz",
            "has_sig": false,
            "md5_digest": "86157fa133b4d02bad484899cd77eb63",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9.0",
            "size": 90945,
            "upload_time": "2025-08-22T17:22:36",
            "upload_time_iso_8601": "2025-08-22T17:22:36.441124Z",
            "url": "https://files.pythonhosted.org/packages/f3/9c/d181c586db8dd300867b5ac9b297f80b297333ada00240485d3e870767cb/actualpy-0.15.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-22 17:22:36",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "bvanelli",
    "github_project": "actualpy",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [
        {
            "name": "requests",
            "specs": [
                [
                    ">=",
                    "2"
                ]
            ]
        },
        {
            "name": "sqlmodel",
            "specs": [
                [
                    ">=",
                    "0.0.18"
                ]
            ]
        },
        {
            "name": "pydantic",
            "specs": [
                [
                    "<",
                    "3"
                ],
                [
                    ">=",
                    "2"
                ]
            ]
        },
        {
            "name": "sqlalchemy",
            "specs": [
                [
                    ">=",
                    "2"
                ]
            ]
        },
        {
            "name": "proto-plus",
            "specs": [
                [
                    ">=",
                    "1"
                ]
            ]
        },
        {
            "name": "protobuf",
            "specs": [
                [
                    ">=",
                    "4"
                ]
            ]
        },
        {
            "name": "cryptography",
            "specs": [
                [
                    ">=",
                    "42"
                ]
            ]
        },
        {
            "name": "python-dateutil",
            "specs": [
                [
                    ">=",
                    "2.9.0"
                ]
            ]
        },
        {
            "name": "rich",
            "specs": [
                [
                    ">=",
                    "13"
                ]
            ]
        },
        {
            "name": "typer",
            "specs": [
                [
                    ">=",
                    "0.12.0"
                ]
            ]
        },
        {
            "name": "pyyaml",
            "specs": [
                [
                    ">=",
                    "6.0"
                ]
            ]
        }
    ],
    "lcname": "actualpy"
}
        
Elapsed time: 1.33725s