tigerstar 0.1.0__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.
Files changed (35) hide show
  1. tigerstar-0.1.0/.github/workflows/publish.yml +29 -0
  2. tigerstar-0.1.0/.gitignore +10 -0
  3. tigerstar-0.1.0/LICENSE +21 -0
  4. tigerstar-0.1.0/PKG-INFO +113 -0
  5. tigerstar-0.1.0/README.md +84 -0
  6. tigerstar-0.1.0/docs/adapters.md +204 -0
  7. tigerstar-0.1.0/docs/usage.md +377 -0
  8. tigerstar-0.1.0/example/__init__.py +0 -0
  9. tigerstar-0.1.0/example/main.py +51 -0
  10. tigerstar-0.1.0/pyproject.toml +40 -0
  11. tigerstar-0.1.0/tigerstar/__init__.py +21 -0
  12. tigerstar-0.1.0/tigerstar/accounts/__init__.py +4 -0
  13. tigerstar-0.1.0/tigerstar/accounts/model.py +26 -0
  14. tigerstar-0.1.0/tigerstar/accounts/service.py +34 -0
  15. tigerstar-0.1.0/tigerstar/core/__init__.py +27 -0
  16. tigerstar-0.1.0/tigerstar/core/exceptions.py +30 -0
  17. tigerstar-0.1.0/tigerstar/core/identity.py +22 -0
  18. tigerstar-0.1.0/tigerstar/core/types.py +37 -0
  19. tigerstar-0.1.0/tigerstar/engine.py +29 -0
  20. tigerstar-0.1.0/tigerstar/ledgers/__init__.py +4 -0
  21. tigerstar-0.1.0/tigerstar/ledgers/model.py +13 -0
  22. tigerstar-0.1.0/tigerstar/ledgers/service.py +17 -0
  23. tigerstar-0.1.0/tigerstar/reporting/__init__.py +3 -0
  24. tigerstar-0.1.0/tigerstar/reporting/service.py +96 -0
  25. tigerstar-0.1.0/tigerstar/storage/__init__.py +5 -0
  26. tigerstar-0.1.0/tigerstar/storage/base.py +79 -0
  27. tigerstar-0.1.0/tigerstar/storage/firestore.py +284 -0
  28. tigerstar-0.1.0/tigerstar/storage/postgres/001_initial.sql +72 -0
  29. tigerstar-0.1.0/tigerstar/storage/postgres/__init__.py +4 -0
  30. tigerstar-0.1.0/tigerstar/storage/postgres/migrations.py +27 -0
  31. tigerstar-0.1.0/tigerstar/storage/postgres/storage.py +336 -0
  32. tigerstar-0.1.0/tigerstar/transfers/__init__.py +5 -0
  33. tigerstar-0.1.0/tigerstar/transfers/model.py +25 -0
  34. tigerstar-0.1.0/tigerstar/transfers/posting.py +16 -0
  35. tigerstar-0.1.0/tigerstar/transfers/service.py +187 -0
