h2gis 0.0.1__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.

Potentially problematic release.


This version of h2gis might be problematic. Click here for more details.

@@ -0,0 +1,4 @@
1
+ # MANIFEST.in
2
+ include README.md
3
+ recursive-include h2gis/lib *.so *.dll *.dylib
4
+ recursive-include docs *.md
h2gis-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: h2gis
3
+ Version: 0.0.1
4
+ Summary: A Python library to use an H2GIS database through a native GraalVM bridge.
5
+ Home-page: https://github.com/yourusername/h2gis
6
+ Author: Lemap
7
+ Author-email: you@example.com
8
+ License: LGPL-3.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.7
13
+ Description-Content-Type: text/markdown
14
+ Dynamic: author
15
+ Dynamic: author-email
16
+ Dynamic: classifier
17
+ Dynamic: description
18
+ Dynamic: description-content-type
19
+ Dynamic: home-page
20
+ Dynamic: license
21
+ Dynamic: requires-python
22
+ Dynamic: summary
23
+
24
+ # h2gis-python
25
+
26
+ A lib thal allows to interract with a h2gis lib. Do not forget when you create an H2gisConnection object to put the lib.so full path as a parameter. Without this, it will not work.
27
+
28
+ To insall run :
29
+ ```
30
+ python setup.py bdist_wheel
31
+ pip install dist/h2gis-0.0.1-py3-none-any.whl --force-reinstall
32
+ ```
h2gis-0.0.1/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # h2gis-python
2
+
3
+ A lib thal allows to interract with a h2gis lib. Do not forget when you create an H2gisConnection object to put the lib.so full path as a parameter. Without this, it will not work.
4
+
5
+ To insall run :
6
+ ```
7
+ python setup.py bdist_wheel
8
+ pip install dist/h2gis-0.0.1-py3-none-any.whl --force-reinstall
9
+ ```
@@ -0,0 +1,46 @@
1
+ # Usage Examples
2
+
3
+ ## Basic Example
4
+
5
+ ```python
6
+ # Example usage:
7
+ from h2gis import H2gisConnector
8
+ import time
9
+ import statistics
10
+
11
+
12
+ h2gis = H2gisConnector("./h2gis/h2gis.so")
13
+ h2gis.connect("./test", "sa", "sa")
14
+
15
+
16
+ #currently the two linew below are not working, you must do it via the .jar of h2gis, otherwise nothing will work
17
+ h2gis.execute("CREATE ALIAS IF NOT EXISTS H2GIS_SPATIAL FOR \"org.h2gis.functions.factory.H2GISFunctions.load\";")
18
+
19
+ h2gis.execute("DROP TABLE TEST IF EXISTS;")
20
+
21
+
22
+ times_nat = []
23
+ i = 0
24
+ for i in range(0, 50):
25
+ start = time.perf_counter()
26
+ h2gis.execute("DROP TABLE TEST IF EXISTS;")
27
+ h2gis.execute("CALL GeoJsonRead('./test.geojson');")
28
+ end = time.perf_counter()
29
+ times_nat.append(end - start)
30
+ print("etape ", i, "finished")
31
+
32
+ print("elapsed native time ", sum(times_nat))
33
+
34
+ print("average native time ", statistics.mean(times_nat))
35
+
36
+ h2gis.close()
37
+ ```
38
+
39
+ ## Custom Native Library Path
40
+
41
+ ```python
42
+ db = H2gisConnector(lib_path="/custom/path/h2gis.so")
43
+ ```
44
+
45
+ ## Notes
46
+ - By default, the native `.so`, `.dll` are already included in the package, in the `lib` folder.
@@ -0,0 +1,35 @@
1
+ # H2gisConnector Class
2
+
3
+ ## Overview
4
+
5
+ `H2gisConnector` is a Python class that wraps a native shared library compiled with GraalVM. It provides access to an embedded H2GIS database using GraalVM isolate and thread objects.
6
+
7
+ ## Constructor
8
+
9
+ ```python
10
+ H2gisConnector(lib_path=None)
11
+ ```
12
+
13
+ - `lib_path`: Optional path to the `.so`, `.dll`, or `.dylib` file. If not provided, a default path is used based on the current OS.
14
+
15
+ ## Methods
16
+
17
+ ### `connect(db_file: str, username="sa", password="")`
18
+
19
+ Connect to a local H2GIS database file.
20
+
21
+ ### `execute(sql: str) -> int`
22
+
23
+ Execute an `INSERT`, `UPDATE`, or `DELETE` SQL statement.
24
+
25
+ ### `fetch(sql: str) -> list[str]`
26
+
27
+ Execute a `SELECT` SQL query and return results as a list of strings (one per row).
28
+
29
+ ### `close()`
30
+
31
+ Close the active database connection.
32
+
33
+ ### `__del__()`
34
+
35
+ Destructor that automatically tears down the GraalVM isolate and closes any connection.
@@ -0,0 +1,9 @@
1
+ # H2GIS Python Package
2
+
3
+ This package provides a Python interface to the native H2GIS spatial database engine using a GraalVM-compiled native library and a C interface.
4
+
5
+ ## Modules
6
+
7
+ - [connector.md](./connector.md): Detailed documentation for the `H2gisConnector` class.
8
+ - [usage.md](./usage.md): Example usage of the library.
9
+ - [installation.md](installation.md): Installation and setup instructions.
@@ -0,0 +1,33 @@
1
+ # Installation
2
+
3
+ ## Requirements
4
+
5
+ - Python 3.7+
6
+ - Native H2GIS shared library compiled with GraalVM
7
+ - Supported platforms: Linux, Windows, macOS
8
+
9
+ ## Install the Python Package
10
+
11
+ ```bash
12
+ pip install .
13
+ ```
14
+
15
+ Ensure the native library file is present in the following structure:
16
+
17
+ ```
18
+ h2gis/
19
+ ├── lib/
20
+ │ ├── h2gis.so # Linux
21
+ │ ├── h2gis.dylib # macOS
22
+ │ └── h2gis.dll # Windows
23
+ ```
24
+
25
+ ## Custom Library Path
26
+
27
+ You can specify a custom path to the native library:
28
+
29
+ ```python
30
+ from h2gis import H2gisConnector
31
+
32
+ db = H2gisConnector(lib_path="/custom/path/to/h2gis.so")
33
+ ```
@@ -0,0 +1,4 @@
1
+ from .h2gisconnector import H2gisConnector
2
+
3
+ __all__ = ["H2gisConnector"]
4
+
@@ -0,0 +1,170 @@
1
+ import ctypes
2
+ import os
3
+ import platform
4
+
5
+
6
+ # --- Opaque struct placeholders for GraalVM native API ---
7
+ class GraalIsolate(ctypes.Structure):
8
+ _fields_ = []
9
+
10
+
11
+ class GraalIsolateThread(ctypes.Structure):
12
+ _fields_ = []
13
+
14
+
15
+ class GraalIsolateParams(ctypes.Structure):
16
+ _fields_ = []
17
+
18
+
19
+ GraalIsolate_p = ctypes.POINTER(GraalIsolate)
20
+ GraalIsolateThread_p = ctypes.POINTER(GraalIsolateThread)
21
+
22
+
23
+ class H2gisConnector:
24
+ """
25
+ Python wrapper for the native H2GIS library compiled with GraalVM.
26
+ """
27
+
28
+ def __init__(self, lib_path=None):
29
+ """
30
+ Initialize the H2GIS connector and create a GraalVM isolate.
31
+
32
+ :param lib_path: Optional path to the native .so/.dll/.dylib library
33
+ """
34
+ if lib_path is None:
35
+ lib_path = self._default_library_path()
36
+
37
+ self.lib = ctypes.CDLL(lib_path)
38
+ self._setup_c_function_signatures()
39
+
40
+ self.isolate = GraalIsolate_p()
41
+ self.thread = GraalIsolateThread_p()
42
+ self.connection = 0
43
+
44
+ params = GraalIsolateParams()
45
+ result = self.lib.graal_create_isolate(
46
+ ctypes.byref(params),
47
+ ctypes.byref(self.isolate),
48
+ ctypes.byref(self.thread)
49
+ )
50
+ if result != 0:
51
+ raise RuntimeError(f"Failed to create GraalVM isolate (code {result})")
52
+
53
+ def _default_library_path(self):
54
+ """Find default library path relative to this file."""
55
+ base_dir = os.path.dirname(__file__)
56
+ system = platform.system()
57
+ if system == "Windows":
58
+ libname = "h2gis.dll"
59
+ else:
60
+ libname = "h2gis.so"
61
+
62
+ return os.path.join(base_dir, "lib", libname)
63
+
64
+ def _setup_c_function_signatures(self):
65
+ """Declare argument and return types for native functions."""
66
+ self.lib.graal_create_isolate.argtypes = [
67
+ ctypes.POINTER(GraalIsolateParams),
68
+ ctypes.POINTER(GraalIsolate_p),
69
+ ctypes.POINTER(GraalIsolateThread_p),
70
+ ]
71
+ self.lib.graal_create_isolate.restype = ctypes.c_int
72
+
73
+ self.lib.graal_tear_down_isolate.argtypes = [GraalIsolateThread_p]
74
+ self.lib.graal_tear_down_isolate.restype = ctypes.c_int
75
+
76
+ self.lib.h2gis_connect.argtypes = [
77
+ GraalIsolateThread_p,
78
+ ctypes.c_char_p,
79
+ ctypes.c_char_p,
80
+ ctypes.c_char_p,
81
+ ]
82
+ self.lib.h2gis_connect.restype = ctypes.c_long
83
+
84
+ self.lib.h2gis_execute_update.argtypes = [
85
+ GraalIsolateThread_p,
86
+ ctypes.c_long,
87
+ ctypes.c_char_p,
88
+ ]
89
+ self.lib.h2gis_execute_update.restype = ctypes.c_int
90
+
91
+ self.lib.h2gis_execute.argtypes = [
92
+ GraalIsolateThread_p,
93
+ ctypes.c_long,
94
+ ctypes.c_char_p,
95
+ ]
96
+ self.lib.h2gis_execute.restype = ctypes.c_long
97
+
98
+ self.lib.h2gis_fetch_row.argtypes = [
99
+ GraalIsolateThread_p,
100
+ ctypes.c_long,
101
+ ]
102
+ self.lib.h2gis_fetch_row.restype = ctypes.c_char_p
103
+
104
+ self.lib.h2gis_close_query.argtypes = [
105
+ GraalIsolateThread_p,
106
+ ctypes.c_long,
107
+ ]
108
+ self.lib.h2gis_close_query.restype = None
109
+
110
+ self.lib.h2gis_close_connection.argtypes = [
111
+ GraalIsolateThread_p,
112
+ ctypes.c_long,
113
+ ]
114
+ self.lib.h2gis_close_connection.restype = None
115
+
116
+ def connect(self, db_file: str, username="sa", password=""):
117
+ """Connect to a H2GIS database file."""
118
+ self.connection = self.lib.h2gis_connect(
119
+ self.thread,
120
+ db_file.encode("utf-8"),
121
+ username.encode("utf-8"),
122
+ password.encode("utf-8"),
123
+ )
124
+ if self.connection == 0:
125
+ raise RuntimeError("Failed to connect to H2GIS database.")
126
+
127
+ def execute(self, sql: str) -> int:
128
+ """Execute INSERT/UPDATE/DELETE statement and return affected row count."""
129
+ return self.lib.h2gis_execute_update(
130
+ self.thread,
131
+ self.connection,
132
+ sql.encode("utf-8"),
133
+ )
134
+
135
+ def fetch(self, sql: str) -> list[str]:
136
+ """Execute SELECT statement and return list of rows."""
137
+ handle = self.lib.h2gis_execute(
138
+ self.thread,
139
+ self.connection,
140
+ sql.encode("utf-8"),
141
+ )
142
+ if handle == 0:
143
+ raise RuntimeError("Query execution failed")
144
+
145
+ rows = []
146
+ while True:
147
+ row = self.lib.h2gis_fetch_row(self.thread, handle)
148
+ if not row:
149
+ break
150
+ rows.append(row.decode("utf-8"))
151
+
152
+ self.lib.h2gis_close_query(self.thread, handle)
153
+ return rows
154
+
155
+ def close(self):
156
+ """Close the database connection."""
157
+ if self.connection:
158
+ self.lib.h2gis_close_connection(self.thread, self.connection)
159
+ self.connection = 0
160
+
161
+ def __del__(self):
162
+ """Destructor to clean up isolate and resources."""
163
+ try:
164
+ self.close()
165
+ except Exception:
166
+ pass
167
+ try:
168
+ self.lib.graal_tear_down_isolate(self.thread)
169
+ except Exception:
170
+ pass
Binary file
Binary file
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: h2gis
3
+ Version: 0.0.1
4
+ Summary: A Python library to use an H2GIS database through a native GraalVM bridge.
5
+ Home-page: https://github.com/yourusername/h2gis
6
+ Author: Lemap
7
+ Author-email: you@example.com
8
+ License: LGPL-3.0
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.7
13
+ Description-Content-Type: text/markdown
14
+ Dynamic: author
15
+ Dynamic: author-email
16
+ Dynamic: classifier
17
+ Dynamic: description
18
+ Dynamic: description-content-type
19
+ Dynamic: home-page
20
+ Dynamic: license
21
+ Dynamic: requires-python
22
+ Dynamic: summary
23
+
24
+ # h2gis-python
25
+
26
+ A lib thal allows to interract with a h2gis lib. Do not forget when you create an H2gisConnection object to put the lib.so full path as a parameter. Without this, it will not work.
27
+
28
+ To insall run :
29
+ ```
30
+ python setup.py bdist_wheel
31
+ pip install dist/h2gis-0.0.1-py3-none-any.whl --force-reinstall
32
+ ```
@@ -0,0 +1,16 @@
1
+ MANIFEST.in
2
+ README.md
3
+ setup.py
4
+ docs/examples.md
5
+ docs/h2gisconnector.md
6
+ docs/index.md
7
+ docs/installation.md
8
+ h2gis/__init__.py
9
+ h2gis/h2gisconnector.py
10
+ h2gis.egg-info/PKG-INFO
11
+ h2gis.egg-info/SOURCES.txt
12
+ h2gis.egg-info/dependency_links.txt
13
+ h2gis.egg-info/top_level.txt
14
+ h2gis/lib/h2gis.dll
15
+ h2gis/lib/h2gis.so
16
+ test/test_h2gis.py
@@ -0,0 +1 @@
1
+ h2gis
h2gis-0.0.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
h2gis-0.0.1/setup.py ADDED
@@ -0,0 +1,27 @@
1
+ from setuptools import find_packages, setup
2
+
3
+ setup(
4
+ name='h2gis',
5
+ version='0.0.1',
6
+ description='A Python library to use an H2GIS database through a native GraalVM bridge.',
7
+ long_description=open("README.md").read(),
8
+ long_description_content_type="text/markdown",
9
+ author='Lemap',
10
+ author_email='you@example.com', # Optional, but recommended
11
+ url='https://github.com/yourusername/h2gis', # Replace with actual URL
12
+ packages=find_packages(),
13
+ include_package_data=True,
14
+ package_data={
15
+ "h2gis": ["lib/*.so", "lib/*.dll", "lib/*.dylib"],
16
+ "h2gis.docs": ["*.md"], # add this if docs is inside the h2gis package
17
+ },
18
+ license="LGPL-3.0",
19
+ classifiers=[
20
+ "Programming Language :: Python :: 3",
21
+ "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
22
+ "Operating System :: OS Independent",
23
+ ],
24
+ python_requires=">=3.7",
25
+ setup_requires=['pytest-runner'],
26
+ tests_require=['pytest==4.4.1'],
27
+ )
@@ -0,0 +1,146 @@
1
+ import unittest
2
+ from unittest.mock import MagicMock, patch
3
+ from h2gis.h2gisconnector import H2gisConnector
4
+
5
+
6
+ @patch("h2gis.h2gisconnector.ctypes.CDLL")
7
+ class TestH2gis(unittest.TestCase):
8
+ def setUp(self):
9
+ """
10
+ Set up mock native library with default return values.
11
+ This is run before each test.
12
+ """
13
+ self.mock_lib = MagicMock()
14
+ self.mock_lib.graal_create_isolate.return_value = 0
15
+ self.mock_lib.h2gis_connect.return_value = 1234
16
+ self.mock_lib.h2gis_execute.return_value = 42
17
+ self.mock_lib.h2gis_fetch_row.side_effect = [b"row1", b"row2", None]
18
+ self.mock_lib.h2gis_execute_update.return_value = 1
19
+ self.mock_lib.graal_tear_down_isolate.return_value = 0
20
+
21
+ def _create_h2gis(self, mock_cdll):
22
+ """
23
+ Helper method to create H2gis instance with mocked native lib.
24
+
25
+ @param mock_cdll: patched ctypes.CDLL
26
+ @return: instance of H2gis
27
+ """
28
+ mock_cdll.return_value = self.mock_lib
29
+ return H2gisConnector(lib_path="fake.so")
30
+
31
+ # ------------------------
32
+ # ✅ SUCCESS TEST CASES
33
+ # ------------------------
34
+
35
+ def test_connect_success(self, mock_cdll):
36
+ """
37
+ Test successful connection to database.
38
+ """
39
+ h2gis = self._create_h2gis(mock_cdll)
40
+ h2gis.connect("/path/to/db")
41
+ self.assertEqual(h2gis.connection, 1234)
42
+
43
+ def test_execute_query_success(self, mock_cdll):
44
+ """
45
+ Test successful SELECT query with multiple rows.
46
+ """
47
+ h2gis = self._create_h2gis(mock_cdll)
48
+ h2gis.connection = 1234
49
+ result = h2gis.execute_query("SELECT * FROM my_table")
50
+ self.assertEqual(result, ["row1", "row2"])
51
+ self.mock_lib.h2gis_close_query.assert_called_once()
52
+
53
+ def test_execute_update_success(self, mock_cdll):
54
+ """
55
+ Test successful UPDATE query with 1 row affected.
56
+ """
57
+ h2gis = self._create_h2gis(mock_cdll)
58
+ h2gis.connection = 1234
59
+ affected = h2gis.execute_update("UPDATE my_table SET x = 1")
60
+ self.assertEqual(affected, 1)
61
+
62
+ def test_close_connection_success(self, mock_cdll):
63
+ """
64
+ Test closing the connection resets the connection handle.
65
+ """
66
+ h2gis = self._create_h2gis(mock_cdll)
67
+ h2gis.connection = 1234
68
+ h2gis.close()
69
+ self.assertEqual(h2gis.connection, 0)
70
+ self.mock_lib.h2gis_close_connection.assert_called_once()
71
+
72
+ # ------------------------
73
+ # ⚠️ LIMIT TEST CASES
74
+ # ------------------------
75
+
76
+ def test_execute_query_empty_result(self, mock_cdll):
77
+ """
78
+ Test SELECT query returning no rows.
79
+ """
80
+ self.mock_lib.h2gis_fetch_row.side_effect = [None]
81
+ h2gis = self._create_h2gis(mock_cdll)
82
+ h2gis.connection = 1234
83
+ result = h2gis.execute_query("SELECT * FROM empty_table")
84
+ self.assertEqual(result, [])
85
+
86
+ def test_execute_update_zero_affected(self, mock_cdll):
87
+ """
88
+ Test UPDATE query with 0 rows affected.
89
+ """
90
+ self.mock_lib.h2gis_execute_update.return_value = 0
91
+ h2gis = self._create_h2gis(mock_cdll)
92
+ h2gis.connection = 1234
93
+ affected = h2gis.execute_update("DELETE FROM my_table WHERE 1=0")
94
+ self.assertEqual(affected, 0)
95
+
96
+ def test_close_when_not_connected(self, mock_cdll):
97
+ """
98
+ Test close() does nothing if already disconnected.
99
+ """
100
+ h2gis = self._create_h2gis(mock_cdll)
101
+ h2gis.connection = 0
102
+ h2gis.close()
103
+ self.mock_lib.h2gis_close_connection.assert_not_called()
104
+
105
+ def test_del_implicit_cleanup(self, mock_cdll):
106
+ """
107
+ Test that __del__ triggers close and cleanup.
108
+ """
109
+ h2gis = self._create_h2gis(mock_cdll)
110
+ h2gis.connection = 1234
111
+ del h2gis # __del__ should be called without exception
112
+
113
+ # ------------------------
114
+ # ❌ ERROR TEST CASES
115
+ # ------------------------
116
+
117
+ def test_connect_failure(self, mock_cdll):
118
+ """
119
+ Test exception when connection fails (native returns 0).
120
+ """
121
+ self.mock_lib.h2gis_connect.return_value = 0
122
+ h2gis = self._create_h2gis(mock_cdll)
123
+ with self.assertRaises(RuntimeError) as ctx:
124
+ h2gis.connect("/bad/path")
125
+ self.assertIn("Failed to connect", str(ctx.exception))
126
+
127
+ def test_execute_query_failure(self, mock_cdll):
128
+ """
129
+ Test exception if query execution fails (native returns 0).
130
+ """
131
+ self.mock_lib.h2gis_execute.return_value = 0
132
+ h2gis = self._create_h2gis(mock_cdll)
133
+ h2gis.connection = 1234
134
+ with self.assertRaises(RuntimeError) as ctx:
135
+ h2gis.execute_query("SELECT * FROM bad_table")
136
+ self.assertIn("Query execution failed", str(ctx.exception))
137
+
138
+ def test_graal_create_isolate_failure(self, mock_cdll):
139
+ """
140
+ Test exception if GraalVM isolate creation fails.
141
+ """
142
+ self.mock_lib.graal_create_isolate.return_value = 1
143
+ mock_cdll.return_value = self.mock_lib
144
+ with self.assertRaises(RuntimeError) as ctx:
145
+ H2gisConnector(lib_path="fake.so")
146
+ self.assertIn("Failed to create GraalVM isolate", str(ctx.exception))