sqlalchemy-postgres-point 0.2.0__tar.gz → 0.2.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sqlalchemy-postgres-point
3
- Version: 0.2.0
3
+ Version: 0.2.2
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
@@ -15,18 +15,15 @@ Description-Content-Type: text/markdown
15
15
  ![GitHub CI Status](https://github.com/iloveitaly/sqlalchemy-postgres-point/actions/workflows/build_and_publish.yml/badge.svg)
16
16
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
17
17
 
18
- sqlalchemy-postgres-point
19
- =========================
18
+ # sqlalchemy-postgres-point
20
19
 
21
20
  Lightweight, pure-Python SQLAlchemy custom type for PostgreSQL `POINT` columns.
22
21
 
23
- Why
24
- ----
22
+ ## Why
25
23
 
26
24
  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.
27
25
 
28
- Features
29
- --------
26
+ ## Features
30
27
 
31
28
  * Simple `(lng, lat)` tuple binding and result conversion.
32
29
  * Safe NULL handling.
@@ -34,23 +31,13 @@ Features
34
31
  * Custom comparator exposing the PostgreSQL earth-distance `<@>` operator (returns a `Float`).
35
32
  * `cache_ok = True` for SQLAlchemy 2.x compilation caching.
36
33
 
37
- Installation
38
- ------------
39
-
40
- Using `uv` (recommended):
34
+ ## Installation
41
35
 
42
36
  ```bash
43
37
  uv add sqlalchemy-postgres-point
44
38
  ```
45
39
 
46
- Or with pip:
47
-
48
- ```bash
49
- pip install sqlalchemy-postgres-point
50
- ```
51
-
52
- Usage
53
- -----
40
+ ## Usage
54
41
 
55
42
  ```python
56
43
  from sqlalchemy import Column, Integer
@@ -59,7 +46,7 @@ from sqlalchemy_postgres_point import PointType
59
46
 
60
47
 
61
48
  class Base(DeclarativeBase):
62
- pass
49
+ pass
63
50
 
64
51
 
65
52
  class Place(Base):
@@ -68,6 +55,7 @@ class Place(Base):
68
55
  # Store as (longitude, latitude)
69
56
  location = Column(PointType)
70
57
 
58
+
71
59
  # Example query using the custom comparator
72
60
  from sqlalchemy import select
73
61
 
@@ -93,14 +81,15 @@ Revises: your_previous_revision
93
81
  Create Date: 2025-01-XX XX:XX:XX.XXXXXX
94
82
 
95
83
  """
84
+
96
85
  from typing import Sequence, Union
97
86
 
98
87
  from alembic import op
99
88
  import sqlalchemy as sa
100
89
 
101
90
  # revision identifiers, used by Alembic.
102
- revision: str = 'your_revision_id'
103
- down_revision: Union[str, None] = 'your_previous_revision'
91
+ revision: str = "your_revision_id"
92
+ down_revision: Union[str, None] = "your_previous_revision"
104
93
  branch_labels: Union[str, Sequence[str], None] = None
105
94
  depends_on: Union[str, Sequence[str], None] = None
106
95
 
@@ -119,81 +108,65 @@ DROP EXTENSION IF EXISTS cube;
119
108
  """)
120
109
  ```
121
110
 
122
- **Integration Steps:**
111
+ #### Integration Steps
123
112
 
124
113
  1. Generate a new migration: `alembic revision -m "add_postgres_extensions"`
125
114
  2. Copy the `upgrade()` and `downgrade()` functions above into your new migration file
126
115
  3. Run the migration: `alembic upgrade head`
127
116
 
128
- Returned Python Values
129
- ----------------------
117
+ ## Returned Python Values
130
118
 
131
119
  Values are loaded as a 2-tuple of floats `(lng, lat)` or `None` when NULL.
132
120
 
133
- Testing
134
- -------
121
+ ## Alembic Integration
135
122
 
136
- Run the test suite with:
123
+ To ensure generated migration files include the correct `PointType` import, pass the provided `render_item` hook to `context.configure()` in your `alembic/env.py`. This must be added to **both** `run_migrations_online` and `run_migrations_offline`:
137
124
 
138
- ```bash
139
- uv run pytest -q
125
+ ```python
126
+ from sqlalchemy_postgres_point.alembic_integration import (
127
+ render_item as render_point_item,
128
+ )
129
+
130
+ context.configure(
131
+ # ... other options ...
132
+ render_item=render_point_item,
133
+ )
140
134
  ```
141
135
 
142
- ## Alembic Integration
136
+ Once wired up, `from sqlalchemy_postgres_point import PointType` will be automatically included in generated migration files whenever a `PointType` column is detected.
143
137
 
144
- When using `PointType` in your models, you can automatically include the necessary imports in migration files by updating your `alembic/env.py` file.
138
+ ### Chaining `render_item`
145
139
 
146
- First, import the integration module:
140
+ If you need to combine this with other packages that also provide a `render_item`, chain them:
147
141
 
148
142
  ```python
149
- import sqlalchemy_postgres_point.alembic_integration
150
- ```
143
+ from sqlalchemy_postgres_point.alembic_integration import (
144
+ render_item as render_point_item,
145
+ )
151
146
 
152
- Then, ensure your `context.configure` call includes a `render_item` hook to handle the `PointType`:
153
147
 
154
- ```python
155
148
  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
149
+ return (
150
+ render_point_item(type_, obj, autogen_context)
151
+ or other_package.render_item(type_, obj, autogen_context)
152
+ or False
153
+ )
154
+
159
155
 
160
- # ... in run_migrations_online and run_migrations_offline ...
161
156
  context.configure(
162
157
  # ... other options ...
163
158
  render_item=render_item,
164
159
  )
165
160
  ```
166
161
 
167
- Once added, `from sqlalchemy_postgres_point import PointType` will be automatically included in generated migration files whenever a `PointType` column is detected.
168
-
169
- Development
170
- -----------
171
-
172
- After cloning:
173
-
174
- ```bash
175
- uv sync # installs runtime + dev deps
176
- uv run pytest -q
177
- ```
178
-
179
- Project Structure
180
- -----------------
162
+ Or you could use something like [`some_fn`](https://funcy.readthedocs.io/en/stable/funcs.html#some_fn) to make this even cleaner.
181
163
 
182
- * `sqlalchemy_postgres_point/point.py` – Implementation of `PointType`.
183
- * `tests/test_point.py` – Unit tests for processors and comparator.
164
+ ## Limitations / Notes
184
165
 
185
- Limitations / Notes
186
- -------------------
187
-
188
- * No automatic validation of longitude/latitude ranges; add your own if required.
166
+ * Coordinates are validated on both write and read: longitude must be within `[-180, 180]`, latitude within `[-90, 90]`, and non-finite values (`NaN`, `inf`) are rejected with a `ValueError`.
189
167
  * Uses simple textual representation `(lng,lat)` accepted by PostgreSQL `POINT` input parser.
190
168
  * If you need advanced spatial indexing / SRID support, look at GeoAlchemy2/PostGIS instead.
191
169
 
192
- License
193
- -------
194
-
195
- MIT (see your project's LICENSE file if added later). Contributions welcome.
196
-
197
170
  ---
198
171
 
199
172
  *This project was created from [iloveitaly/python-package-template](https://github.com/iloveitaly/python-package-template)*
@@ -3,18 +3,15 @@
3
3
  ![GitHub CI Status](https://github.com/iloveitaly/sqlalchemy-postgres-point/actions/workflows/build_and_publish.yml/badge.svg)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- sqlalchemy-postgres-point
7
- =========================
6
+ # sqlalchemy-postgres-point
8
7
 
9
8
  Lightweight, pure-Python SQLAlchemy custom type for PostgreSQL `POINT` columns.
10
9
 
11
- Why
12
- ----
10
+ ## Why
13
11
 
14
12
  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
13
 
16
- Features
17
- --------
14
+ ## Features
18
15
 
19
16
  * Simple `(lng, lat)` tuple binding and result conversion.
20
17
  * Safe NULL handling.
@@ -22,23 +19,13 @@ Features
22
19
  * Custom comparator exposing the PostgreSQL earth-distance `<@>` operator (returns a `Float`).
23
20
  * `cache_ok = True` for SQLAlchemy 2.x compilation caching.
24
21
 
25
- Installation
26
- ------------
27
-
28
- Using `uv` (recommended):
22
+ ## Installation
29
23
 
30
24
  ```bash
31
25
  uv add sqlalchemy-postgres-point
32
26
  ```
33
27
 
34
- Or with pip:
35
-
36
- ```bash
37
- pip install sqlalchemy-postgres-point
38
- ```
39
-
40
- Usage
41
- -----
28
+ ## Usage
42
29
 
43
30
  ```python
44
31
  from sqlalchemy import Column, Integer
@@ -47,7 +34,7 @@ from sqlalchemy_postgres_point import PointType
47
34
 
48
35
 
49
36
  class Base(DeclarativeBase):
50
- pass
37
+ pass
51
38
 
52
39
 
53
40
  class Place(Base):
@@ -56,6 +43,7 @@ class Place(Base):
56
43
  # Store as (longitude, latitude)
57
44
  location = Column(PointType)
58
45
 
46
+
59
47
  # Example query using the custom comparator
60
48
  from sqlalchemy import select
61
49
 
@@ -81,14 +69,15 @@ Revises: your_previous_revision
81
69
  Create Date: 2025-01-XX XX:XX:XX.XXXXXX
82
70
 
83
71
  """
72
+
84
73
  from typing import Sequence, Union
85
74
 
86
75
  from alembic import op
87
76
  import sqlalchemy as sa
88
77
 
89
78
  # revision identifiers, used by Alembic.
90
- revision: str = 'your_revision_id'
91
- down_revision: Union[str, None] = 'your_previous_revision'
79
+ revision: str = "your_revision_id"
80
+ down_revision: Union[str, None] = "your_previous_revision"
92
81
  branch_labels: Union[str, Sequence[str], None] = None
93
82
  depends_on: Union[str, Sequence[str], None] = None
94
83
 
@@ -107,81 +96,65 @@ DROP EXTENSION IF EXISTS cube;
107
96
  """)
