urban-json-elastic 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 black beard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: urban-json-elastic
3
+ Version: 0.1.0
4
+ Summary: Plugin urban-json : recherche Elasticsearch (esconnect / essearch).
5
+ Author-email: Gael Beard <gaelgael5@gmail.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/gaelgael5/Jslt
8
+ Project-URL: Repository, https://github.com/gaelgael5/Jslt
9
+ Keywords: json,transform,template,jslt,elasticsearch,plugin
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: urban-json
26
+ Requires-Dist: elasticsearch>=8
27
+ Dynamic: license-file
28
+
29
+ # urban-json-elastic
30
+
31
+ Plugin [urban-json](https://github.com/gaelgael5/Jslt) : recherche **Elasticsearch**
32
+ depuis les templates, via le client officiel `elasticsearch`.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install urban-json-elastic # tire urban-json et le client elasticsearch
38
+ ```
39
+
40
+ `urban-json` découvre le plugin automatiquement via les entry points (`urban_json.functions`).
41
+
42
+ ## Utilisation
43
+
44
+ ```python
45
+ from urban_json import JsltTemplate
46
+
47
+ tpl = JsltTemplate.parse('''
48
+ {
49
+ "es:": esconnect("https://localhost:9200", $.api_key),
50
+ "docs": [ {
51
+ "$source": essearch(es:, "products", { "match": { "category": "book" } }, 50),
52
+ "nom": $.title, "prix": $.price
53
+ } ]
54
+ }
55
+ ''')
56
+ ```
57
+
58
+ - `esconnect(hosts, api_key, username, password, verify_certs)` → descripteur de
59
+ connexion (à stocker dans une variable).
60
+ - `essearch(connection, index, query, size)` → liste des documents trouvés
61
+ (le `_source` de chaque hit). `query` est une clause du DSL Elasticsearch
62
+ (`match_all` si absente).
63
+
64
+ ## Sécurité
65
+
66
+ `essearch` interroge un cluster Elasticsearch réel. Les identifiants (api_key /
67
+ mot de passe) transitent par le descripteur de connexion : traitez-les comme des
68
+ secrets côté template.
69
+
70
+ ## Licence
71
+
72
+ Apache-2.0.
@@ -0,0 +1,44 @@
1
+ # urban-json-elastic
2
+
3
+ Plugin [urban-json](https://github.com/gaelgael5/Jslt) : recherche **Elasticsearch**
4
+ depuis les templates, via le client officiel `elasticsearch`.
5
+
6
+ ## Installation
7
+
8
+ ```bash
9
+ pip install urban-json-elastic # tire urban-json et le client elasticsearch
10
+ ```
11
+
12
+ `urban-json` découvre le plugin automatiquement via les entry points (`urban_json.functions`).
13
+
14
+ ## Utilisation
15
+
16
+ ```python
17
+ from urban_json import JsltTemplate
18
+
19
+ tpl = JsltTemplate.parse('''
20
+ {
21
+ "es:": esconnect("https://localhost:9200", $.api_key),
22
+ "docs": [ {
23
+ "$source": essearch(es:, "products", { "match": { "category": "book" } }, 50),
24
+ "nom": $.title, "prix": $.price
25
+ } ]
26
+ }
27
+ ''')
28
+ ```
29
+
30
+ - `esconnect(hosts, api_key, username, password, verify_certs)` → descripteur de
31
+ connexion (à stocker dans une variable).
32
+ - `essearch(connection, index, query, size)` → liste des documents trouvés
33
+ (le `_source` de chaque hit). `query` est une clause du DSL Elasticsearch
34
+ (`match_all` si absente).
35
+
36
+ ## Sécurité
37
+
38
+ `essearch` interroge un cluster Elasticsearch réel. Les identifiants (api_key /
39
+ mot de passe) transitent par le descripteur de connexion : traitez-les comme des
40
+ secrets côté template.
41
+
42
+ ## Licence
43
+
44
+ Apache-2.0.
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "urban-json-elastic"
7
+ dynamic = ["version"]
8
+ description = "Plugin urban-json : recherche Elasticsearch (esconnect / essearch)."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "Apache-2.0"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Gael Beard", email = "gaelgael5@gmail.com" }]
14
+ keywords = ["json", "transform", "template", "jslt", "elasticsearch", "plugin"]
15
+ classifiers = [
16
+ "Development Status :: 3 - Alpha",
17
+ "Intended Audience :: Developers",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ "Topic :: Internet :: WWW/HTTP :: Indexing/Search",
27
+ "Typing :: Typed",
28
+ ]
29
+ dependencies = [
30
+ "urban-json",
31
+ "elasticsearch>=8",
32
+ ]
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/gaelgael5/Jslt"
36
+ Repository = "https://github.com/gaelgael5/Jslt"
37
+
38
+ # Découverte automatique par urban-json : installer ce paquet suffit à exposer les fonctions.
39
+ [project.entry-points."urban_json.functions"]
40
+ elastic = "urban_json_elastic:register"
41
+
42
+ [tool.setuptools.dynamic]
43
+ version = { attr = "urban_json_elastic.__version__" }
44
+
45
+ [tool.setuptools.packages.find]
46
+ where = ["src"]
47
+
48
+ [tool.setuptools.package-data]
49
+ urban_json_elastic = ["py.typed"]
50
+
51
+ [tool.pytest.ini_options]
52
+ testpaths = ["tests"]
53
+ pythonpath = ["src"]
54
+
55
+ [tool.ruff]
56
+ line-length = 100
57
+ src = ["src", "tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,112 @@
1
+ """Plugin jslt : recherche Elasticsearch (``esconnect`` / ``essearch``).
2
+
3
+ Deux fonctions de template, dans l'esprit du couple ``connectsql`` / ``readsql`` :
4
+
5
+ * ``esconnect(hosts, api_key, username, password, verify_certs)`` → descripteur de
6
+ connexion (à stocker dans une variable) ;
7
+ * ``essearch(connection, index, query, size)`` → liste des documents trouvés
8
+ (le ``_source`` de chaque hit).
9
+
10
+ Client : `elasticsearch` (client officiel). Importé **paresseusement** pour que la
11
+ découverte/introspection du plugin ne force pas l'import du client.
12
+
13
+ Découvert automatiquement par ``jslt`` via l'entry point ``urban_json.functions``.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from typing import Any
19
+
20
+ __version__ = "0.1.0"
21
+
22
+ __all__ = ["execute_esconnect", "execute_essearch", "register"]
23
+
24
+
25
+ def _build_client(connection: dict) -> Any:
26
+ """Construit le client Elasticsearch depuis un descripteur. Point d'injection (tests)."""
27
+ from elasticsearch import Elasticsearch
28
+
29
+ kwargs: dict[str, Any] = {"hosts": connection.get("hosts")}
30
+ if connection.get("api_key"):
31
+ kwargs["api_key"] = connection["api_key"]
32
+ if connection.get("basic_auth"):
33
+ kwargs["basic_auth"] = tuple(connection["basic_auth"])
34
+ if "verify_certs" in connection:
35
+ kwargs["verify_certs"] = connection["verify_certs"]
36
+ return Elasticsearch(**kwargs)
37
+
38
+
39
+ def execute_esconnect(
40
+ ctx: Any,
41
+ hosts: Any = None,
42
+ api_key: str | None = None,
43
+ username: str | None = None,
44
+ password: str | None = None,
45
+ verify_certs: bool = True,
46
+ ) -> dict:
47
+ """Crée un descripteur de connexion Elasticsearch (port esprit ``connectsql``).
48
+
49
+ Args:
50
+ hosts: URL (ou liste d'URL) du/des nœud(s) Elasticsearch.
51
+ api_key: clé d'API (optionnelle).
52
+ username / password: authentification basique (optionnelle).
53
+ verify_certs: vérifie les certificats TLS (défaut ``True``).
54
+
55
+ Returns:
56
+ Un descripteur à passer à :func:`execute_essearch`.
57
+ """
58
+ if not hosts:
59
+ raise ValueError("esconnect: hosts est requis")
60
+ descriptor: dict[str, Any] = {"hosts": hosts, "verify_certs": verify_certs}
61
+ if api_key:
62
+ descriptor["api_key"] = api_key
63
+ if username is not None and password is not None:
64
+ descriptor["basic_auth"] = [username, password]
65
+ return descriptor
66
+
67
+
68
+ def execute_essearch(
69
+ ctx: Any,
70
+ connection: Any = None,
71
+ index: str | None = None,
72
+ query: Any = None,
73
+ size: int | None = None,
74
+ ) -> list:
75
+ """Exécute une recherche et renvoie les documents (``_source`` des hits).
76
+
77
+ Args:
78
+ connection: descripteur issu de :func:`execute_esconnect`.
79
+ index: index (ou motif d'index) à interroger.
80
+ query: clause de requête (DSL Elasticsearch) ; ``match_all`` si absente.
81
+ size: nombre maximum de résultats (défaut du serveur si absent).
82
+
83
+ Returns:
84
+ La liste des ``_source`` des documents trouvés.
85
+ """
86
+ if not isinstance(connection, dict) or "hosts" not in connection:
87
+ raise ValueError("essearch: connection doit être le descripteur de esconnect")
88
+ if not index:
89
+ raise ValueError("essearch: index est requis")
90
+
91
+ search_kwargs: dict[str, Any] = {"index": index, "query": query or {"match_all": {}}}
92
+ if size is not None:
93
+ search_kwargs["size"] = size
94
+
95
+ client = _build_client(connection)
96
+ response = client.search(**search_kwargs)
97
+ hits = response["hits"]["hits"]
98
+ return [hit.get("_source") for hit in hits]
99
+
100
+
101
+ def register(registry: Any) -> None:
102
+ """Enregistre les fonctions du plugin dans un registre jslt (entry point)."""
103
+ registry.register(
104
+ "esconnect",
105
+ execute_esconnect,
106
+ description="Crée un descripteur de connexion Elasticsearch.",
107
+ )
108
+ registry.register(
109
+ "essearch",
110
+ execute_essearch,
111
+ description="Recherche Elasticsearch -> liste des documents (_source des hits).",
112
+ )
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: urban-json-elastic
3
+ Version: 0.1.0
4
+ Summary: Plugin urban-json : recherche Elasticsearch (esconnect / essearch).
5
+ Author-email: Gael Beard <gaelgael5@gmail.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/gaelgael5/Jslt
8
+ Project-URL: Repository, https://github.com/gaelgael5/Jslt
9
+ Keywords: json,transform,template,jslt,elasticsearch,plugin
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Internet :: WWW/HTTP :: Indexing/Search
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: urban-json
26
+ Requires-Dist: elasticsearch>=8
27
+ Dynamic: license-file
28
+
29
+ # urban-json-elastic
30
+
31
+ Plugin [urban-json](https://github.com/gaelgael5/Jslt) : recherche **Elasticsearch**
32
+ depuis les templates, via le client officiel `elasticsearch`.
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install urban-json-elastic # tire urban-json et le client elasticsearch
38
+ ```
39
+
40
+ `urban-json` découvre le plugin automatiquement via les entry points (`urban_json.functions`).
41
+
42
+ ## Utilisation
43
+
44
+ ```python
45
+ from urban_json import JsltTemplate
46
+
47
+ tpl = JsltTemplate.parse('''
48
+ {
49
+ "es:": esconnect("https://localhost:9200", $.api_key),
50
+ "docs": [ {
51
+ "$source": essearch(es:, "products", { "match": { "category": "book" } }, 50),
52
+ "nom": $.title, "prix": $.price
53
+ } ]
54
+ }
55
+ ''')
56
+ ```
57
+
58
+ - `esconnect(hosts, api_key, username, password, verify_certs)` → descripteur de
59
+ connexion (à stocker dans une variable).
60
+ - `essearch(connection, index, query, size)` → liste des documents trouvés
61
+ (le `_source` de chaque hit). `query` est une clause du DSL Elasticsearch
62
+ (`match_all` si absente).
63
+
64
+ ## Sécurité
65
+
66
+ `essearch` interroge un cluster Elasticsearch réel. Les identifiants (api_key /
67
+ mot de passe) transitent par le descripteur de connexion : traitez-les comme des
68
+ secrets côté template.
69
+
70
+ ## Licence
71
+
72
+ Apache-2.0.
@@ -0,0 +1,12 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/urban_json_elastic/__init__.py
5
+ src/urban_json_elastic/py.typed
6
+ src/urban_json_elastic.egg-info/PKG-INFO
7
+ src/urban_json_elastic.egg-info/SOURCES.txt
8
+ src/urban_json_elastic.egg-info/dependency_links.txt
9
+ src/urban_json_elastic.egg-info/entry_points.txt
10
+ src/urban_json_elastic.egg-info/requires.txt
11
+ src/urban_json_elastic.egg-info/top_level.txt
12
+ tests/test_essearch.py
@@ -0,0 +1,2 @@
1
+ [urban_json.functions]
2
+ elastic = urban_json_elastic:register
@@ -0,0 +1,2 @@
1
+ urban-json
2
+ elasticsearch>=8
@@ -0,0 +1 @@
1
+ urban_json_elastic
@@ -0,0 +1,99 @@
1
+ """Tests du plugin urban-json-elastic (``esconnect`` / ``essearch``).
2
+
3
+ Aucun serveur Elasticsearch requis : on injecte un faux client via le point
4
+ d'injection ``_build_client``. Couvre : descripteur de connexion, recherche +
5
+ extraction des ``_source``, requête par défaut (match_all), paramètre size,
6
+ erreurs, découverte entry point, usage en template.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import pytest
12
+ from urban_json import FunctionRegistry, JsltTemplate
13
+
14
+ import urban_json_elastic
15
+ from urban_json_elastic import execute_esconnect, execute_essearch, register
16
+
17
+
18
+ class _FakeClient:
19
+ def __init__(self, hits):
20
+ self._hits = hits
21
+ self.calls = []
22
+
23
+ def search(self, **kwargs):
24
+ self.calls.append(kwargs)
25
+ return {"hits": {"hits": [{"_source": h} for h in self._hits]}}
26
+
27
+
28
+ @pytest.fixture
29
+ def fake_es(monkeypatch):
30
+ """Installe un faux client ; renvoie l'instance pour inspecter les appels."""
31
+ client = _FakeClient([{"title": "A", "price": 10}, {"title": "B", "price": 20}])
32
+ monkeypatch.setattr(urban_json_elastic, "_build_client", lambda connection: client)
33
+ return client
34
+
35
+
36
+ def test_esconnect_descriptor() -> None:
37
+ cx = execute_esconnect(None, "https://localhost:9200", api_key="k")
38
+ assert cx == {"hosts": "https://localhost:9200", "verify_certs": True, "api_key": "k"}
39
+
40
+
41
+ def test_esconnect_basic_auth() -> None:
42
+ cx = execute_esconnect(None, "https://es:9200", username="u", password="p")
43
+ assert cx["basic_auth"] == ["u", "p"]
44
+
45
+
46
+ def test_esconnect_requires_hosts() -> None:
47
+ with pytest.raises(ValueError, match="hosts"):
48
+ execute_esconnect(None)
49
+
50
+
51
+ def test_essearch_returns_sources(fake_es) -> None:
52
+ cx = {"hosts": "https://es:9200"}
53
+ docs = execute_essearch(None, cx, "products", {"match": {"category": "book"}})
54
+ assert docs == [{"title": "A", "price": 10}, {"title": "B", "price": 20}]
55
+
56
+
57
+ def test_essearch_default_query_is_match_all(fake_es) -> None:
58
+ execute_essearch(None, {"hosts": "x"}, "products")
59
+ assert fake_es.calls[0]["query"] == {"match_all": {}}
60
+
61
+
62
+ def test_essearch_passes_index_and_size(fake_es) -> None:
63
+ execute_essearch(None, {"hosts": "x"}, "products", {"match_all": {}}, size=50)
64
+ call = fake_es.calls[0]
65
+ assert call["index"] == "products"
66
+ assert call["size"] == 50
67
+
68
+
69
+ def test_essearch_requires_descriptor() -> None:
70
+ with pytest.raises(ValueError, match="descripteur"):
71
+ execute_essearch(None, "nope", "products")
72
+
73
+
74
+ def test_essearch_requires_index(fake_es) -> None:
75
+ with pytest.raises(ValueError, match="index"):
76
+ execute_essearch(None, {"hosts": "x"}, None)
77
+
78
+
79
+ def test_register_into_custom_registry() -> None:
80
+ reg = FunctionRegistry()
81
+ register(reg)
82
+ assert "esconnect" in reg and "essearch" in reg
83
+
84
+
85
+ def test_discovered_via_entry_point() -> None:
86
+ from urban_json import default_registry, load_plugins
87
+
88
+ load_plugins(default_registry)
89
+ assert "esconnect" in default_registry and "essearch" in default_registry
90
+
91
+
92
+ def test_in_template(fake_es) -> None:
93
+ template = (
94
+ '{ "es:": esconnect("https://es:9200"),'
95
+ ' "docs": [ { "$source": essearch(es:, "products"), "n": $.title } ] }'
96
+ )
97
+ assert JsltTemplate.parse(template).transform({}) == {
98
+ "docs": [{"n": "A"}, {"n": "B"}]
99
+ }