@@ -0,0 +1,29 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ id-token: write
9
+
10
+ jobs:
11
+ publish:
12
+ runs-on: ubuntu-latest
13
+ environment: pypi
14
+
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.13"
21
+
22
+ - name: Install build tools
23
+ run: pip install hatchling build
24
+
25
+ - name: Build package
26
+ run: python -m build
27
+
28
+ - name: Publish to PyPI
29
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .idea/
4
+ .env
5
+ .venv/
6
+ env/
7
+ *.json
8
+ uv.lock
9
+ dist/
10
+ *.egg-info/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Eric Kweyunga
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,113 @@
1
+ Metadata-Version: 2.4
2
+ Name: tigerstar
3
+ Version: 0.1.0
4
+ Summary: Double-entry ledger engine with pluggable storage backends
5
+ Project-URL: Repository, https://github.com/erickweyunga/tigerstar
6
+ Project-URL: Documentation, https://github.com/erickweyunga/tigerstar/tree/main/docs
7
+ Author-email: Eric Kweyunga <maverickweyunga@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: accounting,double-entry,fintech,ledger,payments
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Office/Business :: Financial :: Accounting
18
+ Requires-Python: >=3.12
19
+ Provides-Extra: all
20
+ Requires-Dist: firebase-admin>=6.0; extra == 'all'
21
+ Requires-Dist: psycopg-pool>=3.1; extra == 'all'
22
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'all'
23
+ Provides-Extra: firestore
24
+ Requires-Dist: firebase-admin>=6.0; extra == 'firestore'
25
+ Provides-Extra: postgres
26
+ Requires-Dist: psycopg-pool>=3.1; extra == 'postgres'
27
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
28
+ Description-Content-Type: text/markdown
29
+
30
+ # TigerStar
31
+
32
+ A double-entry ledger engine with pluggable storage backends.
33
+
34
+ ## Features
35
+
36
+ - **Double-entry bookkeeping** — every transfer debits one account and credits another
37
+ - **Two-phase transfers** — reserve funds (pending), then post or void
38
+ - **Linked transfers** — atomic chains that all succeed or all fail
39
+ - **Balance constraints** — prevent overdrafts with account flags
40
+ - **Multi-currency** — separate ledgers per currency with configurable precision
41
+ - **Financial reporting** — trial balance, balance sheet, general ledger
42
+ - **Storage adapters** — swap between Firestore and PostgreSQL without changing application code
43
+ - **Concurrent-safe** — row-level locking (Postgres) or optimistic transactions (Firestore)
44
+
45
+ ## Install
46
+
47
+ ```bash
48
+ pip install tigerstar[postgres] # PostgreSQL only
49
+ pip install tigerstar[firestore] # Firestore only
50
+ pip install tigerstar[all] # both backends
51
+ ```
52
+
53
+ ## Quick Start
54
+
55
+ ### With Firestore
56
+
57
+ ```python
58
+ from tigerstar import TigerStar, FirestoreStorage
59
+
60
+ storage = FirestoreStorage(credentials_path="path/to/firebase-adminsdk.json")
61
+ engine = TigerStar(storage=storage)
62
+ ```
63
+
64
+ ### With PostgreSQL
65
+
66
+ ```python
67
+ from tigerstar import TigerStar, PostgresStorage
68
+
69
+ storage = PostgresStorage(dsn="postgresql://user:pass@localhost:5432/dbname")
70
+ storage.migrate()
71
+ engine = TigerStar(storage=storage)
72
+ ```
73
+
74
+ ### Create a ledger and move money
75
+
76
+ ```python
77
+ from tigerstar import Ledger, Account, AccountType, AccountFlags, Transfer, TransferCode
78
+
79
+ ledger = Ledger(currency="TZS", precision=0, max_transfer_amount=10_000_000)
80
+ engine.create_ledger(ledger)
81
+
82
+ cash = Account(ledger_id=ledger.id, code=AccountType.ASSET)
83
+ user = Account(
84
+ ledger_id=ledger.id,
85
+ code=AccountType.LIABILITY,
86
+ flags=AccountFlags(debits_must_not_exceed_credits=True),
87
+ )
88
+ engine.create_accounts([cash, user])
89
+
90
+ engine.create_transfers([
91
+ Transfer(
92
+ debit_account_id=cash.id,
93
+ credit_account_id=user.id,
94
+ ledger_id=ledger.id,
95
+ amount=50000,
96
+ code=TransferCode.PAYMENT,
97
+ ),
98
+ ])
99
+ ```
100
+
101
+ ## Storage Adapters
102
+
103
+ | Adapter | Database | Best For |
104
+ |---------|----------|----------|
105
+ | `FirestoreStorage` | Google Cloud Firestore | Serverless, zero-ops, auto-scaling |
106
+ | `PostgresStorage` | PostgreSQL | Financial-grade ACID, SQL flexibility, cost control |
107
+
108
+ Both adapters implement the same interface — swap one for the other with zero application changes. See [docs/adapters.md](docs/adapters.md) for connection pooling, migrations, and writing custom adapters.
109
+
110
+ ## Documentation
111
+
112
+ - [Usage Guide](docs/usage.md) — ledgers, accounts, transfers, reporting
113
+ - [Storage Adapters](docs/adapters.md) — setup, configuration, custom adapters
@@ -0,0 +1,84 @@
1
+ # TigerStar
2
+
3
+ A double-entry ledger engine with pluggable storage backends.
4
+
5
+ ## Features
6
+
7
+ - **Double-entry bookkeeping** — every transfer debits one account and credits another
8
+ - **Two-phase transfers** — reserve funds (pending), then post or void
9
+ - **Linked transfers** — atomic chains that all succeed or all fail
10
+ - **Balance constraints** — prevent overdrafts with account flags
11
+ - **Multi-currency** — separate ledgers per currency with configurable precision
12
+ - **Financial reporting** — trial balance, balance sheet, general ledger
13
+ - **Storage adapters** — swap between Firestore and PostgreSQL without changing application code
14
+ - **Concurrent-safe** — row-level locking (Postgres) or optimistic transactions (Firestore)
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install tigerstar[postgres] # PostgreSQL only
20
+ pip install tigerstar[firestore] # Firestore only
21
+ pip install tigerstar[all] # both backends
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### With Firestore
27
+
28
+ ```python
29
+ from tigerstar import TigerStar, FirestoreStorage
30
+
31
+ storage = FirestoreStorage(credentials_path="path/to/firebase-adminsdk.json")
32
+ engine = TigerStar(storage=storage)
33
+ ```
34
+
35
+ ### With PostgreSQL
36
+
37
+ ```python
38
+ from tigerstar import TigerStar, PostgresStorage
39
+
40
+ storage = PostgresStorage(dsn="postgresql://user:pass@localhost:5432/dbname")
41
+ storage.migrate()
42
+ engine = TigerStar(storage=storage)
43
+ ```
44
+
45
+ ### Create a ledger and move money
46
+
47
+ ```python
48
+ from tigerstar import Ledger, Account, AccountType, AccountFlags, Transfer, TransferCode
49
+
50
+ ledger = Ledger(currency="TZS", precision=0, max_transfer_amount=10_000_000)
51
+ engine.create_ledger(ledger)
52
+
53
+ cash = Account(ledger_id=ledger.id, code=AccountType.ASSET)
54
+ user = Account(
55
+ ledger_id=ledger.id,
56
+ code=AccountType.LIABILITY,
57
+ flags=AccountFlags(debits_must_not_exceed_credits=True),
58
+ )
59
+ engine.create_accounts([cash, user])
60
+
61
+ engine.create_transfers([
62
+ Transfer(
63
+ debit_account_id=cash.id,
64
+ credit_account_id=user.id,
65
+ ledger_id=ledger.id,
66
+ amount=50000,
67
+ code=TransferCode.PAYMENT,
68
+ ),
69
+ ])
70
+ ```
71
+
72
+ ## Storage Adapters
73
+
74
+ | Adapter | Database | Best For |
75
+ |---------|----------|----------|
76
+ | `FirestoreStorage` | Google Cloud Firestore | Serverless, zero-ops, auto-scaling |
77
+ | `PostgresStorage` | PostgreSQL | Financial-grade ACID, SQL flexibility, cost control |
78
+
79
+ Both adapters implement the same interface — swap one for the other with zero application changes. See [docs/adapters.md](docs/adapters.md) for connection pooling, migrations, and writing custom adapters.
80
+
81
+ ## Documentation
82
+
83
+ - [Usage Guide](docs/usage.md) — ledgers, accounts, transfers, reporting
84
+ - [Storage Adapters](docs/adapters.md) — setup, configuration, custom adapters
@@ -0,0 +1,204 @@
1
+ # Storage Adapters
2
+
3
+ TigerStar uses a storage adapter pattern. The engine doesn't care which database you use — swap the adapter and everything works the same.
4
+
5
+ ## Available Adapters
6
+
7
+ | Adapter | Database | Best For |
8
+ |---------|----------|----------|
9
+ | `FirestoreStorage` | Google Cloud Firestore | Serverless, zero-ops, auto-scaling |
10
+ | `PostgresStorage` | PostgreSQL | Financial-grade ACID, SQL flexibility, cost control |
11
+
12
+ ## Firestore Adapter
13
+
14
+ ```python
15
+ from tigerstar import TigerStar, FirestoreStorage
16
+
17
+ storage = FirestoreStorage(
18
+ credentials_path="path/to/firebase-adminsdk.json",
19
+ database_id="default", # optional, defaults to "default"
20
+ )
21
+ engine = TigerStar(storage=storage)
22
+ ```
23
+
24
+ ### Requirements
25
+
26
+ - `firebase-admin` package
27
+ - A Firebase project with Firestore enabled
28
+ - Service account JSON credentials
29
+
30
+ ### Collections Created
31
+
32
+ - `ledgers`
33
+ - `accounts`
34
+ - `transfers`
35
+ - `postings`
36
+
37
+ ### Notes
38
+
39
+ - Transactions use Firestore's built-in optimistic concurrency
40
+ - All reads happen before writes within a transaction
41
+ - Scales automatically — no infrastructure to manage
42
+ - Pay per read/write operation
43
+
44
+ ## PostgreSQL Adapter
45
+
46
+ ```python
47
+ from tigerstar import TigerStar, PostgresStorage
48
+
49
+ storage = PostgresStorage(
50
+ dsn="postgresql://user:password@localhost:5432/dbname",
51
+ min_size=5, # minimum pool connections
52
+ max_size=20, # maximum pool connections
53
+ )
54
+ storage.migrate() # create tables on first run
55
+ engine = TigerStar(storage=storage)
56
+ ```
57
+
58
+ ### Requirements
59
+
60
+ - `psycopg[binary]` and `psycopg-pool` packages
61
+ - PostgreSQL 15+ (recommended: 17+)
62
+
63
+ ### Connection Pooling
64
+
65
+ The adapter uses `psycopg_pool.ConnectionPool` internally. Connections are reused across requests — no per-operation connection overhead.
66
+
67
+ ```python
68
+ # Custom pool sizing for high-throughput
69
+ storage = PostgresStorage(
70
+ dsn="postgresql://user:pass@localhost/otta",
71
+ min_size=10, # keep 10 connections warm
72
+ max_size=50, # burst up to 50 under load
73
+ )
74
+ ```
75
+
76
+ For production with multiple app instances, put PgBouncer in front of PostgreSQL:
77
+
78
+ ```python
79
+ # Point at PgBouncer (transaction pooling mode)
80
+ storage = PostgresStorage(
81
+ dsn="postgresql://user:pass@localhost:6432/otta",
82
+ min_size=5,
83
+ max_size=20,
84
+ )
85
+ ```
86
+
87
+ ### Migrations
88
+
89
+ Tables are created via SQL migration files in `src/storage/postgres/`.
90
+
91
+ **Option 1 — via the storage instance:**
92
+
93
+ ```python
94
+ storage = PostgresStorage(dsn="postgresql://...")
95
+ storage.migrate()
96
+ ```
97
+
98
+ **Option 2 — import and run directly:**
99
+
100
+ ```python
101
+ import psycopg
102
+ from tigerstar.storage.postgres import run_migrations
103
+
104
+ conn = psycopg.connect("postgresql://user:pass@localhost/otta", autocommit=True)
105
+ run_migrations(conn)
106
+ conn.close()
107
+ ```
108
+
109
+ Migrations are versioned. Each `.sql` file runs once — tracked in a `schema_migrations` table. To add a new migration, create a new file like `002_add_column.sql` in the `src/storage/postgres/` folder.
110
+
111
+ ### Concurrency
112
+
113
+ - Uses `SELECT ... FOR UPDATE` with deterministic lock ordering
114
+ - Account IDs are sorted before locking — prevents deadlocks
115
+ - Ledger validation runs outside the transaction to minimize lock hold time
116
+ - Read Committed isolation level (default) + explicit row locks
117
+
118
+ ### Schema Constraints
119
+
120
+ The database enforces integrity at the SQL level:
121
+
122
+ - `CHECK (amount > 0)` — no zero or negative transfers
123
+ - `CHECK (debit_account_id != credit_account_id)` — can't transfer to self
124
+ - `CHECK (debits_pending >= 0 ...)` — balances never go negative
125
+ - Foreign keys on all references
126
+
127
+ ### Indexes
128
+
129
+ Optimized for ledger workloads:
130
+
131
+ - Covering index on `transfers(ledger_id, created_at)` — avoids heap lookups
132
+ - Partial index on pending transfers — fast timeout queries
133
+ - Composite index on `postings(account_id, created_at)` — fast general ledger queries
134
+
135
+ ## Writing a Custom Adapter
136
+
137
+ Implement `StorageBase`:
138
+
139
+ ```python
140
+ from tigerstar.storage.base import StorageBase
141
+
142
+ class MyStorage(StorageBase):
143
+
144
+ def save_ledger(self, ledger):
145
+ ...
146
+
147
+ def get_ledger(self, ledger_id):
148
+ ...
149
+
150
+ def save_account(self, account):
151
+ ...
152
+
153
+ def save_accounts_batch(self, accounts):
154
+ ...
155
+
156
+ def get_account(self, account_id):
157
+ ...
158
+
159
+ def get_accounts_batch(self, account_ids):
160
+ ...
161
+
162
+ def save_transfer(self, transfer):
163
+ ...
164
+
165
+ def get_transfer(self, transfer_id):
166
+ ...
167
+
168
+ def save_posting(self, posting):
169
+ ...
170
+
171
+ def get_postings_for_transfer(self, transfer_id):
172
+ ...
173
+
174
+ def execute_transfer(self, transfers, account_ids, pending_ids, process):
175
+ """
176
+ Must be atomic. Steps:
177
+ 1. Read pending transfers by pending_ids (if any)
178
+ 2. Read and lock accounts by account_ids
179
+ 3. Call process(accounts, pending_transfers) -> (results, updated_accounts, postings)
180
+ 4. Write updated accounts, new transfers, and postings
181
+ 5. Return results
182
+ All within a single transaction.
183
+ """
184
+ ...
185
+
186
+ def query_accounts_by_ledger(self, ledger_id):
187
+ ...
188
+
189
+ def query_transfers_by_ledger(self, ledger_id):
190
+ ...
191
+
192
+ def query_postings_by_account(self, account_id):
193
+ ...
194
+
195
+ def query_postings_all(self):
196
+ ...
197
+ ```
198
+
199
+ Then use it:
200
+
201
+ ```python
202
+ storage = MyStorage(...)
203
+ engine = TigerStar(storage=storage)
204
+ ```