108
97
  ```
109
98
 
110
- **Integration Steps:**
99
+ #### Integration Steps
111
100
 
112
101
  1. Generate a new migration: `alembic revision -m "add_postgres_extensions"`
113
102
  2. Copy the `upgrade()` and `downgrade()` functions above into your new migration file
114
103
  3. Run the migration: `alembic upgrade head`
115
104
 
116
- Returned Python Values
117
- ----------------------
105
+ ## Returned Python Values
118
106
 
119
107
  Values are loaded as a 2-tuple of floats `(lng, lat)` or `None` when NULL.
120
108
 
121
- Testing
122
- -------
109
+ ## Alembic Integration
123
110
 
124
- Run the test suite with:
111
+ To ensure generated migration files include the correct `PointType` import, pass the provided `render_item` hook to `context.configure()` in your `alembic/env.py`. This must be added to **both** `run_migrations_online` and `run_migrations_offline`:
125
112
 
126
- ```bash
127
- uv run pytest -q
113
+ ```python
114
+ from sqlalchemy_postgres_point.alembic_integration import (
115
+ render_item as render_point_item,
116
+ )
117
+
118
+ context.configure(
119
+ # ... other options ...
120
+ render_item=render_point_item,
121
+ )
128
122
  ```
129
123
 
130
- ## Alembic Integration
124
+ Once wired up, `from sqlalchemy_postgres_point import PointType` will be automatically included in generated migration files whenever a `PointType` column is detected.
131
125
 
