Compare commits

..
3 Commits
Author SHA1 Message Date
mathias2784 d2d7c7fa74 black 2026-09-06 12:26:53 +02:00
mathias2784 bbe1406afb ruff 2026-09-06 12:25:37 +02:00
mathias2784 d1c22470bb b4w data factories and tests 2026-09-06 12:23:03 +02:00
15 changed files with 211 additions and 91 deletions
+41 -32
View File
@@ -5,47 +5,56 @@ Revises:
Create Date: 2026-09-04 14:52:00.681450 Create Date: 2026-09-04 14:52:00.681450
""" """
from typing import Sequence, Union
from alembic import op from collections.abc import Sequence
import sqlalchemy as sa import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = '605e46acc08e' revision: str = "605e46acc08e"
down_revision: Union[str, Sequence[str], None] = None down_revision: str | Sequence[str] | None = None
branch_labels: Union[str, Sequence[str], None] = None branch_labels: str | Sequence[str] | None = None
depends_on: Union[str, Sequence[str], None] = None depends_on: str | Sequence[str] | None = None
def upgrade() -> None: def upgrade() -> None:
"""Upgrade schema.""" """Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.create_table('category', op.create_table(
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), "category",
sa.Column('name', sa.String(), nullable=False), sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column('parent_id', sa.Integer(), nullable=True), sa.Column("name", sa.String(), nullable=False),
sa.ForeignKeyConstraint(['parent_id'], ['category.id'], ), sa.Column("parent_id", sa.Integer(), nullable=True),
sa.PrimaryKeyConstraint('id'), sa.ForeignKeyConstraint(
sa.UniqueConstraint('name', 'parent_id', name='uq_category_name_parent') ["parent_id"],
["category.id"],
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("name", "parent_id", name="uq_category_name_parent"),
) )
op.create_table('import_b4w', op.create_table(
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), "import_b4w",
sa.Column('id_b4w', sa.Integer(), nullable=False), sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column('account_iban', sa.String(), nullable=False), sa.Column("id_b4w", sa.Integer(), nullable=False),
sa.Column('account_currency', sa.String(length=3), nullable=False), sa.Column("account_iban", sa.String(), nullable=False),
sa.Column('date_valid', sa.Date(), nullable=False), sa.Column("account_currency", sa.String(length=3), nullable=False),
sa.Column('date_book', sa.Date(), nullable=True), sa.Column("date_valid", sa.Date(), nullable=False),
sa.Column('amount', sa.Numeric(precision=12, scale=2), nullable=False), sa.Column("date_book", sa.Date(), nullable=True),
sa.Column('amount_currency', sa.String(length=3), nullable=False), sa.Column("amount", sa.Numeric(precision=12, scale=2), nullable=False),
sa.Column('reference', sa.String(), nullable=True), sa.Column("amount_currency", sa.String(length=3), nullable=False),
sa.Column('other_name', sa.String(), nullable=True), sa.Column("reference", sa.String(), nullable=True),
sa.Column('other_iban', sa.String(), nullable=True), sa.Column("other_name", sa.String(), nullable=True),
sa.Column('category_id', sa.Integer(), nullable=True), sa.Column("other_iban", sa.String(), nullable=True),
sa.Column('notes', sa.String(), nullable=True), sa.Column("category_id", sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['category_id'], ['category.id'], ), sa.Column("notes", sa.String(), nullable=True),
sa.PrimaryKeyConstraint('id'), sa.ForeignKeyConstraint(
sa.UniqueConstraint('id_b4w') ["category_id"],
["category.id"],
),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("id_b4w"),
) )
# ### end Alembic commands ### # ### end Alembic commands ###
@@ -53,6 +62,6 @@ def upgrade() -> None:
def downgrade() -> None: def downgrade() -> None:
"""Downgrade schema.""" """Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
op.drop_table('import_b4w') op.drop_table("import_b4w")
op.drop_table('category') op.drop_table("category")
# ### end Alembic commands ### # ### end Alembic commands ###
+1
View File
@@ -30,5 +30,6 @@ build-backend = "uv_build"
[dependency-groups] [dependency-groups]
dev = [ dev = [
"black>=26.5.1",
"polyfactory>=3.3.0", "polyfactory>=3.3.0",
] ]
-2
View File
@@ -1,5 +1,3 @@
#!/bin/python
import os import os
from collections.abc import Generator from collections.abc import Generator
from contextlib import contextmanager from contextlib import contextmanager
+16 -4
View File
@@ -1,5 +1,3 @@
#!/bin/python
from typing import Any from typing import Any
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -49,7 +47,14 @@ def upsert_category(
"name": category.name, "name": category.name,
"parent": upsert_category(session, category.parent), "parent": upsert_category(session, category.parent),
} }
return _upsert(session, CategoryDb, parameters, parameters, do_update=False, do_commit=do_commit) return _upsert(
session,
CategoryDb,
parameters,
parameters,
do_update=False,
do_commit=do_commit,
)
def upsert_b4w( def upsert_b4w(
@@ -74,4 +79,11 @@ def upsert_b4w(
"notes": data.notes, "notes": data.notes,
} }
return _upsert(session, B4wDb, parameters_existing, parameters_new, do_update=do_update, do_commit=do_commit) return _upsert(
session,
B4wDb,
parameters_existing,
parameters_new,
do_update=do_update,
do_commit=do_commit,
)
+3 -4
View File
@@ -1,11 +1,9 @@
#!/bin/python
import argparse import argparse
from pathlib import Path from pathlib import Path
from src.reader import reader_b4w, reader_raw
from src.helper_db import upsert_b4w
from src.db import get_session from src.db import get_session
from src.helper_db import upsert_b4w
from src.reader import reader_b4w
def ingest_b4w(path: Path): def ingest_b4w(path: Path):
@@ -27,5 +25,6 @@ def main():
ingest_b4w(path) ingest_b4w(path)
if __name__ == "__main__": if __name__ == "__main__":
main() main()
+11 -9
View File
@@ -1,10 +1,8 @@
#!/bin/python
from datetime import date from datetime import date
from decimal import Decimal from decimal import Decimal
from enum import Enum from enum import Enum
from pydantic import BaseModel, Field, ValidationError, field_validator from pydantic import BaseModel, Field, field_validator
IBAN_PATTERN = r"^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$" IBAN_PATTERN = r"^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$"
@@ -57,12 +55,16 @@ class B4wExport(BaseModel):
@field_validator("category", mode="before") @field_validator("category", mode="before")
@classmethod @classmethod
def category_str_is_parsable(cls, category_str: str) -> Category | None: def category_str_is_parsable(
category = Category.from_str(category_str) cls, category_in: str | Category | None
assert category ) -> Category | None:
category_str_2 = category.to_str() if category_in is None or category_in == "":
if not category_str == category_str_2: return None
raise ValidationError(f"Category string cannot be parsed: {category_str}") if isinstance(category_in, Category):
return category_in
category = Category.from_str(category_in)
if category is None or category.to_str() != category_in:
raise ValueError(f"Category string cannot be parsed: {category_in!r}")
return category return category
-2
View File
@@ -1,5 +1,3 @@
#!/bin/python
from sqlalchemy import ( from sqlalchemy import (
Column, Column,
Date, Date,
-3
View File
@@ -1,6 +1,3 @@
#!/bin/python
import csv import csv
from pathlib import Path from pathlib import Path
-2
View File
@@ -1,5 +1,3 @@
#!/bin/python
import os import os
from collections.abc import Generator from collections.abc import Generator
+3 -8
View File
@@ -1,13 +1,8 @@
#!/bin/python
from polyfactory.factories.pydantic_factory import ModelFactory from polyfactory.factories.pydantic_factory import ModelFactory
from src.models import B4wExport from src.models import B4wExport
CATEGORIES = [ DEFAULT_CATEGORY = "root_category:parent_category:leaf_category"
"single_category",
"root_category:parent_category:leaf_category",
]
class B4wExportFactory(ModelFactory[B4wExport]):
__model__ = B4wExport class B4wExportFactory(ModelFactory[B4wExport]): ...
-2
View File
@@ -1,5 +1,3 @@
#!/bin/python
from sqlalchemy import inspect from sqlalchemy import inspect
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
+26 -13
View File
@@ -1,5 +1,3 @@
#!/bin/python
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -9,26 +7,41 @@ from src.models_db import CategoryDb
def test_upsert_category_returns_orm_object(db_session: Session): def test_upsert_category_returns_orm_object(db_session: Session):
result = upsert_category(db_session, Category.from_str("dummy_parent_1:dummy_parent_2:dummy"), do_commit=False) result = upsert_category(
db_session,
Category.from_str("dummy_parent_1:dummy_parent_2:dummy"),
do_commit=False,
)
assert result assert result
assert isinstance(result, CategoryDb) assert isinstance(result, CategoryDb)
assert str(result.name) == "dummy" assert str(result.name) == "dummy"
assert str(result.parent.name) == "dummy_parent_2" assert str(result.parent.name) == "dummy_parent_2"
assert str(result.parent.parent.name) == "dummy_parent_1" assert str(result.parent.parent.name) == "dummy_parent_1"
def test_upsert_category_writes_new_to_db(db_session: Session): def test_upsert_category_writes_new_to_db(db_session: Session):
upsert_category(db_session, Category.from_str("grand_father:father:new_category"), do_commit=True) upsert_category(
result = db_session.execute(select(CategoryDb).filter_by(name="new_category")).scalar_one() db_session,
Category.from_str("grand_father:father:new_category"),
do_commit=True,
)
result = db_session.execute(
select(CategoryDb).filter_by(name="new_category")
).scalar_one()
assert result assert result
assert str( result.name ) == "new_category" assert str(result.name) == "new_category"
assert str( result.parent.name ) == "father" assert str(result.parent.name) == "father"
assert str( result.parent.parent.name ) == "grand_father" assert str(result.parent.parent.name) == "grand_father"
def test_upsert_category_skips_existing(db_session: Session): def test_upsert_category_skips_existing(db_session: Session):
for _ in range(3): for _ in range(3):
upsert_category(db_session, Category.from_str("grand_father:father:try_again_and_again"), do_commit=True) upsert_category(
result = db_session.execute(select(CategoryDb).filter_by(name="try_again_and_again")).all() db_session,
Category.from_str("grand_father:father:try_again_and_again"),
do_commit=True,
)
result = db_session.execute(
select(CategoryDb).filter_by(name="try_again_and_again")
).all()
assert len(result) == 1 assert len(result) == 1
+13
View File
@@ -0,0 +1,13 @@
from src.models import B4wExport, Category
from tests.factories import B4wExportFactory
def test_b4wexport_simple():
data = B4wExportFactory.build()
assert isinstance(data, B4wExport)
def test_b4export_category():
data = B4wExportFactory.build(Category="fun:stuff")
assert isinstance(data.category, Category)
assert data.category.to_str() == "fun:stuff"
+12 -6
View File
@@ -1,5 +1,3 @@
#!/bin/python
from src.models import Category from src.models import Category
@@ -9,6 +7,7 @@ def test_single_level_category():
assert c.name == "single" assert c.name == "single"
assert c.parent is None assert c.parent is None
def test_two_level_category(): def test_two_level_category():
c = Category.from_str("first:second") c = Category.from_str("first:second")
assert c assert c
@@ -17,6 +16,7 @@ def test_two_level_category():
assert c.parent.name == "first" assert c.parent.name == "first"
assert c.parent.parent is None assert c.parent.parent is None
def test_three_level_category(): def test_three_level_category():
c = Category.from_str("first:second:third") c = Category.from_str("first:second:third")
assert c assert c
@@ -27,24 +27,30 @@ def test_three_level_category():
assert c.parent.parent.name == "first" assert c.parent.parent.name == "first"
assert c.parent.parent.parent is None assert c.parent.parent.parent is None
def test_single_level_category_to_string(): def test_single_level_category_to_string():
c = Category.from_str("single") c = Category.from_str("single")
assert c assert c
assert str(c) == "single" assert str(c) == "single"
def test_two_level_category_to_string(): def test_two_level_category_to_string():
c = Category(name = "second", parent=Category(name = "first")) c = Category(name="second", parent=Category(name="first"))
assert c.to_str() == "first:second" assert c.to_str() == "first:second"
def test_three_level_category_to_string(): def test_three_level_category_to_string():
c = Category(name = "third", parent=Category(name = "second", parent=Category(name="first"))) c = Category(
name="third", parent=Category(name="second", parent=Category(name="first"))
)
assert c.to_str() == "first:second:third" assert c.to_str() == "first:second:third"
def test_category_str(): def test_category_str():
c = Category.from_str("first:second:third") c = Category.from_str("first:second:third")
assert str(c) == "first:second:third" assert str(c) == "first:second:third"
def test_category_repr(): def test_category_repr():
c = Category.from_str("first:second:third") c = Category.from_str("first:second:third")
assert repr(c) == "Category.from_str(\"first:second:third\")" assert repr(c) == 'Category.from_str("first:second:third")'
Generated
+82 -1
View File
@@ -34,6 +34,37 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f2/94/3af39d34be01a24a6e65433d19e107099374224905f1e0cc6bbe1fd22a2f/argparse-1.4.0-py2.py3-none-any.whl", hash = "sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314", size = 23000, upload-time = "2015-09-14T16:03:16.137Z" }, { url = "https://files.pythonhosted.org/packages/f2/94/3af39d34be01a24a6e65433d19e107099374224905f1e0cc6bbe1fd22a2f/argparse-1.4.0-py2.py3-none-any.whl", hash = "sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314", size = 23000, upload-time = "2015-09-14T16:03:16.137Z" },
] ]
[[package]]
name = "black"
version = "26.5.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "mypy-extensions" },
{ name = "packaging" },
{ name = "pathspec" },
{ name = "platformdirs" },
{ name = "pytokens" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/37/5628dd55bf2b34257fc7603f0fe97c40e3aaf24265f416a9c85c95ca1436/black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", size = 679439, upload-time = "2026-05-18T16:53:36.107Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/16/a8da8eb208c51c7f4ce74609a45d0dcc6d8a2141e45e81ee5289d1bb0d59/black-26.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168", size = 2004800, upload-time = "2026-05-18T17:05:38.182Z" },
{ url = "https://files.pythonhosted.org/packages/11/8a/a479296a19e383b70a725882a6cf3d786540601ff03cabbaaf1cce864c5a/black-26.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3", size = 1815576, upload-time = "2026-05-18T17:05:40.309Z" },
{ url = "https://files.pythonhosted.org/packages/81/6b/cfaf3d39f25132c156a068f6b805576c9103a84086019507c70e1911ee7d/black-26.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18", size = 1877927, upload-time = "2026-05-18T17:05:42.463Z" },
{ url = "https://files.pythonhosted.org/packages/66/76/302e313964bcff7e28df329d39f84f5270095730d85ff0acc260610a0d82/black-26.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50", size = 1511860, upload-time = "2026-05-18T17:05:43.943Z" },
{ url = "https://files.pythonhosted.org/packages/27/4e/a3827e35e0e567f9f9ee59e2a0ab979267dca98718f25547ca8c6733afd4/black-26.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae", size = 1316632, upload-time = "2026-05-18T17:05:45.521Z" },
{ url = "https://files.pythonhosted.org/packages/94/51/f975cae76d44274cc2868dc9040ac5d58d464784610234455b4e7b19c6ef/black-26.5.1-py3-none-any.whl", hash = "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2", size = 213693, upload-time = "2026-05-18T16:53:33.964Z" },
]
[[package]]
name = "click"
version = "8.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c7/0e/7fa0ef50764b67090eca4114772a2abf8b6148198475e54c660b97caeee6/click-8.5.0.tar.gz", hash = "sha256:ba0d2089de75ea0310e2dde03160e6ca10009947fb95a182f9b54021bb272e34", size = 382235, upload-time = "2026-08-26T13:33:14.56Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/58/50/6c0d534c5f134586a8e1ba4e330569e32f057e33372ae556463212fb4cd3/click-8.5.0-py3-none-any.whl", hash = "sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360", size = 125251, upload-time = "2026-08-26T13:33:12.928Z" },
]
[[package]] [[package]]
name = "colorama" name = "colorama"
version = "0.4.6" version = "0.4.6"
@@ -82,6 +113,7 @@ dependencies = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "black" },
{ name = "polyfactory" }, { name = "polyfactory" },
] ]
@@ -97,7 +129,10 @@ requires-dist = [
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [{ name = "polyfactory", specifier = ">=3.3.0" }] dev = [
{ name = "black", specifier = ">=26.5.1" },
{ name = "polyfactory", specifier = ">=3.3.0" },
]
[[package]] [[package]]
name = "greenlet" name = "greenlet"
@@ -189,6 +224,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
] ]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "26.3" version = "26.3"
@@ -198,6 +242,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
] ]
[[package]]
name = "pathspec"
version = "1.1.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" },
]
[[package]]
name = "platformdirs"
version = "4.11.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/69/b7/802a56eca9f2fac455b8bab5375a2647b0f0e14a2cd63ef077de3c4a7658/platformdirs-4.11.7.tar.gz", hash = "sha256:4f41487eeeeeb07f3a6625e61d9bc0ae6809f92d3386dbd74392fbb76108104d", size = 35127, upload-time = "2026-09-01T13:35:10.502Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/27/6e/80993e10a0482f630cef528635789233224f36b1ffd11592aa15d13ff9ce/platformdirs-4.11.7-py3-none-any.whl", hash = "sha256:8a02cb259042c79d1cd0450facc2fe6dc9d303ae7901afbe33bf8ea0b188cef6", size = 23938, upload-time = "2026-09-01T13:35:09.02Z" },
]
[[package]] [[package]]
name = "pluggy" name = "pluggy"
version = "1.6.0" version = "1.6.0"
@@ -310,6 +372,25 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" }, { url = "https://files.pythonhosted.org/packages/0d/17/c5c6b53ddc18f297992099b3d9ec16c855c0ccc83263a21fe4d1c625ec6c/python_dotenv-1.2.3-py3-none-any.whl", hash = "sha256:904552145e8bfed22162c09dab1c2b9b54fefa7b23ba780f4f26ca0316b0f0d9", size = 22780, upload-time = "2026-08-16T16:54:52.473Z" },
] ]
[[package]]
name = "pytokens"
version = "0.4.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" },
{ url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" },
{ url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" },
{ url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" },
{ url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" },
{ url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" },
{ url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" },
{ url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" },
{ url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" },
{ url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" },
{ url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" },
]
[[package]] [[package]]
name = "sqlalchemy" name = "sqlalchemy"
version = "2.0.52" version = "2.0.52"