sqlalchemy-postgres-point 0.1.0__tar.gz → 0.2.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.
- {sqlalchemy_postgres_point-0.1.0 → sqlalchemy_postgres_point-0.2.0}/PKG-INFO +85 -2
- sqlalchemy_postgres_point-0.2.0/README.md +187 -0
- {sqlalchemy_postgres_point-0.1.0 → sqlalchemy_postgres_point-0.2.0}/pyproject.toml +28 -6
- sqlalchemy_postgres_point-0.2.0/sqlalchemy_postgres_point/alembic_integration.py +56 -0
- sqlalchemy_postgres_point-0.1.0/README.md +0 -104
- {sqlalchemy_postgres_point-0.1.0 → sqlalchemy_postgres_point-0.2.0}/sqlalchemy_postgres_point/__init__.py +0 -0
- {sqlalchemy_postgres_point-0.1.0 → sqlalchemy_postgres_point-0.2.0}/sqlalchemy_postgres_point/point.py +0 -0
- {sqlalchemy_postgres_point-0.1.0 → sqlalchemy_postgres_point-0.2.0}/sqlalchemy_postgres_point/utils.py +0 -0
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sqlalchemy-postgres-point
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Lightweight SQLAlchemy custom type for PostgreSQL POINT columns with tuple binding and an earth-distance comparator.
|
|
5
5
|
Keywords: sqlalchemy,postgresql,point,geometry,geospatial,gis,database,types,earthdistance
|
|
6
6
|
Author: Michael Bianco
|
|
7
7
|
Author-email: Michael Bianco <mike@mikebian.co>
|
|
8
8
|
Requires-Dist: sqlalchemy>=2.0
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.12
|
|
10
10
|
Project-URL: Repository, https://github.com/iloveitaly/sqlalchemy-postgres-point
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
|
|
13
|
+
[](https://github.com/iloveitaly/sqlalchemy-postgres-point/releases)
|
|
14
|
+
[](https://pepy.tech/project/sqlalchemy-postgres-point)
|
|
15
|
+

|
|
16
|
+
[](https://opensource.org/licenses/MIT)
|
|
17
|
+
|
|
13
18
|
sqlalchemy-postgres-point
|
|
14
19
|
=========================
|
|
15
20
|
|
|
@@ -72,6 +77,54 @@ stmt = select(Place.id, Place.location.earth_distance(origin).label("dist"))
|
|
|
72
77
|
|
|
73
78
|
The comparator translates `Place.location.earth_distance(origin)` into SQL using the `<@>` operator (requires PostgreSQL with the `cube` / `earthdistance` extension for meaningful results; without extensions the operator may not exist—adapt as needed for your environment). This library only *emits* the operator; it does not manage PostgreSQL extensions.
|
|
74
79
|
|
|
80
|
+
## PostgreSQL Extensions Setup
|
|
81
|
+
|
|
82
|
+
To use the earth distance functionality (`earth_distance()` comparator), you need to enable the `cube` and `earthdistance` PostgreSQL extensions. These extensions provide spatial operations for calculating distances between geographic points on Earth's surface using a spherical model.
|
|
83
|
+
|
|
84
|
+
### Alembic Migration Example
|
|
85
|
+
|
|
86
|
+
If you're using Alembic for database migrations, you can create a migration to enable these extensions:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
"""Add PostgreSQL extensions for earth distance calculations
|
|
90
|
+
|
|
91
|
+
Revision ID: your_revision_id
|
|
92
|
+
Revises: your_previous_revision
|
|
93
|
+
Create Date: 2025-01-XX XX:XX:XX.XXXXXX
|
|
94
|
+
|
|
95
|
+
"""
|
|
96
|
+
from typing import Sequence, Union
|
|
97
|
+
|
|
98
|
+
from alembic import op
|
|
99
|
+
import sqlalchemy as sa
|
|
100
|
+
|
|
101
|
+
# revision identifiers, used by Alembic.
|
|
102
|
+
revision: str = 'your_revision_id'
|
|
103
|
+
down_revision: Union[str, None] = 'your_previous_revision'
|
|
104
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
|
105
|
+
depends_on: Union[str, Sequence[str], None] = None
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def upgrade() -> None:
|
|
109
|
+
op.execute("""
|
|
110
|
+
CREATE EXTENSION IF NOT EXISTS cube;
|
|
111
|
+
CREATE EXTENSION IF NOT EXISTS earthdistance;
|
|
112
|
+
""")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def downgrade() -> None:
|
|
116
|
+
op.execute("""
|
|
117
|
+
DROP EXTENSION IF EXISTS earthdistance;
|
|
118
|
+
DROP EXTENSION IF EXISTS cube;
|
|
119
|
+
""")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Integration Steps:**
|
|
123
|
+
|
|
124
|
+
1. Generate a new migration: `alembic revision -m "add_postgres_extensions"`
|
|
125
|
+
2. Copy the `upgrade()` and `downgrade()` functions above into your new migration file
|
|
126
|
+
3. Run the migration: `alembic upgrade head`
|
|
127
|
+
|
|
75
128
|
Returned Python Values
|
|
76
129
|
----------------------
|
|
77
130
|
|
|
@@ -86,6 +139,33 @@ Run the test suite with:
|
|
|
86
139
|
uv run pytest -q
|
|
87
140
|
```
|
|
88
141
|
|
|
142
|
+
## Alembic Integration
|
|
143
|
+
|
|
144
|
+
When using `PointType` in your models, you can automatically include the necessary imports in migration files by updating your `alembic/env.py` file.
|
|
145
|
+
|
|
146
|
+
First, import the integration module:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
import sqlalchemy_postgres_point.alembic_integration
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Then, ensure your `context.configure` call includes a `render_item` hook to handle the `PointType`:
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
def render_item(type_, obj, autogen_context):
|
|
156
|
+
if type_ == "type" and type(obj).__name__ == "PointType":
|
|
157
|
+
return sqlalchemy_postgres_point.alembic_integration.render_point_type(obj, obj, autogen_context)
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
# ... in run_migrations_online and run_migrations_offline ...
|
|
161
|
+
context.configure(
|
|
162
|
+
# ... other options ...
|
|
163
|
+
render_item=render_item,
|
|
164
|
+
)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Once added, `from sqlalchemy_postgres_point import PointType` will be automatically included in generated migration files whenever a `PointType` column is detected.
|
|
168
|
+
|
|
89
169
|
Development
|
|
90
170
|
-----------
|
|
91
171
|
|
|
@@ -114,3 +194,6 @@ License
|
|
|
114
194
|
|
|
115
195
|
MIT (see your project's LICENSE file if added later). Contributions welcome.
|
|
116
196
|
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
*This project was created from [iloveitaly/python-package-template](https://github.com/iloveitaly/python-package-template)*
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
[](https://github.com/iloveitaly/sqlalchemy-postgres-point/releases)
|
|
2
|
+
[](https://pepy.tech/project/sqlalchemy-postgres-point)
|
|
3
|
+

|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
sqlalchemy-postgres-point
|
|
7
|
+
=========================
|
|
8
|
+
|
|
9
|
+
Lightweight, pure-Python SQLAlchemy custom type for PostgreSQL `POINT` columns.
|
|
10
|
+
|
|
11
|
+
Why
|
|
12
|
+
----
|
|
13
|
+
|
|
14
|
+
PostgreSQL has a native `POINT` type (stored internally as a pair of float8 values). SQLAlchemy does not ship a dedicated high-level type wrapper for simple geometric primitives. This package provides a very small `PointType` you can use immediately without pulling in a full spatial stack (e.g. PostGIS + GeoAlchemy2) when all you need is storing and retrieving `(longitude, latitude)` pairs.
|
|
15
|
+
|
|
16
|
+
Features
|
|
17
|
+
--------
|
|
18
|
+
|
|
19
|
+
* Simple `(lng, lat)` tuple binding and result conversion.
|
|
20
|
+
* Safe NULL handling.
|
|
21
|
+
* Literal rendering for DDL / SQL emission.
|
|
22
|
+
* Custom comparator exposing the PostgreSQL earth-distance `<@>` operator (returns a `Float`).
|
|
23
|
+
* `cache_ok = True` for SQLAlchemy 2.x compilation caching.
|
|
24
|
+
|
|
25
|
+
Installation
|
|
26
|
+
------------
|
|
27
|
+
|
|
28
|
+
Using `uv` (recommended):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uv add sqlalchemy-postgres-point
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Or with pip:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install sqlalchemy-postgres-point
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Usage
|
|
41
|
+
-----
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from sqlalchemy import Column, Integer
|
|
45
|
+
from sqlalchemy.orm import DeclarativeBase
|
|
46
|
+
from sqlalchemy_postgres_point import PointType
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Base(DeclarativeBase):
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Place(Base):
|
|
54
|
+
__tablename__ = "places"
|
|
55
|
+
id = Column(Integer, primary_key=True)
|
|
56
|
+
# Store as (longitude, latitude)
|
|
57
|
+
location = Column(PointType)
|
|
58
|
+
|
|
59
|
+
# Example query using the custom comparator
|
|
60
|
+
from sqlalchemy import select
|
|
61
|
+
|
|
62
|
+
origin = (0.0, 0.0)
|
|
63
|
+
stmt = select(Place.id, Place.location.earth_distance(origin).label("dist"))
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The comparator translates `Place.location.earth_distance(origin)` into SQL using the `<@>` operator (requires PostgreSQL with the `cube` / `earthdistance` extension for meaningful results; without extensions the operator may not exist—adapt as needed for your environment). This library only *emits* the operator; it does not manage PostgreSQL extensions.
|
|
67
|
+
|
|
68
|
+
## PostgreSQL Extensions Setup
|
|
69
|
+
|
|
70
|
+
To use the earth distance functionality (`earth_distance()` comparator), you need to enable the `cube` and `earthdistance` PostgreSQL extensions. These extensions provide spatial operations for calculating distances between geographic points on Earth's surface using a spherical model.
|
|
71
|
+
|
|
72
|
+
### Alembic Migration Example
|
|
73
|
+
|
|
74
|
+
If you're using Alembic for database migrations, you can create a migration to enable these extensions:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
"""Add PostgreSQL extensions for earth distance calculations
|
|
78
|
+
|
|
79
|
+
Revision ID: your_revision_id
|
|
80
|
+
Revises: your_previous_revision
|
|
81
|
+
Create Date: 2025-01-XX XX:XX:XX.XXXXXX
|
|
82
|
+
|
|
83
|
+
"""
|
|
84
|
+
from typing import Sequence, Union
|
|
85
|
+
|
|
86
|
+
from alembic import op
|
|
87
|
+
import sqlalchemy as sa
|
|
88
|
+
|
|
89
|
+
# revision identifiers, used by Alembic.
|
|
90
|
+
revision: str = 'your_revision_id'
|
|
91
|
+
down_revision: Union[str, None] = 'your_previous_revision'
|
|
92
|
+
branch_labels: Union[str, Sequence[str], None] = None
|
|
93
|
+
depends_on: Union[str, Sequence[str], None] = None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def upgrade() -> None:
|
|
97
|
+
op.execute("""
|
|
98
|
+
CREATE EXTENSION IF NOT EXISTS cube;
|
|
99
|
+
CREATE EXTENSION IF NOT EXISTS earthdistance;
|
|
100
|
+
""")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def downgrade() -> None:
|
|
104
|
+
op.execute("""
|
|
105
|
+
DROP EXTENSION IF EXISTS earthdistance;
|
|
106
|
+
DROP EXTENSION IF EXISTS cube;
|
|
107
|
+
""")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Integration Steps:**
|
|
111
|
+
|
|
112
|
+
1. Generate a new migration: `alembic revision -m "add_postgres_extensions"`
|
|
113
|
+
2. Copy the `upgrade()` and `downgrade()` functions above into your new migration file
|
|
114
|
+
3. Run the migration: `alembic upgrade head`
|
|
115
|
+
|
|
116
|
+
Returned Python Values
|
|
117
|
+
----------------------
|
|
118
|
+
|
|
119
|
+
Values are loaded as a 2-tuple of floats `(lng, lat)` or `None` when NULL.
|
|
120
|
+
|
|
121
|
+
Testing
|
|
122
|
+
-------
|
|
123
|
+
|
|
124
|
+
Run the test suite with:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
uv run pytest -q
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Alembic Integration
|
|
131
|
+
|
|
132
|
+
When using `PointType` in your models, you can automatically include the necessary imports in migration files by updating your `alembic/env.py` file.
|
|
133
|
+
|
|
134
|
+
First, import the integration module:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
import sqlalchemy_postgres_point.alembic_integration
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Then, ensure your `context.configure` call includes a `render_item` hook to handle the `PointType`:
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
def render_item(type_, obj, autogen_context):
|
|
144
|
+
if type_ == "type" and type(obj).__name__ == "PointType":
|
|
145
|
+
return sqlalchemy_postgres_point.alembic_integration.render_point_type(obj, obj, autogen_context)
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
# ... in run_migrations_online and run_migrations_offline ...
|
|
149
|
+
context.configure(
|
|
150
|
+
# ... other options ...
|
|
151
|
+
render_item=render_item,
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Once added, `from sqlalchemy_postgres_point import PointType` will be automatically included in generated migration files whenever a `PointType` column is detected.
|
|
156
|
+
|
|
157
|
+
Development
|
|
158
|
+
-----------
|
|
159
|
+
|
|
160
|
+
After cloning:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
uv sync # installs runtime + dev deps
|
|
164
|
+
uv run pytest -q
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Project Structure
|
|
168
|
+
-----------------
|
|
169
|
+
|
|
170
|
+
* `sqlalchemy_postgres_point/point.py` – Implementation of `PointType`.
|
|
171
|
+
* `tests/test_point.py` – Unit tests for processors and comparator.
|
|
172
|
+
|
|
173
|
+
Limitations / Notes
|
|
174
|
+
-------------------
|
|
175
|
+
|
|
176
|
+
* No automatic validation of longitude/latitude ranges; add your own if required.
|
|
177
|
+
* Uses simple textual representation `(lng,lat)` accepted by PostgreSQL `POINT` input parser.
|
|
178
|
+
* If you need advanced spatial indexing / SRID support, look at GeoAlchemy2/PostGIS instead.
|
|
179
|
+
|
|
180
|
+
License
|
|
181
|
+
-------
|
|
182
|
+
|
|
183
|
+
MIT (see your project's LICENSE file if added later). Contributions welcome.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
*This project was created from [iloveitaly/python-package-template](https://github.com/iloveitaly/python-package-template)*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlalchemy-postgres-point"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "Lightweight SQLAlchemy custom type for PostgreSQL POINT columns with tuple binding and an earth-distance comparator."
|
|
5
5
|
keywords = [
|
|
6
6
|
"sqlalchemy",
|
|
@@ -14,14 +14,14 @@ keywords = [
|
|
|
14
14
|
"earthdistance",
|
|
15
15
|
]
|
|
16
16
|
readme = "README.md"
|
|
17
|
-
requires-python = ">=3.
|
|
17
|
+
requires-python = ">=3.12"
|
|
18
18
|
dependencies = ["sqlalchemy>=2.0"]
|
|
19
19
|
authors = [{ name = "Michael Bianco", email = "mike@mikebian.co" }]
|
|
20
20
|
urls = { "Repository" = "https://github.com/iloveitaly/sqlalchemy-postgres-point" }
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
[build-system]
|
|
24
|
-
requires = ["uv_build>=0.
|
|
24
|
+
requires = ["uv_build>=0.10.0"]
|
|
25
25
|
build-backend = "uv_build"
|
|
26
26
|
|
|
27
27
|
[tool.uv.build-backend]
|
|
@@ -30,8 +30,30 @@ module-root = ""
|
|
|
30
30
|
|
|
31
31
|
[dependency-groups]
|
|
32
32
|
dev = [
|
|
33
|
-
"pytest>=8.3.
|
|
34
|
-
"sqlmodel>=0.0.
|
|
33
|
+
"pytest>=8.3.4",
|
|
34
|
+
"sqlmodel>=0.0.32",
|
|
35
35
|
"psycopg[binary]>=3.2.0",
|
|
36
|
-
"activemodel",
|
|
36
|
+
"activemodel>=0.15.0",
|
|
37
|
+
"pyright[nodejs]>=1.1.408",
|
|
38
|
+
"ruff>=0.15.0",
|
|
39
|
+
"coverage>=7.13.4",
|
|
40
|
+
"pytest-cov>=7.0.0",
|
|
41
|
+
"covdefaults>=2.3.0",
|
|
42
|
+
"alembic>=1.16.5",
|
|
37
43
|
]
|
|
44
|
+
|
|
45
|
+
[tool.pyright]
|
|
46
|
+
exclude = ["examples/", "playground/", "tmp/", ".venv/", "tests/"]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
addopts = "--cov --cov-report=term-missing --cov-report=html:tmp/htmlcov"
|
|
50
|
+
|
|
51
|
+
[tool.coverage.run]
|
|
52
|
+
plugins = ["covdefaults"]
|
|
53
|
+
source = ["sqlalchemy_postgres_point"]
|
|
54
|
+
|
|
55
|
+
[tool.coverage.report]
|
|
56
|
+
fail_under = 50
|
|
57
|
+
|
|
58
|
+
[tool.ruff]
|
|
59
|
+
extend-exclude = ["playground.py", "playground/"]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Alembic integration to automatically add imports for PointType in migration files.
|
|
3
|
+
|
|
4
|
+
This module hooks into Alembic's autogenerate system to automatically include
|
|
5
|
+
the necessary import statement when PointType is detected in models.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
Add this import to your alembic/env.py file:
|
|
9
|
+
|
|
10
|
+
import sqlalchemy_postgres_point.alembic_integration
|
|
11
|
+
|
|
12
|
+
Once added, `from sqlalchemy_postgres_point import PointType` will be automatically
|
|
13
|
+
included in generated migration files.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import alembic.autogenerate
|
|
17
|
+
import alembic.operations.ops
|
|
18
|
+
from sqlalchemy.types import UserDefinedType
|
|
19
|
+
|
|
20
|
+
from .point import PointType
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def render_point_type(type_, object_, autogen_context):
|
|
24
|
+
"""Render PointType in migration files with proper import."""
|
|
25
|
+
# Add the import to the migration file
|
|
26
|
+
autogen_context.imports.add("from sqlalchemy_postgres_point import PointType")
|
|
27
|
+
return "PointType()"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Register the renderer for PointType
|
|
31
|
+
alembic.autogenerate.render.renderers.dispatch_for(PointType, replace=True)(
|
|
32
|
+
render_point_type
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Also register for UserDefinedType as a fallback if the class doesn't match exactly
|
|
37
|
+
@alembic.autogenerate.render.renderers.dispatch_for(UserDefinedType, replace=True)
|
|
38
|
+
def render_user_defined_type(type_, object_, autogen_context):
|
|
39
|
+
if type(object_).__name__ == "PointType":
|
|
40
|
+
return render_point_type(type_, object_, autogen_context)
|
|
41
|
+
return False
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@alembic.autogenerate.comparators.dispatch_for("schema")
|
|
45
|
+
def compare_point_types(
|
|
46
|
+
autogen_context: alembic.autogenerate.api.AutogenContext,
|
|
47
|
+
upgrade_ops: alembic.operations.ops.UpgradeOps,
|
|
48
|
+
schema_names,
|
|
49
|
+
):
|
|
50
|
+
"""
|
|
51
|
+
Hook into Alembic's schema comparison to detect PointType usage.
|
|
52
|
+
|
|
53
|
+
This ensures the import is added even if PointType is used in existing
|
|
54
|
+
columns that might not trigger the render hook directly.
|
|
55
|
+
"""
|
|
56
|
+
pass
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
sqlalchemy-postgres-point
|
|
2
|
-
=========================
|
|
3
|
-
|
|
4
|
-
Lightweight, pure-Python SQLAlchemy custom type for PostgreSQL `POINT` columns.
|
|
5
|
-
|
|
6
|
-
Why
|
|
7
|
-
----
|
|
8
|
-
|
|
9
|
-
PostgreSQL has a native `POINT` type (stored internally as a pair of float8 values). SQLAlchemy does not ship a dedicated high-level type wrapper for simple geometric primitives. This package provides a very small `PointType` you can use immediately without pulling in a full spatial stack (e.g. PostGIS + GeoAlchemy2) when all you need is storing and retrieving `(longitude, latitude)` pairs.
|
|
10
|
-
|
|
11
|
-
Features
|
|
12
|
-
--------
|
|
13
|
-
|
|
14
|
-
* Simple `(lng, lat)` tuple binding and result conversion.
|
|
15
|
-
* Safe NULL handling.
|
|
16
|
-
* Literal rendering for DDL / SQL emission.
|
|
17
|
-
* Custom comparator exposing the PostgreSQL earth-distance `<@>` operator (returns a `Float`).
|
|
18
|
-
* `cache_ok = True` for SQLAlchemy 2.x compilation caching.
|
|
19
|
-
|
|
20
|
-
Installation
|
|
21
|
-
------------
|
|
22
|
-
|
|
23
|
-
Using `uv` (recommended):
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
uv add sqlalchemy-postgres-point
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Or with pip:
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
pip install sqlalchemy-postgres-point
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Usage
|
|
36
|
-
-----
|
|
37
|
-
|
|
38
|
-
```python
|
|
39
|
-
from sqlalchemy import Column, Integer
|
|
40
|
-
from sqlalchemy.orm import DeclarativeBase
|
|
41
|
-
from sqlalchemy_postgres_point import PointType
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
class Base(DeclarativeBase):
|
|
45
|
-
pass
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
class Place(Base):
|
|
49
|
-
__tablename__ = "places"
|
|
50
|
-
id = Column(Integer, primary_key=True)
|
|
51
|
-
# Store as (longitude, latitude)
|
|
52
|
-
location = Column(PointType)
|
|
53
|
-
|
|
54
|
-
# Example query using the custom comparator
|
|
55
|
-
from sqlalchemy import select
|
|
56
|
-
|
|
57
|
-
origin = (0.0, 0.0)
|
|
58
|
-
stmt = select(Place.id, Place.location.earth_distance(origin).label("dist"))
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
The comparator translates `Place.location.earth_distance(origin)` into SQL using the `<@>` operator (requires PostgreSQL with the `cube` / `earthdistance` extension for meaningful results; without extensions the operator may not exist—adapt as needed for your environment). This library only *emits* the operator; it does not manage PostgreSQL extensions.
|
|
62
|
-
|
|
63
|
-
Returned Python Values
|
|
64
|
-
----------------------
|
|
65
|
-
|
|
66
|
-
Values are loaded as a 2-tuple of floats `(lng, lat)` or `None` when NULL.
|
|
67
|
-
|
|
68
|
-
Testing
|
|
69
|
-
-------
|
|
70
|
-
|
|
71
|
-
Run the test suite with:
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
uv run pytest -q
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Development
|
|
78
|
-
-----------
|
|
79
|
-
|
|
80
|
-
After cloning:
|
|
81
|
-
|
|
82
|
-
```bash
|
|
83
|
-
uv sync # installs runtime + dev deps
|
|
84
|
-
uv run pytest -q
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
Project Structure
|
|
88
|
-
-----------------
|
|
89
|
-
|
|
90
|
-
* `sqlalchemy_postgres_point/point.py` – Implementation of `PointType`.
|
|
91
|
-
* `tests/test_point.py` – Unit tests for processors and comparator.
|
|
92
|
-
|
|
93
|
-
Limitations / Notes
|
|
94
|
-
-------------------
|
|
95
|
-
|
|
96
|
-
* No automatic validation of longitude/latitude ranges; add your own if required.
|
|
97
|
-
* Uses simple textual representation `(lng,lat)` accepted by PostgreSQL `POINT` input parser.
|
|
98
|
-
* If you need advanced spatial indexing / SRID support, look at GeoAlchemy2/PostGIS instead.
|
|
99
|
-
|
|
100
|
-
License
|
|
101
|
-
-------
|
|
102
|
-
|
|
103
|
-
MIT (see your project's LICENSE file if added later). Contributions welcome.
|
|
104
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|