sqlalchemy-cloudflare-d1 0.3.9__tar.gz → 0.3.10__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.
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/CHANGELOG.md +11 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/PKG-INFO +1 -1
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/examples/workers/src/entry.py +499 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/examples/workers/uv.lock +1 -1
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/pyproject.toml +1 -1
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/src/sqlalchemy_cloudflare_d1/__init__.py +3 -1
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/src/sqlalchemy_cloudflare_d1/compiler.py +54 -34
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/src/sqlalchemy_cloudflare_d1/connection.py +30 -26
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/src/sqlalchemy_cloudflare_d1/dialect.py +90 -11
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/src/sqlalchemy_cloudflare_d1/dialect_async.py +31 -39
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/tests/integration/test_restapi_integration.py +301 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/tests/integration/test_worker_integration.py +127 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/uv.lock +1 -1
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/.github/DEVELOPMENT.md +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/.github/workflows/ci.yml +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/.github/workflows/release.yml +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/.gitignore +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/.pre-commit-config.yaml +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/.python-version +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/LICENSE +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/Makefile +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/README.md +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/assets/d1-logo.png +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/assets/sqlalchemy-logo.png +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/examples/restapi/example.py +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/examples/workers/README.md +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/examples/workers/db_init.sql +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/examples/workers/pyproject.toml +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/examples/workers/wrangler.jsonc +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/src/sqlalchemy_cloudflare_d1/py.typed +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/tests/__init__.py +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/tests/conftest.py +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/tests/integration/test_base_url_integration.py +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/tests/test_utils.py +0 -0
- {sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/tests/unit/test_dialect.py +0 -0
|
@@ -13,6 +13,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
13
13
|
### Fixed
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
## [0.3.10]
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- `UUID` and `Enum` column type support ([#24](https://github.com/CollierKing/sqlalchemy-cloudflare-d1/issues/24))
|
|
21
|
+
- Added `D1UUID` type processor that converts `uuid.UUID` objects to hyphenated strings on bind and parses them back on result; supports UUID as primary key and nullable columns
|
|
22
|
+
- Added `D1Enum` type processor that converts Python `enum.Enum` objects to their string values on bind and reconstructs them on result; supports both string-value enums and Python enum classes
|
|
23
|
+
- Both types store as `TEXT` in D1 (SQLite-compatible)
|
|
24
|
+
- Works in both REST API and Worker modes
|
|
25
|
+
|
|
26
|
+
|
|
16
27
|
## [0.3.9]
|
|
17
28
|
|
|
18
29
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlalchemy-cloudflare-d1
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.10
|
|
4
4
|
Summary: A SQLAlchemy dialect for Cloudflare's D1 Serverless SQLite Database
|
|
5
5
|
Project-URL: Homepage, https://github.com/collierking/sqlalchemy-cloudflare-d1
|
|
6
6
|
Project-URL: Repository, https://github.com/collierking/sqlalchemy-cloudflare-d1
|
{sqlalchemy_cloudflare_d1-0.3.9 → sqlalchemy_cloudflare_d1-0.3.10}/examples/workers/src/entry.py
RENAMED
|
@@ -145,6 +145,24 @@ class Default(WorkerEntrypoint):
|
|
|
145
145
|
return await self.test_time_nullable()
|
|
146
146
|
elif path == "time-orm":
|
|
147
147
|
return await self.test_time_orm()
|
|
148
|
+
# UUID tests (GitHub issue #24)
|
|
149
|
+
elif path == "uuid-basic":
|
|
150
|
+
return await self.test_uuid_basic()
|
|
151
|
+
elif path == "uuid-nullable":
|
|
152
|
+
return await self.test_uuid_nullable()
|
|
153
|
+
elif path == "uuid-orm":
|
|
154
|
+
return await self.test_uuid_orm()
|
|
155
|
+
# Enum tests (GitHub issue #24)
|
|
156
|
+
elif path == "enum-basic":
|
|
157
|
+
return await self.test_enum_basic()
|
|
158
|
+
elif path == "enum-python-class":
|
|
159
|
+
return await self.test_enum_python_class()
|
|
160
|
+
elif path == "enum-nullable":
|
|
161
|
+
return await self.test_enum_nullable()
|
|
162
|
+
elif path == "enum-orm":
|
|
163
|
+
return await self.test_enum_orm()
|
|
164
|
+
elif path == "uuid-pk":
|
|
165
|
+
return await self.test_uuid_pk()
|
|
148
166
|
# Parallel query tests (GitHub issue #20)
|
|
149
167
|
elif path == "parallel-queries-engine":
|
|
150
168
|
return await self.test_parallel_queries_engine()
|
|
@@ -3642,6 +3660,487 @@ class Default(WorkerEntrypoint):
|
|
|
3642
3660
|
status=500,
|
|
3643
3661
|
)
|
|
3644
3662
|
|
|
3663
|
+
# MARK: - UUID Tests (GitHub issue #24)
|
|
3664
|
+
|
|
3665
|
+
async def test_uuid_basic(self):
|
|
3666
|
+
"""Test UUID column insert and retrieve."""
|
|
3667
|
+
import uuid as uuid_module
|
|
3668
|
+
from sqlalchemy import Column, Integer, MetaData, String, Table, Uuid, select
|
|
3669
|
+
|
|
3670
|
+
table_name = f"test_uuid_{uuid_module.uuid4().hex[:8]}"
|
|
3671
|
+
|
|
3672
|
+
try:
|
|
3673
|
+
engine = self.get_engine()
|
|
3674
|
+
metadata = MetaData()
|
|
3675
|
+
|
|
3676
|
+
test_table = Table(
|
|
3677
|
+
table_name,
|
|
3678
|
+
metadata,
|
|
3679
|
+
Column("id", Integer, primary_key=True),
|
|
3680
|
+
Column("title", String(127)),
|
|
3681
|
+
Column("item_uuid", Uuid(as_uuid=True)),
|
|
3682
|
+
)
|
|
3683
|
+
|
|
3684
|
+
metadata.create_all(engine)
|
|
3685
|
+
|
|
3686
|
+
test_uuid = uuid_module.uuid4()
|
|
3687
|
+
|
|
3688
|
+
with engine.connect() as conn:
|
|
3689
|
+
conn.execute(
|
|
3690
|
+
test_table.insert().values(title="Test", item_uuid=test_uuid)
|
|
3691
|
+
)
|
|
3692
|
+
conn.commit()
|
|
3693
|
+
|
|
3694
|
+
result = conn.execute(
|
|
3695
|
+
select(test_table.c.title, test_table.c.item_uuid)
|
|
3696
|
+
)
|
|
3697
|
+
row = result.fetchone()
|
|
3698
|
+
|
|
3699
|
+
metadata.drop_all(engine)
|
|
3700
|
+
|
|
3701
|
+
success = (
|
|
3702
|
+
row is not None
|
|
3703
|
+
and row[0] == "Test"
|
|
3704
|
+
and isinstance(row[1], uuid_module.UUID)
|
|
3705
|
+
and row[1] == test_uuid
|
|
3706
|
+
)
|
|
3707
|
+
|
|
3708
|
+
return Response.json(
|
|
3709
|
+
{
|
|
3710
|
+
"test": "uuid_basic",
|
|
3711
|
+
"success": success,
|
|
3712
|
+
"uuid_type": type(row[1]).__name__ if row else None,
|
|
3713
|
+
"uuid_match": row[1] == test_uuid if row else False,
|
|
3714
|
+
}
|
|
3715
|
+
)
|
|
3716
|
+
except Exception as e:
|
|
3717
|
+
try:
|
|
3718
|
+
metadata.drop_all(engine)
|
|
3719
|
+
except Exception:
|
|
3720
|
+
pass
|
|
3721
|
+
return Response.json(
|
|
3722
|
+
{"test": "uuid_basic", "success": False, "error": str(e)},
|
|
3723
|
+
status=500,
|
|
3724
|
+
)
|
|
3725
|
+
|
|
3726
|
+
async def test_uuid_nullable(self):
|
|
3727
|
+
"""Test nullable UUID columns handle NULL correctly."""
|
|
3728
|
+
import uuid as uuid_module
|
|
3729
|
+
from sqlalchemy import Column, Integer, MetaData, String, Table, Uuid, select
|
|
3730
|
+
|
|
3731
|
+
table_name = f"test_uuid_null_{uuid_module.uuid4().hex[:8]}"
|
|
3732
|
+
|
|
3733
|
+
try:
|
|
3734
|
+
engine = self.get_engine()
|
|
3735
|
+
metadata = MetaData()
|
|
3736
|
+
|
|
3737
|
+
test_table = Table(
|
|
3738
|
+
table_name,
|
|
3739
|
+
metadata,
|
|
3740
|
+
Column("id", Integer, primary_key=True),
|
|
3741
|
+
Column("title", String(127)),
|
|
3742
|
+
Column("item_uuid", Uuid(as_uuid=True), nullable=True),
|
|
3743
|
+
)
|
|
3744
|
+
|
|
3745
|
+
metadata.create_all(engine)
|
|
3746
|
+
|
|
3747
|
+
with engine.connect() as conn:
|
|
3748
|
+
conn.execute(
|
|
3749
|
+
test_table.insert().values(
|
|
3750
|
+
title="With UUID", item_uuid=uuid_module.uuid4()
|
|
3751
|
+
)
|
|
3752
|
+
)
|
|
3753
|
+
conn.execute(
|
|
3754
|
+
test_table.insert().values(title="No UUID", item_uuid=None)
|
|
3755
|
+
)
|
|
3756
|
+
conn.commit()
|
|
3757
|
+
|
|
3758
|
+
result = conn.execute(
|
|
3759
|
+
select(test_table.c.title, test_table.c.item_uuid).order_by(
|
|
3760
|
+
test_table.c.id
|
|
3761
|
+
)
|
|
3762
|
+
)
|
|
3763
|
+
rows = result.fetchall()
|
|
3764
|
+
|
|
3765
|
+
metadata.drop_all(engine)
|
|
3766
|
+
|
|
3767
|
+
success = (
|
|
3768
|
+
len(rows) == 2
|
|
3769
|
+
and isinstance(rows[0][1], uuid_module.UUID)
|
|
3770
|
+
and rows[1][1] is None
|
|
3771
|
+
)
|
|
3772
|
+
|
|
3773
|
+
return Response.json(
|
|
3774
|
+
{
|
|
3775
|
+
"test": "uuid_nullable",
|
|
3776
|
+
"success": success,
|
|
3777
|
+
"with_uuid_is_uuid": isinstance(rows[0][1], uuid_module.UUID)
|
|
3778
|
+
if rows
|
|
3779
|
+
else False,
|
|
3780
|
+
"no_uuid_is_none": rows[1][1] is None if len(rows) > 1 else False,
|
|
3781
|
+
}
|
|
3782
|
+
)
|
|
3783
|
+
except Exception as e:
|
|
3784
|
+
try:
|
|
3785
|
+
metadata.drop_all(engine)
|
|
3786
|
+
except Exception:
|
|
3787
|
+
pass
|
|
3788
|
+
return Response.json(
|
|
3789
|
+
{"test": "uuid_nullable", "success": False, "error": str(e)},
|
|
3790
|
+
status=500,
|
|
3791
|
+
)
|
|
3792
|
+
|
|
3793
|
+
async def test_uuid_orm(self):
|
|
3794
|
+
"""Test UUID via ORM session."""
|
|
3795
|
+
import uuid as uuid_module
|
|
3796
|
+
from sqlalchemy import Integer, String, Uuid
|
|
3797
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
|
|
3798
|
+
|
|
3799
|
+
engine = create_engine_from_binding(self.env.DB)
|
|
3800
|
+
table_name = f"test_uuid_orm_{uuid_module.uuid4().hex[:8]}"
|
|
3801
|
+
|
|
3802
|
+
try:
|
|
3803
|
+
|
|
3804
|
+
class Base(DeclarativeBase):
|
|
3805
|
+
pass
|
|
3806
|
+
|
|
3807
|
+
class Item(Base):
|
|
3808
|
+
__tablename__ = table_name
|
|
3809
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
3810
|
+
title: Mapped[str] = mapped_column(String(127))
|
|
3811
|
+
item_uuid: Mapped[uuid_module.UUID] = mapped_column(Uuid(as_uuid=True))
|
|
3812
|
+
|
|
3813
|
+
Base.metadata.create_all(engine)
|
|
3814
|
+
|
|
3815
|
+
test_uuid = uuid_module.uuid4()
|
|
3816
|
+
|
|
3817
|
+
with Session(engine) as session:
|
|
3818
|
+
item = Item(title="UUID ORM Test", item_uuid=test_uuid)
|
|
3819
|
+
session.add(item)
|
|
3820
|
+
session.commit()
|
|
3821
|
+
session.refresh(item)
|
|
3822
|
+
entry_title = item.title
|
|
3823
|
+
uuid_is_uuid = isinstance(item.item_uuid, uuid_module.UUID)
|
|
3824
|
+
uuid_match = item.item_uuid == test_uuid
|
|
3825
|
+
|
|
3826
|
+
Base.metadata.drop_all(engine)
|
|
3827
|
+
|
|
3828
|
+
return Response.json(
|
|
3829
|
+
{
|
|
3830
|
+
"test": "uuid_orm",
|
|
3831
|
+
"success": uuid_is_uuid and uuid_match,
|
|
3832
|
+
"entry_title": entry_title,
|
|
3833
|
+
"uuid_is_uuid": uuid_is_uuid,
|
|
3834
|
+
"uuid_match": uuid_match,
|
|
3835
|
+
}
|
|
3836
|
+
)
|
|
3837
|
+
except Exception as e:
|
|
3838
|
+
try:
|
|
3839
|
+
from sqlalchemy import MetaData, Table
|
|
3840
|
+
|
|
3841
|
+
md = MetaData()
|
|
3842
|
+
Table(table_name, md)
|
|
3843
|
+
md.drop_all(engine)
|
|
3844
|
+
except Exception:
|
|
3845
|
+
pass
|
|
3846
|
+
return Response.json(
|
|
3847
|
+
{"test": "uuid_orm", "success": False, "error": str(e)},
|
|
3848
|
+
status=500,
|
|
3849
|
+
)
|
|
3850
|
+
|
|
3851
|
+
async def test_uuid_pk(self):
|
|
3852
|
+
"""Test UUID used as primary key."""
|
|
3853
|
+
import uuid as uuid_module
|
|
3854
|
+
from sqlalchemy import Column, MetaData, String, Table, Uuid, select
|
|
3855
|
+
|
|
3856
|
+
table_name = f"test_uuid_pk_{uuid_module.uuid4().hex[:8]}"
|
|
3857
|
+
|
|
3858
|
+
try:
|
|
3859
|
+
engine = self.get_engine()
|
|
3860
|
+
metadata = MetaData()
|
|
3861
|
+
|
|
3862
|
+
test_table = Table(
|
|
3863
|
+
table_name,
|
|
3864
|
+
metadata,
|
|
3865
|
+
Column("id", Uuid(as_uuid=True), primary_key=True),
|
|
3866
|
+
Column("title", String(127)),
|
|
3867
|
+
)
|
|
3868
|
+
|
|
3869
|
+
metadata.create_all(engine)
|
|
3870
|
+
|
|
3871
|
+
pk_uuid = uuid_module.uuid4()
|
|
3872
|
+
|
|
3873
|
+
with engine.connect() as conn:
|
|
3874
|
+
conn.execute(test_table.insert().values(id=pk_uuid, title="PK Test"))
|
|
3875
|
+
conn.commit()
|
|
3876
|
+
|
|
3877
|
+
result = conn.execute(
|
|
3878
|
+
select(test_table).where(test_table.c.id == pk_uuid)
|
|
3879
|
+
)
|
|
3880
|
+
row = result.fetchone()
|
|
3881
|
+
|
|
3882
|
+
metadata.drop_all(engine)
|
|
3883
|
+
|
|
3884
|
+
success = (
|
|
3885
|
+
row is not None
|
|
3886
|
+
and isinstance(row[0], uuid_module.UUID)
|
|
3887
|
+
and row[0] == pk_uuid
|
|
3888
|
+
and row[1] == "PK Test"
|
|
3889
|
+
)
|
|
3890
|
+
|
|
3891
|
+
return Response.json(
|
|
3892
|
+
{
|
|
3893
|
+
"test": "uuid_pk",
|
|
3894
|
+
"success": success,
|
|
3895
|
+
"pk_is_uuid": isinstance(row[0], uuid_module.UUID)
|
|
3896
|
+
if row
|
|
3897
|
+
else False,
|
|
3898
|
+
"pk_match": row[0] == pk_uuid if row else False,
|
|
3899
|
+
}
|
|
3900
|
+
)
|
|
3901
|
+
except Exception as e:
|
|
3902
|
+
try:
|
|
3903
|
+
metadata.drop_all(engine)
|
|
3904
|
+
except Exception:
|
|
3905
|
+
pass
|
|
3906
|
+
return Response.json(
|
|
3907
|
+
{"test": "uuid_pk", "success": False, "error": str(e)},
|
|
3908
|
+
status=500,
|
|
3909
|
+
)
|
|
3910
|
+
|
|
3911
|
+
# MARK: - Enum Tests (GitHub issue #24)
|
|
3912
|
+
|
|
3913
|
+
async def test_enum_basic(self):
|
|
3914
|
+
"""Test Enum column with string values insert and retrieve."""
|
|
3915
|
+
import uuid as uuid_module
|
|
3916
|
+
from sqlalchemy import Column, Enum, Integer, MetaData, String, Table, select
|
|
3917
|
+
|
|
3918
|
+
table_name = f"test_enum_{uuid_module.uuid4().hex[:8]}"
|
|
3919
|
+
|
|
3920
|
+
try:
|
|
3921
|
+
engine = self.get_engine()
|
|
3922
|
+
metadata = MetaData()
|
|
3923
|
+
|
|
3924
|
+
test_table = Table(
|
|
3925
|
+
table_name,
|
|
3926
|
+
metadata,
|
|
3927
|
+
Column("id", Integer, primary_key=True),
|
|
3928
|
+
Column("title", String(127)),
|
|
3929
|
+
Column("status", Enum("active", "inactive", "pending")),
|
|
3930
|
+
)
|
|
3931
|
+
|
|
3932
|
+
metadata.create_all(engine)
|
|
3933
|
+
|
|
3934
|
+
with engine.connect() as conn:
|
|
3935
|
+
conn.execute(test_table.insert().values(title="Test", status="active"))
|
|
3936
|
+
conn.commit()
|
|
3937
|
+
|
|
3938
|
+
result = conn.execute(select(test_table.c.title, test_table.c.status))
|
|
3939
|
+
row = result.fetchone()
|
|
3940
|
+
|
|
3941
|
+
metadata.drop_all(engine)
|
|
3942
|
+
|
|
3943
|
+
success = row is not None and row[0] == "Test" and row[1] == "active"
|
|
3944
|
+
|
|
3945
|
+
return Response.json(
|
|
3946
|
+
{
|
|
3947
|
+
"test": "enum_basic",
|
|
3948
|
+
"success": success,
|
|
3949
|
+
"status_value": row[1] if row else None,
|
|
3950
|
+
"status_type": type(row[1]).__name__ if row else None,
|
|
3951
|
+
}
|
|
3952
|
+
)
|
|
3953
|
+
except Exception as e:
|
|
3954
|
+
try:
|
|
3955
|
+
metadata.drop_all(engine)
|
|
3956
|
+
except Exception:
|
|
3957
|
+
pass
|
|
3958
|
+
return Response.json(
|
|
3959
|
+
{"test": "enum_basic", "success": False, "error": str(e)},
|
|
3960
|
+
status=500,
|
|
3961
|
+
)
|
|
3962
|
+
|
|
3963
|
+
async def test_enum_python_class(self):
|
|
3964
|
+
"""Test Enum column with Python enum.Enum class."""
|
|
3965
|
+
import enum as enum_module
|
|
3966
|
+
import uuid as uuid_module
|
|
3967
|
+
from sqlalchemy import Column, Enum, Integer, MetaData, String, Table, select
|
|
3968
|
+
|
|
3969
|
+
class Status(enum_module.Enum):
|
|
3970
|
+
active = "active"
|
|
3971
|
+
inactive = "inactive"
|
|
3972
|
+
pending = "pending"
|
|
3973
|
+
|
|
3974
|
+
table_name = f"test_enum_cls_{uuid_module.uuid4().hex[:8]}"
|
|
3975
|
+
|
|
3976
|
+
try:
|
|
3977
|
+
engine = self.get_engine()
|
|
3978
|
+
metadata = MetaData()
|
|
3979
|
+
|
|
3980
|
+
test_table = Table(
|
|
3981
|
+
table_name,
|
|
3982
|
+
metadata,
|
|
3983
|
+
Column("id", Integer, primary_key=True),
|
|
3984
|
+
Column("title", String(127)),
|
|
3985
|
+
Column("status", Enum(Status)),
|
|
3986
|
+
)
|
|
3987
|
+
|
|
3988
|
+
metadata.create_all(engine)
|
|
3989
|
+
|
|
3990
|
+
with engine.connect() as conn:
|
|
3991
|
+
conn.execute(
|
|
3992
|
+
test_table.insert().values(title="Test", status=Status.active)
|
|
3993
|
+
)
|
|
3994
|
+
conn.commit()
|
|
3995
|
+
|
|
3996
|
+
result = conn.execute(select(test_table.c.title, test_table.c.status))
|
|
3997
|
+
row = result.fetchone()
|
|
3998
|
+
|
|
3999
|
+
metadata.drop_all(engine)
|
|
4000
|
+
|
|
4001
|
+
success = row is not None and row[0] == "Test" and row[1] == Status.active
|
|
4002
|
+
|
|
4003
|
+
return Response.json(
|
|
4004
|
+
{
|
|
4005
|
+
"test": "enum_python_class",
|
|
4006
|
+
"success": success,
|
|
4007
|
+
"status_is_enum": isinstance(row[1], Status) if row else False,
|
|
4008
|
+
"status_value": row[1].value
|
|
4009
|
+
if row and isinstance(row[1], Status)
|
|
4010
|
+
else str(row[1])
|
|
4011
|
+
if row
|
|
4012
|
+
else None,
|
|
4013
|
+
}
|
|
4014
|
+
)
|
|
4015
|
+
except Exception as e:
|
|
4016
|
+
try:
|
|
4017
|
+
metadata.drop_all(engine)
|
|
4018
|
+
except Exception:
|
|
4019
|
+
pass
|
|
4020
|
+
return Response.json(
|
|
4021
|
+
{"test": "enum_python_class", "success": False, "error": str(e)},
|
|
4022
|
+
status=500,
|
|
4023
|
+
)
|
|
4024
|
+
|
|
4025
|
+
async def test_enum_nullable(self):
|
|
4026
|
+
"""Test nullable Enum columns handle NULL correctly."""
|
|
4027
|
+
import uuid as uuid_module
|
|
4028
|
+
from sqlalchemy import Column, Enum, Integer, MetaData, String, Table, select
|
|
4029
|
+
|
|
4030
|
+
table_name = f"test_enum_null_{uuid_module.uuid4().hex[:8]}"
|
|
4031
|
+
|
|
4032
|
+
try:
|
|
4033
|
+
engine = self.get_engine()
|
|
4034
|
+
metadata = MetaData()
|
|
4035
|
+
|
|
4036
|
+
test_table = Table(
|
|
4037
|
+
table_name,
|
|
4038
|
+
metadata,
|
|
4039
|
+
Column("id", Integer, primary_key=True),
|
|
4040
|
+
Column("title", String(127)),
|
|
4041
|
+
Column("status", Enum("active", "inactive"), nullable=True),
|
|
4042
|
+
)
|
|
4043
|
+
|
|
4044
|
+
metadata.create_all(engine)
|
|
4045
|
+
|
|
4046
|
+
with engine.connect() as conn:
|
|
4047
|
+
conn.execute(
|
|
4048
|
+
test_table.insert().values(title="With Status", status="active")
|
|
4049
|
+
)
|
|
4050
|
+
conn.execute(test_table.insert().values(title="No Status", status=None))
|
|
4051
|
+
conn.commit()
|
|
4052
|
+
|
|
4053
|
+
result = conn.execute(
|
|
4054
|
+
select(test_table.c.title, test_table.c.status).order_by(
|
|
4055
|
+
test_table.c.id
|
|
4056
|
+
)
|
|
4057
|
+
)
|
|
4058
|
+
rows = result.fetchall()
|
|
4059
|
+
|
|
4060
|
+
metadata.drop_all(engine)
|
|
4061
|
+
|
|
4062
|
+
success = len(rows) == 2 and rows[0][1] == "active" and rows[1][1] is None
|
|
4063
|
+
|
|
4064
|
+
return Response.json(
|
|
4065
|
+
{
|
|
4066
|
+
"test": "enum_nullable",
|
|
4067
|
+
"success": success,
|
|
4068
|
+
"with_status_value": rows[0][1] if rows else None,
|
|
4069
|
+
"no_status_is_none": rows[1][1] is None if len(rows) > 1 else False,
|
|
4070
|
+
}
|
|
4071
|
+
)
|
|
4072
|
+
except Exception as e:
|
|
4073
|
+
try:
|
|
4074
|
+
metadata.drop_all(engine)
|
|
4075
|
+
except Exception:
|
|
4076
|
+
pass
|
|
4077
|
+
return Response.json(
|
|
4078
|
+
{"test": "enum_nullable", "success": False, "error": str(e)},
|
|
4079
|
+
status=500,
|
|
4080
|
+
)
|
|
4081
|
+
|
|
4082
|
+
async def test_enum_orm(self):
|
|
4083
|
+
"""Test Enum via ORM session with Python enum class."""
|
|
4084
|
+
import enum as enum_module
|
|
4085
|
+
import uuid as uuid_module
|
|
4086
|
+
from sqlalchemy import Enum, Integer, String
|
|
4087
|
+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, Session
|
|
4088
|
+
|
|
4089
|
+
class Priority(enum_module.Enum):
|
|
4090
|
+
low = "low"
|
|
4091
|
+
medium = "medium"
|
|
4092
|
+
high = "high"
|
|
4093
|
+
|
|
4094
|
+
engine = create_engine_from_binding(self.env.DB)
|
|
4095
|
+
table_name = f"test_enum_orm_{uuid_module.uuid4().hex[:8]}"
|
|
4096
|
+
|
|
4097
|
+
try:
|
|
4098
|
+
|
|
4099
|
+
class Base(DeclarativeBase):
|
|
4100
|
+
pass
|
|
4101
|
+
|
|
4102
|
+
class Task(Base):
|
|
4103
|
+
__tablename__ = table_name
|
|
4104
|
+
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
4105
|
+
title: Mapped[str] = mapped_column(String(127))
|
|
4106
|
+
priority: Mapped[Priority] = mapped_column(Enum(Priority))
|
|
4107
|
+
|
|
4108
|
+
Base.metadata.create_all(engine)
|
|
4109
|
+
|
|
4110
|
+
with Session(engine) as session:
|
|
4111
|
+
task = Task(title="Enum ORM Task", priority=Priority.high)
|
|
4112
|
+
session.add(task)
|
|
4113
|
+
session.commit()
|
|
4114
|
+
session.refresh(task)
|
|
4115
|
+
entry_title = task.title
|
|
4116
|
+
priority_is_enum = isinstance(task.priority, Priority)
|
|
4117
|
+
priority_match = task.priority == Priority.high
|
|
4118
|
+
|
|
4119
|
+
Base.metadata.drop_all(engine)
|
|
4120
|
+
|
|
4121
|
+
return Response.json(
|
|
4122
|
+
{
|
|
4123
|
+
"test": "enum_orm",
|
|
4124
|
+
"success": priority_is_enum and priority_match,
|
|
4125
|
+
"entry_title": entry_title,
|
|
4126
|
+
"priority_is_enum": priority_is_enum,
|
|
4127
|
+
"priority_match": priority_match,
|
|
4128
|
+
}
|
|
4129
|
+
)
|
|
4130
|
+
except Exception as e:
|
|
4131
|
+
try:
|
|
4132
|
+
from sqlalchemy import MetaData, Table
|
|
4133
|
+
|
|
4134
|
+
md = MetaData()
|
|
4135
|
+
Table(table_name, md)
|
|
4136
|
+
md.drop_all(engine)
|
|
4137
|
+
except Exception:
|
|
4138
|
+
pass
|
|
4139
|
+
return Response.json(
|
|
4140
|
+
{"test": "enum_orm", "success": False, "error": str(e)},
|
|
4141
|
+
status=500,
|
|
4142
|
+
)
|
|
4143
|
+
|
|
3645
4144
|
# MARK: - Parallel Query Tests (GitHub issue #20)
|
|
3646
4145
|
|
|
3647
4146
|
async def test_parallel_queries_engine(self):
|
|
@@ -15,6 +15,8 @@ Async usage (requires greenlet: pip install sqlalchemy-cloudflare-d1[async]):
|
|
|
15
15
|
engine = create_async_engine("cloudflare_d1+async://account_id:api_token@database_id")
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
18
20
|
from .dialect import CloudflareD1Dialect
|
|
19
21
|
from .connection import (
|
|
20
22
|
# Sync classes
|
|
@@ -47,7 +49,7 @@ from .connection import (
|
|
|
47
49
|
)
|
|
48
50
|
|
|
49
51
|
|
|
50
|
-
def __getattr__(name: str):
|
|
52
|
+
def __getattr__(name: str) -> Any:
|
|
51
53
|
"""Lazy import for async dialect to avoid requiring greenlet at import time.
|
|
52
54
|
|
|
53
55
|
The async SQLAlchemy dialect (CloudflareD1Dialect_async) requires greenlet
|