GeoAlchemy2 0.15.2__py3-none-any.whl → 0.17.0__py3-none-any.whl

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
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: GeoAlchemy2
3
- Version: 0.15.2
3
+ Version: 0.17.0
4
4
  Summary: Using SQLAlchemy with Spatial Databases
5
5
  Home-page: https://geoalchemy-2.readthedocs.io/en/stable/
6
6
  Author: Eric Lemoine
@@ -24,10 +24,22 @@ Classifier: License :: OSI Approved :: MIT License
24
24
  Classifier: Topic :: Scientific/Engineering :: GIS
25
25
  Requires-Python: >=3.7
26
26
  License-File: COPYING.rst
27
- Requires-Dist: SQLAlchemy >=1.4
27
+ Requires-Dist: SQLAlchemy>=1.4
28
28
  Requires-Dist: packaging
29
29
  Provides-Extra: shapely
30
- Requires-Dist: Shapely >=1.7 ; extra == 'shapely'
30
+ Requires-Dist: Shapely>=1.7; extra == "shapely"
31
+ Dynamic: author
32
+ Dynamic: author-email
33
+ Dynamic: classifier
34
+ Dynamic: description
35
+ Dynamic: home-page
36
+ Dynamic: keywords
37
+ Dynamic: license
38
+ Dynamic: project-url
39
+ Dynamic: provides-extra
40
+ Dynamic: requires-dist
41
+ Dynamic: requires-python
42
+ Dynamic: summary
31
43
 
32
44
  ============
33
45
  GeoAlchemy 2
@@ -0,0 +1,35 @@
1
+ geoalchemy2/__init__.py,sha256=aGo3WcjOBcqrvqYXBuzCff1d_WiFUFObgtcyR11gg6M,2043
2
+ geoalchemy2/_functions.py,sha256=e8v584Fx_fmh8XnJvkuYEXrcyPDiWV95Mu2yw6L-LHY,62863
3
+ geoalchemy2/_functions_helpers.py,sha256=hVM9DDTgf-oM27EZu6ycW1y2ENnSxUJixdINetWsu0E,2651
4
+ geoalchemy2/alembic_helpers.py,sha256=WYjKiVVyHtlB4DI22z-G0-x8NnG8othAINUwmxBCMcs,27885
5
+ geoalchemy2/comparator.py,sha256=WUDXn10doDlJVnYiWbVIAhhBu7N5AO9BI8sNf2mhpA4,8100
6
+ geoalchemy2/elements.py,sha256=5yd_7dUQGbrLvgYvo6A2YYAFwTvP4_BskIdBCB0DrlM,13002
7
+ geoalchemy2/exc.py,sha256=Nn9bRKB_35skWDMkEf4_Y2GL6gvguPVycPZcbOfa69g,226
8
+ geoalchemy2/functions.py,sha256=0he8hy_SAWpXAFPdfg32NMW5G0FaZP7yVl0jn0MRLBQ,10320
9
+ geoalchemy2/functions.pyi,sha256=rKCKdDSVTgeUQHItkOKqJSEuyKmVJ30bpjLpCPwMI5g,108636
10
+ geoalchemy2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ geoalchemy2/shape.py,sha256=TLeWa6NwXZqBxh3Zj-l96wBB5kPviEhnZ44kyr8v678,2735
12
+ geoalchemy2/utils.py,sha256=OYWYnT64tjp4DWhPdq3KIxHbVti932xPMtGGxajtu-I,488
13
+ geoalchemy2/admin/__init__.py,sha256=O5kr1ETm3lTpEELm16Oxq6344KztOZmUaapf0ZIP6b8,4016
14
+ geoalchemy2/admin/plugin.py,sha256=2U5p4_JdZM7b2MOXXBfP8KhtXlLHu-ASjxTq4mF-thg,4332
15
+ geoalchemy2/admin/dialects/__init__.py,sha256=z4gBrdglClnmp9VdDW0WwEAGNoemMUSvMCeFFvuQcgs,422
16
+ geoalchemy2/admin/dialects/common.py,sha256=p-CGtcooIESy-BiwZ8bmmdAORjuJtTK-sq_X0jnjHB0,3036
17
+ geoalchemy2/admin/dialects/geopackage.py,sha256=PgOhRftDN4I_OIqqOfop7jgmospReCq6qJi6BGW-Gts,13596
18
+ geoalchemy2/admin/dialects/mariadb.py,sha256=4PtkC1Gv4jcAl_grO5lJwxPsIChi4_8g36soZXnMM3w,4267
19
+ geoalchemy2/admin/dialects/mysql.py,sha256=NP0qXGsvssHZKegP8m8-4Vqbx8CosNHTsq6qfSD2MHs,7181
20
+ geoalchemy2/admin/dialects/postgresql.py,sha256=VwB_h3TC8M5aQ6aKE3UYgxHfbEKav3eIHJeLx534Zzg,6191
21
+ geoalchemy2/admin/dialects/sqlite.py,sha256=iW5MBhbxVKGMVghN5Do0iF-_9Pbtrv65NTXmW9mOyZA,13802
22
+ geoalchemy2/types/__init__.py,sha256=Ud1_6gukAs3gSzN3BThZnC-p0naHnTcNEjlyD5Ahm-M,14240
23
+ geoalchemy2/types/dialects/__init__.py,sha256=GYqO6nDtzElvsV22VN1SSUet2elaZG4ZuRrM-asbstM,414
24
+ geoalchemy2/types/dialects/common.py,sha256=eiIKe-sFAdzgvQ7YT0cV29tYAPrs55GXKJi3p1UvjPQ,158
25
+ geoalchemy2/types/dialects/geopackage.py,sha256=nRmN_PnF-CWMEHkWhKVdsybnw3SvXZIBXAHIHXJLTqw,147
26
+ geoalchemy2/types/dialects/mariadb.py,sha256=tPZpfj95XlVSOm8zC52iFbqVrIOdj-EDH5KVJP1Gd9c,1723
27
+ geoalchemy2/types/dialects/mysql.py,sha256=zMNi1920v0XlVaCJWSwtq0b0P5YQRn8y3X_rBxC5KEw,1790
28
+ geoalchemy2/types/dialects/postgresql.py,sha256=7NBKEbDJXMwX8Sgs6o_N2bAUHgjUcjbrdmYOA7sDiRw,1117
29
+ geoalchemy2/types/dialects/sqlite.py,sha256=B-yLzaQcqL_4dXoOPX9D9IXw2ZQlq-Tibv_kqUIBeb4,2104
30
+ GeoAlchemy2-0.17.0.dist-info/COPYING.rst,sha256=-bQKftq9uMOROzF7oN65kYBBIJKxTmBuDoftp27IC3I,1056
31
+ GeoAlchemy2-0.17.0.dist-info/METADATA,sha256=mc9wg4zIYUEuRbFYELFA4LygDIo6hKXdM9CDR-KyHQo,2327
32
+ GeoAlchemy2-0.17.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
33
+ GeoAlchemy2-0.17.0.dist-info/entry_points.txt,sha256=izFHecGE8cNV6IjoLkc0uEmKH13rYbbvozc4fEwRl6Y,70
34
+ GeoAlchemy2-0.17.0.dist-info/top_level.txt,sha256=3kGUTcfBeXd61zFpof6-qiuw1peNF_HuZabgHQgrdis,12
35
+ GeoAlchemy2-0.17.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.3.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -0,0 +1,2 @@
1
+ [sqlalchemy.plugins]
2
+ geoalchemy2 = geoalchemy2.admin.plugin:GeoEngine
geoalchemy2/__init__.py CHANGED
@@ -8,6 +8,7 @@ from geoalchemy2 import shape # noqa
8
8
  from geoalchemy2 import types # noqa
