educelab-hercdb 0.1.0.dev1__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,22 @@
1
+ Metadata-Version: 2.1
2
+ Name: educelab-hercdb
3
+ Version: 0.1.0.dev1
4
+ Summary: Graph database API for Herculaneum data
5
+ Author-email: Mami Hayashida <mami.hayashida@uky.edu>, Seth Parker <c.seth.parker@uky.edu>
6
+ Project-URL: Repository, https://gitlab.com/educelab/herculaneum-graph-db
7
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: neo4j>=5.20
16
+ Requires-Dist: numpy>=2.0
17
+ Requires-Dist: pandas>=2.2
18
+ Requires-Dist: prompt-toolkit
19
+
20
+ # Herculaneum Graph Database Project
21
+
22
+
@@ -0,0 +1,3 @@
1
+ # Herculaneum Graph Database Project
2
+
3
+
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 62"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [tool.setuptools.packages.find]
6
+ where = ["src/"]
7
+ include = ["educelab.hercdb"]
8
+
9
+ [project]
10
+ name = "educelab-hercdb"
11
+ version = "0.1.0.dev1"
12
+ dependencies = [
13
+ "neo4j>=5.20",
14
+ "numpy>=2.0",
15
+ "pandas>=2.2",
16
+ "prompt-toolkit"
17
+ ]
18
+ requires-python = ">= 3.10"
19
+ authors = [
20
+ {name = "Mami Hayashida", email = "mami.hayashida@uky.edu"},
21
+ {name = "Seth Parker", email = "c.seth.parker@uky.edu"}
22
+ ]
23
+ description = "Graph database API for Herculaneum data"
24
+ readme = "README.md"
25
+ classifiers = [
26
+ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
27
+ "Operating System :: OS Independent",
28
+
29
+ "Programming Language :: Python :: 3",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ ]
34
+
35
+ [project.urls]
36
+ Repository = "https://gitlab.com/educelab/herculaneum-graph-db"
37
+
38
+ [project.scripts]
39
+ el-hercdb-search = "educelab.hercdb.apps.search:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,6 @@
1
+ from educelab.hercdb import config
2
+ from educelab.hercdb.api import (connect, node_count, disconnect,
3
+ list_cornici_pezzi, find_datasets,
4
+ get_display_name, verify_connection)
5
+
6
+ from educelab.hercdb.api import (FlatbedScanType, PGSRawType, SpectralRawType)
@@ -0,0 +1,232 @@
1
+ import logging
2
+ from typing import Optional
3
+ from enum import Enum
4
+
5
+ from neo4j import GraphDatabase
6
+
7
+
8
+ class DatasetType(Enum):
9
+ FlatbedScan = 'FlatbedScanDataset'
10
+ PGSRaw = 'PGSRaw'
11
+ SpectralRaw = 'SpectralRaw'
12
+
13
+ def __str__(self):
14
+ return f'{self.value}'
15
+
16
+
17
+ FlatbedScanType = DatasetType.FlatbedScan
18
+ PGSRawType = DatasetType.PGSRaw
19
+ SpectralRawType = DatasetType.SpectralRaw
20
+
21
+
22
+ class _GraphConnector:
23
+ logger = logging.getLogger('hercdb.connector')
24
+ uri: str = None
25
+ user: str = None
26
+
27
+ def __init__(self, uri, user, password):
28
+ self.uri = uri
29
+ self.user = user
30
+ self.driver = GraphDatabase.driver(uri, auth=(user, password))
31
+
32
+ def __del__(self):
33
+ self.close()
34
+
35
+ def close(self):
36
+ self.driver.close()
37
+
38
+ def verify_conn(self):
39
+ self.driver.verify_connectivity()
40
+ self.logger.info(f"connected to {self.uri} as '{self.user}'")
41
+
42
+ def delete_all(self):
43
+ records, summary, keys = self.driver.execute_query(
44
+ """
45
+ MATCH (n)
46
+ DETACH DELETE n
47
+ """,
48
+ database_="neo4j",
49
+ )
50
+
51
+ def return_all(self):
52
+ records, summary, keys = self.driver.execute_query(
53
+ """
54
+ MATCH (n)
55
+ RETURN n
56
+ """,
57
+ database_="neo4j",
58
+ )
59
+ self.logger.debug(records)
60
+ self.logger.debug(summary)
61
+ self.logger.debug(keys)
62
+
63
+ def node_count(self) -> int:
64
+ record, keys, summary = self.driver.execute_query(
65
+ """
66
+ MATCH (n)
67
+ RETURN count(n)
68
+ """,
69
+ database_="neo4j",
70
+ )
71
+ count = record[0]
72
+ assert isinstance(count, int)
73
+ return count
74
+
75
+ def get_human_readable_name(self, pherc, cornice=None, pezzo=None):
76
+
77
+ pherc_n = None
78
+ corn_n = None
79
+ pezzo_n = None
80
+
81
+ if cornice:
82
+ records, summary, keys = self.driver.execute_query(
83
+ """
84
+ MATCH (ph:PHerc {name: $ph})-[:HAS]->(cr:Cornice {name: $cor})
85
+ RETURN ph.human_name, cr.human_name
86
+ """, ph=pherc, cor=cornice,
87
+ database_="neo4j",
88
+ )
89
+ if records:
90
+ pherc_n = records[0]["ph.human_name"]
91
+ corn_n = records[0]["c.human_name"]
92
+
93
+ if pezzo:
94
+ # Currently this is irrelevant since there are no pezzo with "names"
95
+ records, summary, keys = self.driver.execute_query(
96
+ """
97
+ MATCH (ph:PHerc {name: $ph})-[:HAS]->(:Cornice)
98
+ -[:HAS]->(pz:Pezzo {name: $pz})
99
+ RETURN ph.human_name, pz.human_name
100
+ """, ph=pherc, pz=pezzo,
101
+ database_="neo4j",
102
+ )
103
+ if records:
104
+ pherc_n = records[0]["ph.human_name"]
105
+ pezzo_n = records[0]["pz.human_name"]
106
+
107
+ if not pherc_n:
108
+ # If there was neither cornice nor pezzo names given
109
+ records, summary, keys = self.driver.execute_query(
110
+ """
111
+ MATCH (ph:PHerc {name: $ph})
112
+ RETURN ph.human_name
113
+ """, ph=pherc,
114
+ database_="neo4j",
115
+ )
116
+ if records:
117
+ pherc_n = records[0]["ph.human_name"]
118
+
119
+ return pherc_n, corn_n, pezzo_n
120
+
121
+ def list_cornici_pezzi(self, pherc):
122
+ # Use display names
123
+ records, summary, keys = self.driver.execute_query(
124
+ """
125
+ MATCH (ph:PHerc {human_name: $ph})
126
+ OPTIONAL MATCH (ph)-[:HAS]-(cr:Cornice)
127
+ OPTIONAL MATCH (cr)-[:HAS]-(pz:Pezzo)
128
+ RETURN ph, cr, pz
129
+ """, ph=pherc,
130
+ database_="neo4j",
131
+ )
132
+ return records
133
+
134
+ def find_datasets(self, ds_type: DatasetType, pherc, cornice=None,
135
+ pezzo=None):
136
+ # Use display names
137
+ if cornice:
138
+ records, summary, keys = self.driver.execute_query(
139
+ """
140
+ MATCH (ph:PHerc {name: $ph})-[:HAS]-(cr:Cornice {name: $cor})
141
+ MATCH (cr)<-[:ASSIGNED_TO]-(e:EduceLabID)
142
+ MATCH (e)<-[:BELONGS_TO]-(n)
143
+ WHERE $data_t IN LABELS(n)
144
+ RETURN n
145
+ """, data_t=str(ds_type), ph=pherc, cor=cornice,
146
+ database_="neo4j",
147
+ )
148
+
149
+ else:
150
+ # Pezzo
151
+ records, summary, keys = self.driver.execute_query(
152
+ """
153
+ MATCH (ph:PHerc {name: $ph})-[:HAS]->(:Cornice)
154
+ -[:HAS]->(pz:Pezzo {human_name: $pz})
155
+ MATCH (pz)<-[:ASSIGNED_TO]-(e:EduceLabID)
156
+ MATCH (e)<-[:BELONGS_TO]-(n)
157
+ WHERE $data_t IN LABELS(n)
158
+ RETURN n
159
+ """, data_t=str(ds_type), ph=pherc, pz=pezzo,
160
+ database_="neo4j",
161
+ )
162
+
163
+ properties = []
164
+ for record in records:
165
+ dataset = record[0]
166
+ properties.append(dict(dataset))
167
+
168
+ return properties
169
+
170
+
171
+ # Server connector instance
172
+ _connector: Optional[_GraphConnector] = None
173
+
174
+
175
+ def connect(uri=None, user=None, password=None) -> bool:
176
+ # use system config values if not provided
177
+ from educelab.hercdb import config
178
+ if uri is None:
179
+ uri = config.uri
180
+ if user is None:
181
+ user = config.username
182
+ if password is None:
183
+ password = config.password
184
+
185
+ # close existing connection
186
+ global _connector
187
+ if _connector is not None:
188
+ disconnect()
189
+
190
+ # open new connection
191
+ _connector = _GraphConnector(uri, user, password)
192
+ return verify_connection()
193
+
194
+
195
+ def disconnect():
196
+ global _connector
197
+ if _connector is not None:
198
+ _connector.close()
199
+ _connector = None
200
+
201
+
202
+ def verify_connection() -> bool:
203
+ if _connector is None:
204
+ return False
205
+ try:
206
+ _connector.verify_conn()
207
+ return True
208
+ except Exception as e:
209
+ logging.getLogger(__name__).debug('failed to connect', exc_info=e)
210
+ return False
211
+
212
+
213
+ def node_count() -> int:
214
+ if _connector is None:
215
+ return -1
216
+ else:
217
+ return _connector.node_count()
218
+
219
+
220
+ def get_display_name(*args, **kwargs):
221
+ if _connector is not None:
222
+ return _connector.get_human_readable_name(*args, **kwargs)
223
+
224
+
225
+ def list_cornici_pezzi(*args, **kwargs):
226
+ if _connector is not None:
227
+ return _connector.list_cornici_pezzi(*args, **kwargs)
228
+
229
+
230
+ def find_datasets(*args, **kwargs):
231
+ if _connector is not None:
232
+ return _connector.find_datasets(*args, **kwargs)
@@ -0,0 +1,63 @@
1
+ import json
2
+ import os
3
+ from functools import lru_cache
4
+ from pathlib import Path
5
+
6
+ from prompt_toolkit import prompt
7
+ from getpass import getpass, GetPassWarning
8
+
9
+
10
+ @lru_cache(maxsize=2)
11
+ def _load_config():
12
+ """Load ~/.educedb as a dictionary."""
13
+ cfg_path = Path.home() / '.educedb'
14
+ if not cfg_path.exists():
15
+ return None
16
+
17
+ with cfg_path.open() as f:
18
+ cfg = json.load(f)
19
+
20
+ return cfg
21
+
22
+
23
+ def _get_cfg_val(env_key, config_key):
24
+ """Return a config value from the environment or config file."""
25
+ # Prefer env val
26
+ val = os.getenv(env_key, None)
27
+ if val is not None:
28
+ return val
29
+
30
+ # Load config
31
+ cfg = _load_config()
32
+ if cfg is None:
33
+ _load_config.cache_clear()
34
+ return None
35
+
36
+ return cfg.get(config_key, None)
37
+
38
+
39
+ def __getattr__(name):
40
+ """Stub to avoid attribute errors on this module"""
41
+ pass
42
+
43
+
44
+ # Default properties #
45
+ uri = _get_cfg_val('EDUCEDB_URI', 'uri')
46
+ username = _get_cfg_val('EDUCEDB_USER', 'username')
47
+ password = _get_cfg_val('EDUCEDB_PASSWORD', 'password')
48
+
49
+
50
+ def request_required():
51
+ """Prompt the user to provide required configuration information."""
52
+ global uri, username, password
53
+ if uri is None:
54
+ uri = prompt('Enter URI: ')
55
+
56
+ if username is None:
57
+ username = prompt('Enter username: ')
58
+
59
+ if password is None:
60
+ try:
61
+ password = getpass('Enter password: ')
62
+ except GetPassWarning:
63
+ pass
@@ -0,0 +1,22 @@
1
+ Metadata-Version: 2.1
2
+ Name: educelab-hercdb
3
+ Version: 0.1.0.dev1
4
+ Summary: Graph database API for Herculaneum data
5
+ Author-email: Mami Hayashida <mami.hayashida@uky.edu>, Seth Parker <c.seth.parker@uky.edu>
6
+ Project-URL: Repository, https://gitlab.com/educelab/herculaneum-graph-db
7
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
8
+ Classifier: Operating System :: OS Independent
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+ Requires-Dist: neo4j>=5.20
16
+ Requires-Dist: numpy>=2.0
17
+ Requires-Dist: pandas>=2.2
18
+ Requires-Dist: prompt-toolkit
19
+
20
+ # Herculaneum Graph Database Project
21
+
22
+
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/educelab/hercdb/__init__.py
4
+ src/educelab/hercdb/api.py
5
+ src/educelab/hercdb/config.py
6
+ src/educelab_hercdb.egg-info/PKG-INFO
7
+ src/educelab_hercdb.egg-info/SOURCES.txt
8
+ src/educelab_hercdb.egg-info/dependency_links.txt
9
+ src/educelab_hercdb.egg-info/entry_points.txt
10
+ src/educelab_hercdb.egg-info/requires.txt
11
+ src/educelab_hercdb.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ el-hercdb-search = educelab.hercdb.apps.search:main
@@ -0,0 +1,4 @@
1
+ neo4j>=5.20
2
+ numpy>=2.0
3
+ pandas>=2.2
4
+ prompt-toolkit