procrustus-indexer 2.0.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,77 @@
1
+ Metadata-Version: 2.3
2
+ Name: procrustus-indexer
3
+ Version: 2.0.0
4
+ Summary:
5
+ Author: Rob Zeeman
6
+ Author-email: rob.zeeman@di.huc.knaw.nl
7
+ Requires-Python: >=3.13
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.13
10
+ Requires-Dist: elasticsearch (>=8.17.2,<9.0.0)
11
+ Requires-Dist: jmespath (>=1.0.1,<2.0.0)
12
+ Requires-Dist: pytest (>=8.3.5,<9.0.0)
13
+ Description-Content-Type: text/markdown
14
+
15
+ # Procrustus Indexer
16
+ Python app for indexing HuC datasets.
17
+
18
+ This is a first experimental version, for indexing files based on a TOML configuration.
19
+ For now, input files need to be json.
20
+
21
+ Modules will be added for:
22
+ * XML
23
+ * Relational databases (MySQL, Postgres)
24
+ * Linked data (SPARQL, RDF)
25
+ * Tabular data (CSV, MS-Excel)
26
+
27
+ ## Installation
28
+
29
+ In order to install this library, you can use pip:
30
+
31
+ ```
32
+ pip install procrustus-indexer
33
+ ```
34
+
35
+ ## Usage
36
+ In order to use the indexer you need to configure it first. Use the `build_indexer`
37
+ function to create an indexer. It requires the configuration TOML file, the name of
38
+ the index to use and an Elasticsearch client.
39
+
40
+ For this example, assume we have the following file structure:
41
+
42
+ ```
43
+ json-files/
44
+ a.json
45
+ b.json
46
+ config.toml
47
+ ```
48
+
49
+ First we create the indexer:
50
+
51
+ ```python
52
+ from elasticsearch import Elasticsearch
53
+ from procrustus_indexer import build_indexer
54
+
55
+ indexer = build_indexer('config.toml', 'index-name', Elasticsearch())
56
+ ```
57
+
58
+ ### Create index and mapping:
59
+ We can use the indexer to generate a mapping for the Elasticsearch index.
60
+
61
+ ```python
62
+ indexer.create_mapping(overwrite=True)
63
+ ```
64
+ The `overwrite` parameter can be used to re-create the index if it already exists.
65
+
66
+ ### Index json files
67
+ Now we can import our json files in Elasticsearch.
68
+
69
+ ```python
70
+ indexer.import_folder("json-files")
71
+ ```
72
+
73
+ or
74
+
75
+ ```python
76
+ indexer.import_files(["json-files/a.json", "json-files/b.json"])
77
+ ```
@@ -0,0 +1,63 @@
1
+ # Procrustus Indexer
2
+ Python app for indexing HuC datasets.
3
+
4
+ This is a first experimental version, for indexing files based on a TOML configuration.
5
+ For now, input files need to be json.
6
+
7
+ Modules will be added for:
8
+ * XML
9
+ * Relational databases (MySQL, Postgres)
10
+ * Linked data (SPARQL, RDF)
11
+ * Tabular data (CSV, MS-Excel)
12
+
13
+ ## Installation
14
+
15
+ In order to install this library, you can use pip:
16
+
17
+ ```
18
+ pip install procrustus-indexer
19
+ ```
20
+
21
+ ## Usage
22
+ In order to use the indexer you need to configure it first. Use the `build_indexer`
23
+ function to create an indexer. It requires the configuration TOML file, the name of
24
+ the index to use and an Elasticsearch client.
25
+
26
+ For this example, assume we have the following file structure:
27
+
28
+ ```
29
+ json-files/
30
+ a.json
31
+ b.json
32
+ config.toml
33
+ ```
34
+
35
+ First we create the indexer:
36
+
37
+ ```python
38
+ from elasticsearch import Elasticsearch
39
+ from procrustus_indexer import build_indexer
40
+
41
+ indexer = build_indexer('config.toml', 'index-name', Elasticsearch())
42
+ ```
43
+
44
+ ### Create index and mapping:
45
+ We can use the indexer to generate a mapping for the Elasticsearch index.
46
+
47
+ ```python
48
+ indexer.create_mapping(overwrite=True)
49
+ ```
50
+ The `overwrite` parameter can be used to re-create the index if it already exists.
51
+
52
+ ### Index json files
53
+ Now we can import our json files in Elasticsearch.
54
+
55
+ ```python
56
+ indexer.import_folder("json-files")
57
+ ```
58
+
59
+ or
60
+
61
+ ```python
62
+ indexer.import_files(["json-files/a.json", "json-files/b.json"])
63
+ ```
@@ -0,0 +1,2 @@
1
+ from .indexer import Indexer
2
+ from .indexer_builder import build_indexer
@@ -0,0 +1,92 @@
1
+ from elasticsearch import Elasticsearch
2
+ from elasticsearch.helpers import bulk
3
+
4
+ from procrustus_indexer.parsers import Parser
5
+
6
+
7
+ class Indexer:
8
+ es: Elasticsearch = None
9
+ config: dict
10
+ index_name: str
11
+ parser: Parser
12
+
13
+ def __init__(self, es: Elasticsearch, config: dict, parser: Parser, index_name: str):
14
+ self.es = es
15
+ self.config = config
16
+ self.index_name = index_name
17
+ self.parser = parser
18
+
19
+
20
+ def create_mapping(self, overwrite: bool = False) -> dict:
21
+ """
22
+ Create the elasticsearch index mapping according to config and return resulting dict.
23
+ :return:
24
+ """
25
+ if overwrite:
26
+ self.es.indices.delete(index=self.index_name, ignore=[400, 404])
27
+
28
+ properties = {}
29
+ for facet_name in self.config['index']['facet'].keys():
30
+ facet = self.config["index"]["facet"][facet_name]
31
+ property_type = facet.get('type', 'text')
32
+ if property_type == 'text':
33
+ properties[facet_name] = {
34
+ 'type': 'text',
35
+ 'fields': {
36
+ 'keyword': {
37
+ 'type': 'keyword',
38
+ 'ignore_above': 256
39
+ },
40
+ }
41
+ }
42
+ elif property_type == 'keyword':
43
+ properties[facet_name] = {
44
+ 'type': 'keyword',
45
+ }
46
+ elif property_type == 'number':
47
+ properties[facet_name] = {
48
+ 'type': 'integer',
49
+ }
50
+ elif property_type == 'date':
51
+ properties[facet_name] = {
52
+ 'type': 'date',
53
+ }
54
+
55
+ mappings = {
56
+ 'properties': properties
57
+ }
58
+
59
+ settings = {
60
+ 'number_of_shards': 1,
61
+ 'number_of_replicas': 0
62
+ }
63
+
64
+ self.es.indices.create(index=self.index_name, mappings=mappings, settings=settings)
65
+ return mappings
66
+
67
+
68
+ def import_files(self, files: list[str]):
69
+ """
70
+ Import files into an elasticsearch index based on the given config.
71
+ :param files: list of files to import
72
+ :param index: Elasticsearch index
73
+ :return:
74
+ """
75
+ actions = []
76
+ for inv in files:
77
+ doc = {}
78
+ with open(inv) as f:
79
+ doc = self.parser.parse_file(f)
80
+ actions.append({'_index': self.index_name, '_id': doc['id'], '_source': doc})
81
+ # add to index:
82
+ result = bulk(self.es, actions)
83
+
84
+
85
+ def import_folder(self, folder: str):
86
+ """
87
+ Import all files in a folder.
88
+ :param folder:
89
+ :return:
90
+ """
91
+ input_list = glob.glob(f'{folder}/*')
92
+ self.import_files(input_list)
@@ -0,0 +1,25 @@
1
+ import tomllib
2
+
3
+ from elasticsearch import Elasticsearch
4
+ from procrustus_indexer import Indexer
5
+ from procrustus_indexer.parsers import JsonParser
6
+
7
+
8
+ def build_indexer(config_file: str, es_index: str, es_client: Elasticsearch) -> Indexer:
9
+ """
10
+ Build a new Indexer based on the given config
11
+ :param config_file: Location of the config toml file
12
+ :param es_index: The name of the Elasticsearch index
13
+ :param es_client: The Elasticsearch client
14
+ :return:
15
+ """
16
+ with open(config_file, "rb") as f:
17
+ config = tomllib.load(f)
18
+
19
+ input_format = config["index"]["input"]["format"]
20
+ if input_format == "json":
21
+ parser = JsonParser(config)
22
+ else:
23
+ raise ValueError(f"Invalid input format '{input_format}'")
24
+
25
+ return Indexer(es_client, config, parser, es_index)
@@ -0,0 +1,2 @@
1
+ from .parser import Parser
2
+ from .json_parser import JsonParser
@@ -0,0 +1,35 @@
1
+ import json
2
+ from typing import IO
3
+
4
+ import jmespath
5
+
6
+ from procrustus_indexer.parsers import Parser
7
+
8
+ class JsonParser(Parser):
9
+
10
+ config: dict
11
+
12
+ def __init__(self, config: dict):
13
+ if config["index"]["input"]["format"] != "json":
14
+ raise ValueError("JsonParser only supports JSON files")
15
+ self.config = config
16
+
17
+
18
+ @staticmethod
19
+ def resolve_path(rec, path):
20
+ if path.startswith("jmes:"):
21
+ # for jmes: 5:
22
+ return jmespath.search(path[5:], rec)
23
+
24
+
25
+ def parse_file(self, file: IO) -> dict:
26
+ data = json.load(file)
27
+
28
+ path_id = self.config['index']['id']['path']
29
+ doc_id = self.resolve_path(data, path_id)
30
+ doc = {'id': doc_id}
31
+ for key in self.config['index']['facet'].keys():
32
+ facet = self.config["index"]["facet"][key]
33
+ path_facet = facet["path"]
34
+ doc[key] = self.resolve_path(data, path_facet)
35
+ return doc
@@ -0,0 +1,7 @@
1
+ from abc import ABC
2
+ from typing import IO
3
+
4
+
5
+ class Parser(ABC):
6
+ def parse_file(self, file: IO) -> dict:
7
+ pass
@@ -0,0 +1,21 @@
1
+ [project]
2
+ name = "procrustus-indexer"
3
+ version = "2.0.0"
4
+ description = ""
5
+ authors = [
6
+ {name = "Rob Zeeman",email = "rob.zeeman@di.huc.knaw.nl"},
7
+ {name = "Meindert Kroese",email = "meindert.kroese@di.huc.knaw.nl"},
8
+ {name = "Jarno Bakker",email = "jarno.bakker@di.huc.knaw.nl"}
9
+ ]
10
+ readme = "README.md"
11
+ requires-python = ">=3.13"
12
+ dependencies = [
13
+ "jmespath (>=1.0.1,<2.0.0)",
14
+ "elasticsearch (>=8.17.2,<9.0.0)",
15
+ "pytest (>=8.3.5,<9.0.0)"
16
+ ]
17
+
18
+
19
+ [build-system]
20
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
21
+ build-backend = "poetry.core.masonry.api"