9
9
  from geoalchemy2.admin.dialects.geopackage import load_spatialite_gpkg # noqa
10
10
  from geoalchemy2.admin.dialects.sqlite import load_spatialite # noqa
11
+ from geoalchemy2.admin.plugin import GeoEngine # noqa
11
12
  from geoalchemy2.elements import CompositeElement # noqa
12
13
  from geoalchemy2.elements import RasterElement # noqa
13
14
  from geoalchemy2.elements import WKBElement # noqa
@@ -50,6 +51,7 @@ __all__ = [
50
51
  "__version__",
51
52
  "ArgumentError",
52
53
  "CompositeElement",
54
+ "GeoEngine",
53
55
  "Geography",
54
56
  "Geometry",
55
57
  "Raster",
@@ -20,7 +20,7 @@ def select_dialect(dialect_name):
20
20
  known_dialects = {
21
21
  "geopackage": dialects.geopackage,
22
22
  "mysql": dialects.mysql,
23
- "mariadb": dialects.mysql,
23
+ "mariadb": dialects.mariadb,
24
24
  "postgresql": dialects.postgresql,
25
25
  "sqlite": dialects.sqlite,
26
26
  }
@@ -53,7 +53,7 @@ def setup_ddl_event_listeners():
53
53
  @event.listens_for(Column, "after_parent_attach")
54
54
  def after_parent_attach(column, table):
55
55
  """Automatically add spatial indexes."""
56
- if not isinstance(table, Table):
56
+ if not isinstance(table, Table): # pragma: no cover
57
57
  # For old versions of SQLAlchemy, subqueries might trigger the after_parent_attach event
58
58
  # with a selectable as table, so we want to skip this case.
59
59
  return
@@ -75,7 +75,7 @@ def setup_ddl_event_listeners():
75
75
  try:
76
76
  if column.type._spatial_index_reflected:
77
77
  return
78
- except AttributeError:
78
+ except AttributeError: # pragma: no cover
79
79
  pass
80
80
 
81
81
  kwargs = {
@@ -98,7 +98,7 @@ def setup_ddl_event_listeners():
98
98
  )
99
99
 
100
100
  @event.listens_for(Table, "column_reflect")
101
- def _reflect_geometry_column(inspector, table, column_info):
101
+ def column_reflect(inspector, table, column_info):
102
102
  select_dialect(inspector.bind.dialect.name).reflect_geometry_column(
103
103
  inspector, table, column_info
104
104
  )
@@ -2,6 +2,7 @@
2
2
 
3
3
  from geoalchemy2.admin.dialects import common # noqa
4
4
  from geoalchemy2.admin.dialects import geopackage # noqa
5
+ from geoalchemy2.admin.dialects import mariadb # noqa
5
6
  from geoalchemy2.admin.dialects import mysql # noqa
6
7
  from geoalchemy2.admin.dialects import postgresql # noqa
7
8
  from geoalchemy2.admin.dialects import sqlite # noqa
@@ -85,20 +85,20 @@ def setup_create_drop(table, bind, check_col_management=None):
85
85
 
86
86
 
87
87
  def reflect_geometry_column(inspector, table, column_info):
88
- return
88
+ return # pragma: no cover
89
89
 
90
90
 
91
91
  def before_create(table, bind, **kw):
92
- return
92
+ return # pragma: no cover
93
93
 
94
94
 
95
95
  def after_create(table, bind, **kw):
96
- return
96
+ return # pragma: no cover
97
97
 
98
98
 
99
99
  def before_drop(table, bind, **kw):
100
- return
100
+ return # pragma: no cover
101
101
 
102
102
 
103
103
  def after_drop(table, bind, **kw):
104
- return
104
+ return # pragma: no cover
@@ -71,15 +71,15 @@ def init_geopackage(dbapi_conn, *args):
71
71
  dbapi_conn.execute("SELECT gpkgCreateBaseTables();")
72
72
 
73
73
 
74
- def load_spatialite_gpkg(*args, **kwargs):
74
+ def load_spatialite_gpkg(dbapi_conn, *args, **kwargs):
75
75
  """Load SpatiaLite extension in GeoPackage and initialize internal tables.
76
76
 
77
77
  See :func:`geoalchemy2.admin.dialects.geopackage.load_geopackage_driver` and
78
78
  :func:`geoalchemy2.admin.dialects.geopackage.init_geopackage` functions for details about
79
79
  arguments.
80
80
  """
81
- load_geopackage_driver(*args)
82
- init_geopackage(*args, **kwargs)
81
+ load_geopackage_driver(dbapi_conn)
82
+ init_geopackage(dbapi_conn, **kwargs)
83
83
 
84
84
 
85
85
  def _get_spatialite_attrs(bind, table_name, col_name):
@@ -188,6 +188,10 @@ def reflect_geometry_column(inspector, table, column_info):
188
188
  column_info["type"]._spatial_index_reflected = False
189
189
 
190
190
 
191
+ def connect(dbapi_conn, *args, **kwargs):
192
+ return load_spatialite_gpkg(dbapi_conn, *args, **kwargs)
193
+
194
+
191
195
  def before_create(table, bind, **kw):
192
196
  """Handle spatial indexes during the before_create event."""
193
197
  dialect, gis_cols, regular_cols = setup_create_drop(table, bind)
@@ -0,0 +1,134 @@
1
+ """This module defines specific functions for MariaDB dialect."""
2
+
3
+ from sqlalchemy.ext.compiler import compiles
4
+
5
+ from geoalchemy2 import functions
6
+ from geoalchemy2.admin.dialects.mysql import after_create # noqa
7
+ from geoalchemy2.admin.dialects.mysql import after_drop # noqa
8
+ from geoalchemy2.admin.dialects.mysql import before_create # noqa
9
+ from geoalchemy2.admin.dialects.mysql import before_drop # noqa
10
+ from geoalchemy2.admin.dialects.mysql import reflect_geometry_column # noqa
11
+ from geoalchemy2.elements import WKBElement
12
+ from geoalchemy2.elements import WKTElement
13
+ from geoalchemy2.shape import to_shape
14
+
15
+
16
+ def _cast(param):
17
+ if isinstance(param, memoryview):
18
+ param = param.tobytes()
19
+ if isinstance(param, bytes):
20
+ data_element = WKBElement(param)
21
+ param = to_shape(data_element).wkt.encode("utf-8")
22
+ return param
23
+
24
+
25
+ def before_cursor_execute(
26
+ conn, cursor, statement, parameters, context, executemany, convert=True
27
+ ): # noqa: D417
28
+ """Event handler to cast the parameters properly.
29
+
30
+ Args:
31
+ convert (bool): Trigger the conversion.
32
+ """
33
+ if convert:
34
+ if isinstance(parameters, (tuple, list)):
35
+ parameters = tuple(_cast(x) for x in parameters)
36
+ elif isinstance(parameters, dict):
37
+ for k in parameters:
38
+ parameters[k] = _cast(parameters[k])
39
+
40
+ return statement, parameters
41
+
42
+
43
+ _MARIADB_FUNCTIONS = {
44
+ "ST_AsEWKB": "ST_AsBinary",
45
+ }
46
+
47
+
48
+ def _compiles_mariadb(cls, fn):
49
+ def _compile_mariadb(element, compiler, **kw):
50
+ return "{}({})".format(fn, compiler.process(element.clauses, **kw))
51
+
52
+ compiles(getattr(functions, cls), "mariadb")(_compile_mariadb)
53
+
54
+
55
+ def register_mariadb_mapping(mapping):
56
+ """Register compilation mappings for the given functions.
57
+
58
+ Args:
59
+ mapping: Should have the following form::
60
+
61
+ {
62
+ "function_name_1": "mariadb_function_name_1",
63
+ "function_name_2": "mariadb_function_name_2",
64
+ ...
65
+ }
66
+ """
67
+ for cls, fn in mapping.items():
68
+ _compiles_mariadb(cls, fn)
69
+
70
+
71
+ register_mariadb_mapping(_MARIADB_FUNCTIONS)
72
+
73
+
74
+ def _compile_GeomFromText_MariaDB(element, compiler, **kw):
75
+ element.identifier = "ST_GeomFromText"
76
+ compiled = compiler.process(element.clauses, **kw)
77
+ try:
78
+ clauses = list(element.clauses)
79
+ data_element = WKTElement(clauses[0].value)
80
+ srid = max(0, data_element.srid)
81
+ if srid <= 0:
82
+ srid = max(0, element.type.srid)
83
+ if len(clauses) > 1 and srid > 0:
84
+ clauses[1].value = srid
85
+ except Exception:
86
+ srid = max(0, element.type.srid)
87
+
88
+ if srid > 0:
89
+ res = "{}({}, {})".format(element.identifier, compiled, srid)
90
+ else:
91
+ res = "{}({})".format(element.identifier, compiled)
92
+ return res
93
+
94
+
95
+ def _compile_GeomFromWKB_MariaDB(element, compiler, **kw):
96
+ element.identifier = "ST_GeomFromText"
97
+
98
+ try:
99
+ clauses = list(element.clauses)
100
+ data_element = WKBElement(clauses[0].value)
101
+ srid = max(0, data_element.srid)
102
+ if srid <= 0:
103
+ srid = max(0, element.type.srid)
104
+ if len(clauses) > 1 and srid > 0:
105
+ clauses[1].value = srid
106
+ except Exception:
107
+ srid = max(0, element.type.srid)
108
+ compiled = compiler.process(element.clauses, **kw)
109
+
110
+ if srid > 0:
111
+ res = "{}({}, {})".format(element.identifier, compiled, srid)
112
+ else:
113
+ res = "{}({})".format(element.identifier, compiled)
114
+ return res
115
+
116
+
117
+ @compiles(functions.ST_GeomFromText, "mariadb") # type: ignore
118
+ def _MariaDB_ST_GeomFromText(element, compiler, **kw):
119
+ return _compile_GeomFromText_MariaDB(element, compiler, **kw)
120
+
121
+
122
+ @compiles(functions.ST_GeomFromEWKT, "mariadb") # type: ignore
123
+ def _MariaDB_ST_GeomFromEWKT(element, compiler, **kw):
124
+ return _compile_GeomFromText_MariaDB(element, compiler, **kw)
125
+
126
+
127
+ @compiles(functions.ST_GeomFromWKB, "mariadb") # type: ignore
128
+ def _MariaDB_ST_GeomFromWKB(element, compiler, **kw):
129
+ return _compile_GeomFromWKB_MariaDB(element, compiler, **kw)
130
+
131
+
132
+ @compiles(functions.ST_GeomFromEWKB, "mariadb") # type: ignore
133
+ def _MariaDB_ST_GeomFromEWKB(element, compiler, **kw):
134
+ return _compile_GeomFromWKB_MariaDB(element, compiler, **kw)
@@ -31,11 +31,16 @@ def reflect_geometry_column(inspector, table, column_info):
31
31
  column_name = column_info.get("name")
32
32
  schema = table.schema or inspector.default_schema_name
33
33
 
34
+ if inspector.dialect.name == "mariadb":
35
+ select_srid = "-1, "
36
+ else:
37
+ select_srid = "SRS_ID, "
38
+
34
39
  # Check geometry type, SRID and if the column is nullable
35
- geometry_type_query = """SELECT DATA_TYPE, SRS_ID, IS_NULLABLE
40
+ geometry_type_query = """SELECT DATA_TYPE, {}IS_NULLABLE
36
41
  FROM INFORMATION_SCHEMA.COLUMNS
37
42
  WHERE TABLE_NAME = '{}' and COLUMN_NAME = '{}'""".format(
38
- table.name, column_name
43
+ select_srid, table.name, column_name
39
44
  )
40
45
  if schema is not None:
41
46
  geometry_type_query += """ and table_schema = '{}'""".format(schema)
@@ -43,7 +48,7 @@ def reflect_geometry_column(inspector, table, column_info):
43
48
  is_nullable = str(nullable_str).lower() == "yes"
44
49
 
45
50
  if geometry_type not in _POSSIBLE_TYPES:
46
- return
51
+ return # pragma: no cover
47
52
 
48
53
  # Check if the column has spatial index
49
54
  has_index_query = """SELECT DISTINCT
@@ -67,6 +72,25 @@ def reflect_geometry_column(inspector, table, column_info):
67
72
  )
