agriql 3.10.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.
agriql-3.10.1/LICENSE ADDED
@@ -0,0 +1,17 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Copyright 2026 Sbruice Singh, Agricultural Service of Product Farm (ASPF)
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
agriql-3.10.1/PKG-INFO ADDED
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: agriql
3
+ Version: 3.10.1
4
+ Summary: Client for the AgriQL engine. This package is not a database.
5
+ Author-email: Sbruice Singh <sbruicesingh@haclon.in>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/SbruiceS/agriql-python
8
+ Project-URL: Issues, https://github.com/SbruiceS/agriql-python/issues
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Provides-Extra: pandas
13
+ Requires-Dist: pandas; extra == "pandas"
14
+ Provides-Extra: polars
15
+ Requires-Dist: polars; extra == "polars"
16
+ Provides-Extra: arrow
17
+ Requires-Dist: pyarrow; extra == "arrow"
18
+ Provides-Extra: geo
19
+ Requires-Dist: geopandas; extra == "geo"
20
+ Provides-Extra: ml
21
+ Requires-Dist: numpy; extra == "ml"
22
+ Provides-Extra: all
23
+ Requires-Dist: pandas; extra == "all"
24
+ Requires-Dist: polars; extra == "all"
25
+ Requires-Dist: pyarrow; extra == "all"
26
+ Requires-Dist: numpy; extra == "all"
27
+ Dynamic: license-file
28
+
29
+ # agriql
30
+
31
+ Python client for the AgriQL engine. `import agriql` does not start a second database.
32
+
33
+ ```python
34
+ import agriql
35
+
36
+ db = agriql.connect("farm.aqdb")
37
+ result = db.query("SELECT plant_id, soil_moisture FROM agriculture.soil")
38
+ result.to_records()
39
+ ```
40
+
41
+ `to_pandas()`, `to_polars()`, and `to_arrow()` import those libraries only when called. Arrow IPC is not produced by the engine. `to_arrow()` builds a `pyarrow` table from the result grid.
42
+
43
+ ## Install
44
+
45
+ `libagriql_ffi` must already be on the library path. Build it from [AgriQL](https://github.com/SbruiceS/AgriQL):
46
+
47
+ ```bash
48
+ git clone https://github.com/SbruiceS/AgriQL.git
49
+ cd AgriQL
50
+ cargo build -p agriql-ffi --release
51
+ sudo cp target/release/libagriql_ffi.so /usr/local/lib
52
+ sudo ldconfig
53
+ ```
54
+
55
+ Then:
56
+
57
+ ```bash
58
+ pip install git+https://github.com/SbruiceS/agriql-python.git
59
+ pip install "agriql[pandas] @ git+https://github.com/SbruiceS/agriql-python.git"
60
+ ```
61
+
62
+ If the library is not in a default directory, set `AGRIQL_FFI` to the full path of `libagriql_ffi.so`.
63
+
64
+ `agriql.connect("agriql://127.0.0.1:5471/farm")` uses the server port instead of the library. This package is not published on PyPI.
@@ -0,0 +1,36 @@
1
+ # agriql
2
+
3
+ Python client for the AgriQL engine. `import agriql` does not start a second database.
4
+
5
+ ```python
6
+ import agriql
7
+
8
+ db = agriql.connect("farm.aqdb")
9
+ result = db.query("SELECT plant_id, soil_moisture FROM agriculture.soil")
10
+ result.to_records()
11
+ ```
12
+
13
+ `to_pandas()`, `to_polars()`, and `to_arrow()` import those libraries only when called. Arrow IPC is not produced by the engine. `to_arrow()` builds a `pyarrow` table from the result grid.
14
+
15
+ ## Install
16
+
17
+ `libagriql_ffi` must already be on the library path. Build it from [AgriQL](https://github.com/SbruiceS/AgriQL):
18
+
19
+ ```bash
20
+ git clone https://github.com/SbruiceS/AgriQL.git
21
+ cd AgriQL
22
+ cargo build -p agriql-ffi --release
23
+ sudo cp target/release/libagriql_ffi.so /usr/local/lib
24
+ sudo ldconfig
25
+ ```
26
+
27
+ Then:
28
+
29
+ ```bash
30
+ pip install git+https://github.com/SbruiceS/agriql-python.git
31
+ pip install "agriql[pandas] @ git+https://github.com/SbruiceS/agriql-python.git"
32
+ ```
33
+
34
+ If the library is not in a default directory, set `AGRIQL_FFI` to the full path of `libagriql_ffi.so`.
35
+
36
+ `agriql.connect("agriql://127.0.0.1:5471/farm")` uses the server port instead of the library. This package is not published on PyPI.
@@ -0,0 +1,7 @@
1
+ """AgriQL client. Queries run in the AgriQL engine through its C ABI or its port."""
2
+
3
+ from agriql.connection import connect
4
+ from agriql.result import Result
5
+
6
+ __all__ = ["connect", "Result"]
7
+ __version__ = "3.10.1"
@@ -0,0 +1,13 @@
1
+ import sys
2
+
3
+ from agriql import connect
4
+
5
+
6
+ def main():
7
+ target = sys.argv[1] if len(sys.argv) > 1 else ":memory:"
8
+ sql = sys.argv[2] if len(sys.argv) > 2 else "SHOW VERSION;"
9
+ print(connect(target).query(sql).text)
10
+
11
+
12
+ if __name__ == "__main__":
13
+ main()
@@ -0,0 +1,52 @@
1
+ """ctypes bindings for libagriql_ffi. The library owns the engine."""
2
+
3
+ import ctypes
4
+ import os
5
+ from pathlib import Path
6
+
7
+
8
+ def load():
9
+ requested = os.environ.get("AGRIQL_FFI")
10
+ names = [
11
+ requested,
12
+ "libagriql_ffi.so",
13
+ "libagriql_ffi.dylib",
14
+ "agriql_ffi.dll",
15
+ ]
16
+ roots = [
17
+ Path.cwd(),
18
+ Path(__file__).resolve().parents[3] / "target" / "debug",
19
+ Path(__file__).resolve().parents[3] / "target" / "release",
20
+ ]
21
+ candidates = []
22
+ for name in names:
23
+ if not name:
24
+ continue
25
+ path = Path(name)
26
+ if path.is_file():
27
+ candidates.append(path)
28
+ for root in roots:
29
+ found = root / name
30
+ if found.is_file():
31
+ candidates.append(found)
32
+ if not candidates:
33
+ raise FileNotFoundError(
34
+ "libagriql_ffi was not found. Build it with `cargo build -p agriql-ffi` "
35
+ "or set AGRIQL_FFI to the library path."
36
+ )
37
+ chosen = max(candidates, key=lambda path: path.stat().st_mtime)
38
+ lib = ctypes.CDLL(str(chosen))
39
+ lib.agriql_open.argtypes = [ctypes.c_char_p, ctypes.POINTER(ctypes.c_void_p)]
40
+ lib.agriql_open.restype = ctypes.c_int
41
+ lib.agriql_close.argtypes = [ctypes.c_void_p]
42
+ lib.agriql_close.restype = ctypes.c_int
43
+ lib.agriql_execute.argtypes = [ctypes.c_void_p, ctypes.c_char_p]
44
+ lib.agriql_execute.restype = ctypes.c_void_p
45
+ lib.agriql_result_text.argtypes = [ctypes.c_void_p]
46
+ lib.agriql_result_text.restype = ctypes.c_char_p
47
+ lib.agriql_result_status.argtypes = [ctypes.c_void_p]
48
+ lib.agriql_result_status.restype = ctypes.c_int
49
+ lib.agriql_free_result.argtypes = [ctypes.c_void_p]
50
+ lib.agriql_thread_error.argtypes = []
51
+ lib.agriql_thread_error.restype = ctypes.c_char_p
52
+ return lib
@@ -0,0 +1,105 @@
1
+ """Connections. A file path uses the C ABI. agriql:// uses the server port."""
2
+
3
+ import socket
4
+
5
+ from agriql._ffi import load
6
+ from agriql.result import Result
7
+
8
+ _lib = None
9
+
10
+
11
+ def connect(target=":memory:"):
12
+ if str(target).startswith("agriql://"):
13
+ return PortConnection(str(target))
14
+ return EngineConnection(str(target))
15
+
16
+
17
+ class EngineConnection:
18
+ def __init__(self, path):
19
+ global _lib
20
+ if _lib is None:
21
+ _lib = load()
22
+ self._lib = _lib
23
+ self._db = ctypes_open(self._lib, path)
24
+
25
+ def query(self, sql):
26
+ result = self._lib.agriql_execute(self._db, sql.encode())
27
+ if not result:
28
+ raise RuntimeError(_error(self._lib))
29
+ status = self._lib.agriql_result_status(result)
30
+ raw = self._lib.agriql_result_text(result)
31
+ text = raw.decode() if raw else ""
32
+ self._lib.agriql_free_result(result)
33
+ if status != 0:
34
+ raise RuntimeError(text or _error(self._lib))
35
+ return Result(text)
36
+
37
+ def close(self):
38
+ if self._db:
39
+ self._lib.agriql_close(self._db)
40
+ self._db = None
41
+
42
+ def __enter__(self):
43
+ return self
44
+
45
+ def __exit__(self, *_args):
46
+ self.close()
47
+
48
+
49
+ class PortConnection:
50
+ def __init__(self, url):
51
+ raw = url.replace("agriql://", "", 1)
52
+ database = None
53
+ if "/" in raw:
54
+ raw, database = raw.split("/", 1)
55
+ host, port = raw.rsplit(":", 1)
56
+ self.host = host
57
+ self.port = int(port)
58
+ self.database = database
59
+
60
+ def query(self, sql):
61
+ sock = socket.create_connection((self.host, self.port), timeout=5)
62
+ try:
63
+ payload = ""
64
+ if self.database:
65
+ payload += f"USE DATABASE {self.database};\n"
66
+ payload += sql.strip() + "\n.quit\n"
67
+ sock.sendall(payload.encode())
68
+ data = b""
69
+ while True:
70
+ chunk = sock.recv(65536)
71
+ if not chunk:
72
+ break
73
+ data += chunk
74
+ finally:
75
+ sock.close()
76
+ if not data:
77
+ raise ConnectionError(f"agriql://{self.host}:{self.port} closed without a reply")
78
+ return Result(data.decode())
79
+
80
+ def close(self):
81
+ return None
82
+
83
+ def __enter__(self):
84
+ return self
85
+
86
+ def __exit__(self, *_args):
87
+ return None
88
+
89
+
90
+ def ctypes_open(lib, path):
91
+ import ctypes
92
+
93
+ out = ctypes.c_void_p()
94
+ encoded = None if path in ("", ":memory:") else path.encode()
95
+ status = lib.agriql_open(encoded, ctypes.byref(out))
96
+ if status != 0 or not out.value:
97
+ raise RuntimeError(_error(lib))
98
+ return out.value
99
+
100
+
101
+ def _error(lib):
102
+ raw = lib.agriql_thread_error()
103
+ if not raw:
104
+ return "AGRIQL-2701: the engine returned no message"
105
+ return raw.decode()
@@ -0,0 +1,37 @@
1
+ """A query result is a grid from the engine. pandas, Polars, and Arrow are optional views."""
2
+
3
+
4
+ class Result:
5
+ def __init__(self, text):
6
+ self.text = text
7
+
8
+ def to_records(self):
9
+ lines = [line for line in self.text.splitlines() if line]
10
+ if len(lines) < 2 or "\t" not in lines[0]:
11
+ return [{"text": line} for line in lines]
12
+ header = lines[0].split("\t")
13
+ return [dict(zip(header, line.split("\t"))) for line in lines[1:]]
14
+
15
+ def to_pandas(self):
16
+ try:
17
+ import pandas as pd
18
+ except ImportError as exc:
19
+ raise ImportError("pandas is not installed. Use pip install 'agriql[pandas]'.") from exc
20
+ return pd.DataFrame(self.to_records())
21
+
22
+ def to_polars(self):
23
+ try:
24
+ import polars as pl
25
+ except ImportError as exc:
26
+ raise ImportError("polars is not installed. Use pip install 'agriql[polars]'.") from exc
27
+ return pl.DataFrame(self.to_records())
28
+
29
+ def to_arrow(self):
30
+ try:
31
+ import pyarrow as pa
32
+ except ImportError as exc:
33
+ raise ImportError(
34
+ "pyarrow is not installed. The engine does not emit Arrow IPC yet. "
35
+ "Use pip install 'agriql[arrow]' to build a table from the result grid."
36
+ ) from exc
37
+ return pa.Table.from_pylist(self.to_records())
@@ -0,0 +1,64 @@
1
+ Metadata-Version: 2.4
2
+ Name: agriql
3
+ Version: 3.10.1
4
+ Summary: Client for the AgriQL engine. This package is not a database.
5
+ Author-email: Sbruice Singh <sbruicesingh@haclon.in>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/SbruiceS/agriql-python
8
+ Project-URL: Issues, https://github.com/SbruiceS/agriql-python/issues
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Provides-Extra: pandas
13
+ Requires-Dist: pandas; extra == "pandas"
14
+ Provides-Extra: polars
15
+ Requires-Dist: polars; extra == "polars"
16
+ Provides-Extra: arrow
17
+ Requires-Dist: pyarrow; extra == "arrow"
18
+ Provides-Extra: geo
19
+ Requires-Dist: geopandas; extra == "geo"
20
+ Provides-Extra: ml
21
+ Requires-Dist: numpy; extra == "ml"
22
+ Provides-Extra: all
23
+ Requires-Dist: pandas; extra == "all"
24
+ Requires-Dist: polars; extra == "all"
25
+ Requires-Dist: pyarrow; extra == "all"
26
+ Requires-Dist: numpy; extra == "all"
27
+ Dynamic: license-file
28
+
29
+ # agriql
30
+
31
+ Python client for the AgriQL engine. `import agriql` does not start a second database.
32
+
33
+ ```python
34
+ import agriql
35
+
36
+ db = agriql.connect("farm.aqdb")
37
+ result = db.query("SELECT plant_id, soil_moisture FROM agriculture.soil")
38
+ result.to_records()
39
+ ```
40
+
41
+ `to_pandas()`, `to_polars()`, and `to_arrow()` import those libraries only when called. Arrow IPC is not produced by the engine. `to_arrow()` builds a `pyarrow` table from the result grid.
42
+
43
+ ## Install
44
+
45
+ `libagriql_ffi` must already be on the library path. Build it from [AgriQL](https://github.com/SbruiceS/AgriQL):
46
+
47
+ ```bash
48
+ git clone https://github.com/SbruiceS/AgriQL.git
49
+ cd AgriQL
50
+ cargo build -p agriql-ffi --release
51
+ sudo cp target/release/libagriql_ffi.so /usr/local/lib
52
+ sudo ldconfig
53
+ ```
54
+
55
+ Then:
56
+
57
+ ```bash
58
+ pip install git+https://github.com/SbruiceS/agriql-python.git
59
+ pip install "agriql[pandas] @ git+https://github.com/SbruiceS/agriql-python.git"
60
+ ```
61
+
62
+ If the library is not in a default directory, set `AGRIQL_FFI` to the full path of `libagriql_ffi.so`.
63
+
64
+ `agriql.connect("agriql://127.0.0.1:5471/farm")` uses the server port instead of the library. This package is not published on PyPI.
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ agriql/__init__.py
5
+ agriql/__main__.py
6
+ agriql/_ffi.py
7
+ agriql/connection.py
8
+ agriql/result.py
9
+ agriql.egg-info/PKG-INFO
10
+ agriql.egg-info/SOURCES.txt
11
+ agriql.egg-info/dependency_links.txt
12
+ agriql.egg-info/entry_points.txt
13
+ agriql.egg-info/requires.txt
14
+ agriql.egg-info/top_level.txt
15
+ tests/test_client.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ agriql = agriql.__main__:main
@@ -0,0 +1,21 @@
1
+
2
+ [all]
3
+ pandas
4
+ polars
5
+ pyarrow
6
+ numpy
7
+
8
+ [arrow]
9
+ pyarrow
10
+
11
+ [geo]
12
+ geopandas
13
+
14
+ [ml]
15
+ numpy
16
+
17
+ [pandas]
18
+ pandas
19
+
20
+ [polars]
21
+ polars
@@ -0,0 +1 @@
1
+ agriql
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "agriql"
3
+ version = "3.10.1"
4
+ description = "Client for the AgriQL engine. This package is not a database."
5
+ readme = "README.md"
6
+ requires-python = ">=3.9"
7
+ license = "Apache-2.0"
8
+ authors = [{ name = "Sbruice Singh", email = "sbruicesingh@haclon.in" }]
9
+ dependencies = []
10
+
11
+ [project.urls]
12
+ Homepage = "https://github.com/SbruiceS/agriql-python"
13
+ Issues = "https://github.com/SbruiceS/agriql-python/issues"
14
+
15
+ [project.optional-dependencies]
16
+ pandas = ["pandas"]
17
+ polars = ["polars"]
18
+ arrow = ["pyarrow"]
19
+ geo = ["geopandas"]
20
+ ml = ["numpy"]
21
+ all = ["pandas", "polars", "pyarrow", "numpy"]
22
+
23
+ [project.scripts]
24
+ agriql = "agriql.__main__:main"
25
+
26
+ [build-system]
27
+ requires = ["setuptools>=68"]
28
+ build-backend = "setuptools.build_meta"
29
+
30
+ [tool.setuptools.packages.find]
31
+ where = ["."]
32
+ include = ["agriql*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,44 @@
1
+ import os
2
+ import tempfile
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+ from agriql import connect
7
+ from agriql.result import Result
8
+
9
+
10
+ class ClientTest(unittest.TestCase):
11
+ def test_memory_query_uses_the_engine(self):
12
+ db = connect(":memory:")
13
+ text = db.query("SHOW VERSION;").text
14
+ self.assertIn("engine=3.10.1", text)
15
+ db.close()
16
+
17
+ def test_aqdb_file_round_trip(self):
18
+ with tempfile.TemporaryDirectory() as tmp:
19
+ os.environ["AGRIQL_DATA_DIR"] = tmp
20
+ created = connect(":memory:")
21
+ created.query("CREATE DATABASE farm;")
22
+ created.close()
23
+ path = Path(tmp) / "databases" / "farm.aqdb"
24
+ self.assertTrue(path.is_file(), path)
25
+ db = connect(str(path))
26
+ db.query("CREATE SCHEMA fields;")
27
+ db.close()
28
+ again = connect(str(path))
29
+ self.assertIn("fields", again.query("SHOW SCHEMAS;").text)
30
+ again.close()
31
+
32
+ def test_records_and_missing_optional_libraries(self):
33
+ result = Result("plant_id\tsoil_moisture\na\t10\n")
34
+ self.assertEqual(result.to_records(), [{"plant_id": "a", "soil_moisture": "10"}])
35
+ with self.assertRaises(ImportError):
36
+ result.to_pandas()
37
+ with self.assertRaises(ImportError):
38
+ result.to_polars()
39
+ with self.assertRaises(ImportError):
40
+ result.to_arrow()
41
+
42
+
43
+ if __name__ == "__main__":
44
+ unittest.main()