132
- When using `PointType` in your models, you can automatically include the necessary imports in migration files by updating your `alembic/env.py` file.
126
+ ### Chaining `render_item`
133
127
 
134
- First, import the integration module:
128
+ If you need to combine this with other packages that also provide a `render_item`, chain them:
135
129
 
136
130
  ```python
137
- import sqlalchemy_postgres_point.alembic_integration
138
- ```
131
+ from sqlalchemy_postgres_point.alembic_integration import (
132
+ render_item as render_point_item,
133
+ )
139
134
 
140
- Then, ensure your `context.configure` call includes a `render_item` hook to handle the `PointType`:
141
135
 
142
- ```python
143
136
  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
137
+ return (
138
+ render_point_item(type_, obj, autogen_context)
139
+ or other_package.render_item(type_, obj, autogen_context)
140
+ or False
141
+ )
142
+
147
143
 
148
- # ... in run_migrations_online and run_migrations_offline ...
149
144
  context.configure(
150
145
  # ... other options ...
151
146
  render_item=render_item,
152
147
  )
153
148
  ```
154
149
 
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
- -----------------
150
+ Or you could use something like [`some_fn`](https://funcy.readthedocs.io/en/stable/funcs.html#some_fn) to make this even cleaner.
169
151
 
170
- * `sqlalchemy_postgres_point/point.py` – Implementation of `PointType`.
171
- * `tests/test_point.py` – Unit tests for processors and comparator.
152
+ ## Limitations / Notes
172
153
 
173
- Limitations / Notes
174
- -------------------
175
-
176
- * No automatic validation of longitude/latitude ranges; add your own if required.
154
+ * Coordinates are validated on both write and read: longitude must be within `[-180, 180]`, latitude within `[-90, 90]`, and non-finite values (`NaN`, `inf`) are rejected with a `ValueError`.
177
155
  * Uses simple textual representation `(lng,lat)` accepted by PostgreSQL `POINT` input parser.
178
156
  * If you need advanced spatial indexing / SRID support, look at GeoAlchemy2/PostGIS instead.
179
157
 
180
- License
181
- -------
182
-
183
- MIT (see your project's LICENSE file if added later). Contributions welcome.
184
-
185
158
  ---
186
159
 
187
160
  *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.2.0"
3
+ version = "0.2.2"
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",
@@ -21,7 +21,7 @@ urls = { "Repository" = "https://github.com/iloveitaly/sqlalchemy-postgres-point
21
21
 
22
22
 
23
23
  [build-system]
24
- requires = ["uv_build>=0.10.0"]
24
+ requires = ["uv_build>=0.11.0,<0.13"]
25
25
  build-backend = "uv_build"
26
26
 
27
27
  [tool.uv.build-backend]
@@ -0,0 +1,32 @@
1
+ """
2
+ Alembic integration for rendering PointType in generated migration files.
3
+
4
+ Usage — add two lines to your alembic/env.py:
5
+
6
+ from sqlalchemy_postgres_point.alembic_integration import render_item
7
+
8
+ Then pass it to both context.configure() calls:
9
+
10
+ context.configure(
11
+ ...,
12
+ render_item=render_item,
13
+ )
14
+
15
+ Once wired up, ``from sqlalchemy_postgres_point import PointType`` will be
16
+ automatically included in generated migration files whenever PointType is used.
17
+ """
18
+
19
+ from .point import PointType
20
+
21
+
22
+ def render_item(type_, obj, autogen_context):
23
+ """Alembic render_item hook that handles PointType columns.
24
+
25
+ Returns "PointType()" and registers the import when the rendered item is a
26
+ PointType instance. Returns False for everything else so Alembic falls back
27
+ to its default rendering.
28
+ """
29
+ if type_ == "type" and isinstance(obj, PointType):
30
+ autogen_context.imports.add("from sqlalchemy_postgres_point import PointType")
31
+ return "PointType()"
32
+ return False
@@ -11,7 +11,6 @@ References:
11
11
 
12
12
  import math
13
13
  import re
14
- from typing import Tuple
15
14
 
16
15
  from sqlalchemy.types import Float, UserDefinedType
17
16
 
@@ -23,7 +22,7 @@ class PointType(UserDefinedType):
23
22
  return "POINT"
24
23
 
25
24
  @staticmethod
26
- def _validate_point(value: Tuple[float, float]) -> Tuple[float, float]:
25
+ def _validate_point(value: tuple[float, float]) -> tuple[float, float]:
27
26
  """Validate and normalize a (lng, lat) tuple.
28
27
 
29
28
  - Ensures it's a 2-length tuple/list
@@ -1,56 +0,0 @@
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