68
73
 
69
74
 
75
+ def before_cursor_execute(
76
+ conn, cursor, statement, parameters, context, executemany, convert=True
77
+ ): # noqa: D417
78
+ """Event handler to cast the parameters properly.
79
+
80
+ Args:
81
+ convert (bool): Trigger the conversion.
82
+ """
83
+ if convert:
84
+ if isinstance(parameters, (tuple, list)):
85
+ parameters = tuple(x.tobytes() if isinstance(x, memoryview) else x for x in parameters)
86
+ elif isinstance(parameters, dict):
87
+ for k in parameters:
88
+ if isinstance(parameters[k], memoryview):
89
+ parameters[k] = parameters[k].tobytes()
90
+
91
+ return statement, parameters
92
+
93
+
70
94
  def before_create(table, bind, **kw):
71
95
  """Handle spatial indexes during the before_create event."""
72
96
  dialect, gis_cols, regular_cols = setup_create_drop(table, bind)
@@ -129,7 +153,6 @@ def _compiles_mysql(cls, fn):
129
153
  return "{}({})".format(fn, compiler.process(element.clauses, **kw))
130
154
 
131
155
  compiles(getattr(functions, cls), "mysql")(_compile_mysql)
132
- compiles(getattr(functions, cls), "mariadb")(_compile_mysql)
133
156
 
