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.
- procrustus_indexer-2.0.0/PKG-INFO +77 -0
- procrustus_indexer-2.0.0/README.md +63 -0
- procrustus_indexer-2.0.0/procrustus_indexer/__init__.py +2 -0
- procrustus_indexer-2.0.0/procrustus_indexer/indexer.py +92 -0
- procrustus_indexer-2.0.0/procrustus_indexer/indexer_builder.py +25 -0
- procrustus_indexer-2.0.0/procrustus_indexer/parsers/__init__.py +2 -0
- procrustus_indexer-2.0.0/procrustus_indexer/parsers/json_parser.py +35 -0
- procrustus_indexer-2.0.0/procrustus_indexer/parsers/parser.py +7 -0
- procrustus_indexer-2.0.0/pyproject.toml +21 -0
|
@@ -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,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,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,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"
|