134
157
 
135
158
  def register_mysql_mapping(mapping):
@@ -164,9 +187,6 @@ def _compile_GeomFromText_MySql(element, compiler, **kw):
164
187
 
165
188
  def _compile_GeomFromWKB_MySql(element, compiler, **kw):
166
189
  element.identifier = "ST_GeomFromWKB"
167
- wkb_data = list(element.clauses)[0].value
168
- if isinstance(wkb_data, memoryview):
169
- list(element.clauses)[0].value = wkb_data.tobytes()
170
190
  compiled = compiler.process(element.clauses, **kw)
171
191
  srid = element.type.srid
172
192
 
@@ -177,24 +197,20 @@ def _compile_GeomFromWKB_MySql(element, compiler, **kw):
177
197
 
178
198
 
179
199
  @compiles(functions.ST_GeomFromText, "mysql") # type: ignore
180
- @compiles(functions.ST_GeomFromText, "mariadb") # type: ignore
181
200
  def _MySQL_ST_GeomFromText(element, compiler, **kw):
182
201
  return _compile_GeomFromText_MySql(element, compiler, **kw)
183
202
 
184
203
 
185
204
  @compiles(functions.ST_GeomFromEWKT, "mysql") # type: ignore
186
- @compiles(functions.ST_GeomFromEWKT, "mariadb") # type: ignore
187
205
  def _MySQL_ST_GeomFromEWKT(element, compiler, **kw):
188
206
  return _compile_GeomFromText_MySql(element, compiler, **kw)
189
207
 
190
208
 
191
209
  @compiles(functions.ST_GeomFromWKB, "mysql") # type: ignore
192
- @compiles(functions.ST_GeomFromWKB, "mariadb") # type: ignore
193
210
  def _MySQL_ST_GeomFromWKB(element, compiler, **kw):
194
211
  return _compile_GeomFromWKB_MySql(element, compiler, **kw)
195
212
 
196
213
 
197
214
  @compiles(functions.ST_GeomFromEWKB, "mysql") # type: ignore
198
- @compiles(functions.ST_GeomFromEWKB, "mariadb") # type: ignore
199
215
  def _MySQL_ST_GeomFromEWKB(element, compiler, **kw):
200
216
  return _compile_GeomFromWKB_MySql(element, compiler, **kw)
@@ -51,6 +51,8 @@ def init_spatialite(
51
51
 
52
52
  Args:
53
53
  dbapi_conn: The DBAPI connection.
54
+ transaction: If set to `True` the whole operation will be handled as a single Transaction
55
+ (faster). The default value is `False` (slower, but safer).
54
56
  init_mode: Can be `None` to load all EPSG SRIDs, `'WGS84'` to load only the ones related
55
57
  to WGS84 or `'EMPTY'` to not load any EPSG SRID.
56
58
 
@@ -58,8 +60,6 @@ def init_spatialite(
58
60
 
59
61
  It is possible to load other EPSG SRIDs afterwards using `InsertEpsgSrid(srid)`.
60
62
 
61
- transaction: If set to `True` the whole operation will be handled as a single Transaction
62
- (faster). The default value is `False` (slower, but safer).
63
63
  journal_mode: Change the journal mode to the given value. This can make the table creation
64
64
  much faster. The possible values are the following: <JOURNAL_MODE_VALUES>. See
65
65
  https://www.sqlite.org/pragma.html#pragma_journal_mode for more details.
@@ -127,15 +127,15 @@ def init_spatialite(
127
127
  dbapi_conn.execute("PRAGMA journal_mode = {}".format(current_journal_mode))
128
128
 
129
129
 
130
- def load_spatialite(*args, **kwargs):
130
+ def load_spatialite(dbapi_conn, *args, **kwargs):
131
131
  """Load SpatiaLite extension in SQLite DB and initialize internal tables.
132
132
 
133
133
  See :func:`geoalchemy2.admin.dialects.sqlite.load_spatialite_driver` and
134
134
  :func:`geoalchemy2.admin.dialects.sqlite.init_spatialite` functions for details about
135
135
  arguments.
136
136
  """
137
- load_spatialite_driver(*args)
138
- init_spatialite(*args, **kwargs)
137
+ load_spatialite_driver(dbapi_conn)
138
+ init_spatialite(dbapi_conn, **kwargs)
139
139
 
140
140
 
141
141
  def _get_spatialite_attrs(bind, table_name, col_name):
@@ -260,6 +260,11 @@ def reflect_geometry_column(inspector, table, column_info):
260
260
  column_info["type"]._spatial_index_reflected = False
261
261
 
262
262
 
263
+ def connect(dbapi_conn, *args, **kwargs):
264
+ """Even handler to load spatial extension when a new connection is created."""
265
+ return load_spatialite(dbapi_conn, *args, **kwargs)
266
+
267
+
263
268
  def before_create(table, bind, **kw):
264
269
  """Handle spatial indexes during the before_create event."""
265
270
  dialect, gis_cols, regular_cols = setup_create_drop(table, bind)
@@ -0,0 +1,98 @@
1
+ from collections import defaultdict
2
+ from functools import partial
3
+
4
+ from sqlalchemy import event
5
+ from sqlalchemy.engine import CreateEnginePlugin
6
+
7
+ from geoalchemy2.admin import select_dialect
8
+
9
+
10
+ class GeoEngine(CreateEnginePlugin):
11
+ """A plugin to create `sqlalchemy.Engine` objects with specific events to handle spatial data.
12
+
13
+ This plugin should only be used with the :func:`sqlalchemy.create_engine` function. This plugin
14
+ will automatically attach the relevant event listeners to the new engine depending on its
15
+ dialect. For some specific dialects it's even possible to pass arguments to customize the
16
+ functions called by the listeners. These arguments have the following form:
17
+ `geoalchemy2_<event>_<dialect>_<argument_name>`.
18
+ For example, creating a new SQLite DB and initialize the SpatiaLite extension with only the
19
+ WGS84 SRID can be performed this way:
20
+
21
+ .. code-block:: python
22
+
23
+ db_url = "sqlite:////tmp/test_db.sqlite?geoalchemy2_connect_sqlite_init_mode=WGS84"
24
+ engine = sqlalchemy.create_engine(db_url, plugins=["geoalchemy2"])
25
+
26
+ The names of the parameters can be found in the event listener of each dialect. Note that all
27
+ dialects don't have listeners for all events.
28
+ """
29
+
30
+ def __init__(self, url, kwargs):
31
+ super().__init__(url, kwargs)
32
+
33
+ # Consume the parameters from the URL query
34
+ self.params = defaultdict(lambda: defaultdict(dict))
35
+
36
+ transaction = url.query.get("geoalchemy2_connect_sqlite_transaction", None)
37
+ if transaction is not None:
38
+ self.params["connect"]["sqlite"]["transaction"] = self.str_to_bool(transaction)
39
+
40
+ init_mode = url.query.get("geoalchemy2_connect_sqlite_init_mode", None)
41
+ if init_mode is not None:
42
+ self.params["connect"]["sqlite"]["init_mode"] = init_mode
43
+
44
+ journal_mode = url.query.get("geoalchemy2_connect_sqlite_journal_mode", None)
45
+ if journal_mode is not None:
46
+ self.params["connect"]["sqlite"]["journal_mode"] = journal_mode
47
+
48
+ before_cursor_execute_convert_mysql = url.query.get(
49
+ "geoalchemy2_before_cursor_execute_mysql_convert", None
50
+ )
51
+ if before_cursor_execute_convert_mysql is not None:
52
+ self.params["before_cursor_execute"]["mysql"]["convert"] = self.str_to_bool(
53
+ before_cursor_execute_convert_mysql
54
+ )
55
+
56
+ before_cursor_execute_convert_mariadb = url.query.get(
57
+ "geoalchemy2_before_cursor_execute_mariadb_convert", None
58
+ )
59
+ if before_cursor_execute_convert_mariadb is not None:
60
+ self.params["before_cursor_execute"]["mariadb"]["convert"] = self.str_to_bool(
61
+ before_cursor_execute_convert_mariadb
62
+ )
63
+
64
+ @staticmethod
65
+ def str_to_bool(argument):
66
+ """Cast argument to bool."""
67
+ lowered = str(argument).lower()
68
+ if lowered in ("yes", "y", "true", "t", "1", "enable", "on"):
69
+ return True
70
+ elif lowered in ("no", "n", "false", "f", "0", "disable", "off"):
71
+ return False
72
+ raise ValueError(argument)
73
+
74
+ def update_url(self, url):
75
+ """Update the URL to one that no longer includes specific parameters."""
76
+ return url.difference_update_query(
77
+ [
78
+ "geoalchemy2_connect_sqlite_transaction",
79
+ "geoalchemy2_connect_sqlite_init_mode",
80
+ "geoalchemy2_connect_sqlite_journal_mode",
81
+ "geoalchemy2_before_cursor_execute_mysql_convert",
82
+ "geoalchemy2_before_cursor_execute_mariadb_convert",
83
+ ],
84
+ )
85
+
86
+ def engine_created(self, engine):
87
+ """Attach event listeners after the new Engine object is created."""
88
+ dialect_module = select_dialect(engine.dialect.name)
89
+
90
+ if hasattr(dialect_module, "connect"):
91
+ params = dict(self.params["connect"].get(engine.dialect.name, {}))
92
+ func = partial(dialect_module.connect, **params)
93
+ event.listen(engine, "connect", func)
94
+
95
+ if hasattr(dialect_module, "before_cursor_execute"):
96
+ params = dict(self.params["before_cursor_execute"].get(engine.dialect.name, {}))
97
+ func = partial(dialect_module.before_cursor_execute, **params)
98
+ event.listen(engine, "before_cursor_execute", func, retval=True)
geoalchemy2/shape.py CHANGED
@@ -48,7 +48,6 @@ def to_shape(element: Union[WKBElement, WKTElement]):
48
48
  lake = Session.query(Lake).get(1)
49
49
  polygon = to_shape(lake.geom)
50
50
  """
51
- assert isinstance(element, (WKBElement, WKTElement))
52
51
  if isinstance(element, WKBElement):
53
52
  data, hex = (
54
53
  (element.data, True) if isinstance(element.data, str) else (bytes(element.data), False)
@@ -40,7 +40,7 @@ def select_dialect(dialect_name):
40
40
  known_dialects = {
41
41
  "geopackage": dialects.geopackage,
42
42
  "mysql": dialects.mysql,
43
- "mariadb": dialects.mysql,
43
+ "mariadb": dialects.mariadb,
44
44
  "postgresql": dialects.postgresql,
45
45
  "sqlite": dialects.sqlite,
46
46
  }
@@ -198,9 +198,9 @@ class _GISType(UserDefinedType):
198
198
  return geometry_type, srid
199
199
 
200
200
 
201
- @compiles(_GISType, "mariadb")
202
201
  @compiles(_GISType, "mysql")
203
- def get_col_spec(self, *args, **kwargs):
202
+ @compiles(_GISType, "mariadb")
203
+ def get_col_spec_mysql(self, compiler, *args, **kwargs):
204
204
  if self.geometry_type is not None:
205
205
  spec = "%s" % self.geometry_type
206
206
  else:
@@ -208,7 +208,7 @@ def get_col_spec(self, *args, **kwargs):
208
208
 
209
209
  if not self.nullable or self.spatial_index:
210
210
  spec += " NOT NULL"
211
- if self.srid > 0:
211
+ if self.srid > 0 and compiler.dialect.name != "mariadb":
212
212
  spec += " SRID %d" % self.srid
213
213
  return spec
214
214
 
@@ -245,7 +245,7 @@ class Geometry(_GISType):
245
245
  """ The element class to use. Used by the parent class'
246
246
  ``result_processor`` method. """
247
247
 
248
- cache_ok = False
248
+ cache_ok = True
249
249
  """ Disable cache for this type. """
250
250
 
251
251
 
@@ -275,7 +275,7 @@ class Geography(_GISType):
275
275
  """ The element class to use. Used by the parent class'
276
276
  ``result_processor`` method. """
277
277
 
278
- cache_ok = False
278
+ cache_ok = True
279
279
  """ Disable cache for this type. """
280
280
 
281
281
 
@@ -315,7 +315,7 @@ class Raster(_GISType):
315
315
  """ The element class to use. Used by the parent class'
316
316
  ``result_processor`` method. """
317
317
 
318
- cache_ok = False
318
+ cache_ok = True
319
319
  """ Disable cache for this type. """
320
320
 
321
321
  def __init__(self, spatial_index=True, from_text=None, name=None, nullable=True) -> None:
@@ -2,6 +2,7 @@
2
2
 
3
3
  from geoalchemy2.types.dialects import common # noqa
4
4
  from geoalchemy2.types.dialects import geopackage # noqa
5
+ from geoalchemy2.types.dialects import mariadb # noqa
5
6
  from geoalchemy2.types.dialects import mysql # noqa
6
7
  from geoalchemy2.types.dialects import postgresql # noqa
7
8
  from geoalchemy2.types.dialects import sqlite # noqa
@@ -2,4 +2,4 @@
2
2
 
3
3
 
4
4
  def bind_processor_process(spatial_type, bindvalue):
5
- return bindvalue
5
+ return bindvalue # pragma: no cover
@@ -0,0 +1,47 @@
1
+ """This module defines specific functions for MySQL dialect."""
2
+
3
+ from geoalchemy2.elements import WKBElement
4
+ from geoalchemy2.elements import WKTElement
5
+ from geoalchemy2.elements import _SpatialElement
6
+ from geoalchemy2.exc import ArgumentError
7
+ from geoalchemy2.shape import to_shape
8
+
9
+
10
+ def bind_processor_process(spatial_type, bindvalue):
11
+ if isinstance(bindvalue, str):
12
+ wkt_match = WKTElement._REMOVE_SRID.match(bindvalue)
13
+ srid = wkt_match.group(2)
14
+ try:
15
+ if srid is not None:
16
+ srid = int(srid)
17
+ except (ValueError, TypeError): # pragma: no cover
18
+ raise ArgumentError(
19
+ f"The SRID ({srid}) of the supplied value can not be casted to integer"
20
+ )
21
+
22
+ if srid is not None and srid != spatial_type.srid:
23
+ raise ArgumentError(
24
+ f"The SRID ({srid}) of the supplied value is different "
25
+ f"from the one of the column ({spatial_type.srid})"
26
+ )
27
+ return wkt_match.group(3)
28
+
29
+ if (
30
+ isinstance(bindvalue, _SpatialElement)
31
+ and bindvalue.srid != -1
32
+ and bindvalue.srid != spatial_type.srid
33
+ ):
34
+ raise ArgumentError(
35
+ f"The SRID ({bindvalue.srid}) of the supplied value is different "
36
+ f"from the one of the column ({spatial_type.srid})"
37
+ )
38
+
39
+ if isinstance(bindvalue, WKTElement):
40
+ bindvalue = bindvalue.as_wkt()
41
+ if bindvalue.srid <= 0:
42
+ bindvalue.srid = spatial_type.srid
43
+ return bindvalue
44
+ elif isinstance(bindvalue, WKBElement):
45
+ # With MariaDB we use Shapely to convert the WKBElement to an EWKT string
46
+ return to_shape(bindvalue).wkt
47
+ return bindvalue
@@ -1,31 +0,0 @@
1
- geoalchemy2/__init__.py,sha256=Wb5f11AM_hYn56yrxOAOjCT3z76TCtseqjFo2hKsob4,1971
2
- geoalchemy2/_functions.py,sha256=e8v584Fx_fmh8XnJvkuYEXrcyPDiWV95Mu2yw6L-LHY,62863
3
- geoalchemy2/_functions_helpers.py,sha256=hVM9DDTgf-oM27EZu6ycW1y2ENnSxUJixdINetWsu0E,2651
4
- geoalchemy2/alembic_helpers.py,sha256=WYjKiVVyHtlB4DI22z-G0-x8NnG8othAINUwmxBCMcs,27885
5
- geoalchemy2/comparator.py,sha256=WUDXn10doDlJVnYiWbVIAhhBu7N5AO9BI8sNf2mhpA4,8100
6
- geoalchemy2/elements.py,sha256=5yd_7dUQGbrLvgYvo6A2YYAFwTvP4_BskIdBCB0DrlM,13002
7
- geoalchemy2/exc.py,sha256=Nn9bRKB_35skWDMkEf4_Y2GL6gvguPVycPZcbOfa69g,226
8
- geoalchemy2/functions.py,sha256=0he8hy_SAWpXAFPdfg32NMW5G0FaZP7yVl0jn0MRLBQ,10320
9
- geoalchemy2/functions.pyi,sha256=rKCKdDSVTgeUQHItkOKqJSEuyKmVJ30bpjLpCPwMI5g,108636
10
- geoalchemy2/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- geoalchemy2/shape.py,sha256=TZJIhCN4p4ZVwZMXidxYxTMeeyReAyoFzyYwspJRqSA,2792
12
- geoalchemy2/utils.py,sha256=OYWYnT64tjp4DWhPdq3KIxHbVti932xPMtGGxajtu-I,488
13
- geoalchemy2/admin/__init__.py,sha256=s8L5C9pYC5dTxhIp9j-JzWeg9kEJevXUW8Pq4_L4aZ8,3984
14
- geoalchemy2/admin/dialects/__init__.py,sha256=XodzuBbWKkkSQzM5EL3I33azuE-y_go0nVOlmIyJ13g,367
15
- geoalchemy2/admin/dialects/common.py,sha256=8OkGa7T2Gxz2vDVnC9nva8Ma0YqBwGxHWfBJoZF1cNQ,2936
16
- geoalchemy2/admin/dialects/geopackage.py,sha256=UaJignKtlo13oHxq92yescVJtXFEXkm4fy5WzR6sLT8,13469
17
- geoalchemy2/admin/dialects/mysql.py,sha256=z2gmkTGav60_myDCFxO1yeezDKBcNcMxFvcpH-OqVKw,6867
18
- geoalchemy2/admin/dialects/postgresql.py,sha256=VwB_h3TC8M5aQ6aKE3UYgxHfbEKav3eIHJeLx534Zzg,6191
19
- geoalchemy2/admin/dialects/sqlite.py,sha256=UYQuNDDy-1-lsvYlqyKy01UtHgLF4iEN42fE5D-9QQk,13597
20
- geoalchemy2/types/__init__.py,sha256=dD2-E8rr1nXr_d4CqyiEvRjO1H-dDRQnzr5B1g3_an8,14186
21
- geoalchemy2/types/dialects/__init__.py,sha256=sw__RqGAFVrUPGrICjsva9SPoYLBNfyAkBHmsJkT7k0,359
22
- geoalchemy2/types/dialects/common.py,sha256=gxKaRQhIODJhu_yBurlvFNAqGh3LfPNu_DcObEG1wlU,138
23
- geoalchemy2/types/dialects/geopackage.py,sha256=nRmN_PnF-CWMEHkWhKVdsybnw3SvXZIBXAHIHXJLTqw,147
24
- geoalchemy2/types/dialects/mysql.py,sha256=zMNi1920v0XlVaCJWSwtq0b0P5YQRn8y3X_rBxC5KEw,1790
25
- geoalchemy2/types/dialects/postgresql.py,sha256=7NBKEbDJXMwX8Sgs6o_N2bAUHgjUcjbrdmYOA7sDiRw,1117
26
- geoalchemy2/types/dialects/sqlite.py,sha256=B-yLzaQcqL_4dXoOPX9D9IXw2ZQlq-Tibv_kqUIBeb4,2104
27
- GeoAlchemy2-0.15.2.dist-info/COPYING.rst,sha256=-bQKftq9uMOROzF7oN65kYBBIJKxTmBuDoftp27IC3I,1056
28
- GeoAlchemy2-0.15.2.dist-info/METADATA,sha256=7ti009u_zE-vAPIyGyfVcC-oZzloZ-TYOKOV7ZahmKo,2087
29
- GeoAlchemy2-0.15.2.dist-info/WHEEL,sha256=Z4pYXqR_rTB7OWNDYFOm1qRk0RX6GFP2o8LgvP453Hk,91
30
- GeoAlchemy2-0.15.2.dist-info/top_level.txt,sha256=3kGUTcfBeXd61zFpof6-qiuw1peNF_HuZabgHQgrdis,12
31
- GeoAlchemy2-0.15.2.dist-info/RECORD,,