tg-prepare 1.0.0__py3-none-any.whl
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.
Potentially problematic release.
This version of tg-prepare might be problematic. Click here for more details.
- tg_prepare-1.0.0.dist-info/METADATA +19 -0
- tg_prepare-1.0.0.dist-info/RECORD +46 -0
- tg_prepare-1.0.0.dist-info/WHEEL +5 -0
- tg_prepare-1.0.0.dist-info/entry_points.txt +3 -0
- tg_prepare-1.0.0.dist-info/licenses/LICENSE +202 -0
- tg_prepare-1.0.0.dist-info/top_level.txt +2 -0
- tgp_backend/__init__.py +18 -0
- tgp_backend/auth.py +71 -0
- tgp_backend/cli.py +22 -0
- tgp_backend/directories.py +85 -0
- tgp_backend/interfaces.py +3 -0
- tgp_backend/nextcloud.py +125 -0
- tgp_backend/project.py +267 -0
- tgp_backend/tgclient.py +92 -0
- tgp_backend/util.py +83 -0
- tgp_ui/__init__.py +0 -0
- tgp_ui/app.py +424 -0
- tgp_ui/static/css/bootstrap-icons.min.css +5 -0
- tgp_ui/static/css/bootstrap.min.css +7 -0
- tgp_ui/static/css/main.css +146 -0
- tgp_ui/static/css/simpleXML.css +67 -0
- tgp_ui/static/js/bootstrap.bundle.min.js +7 -0
- tgp_ui/static/js/jquery.min.js +2 -0
- tgp_ui/static/js/jstree.min.js +3 -0
- tgp_ui/static/js/main.js +454 -0
- tgp_ui/static/js/simpleXML.js +193 -0
- tgp_ui/templates/collection.html +194 -0
- tgp_ui/templates/empty.html +4 -0
- tgp_ui/templates/file_tree.html +39 -0
- tgp_ui/templates/file_upload.html +24 -0
- tgp_ui/templates/jsfiler.html +6 -0
- tgp_ui/templates/layout.html +59 -0
- tgp_ui/templates/layout2.html +99 -0
- tgp_ui/templates/macros.html +91 -0
- tgp_ui/templates/main.html +5 -0
- tgp_ui/templates/nxc_file_tree.html +33 -0
- tgp_ui/templates/nxc_login.html +25 -0
- tgp_ui/templates/nxc_tab.html +15 -0
- tgp_ui/templates/project.html +25 -0
- tgp_ui/templates/project_nxc.html +37 -0
- tgp_ui/templates/projects.html +73 -0
- tgp_ui/templates/publish.html +120 -0
- tgp_ui/templates/storage.html +49 -0
- tgp_ui/templates/tei_browser.html +14 -0
- tgp_ui/templates/tei_explorer.html +48 -0
- tgp_ui/templates/xpath_parser_modal_content.html +37 -0
tgp_backend/project.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright (C) 2023-2025 TU-Dresden (ZIH)
|
|
3
|
+
# ralf.klammer@tu-dresden.de
|
|
4
|
+
# moritz.wilhelm@tu-dresden.de
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
from tg_model.collection import CollectionModeler # type: ignore
|
|
10
|
+
from tg_model.tei import TEIParser # type: ignore
|
|
11
|
+
|
|
12
|
+
from tg_model.yaml import ( # type: ignore
|
|
13
|
+
CollectionConfig,
|
|
14
|
+
CollectionConfigTemplate,
|
|
15
|
+
ProjectConfigTemplate,
|
|
16
|
+
ProjectConfig,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from tgp_backend.tgclient import TGclient
|
|
20
|
+
from tgp_backend.util import config, list_files_and_folders
|
|
21
|
+
|
|
22
|
+
log = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
MAIN_PATH = config("main", "path", default="./projects")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Project(object):
|
|
28
|
+
def __init__(self, projectname):
|
|
29
|
+
self.name = projectname
|
|
30
|
+
self.fullpath = f"{MAIN_PATH}/{projectname}"
|
|
31
|
+
self.datapath = f"{self.fullpath}/data"
|
|
32
|
+
self.metadatapath = f"{self.fullpath}/metadata"
|
|
33
|
+
self._main_config = None
|
|
34
|
+
self._collections = None
|
|
35
|
+
self._nextcloud = None
|
|
36
|
+
|
|
37
|
+
def create(self):
|
|
38
|
+
if not os.path.exists(self.fullpath):
|
|
39
|
+
os.makedirs(self.fullpath)
|
|
40
|
+
os.makedirs(self.datapath)
|
|
41
|
+
os.makedirs(self.metadatapath)
|
|
42
|
+
else:
|
|
43
|
+
log.warning("Project already exists!")
|
|
44
|
+
|
|
45
|
+
def clone_git_project(self, url):
|
|
46
|
+
repo_name = url.split("/")[-1].replace(".git", "")
|
|
47
|
+
repo_path = os.path.join(self.datapath, repo_name)
|
|
48
|
+
|
|
49
|
+
if not os.path.exists(repo_path):
|
|
50
|
+
subprocess.run(["git", "clone", url, repo_path])
|
|
51
|
+
else:
|
|
52
|
+
log.warning("Repository already exists!")
|
|
53
|
+
|
|
54
|
+
def file_upload(self, files):
|
|
55
|
+
for file in files:
|
|
56
|
+
if file:
|
|
57
|
+
filename = file.filename
|
|
58
|
+
filepath = os.path.join(self.datapath, filename)
|
|
59
|
+
directory = os.path.dirname(filepath)
|
|
60
|
+
if not os.path.exists(directory):
|
|
61
|
+
os.makedirs(directory)
|
|
62
|
+
file.save(filepath)
|
|
63
|
+
|
|
64
|
+
def list_files_and_folders(self):
|
|
65
|
+
print(self.datapath)
|
|
66
|
+
return list_files_and_folders(self.datapath)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def main_config(self):
|
|
70
|
+
if not self._main_config:
|
|
71
|
+
self._main_config = ProjectConfig(projectpath=self.metadatapath)
|
|
72
|
+
return self._main_config
|
|
73
|
+
|
|
74
|
+
@main_config.setter
|
|
75
|
+
def main_config(self, tei_directories):
|
|
76
|
+
self._main_config = ProjectConfigTemplate(self.metadatapath).render(
|
|
77
|
+
tei_directories=tei_directories
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def init_configs(self, tei_directories):
|
|
81
|
+
if not self.main_config.exists():
|
|
82
|
+
self.main_config = tei_directories
|
|
83
|
+
else:
|
|
84
|
+
self.main_config.update(tei_directories=tei_directories)
|
|
85
|
+
# for tei_dir in tei_directories:z
|
|
86
|
+
# # if not self.main_config.get_subproject(inpath=tei_dir):
|
|
87
|
+
# break
|
|
88
|
+
self._set_collections()
|
|
89
|
+
|
|
90
|
+
def get_subproject_inpath(self, name):
|
|
91
|
+
for subproject in self.main_config.content["subprojects"]:
|
|
92
|
+
if subproject["name"] == name:
|
|
93
|
+
return subproject["inpath"]
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
def _set_collections(self):
|
|
97
|
+
self._collections = {}
|
|
98
|
+
if self.main_config.exists():
|
|
99
|
+
for subproject in self.main_config.content["subprojects"]:
|
|
100
|
+
collection_config = CollectionConfig(subproject["basepath"])
|
|
101
|
+
if not collection_config.exists():
|
|
102
|
+
collection_config = CollectionConfigTemplate(
|
|
103
|
+
projectpath=self.metadatapath,
|
|
104
|
+
subproject=subproject,
|
|
105
|
+
files=[
|
|
106
|
+
TEIParser(fullpath=file)
|
|
107
|
+
for file in subproject["files"]
|
|
108
|
+
],
|
|
109
|
+
).render(overwrite=False)
|
|
110
|
+
|
|
111
|
+
self._collections[subproject["name"]] = {
|
|
112
|
+
"config": collection_config,
|
|
113
|
+
"paths": subproject,
|
|
114
|
+
"parser": CollectionParser(collection_config),
|
|
115
|
+
"modeler": CollectionModeler(
|
|
116
|
+
subproject, self.metadatapath
|
|
117
|
+
),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def collections(self):
|
|
122
|
+
if self._collections is None:
|
|
123
|
+
self._set_collections()
|
|
124
|
+
return self._collections
|
|
125
|
+
|
|
126
|
+
def get_collection(self, title):
|
|
127
|
+
return self.collections.get(title)
|
|
128
|
+
|
|
129
|
+
def get_collection_parser(self, collection):
|
|
130
|
+
return CollectionParser(collection)
|
|
131
|
+
|
|
132
|
+
def get_tgp(self, instance):
|
|
133
|
+
return TGProject(self, instance)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class TGProject(object):
|
|
137
|
+
|
|
138
|
+
def __init__(self, project, instance):
|
|
139
|
+
if isinstance(project, Project):
|
|
140
|
+
self.project = project
|
|
141
|
+
else:
|
|
142
|
+
self.project = Project(project)
|
|
143
|
+
self.instance = instance
|
|
144
|
+
self.main_config = self.project.main_config
|
|
145
|
+
self.collections = self.project.collections
|
|
146
|
+
self._tg_client = None
|
|
147
|
+
self._tg_session_id = None
|
|
148
|
+
self._tg_project_id = None
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def tg_session_id(self):
|
|
152
|
+
if not self._tg_session_id:
|
|
153
|
+
self._tg_session_id = self.main_config.get_tg_session_id(
|
|
154
|
+
self.instance
|
|
155
|
+
)
|
|
156
|
+
return self._tg_session_id
|
|
157
|
+
|
|
158
|
+
@tg_session_id.setter
|
|
159
|
+
def tg_session_id(self, session_id):
|
|
160
|
+
self._tg_session_id = session_id
|
|
161
|
+
self.main_config.set_tg_session_id(session_id, self.instance)
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def tg_project_id(self):
|
|
165
|
+
if not self._tg_project_id:
|
|
166
|
+
self._tg_project_id = self.main_config.get_tg_project_id(
|
|
167
|
+
self.instance
|
|
168
|
+
)
|
|
169
|
+
return self._tg_project_id
|
|
170
|
+
|
|
171
|
+
@tg_project_id.setter
|
|
172
|
+
def tg_project_id(self, project_id):
|
|
173
|
+
self._tg_project_id = project_id
|
|
174
|
+
self.main_config.set_tg_project_id(project_id, self.instance)
|
|
175
|
+
|
|
176
|
+
@property
|
|
177
|
+
def tg_client(self):
|
|
178
|
+
if not self._tg_client:
|
|
179
|
+
self._tg_client = TGclient(self.tg_session_id, self.instance)
|
|
180
|
+
return self._tg_client
|
|
181
|
+
|
|
182
|
+
def create_tg_project(self, name, instance="test", description=""):
|
|
183
|
+
if not self.tg_session_id:
|
|
184
|
+
return []
|
|
185
|
+
tg_project_id = self.tg_client.create_project(name, description)
|
|
186
|
+
if tg_project_id:
|
|
187
|
+
self.tg_project_id = tg_project_id
|
|
188
|
+
|
|
189
|
+
def delete_tg_project(self, tg_project_id, instance="test"):
|
|
190
|
+
if not self.tg_session_id:
|
|
191
|
+
return False
|
|
192
|
+
# delete tg-project at textgrid server
|
|
193
|
+
res = self.tg_client.delete_project(tg_project_id)
|
|
194
|
+
# delete tg-project at local config if successful AND
|
|
195
|
+
# is the currently defined project_id
|
|
196
|
+
if res and tg_project_id == self.tg_project_id:
|
|
197
|
+
self.tg_project_id = None
|
|
198
|
+
return True
|
|
199
|
+
|
|
200
|
+
def get_tg_projects(self, instance="test"):
|
|
201
|
+
if not self.tg_session_id:
|
|
202
|
+
return []
|
|
203
|
+
return self.tg_client.get_assigned_projects()
|
|
204
|
+
|
|
205
|
+
def get_tg_project_hits(self, project_id, instance="test"):
|
|
206
|
+
if self.tg_session_id:
|
|
207
|
+
return self.tg_client.get_project_content(project_id).hits
|
|
208
|
+
|
|
209
|
+
def publish_tg_project(self, instance="test"):
|
|
210
|
+
if not self.tg_session_id:
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
# step 1: create required metadata
|
|
214
|
+
for c_name in self.collections:
|
|
215
|
+
collection = self.collections[c_name]
|
|
216
|
+
collection["modeler"].render_collection()
|
|
217
|
+
|
|
218
|
+
# step 2: push project to textgrid server
|
|
219
|
+
for collection_name in self.collections:
|
|
220
|
+
collection = self.collections[collection_name]
|
|
221
|
+
self.tg_client.put_aggregation(
|
|
222
|
+
self.tg_project_id,
|
|
223
|
+
collection["modeler"].get_collection_path(),
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class CollectionParser(object):
|
|
228
|
+
|
|
229
|
+
def __init__(self, collection):
|
|
230
|
+
self.collection = collection
|
|
231
|
+
self._elements = None
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def elements(self):
|
|
235
|
+
if self._elements is None:
|
|
236
|
+
self._elements = []
|
|
237
|
+
for file in self.collection.elements:
|
|
238
|
+
self._elements.append(
|
|
239
|
+
{
|
|
240
|
+
"file": file,
|
|
241
|
+
"tei_parser": TEIParser(fullpath=file["fullpath"]),
|
|
242
|
+
}
|
|
243
|
+
)
|
|
244
|
+
return self._elements
|
|
245
|
+
|
|
246
|
+
def test_xpath(self, xpath):
|
|
247
|
+
results = []
|
|
248
|
+
for element in self.elements:
|
|
249
|
+
result = element["tei_parser"].find(xpath)
|
|
250
|
+
print(result)
|
|
251
|
+
if result is not None:
|
|
252
|
+
results.append(
|
|
253
|
+
{
|
|
254
|
+
"filename": element["file"]["filename"],
|
|
255
|
+
"result": result,
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
return {
|
|
259
|
+
"results": results,
|
|
260
|
+
"count": {
|
|
261
|
+
"total": len(self.collection.elements),
|
|
262
|
+
"found": len(results),
|
|
263
|
+
"percentage": round(
|
|
264
|
+
len(results) / len(self.collection.elements) * 100
|
|
265
|
+
),
|
|
266
|
+
},
|
|
267
|
+
}
|
tgp_backend/tgclient.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright (C) 2023,2024 TU-Dresden (ZIH)
|
|
3
|
+
# ralf.klammer@tu-dresden.de
|
|
4
|
+
# moritz.wilhelm@tu-dresden.de
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
from tgadmin.tgimport import aggregation_import
|
|
8
|
+
from tgadmin.tgadmin import _crud_delete_op
|
|
9
|
+
|
|
10
|
+
from tgclients import (
|
|
11
|
+
TextgridAuth,
|
|
12
|
+
TextgridConfig,
|
|
13
|
+
TextgridCrud,
|
|
14
|
+
TextgridSearch,
|
|
15
|
+
)
|
|
16
|
+
from tgclients.config import PROD_SERVER, TEST_SERVER
|
|
17
|
+
|
|
18
|
+
log = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TGclient(object):
|
|
22
|
+
def __init__(self, sid, instance="test", verbose=False):
|
|
23
|
+
self.sid = sid
|
|
24
|
+
|
|
25
|
+
if instance == "live":
|
|
26
|
+
self.server = PROD_SERVER
|
|
27
|
+
else:
|
|
28
|
+
self.server = TEST_SERVER
|
|
29
|
+
self.config = TextgridConfig(self.server)
|
|
30
|
+
|
|
31
|
+
self.crud = TextgridCrud(self.config)
|
|
32
|
+
self.tgauth = TextgridAuth(self.config)
|
|
33
|
+
self.tgsearch = TextgridSearch(self.config, nonpublic=True)
|
|
34
|
+
|
|
35
|
+
def create_project(self, name, description=""):
|
|
36
|
+
log.info(f"Creating project {name}")
|
|
37
|
+
return self.tgauth.create_project(self.sid, name, description)
|
|
38
|
+
|
|
39
|
+
def delete_project(self, project_id):
|
|
40
|
+
log.info(f"Deleting project {project_id}")
|
|
41
|
+
content = self.get_project_content(project_id)
|
|
42
|
+
|
|
43
|
+
# delete content of project
|
|
44
|
+
# repeat until whole content has been deleted
|
|
45
|
+
# (necessary because of default limit in '_crud_delete_op')
|
|
46
|
+
while int(content.hits) > 0:
|
|
47
|
+
for tgobj in content.result:
|
|
48
|
+
_crud_delete_op(self, tgobj)
|
|
49
|
+
content = self.get_project_content(project_id)
|
|
50
|
+
|
|
51
|
+
return self.tgauth.delete_project(self.sid, project_id)
|
|
52
|
+
|
|
53
|
+
def get_project_content(self, project_id):
|
|
54
|
+
contents = self.tgsearch.search(
|
|
55
|
+
filters=["project.id:" + project_id], sid=self.sid
|
|
56
|
+
)
|
|
57
|
+
return contents
|
|
58
|
+
|
|
59
|
+
def get_assigned_projects(self):
|
|
60
|
+
log.info("Listing assigned projects")
|
|
61
|
+
# TODO: this needs a better error handling to indicate user,
|
|
62
|
+
# that the session-id seeems to be invalid
|
|
63
|
+
try:
|
|
64
|
+
_projects = self.tgauth.list_assigned_projects(self.sid)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
log.error(f"Error listing assigned projects: {e}")
|
|
67
|
+
return []
|
|
68
|
+
|
|
69
|
+
for project_id in _projects:
|
|
70
|
+
desc = self.tgauth.get_project_description(project_id)
|
|
71
|
+
if desc:
|
|
72
|
+
yield {
|
|
73
|
+
"id": project_id,
|
|
74
|
+
"name": desc.name,
|
|
75
|
+
"description": desc.description,
|
|
76
|
+
"contents": self.get_project_content(project_id).hits,
|
|
77
|
+
}
|
|
78
|
+
else:
|
|
79
|
+
log.warning(
|
|
80
|
+
f"Cannot find project description for: {project_id}"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def put_aggregation(self, project_id, aggregation_file):
|
|
84
|
+
res = aggregation_import(
|
|
85
|
+
self.crud,
|
|
86
|
+
self.sid,
|
|
87
|
+
project_id,
|
|
88
|
+
aggregation_file,
|
|
89
|
+
threaded=True,
|
|
90
|
+
ignore_warnings=True,
|
|
91
|
+
)
|
|
92
|
+
print(res)
|
tgp_backend/util.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# Copyright (C) 2023-2024 TU-Dresden (ZIH)
|
|
3
|
+
# ralf.klammer@tu-dresden.de
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
from configparser import ConfigParser
|
|
10
|
+
|
|
11
|
+
log = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def config(section, parameter, default=None):
|
|
15
|
+
_config = ConfigParser()
|
|
16
|
+
_config.read("config.ini")
|
|
17
|
+
|
|
18
|
+
if section not in _config:
|
|
19
|
+
log.warn("Section: %s not in *.ini -> using default" % section)
|
|
20
|
+
return default
|
|
21
|
+
config_val = _config[section].get(parameter)
|
|
22
|
+
if not config_val:
|
|
23
|
+
log.info(
|
|
24
|
+
"Parameter: %s not in section: (%s) of *.ini -> using default: %s"
|
|
25
|
+
% (parameter, section, default)
|
|
26
|
+
)
|
|
27
|
+
return default
|
|
28
|
+
else:
|
|
29
|
+
return config_val
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def cli_startup(log_level=logging.INFO, log_file=None):
|
|
33
|
+
log_config = dict(
|
|
34
|
+
level=log_level,
|
|
35
|
+
format="%(asctime)s %(name)-10s %(levelname)-4s %(message)s",
|
|
36
|
+
)
|
|
37
|
+
if log_file:
|
|
38
|
+
log_config["filename"] = log_file
|
|
39
|
+
|
|
40
|
+
logging.basicConfig(**log_config)
|
|
41
|
+
logging.getLogger("").setLevel(log_level)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_file_extension(fname):
|
|
45
|
+
found_extension = re.search("\.[A-Za-z0-9]*$", fname, re.IGNORECASE)
|
|
46
|
+
if found_extension:
|
|
47
|
+
return found_extension[0][1:].lower()
|
|
48
|
+
return ""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def list_files_and_folders(path):
|
|
52
|
+
|
|
53
|
+
def recursive_list(dir_path, depth=0):
|
|
54
|
+
items = []
|
|
55
|
+
for entry in os.scandir(dir_path):
|
|
56
|
+
if entry.name.startswith("."):
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
if entry.is_dir():
|
|
60
|
+
children = recursive_list(entry.path, depth=depth + 1)
|
|
61
|
+
items.append(
|
|
62
|
+
{
|
|
63
|
+
"type": "folder",
|
|
64
|
+
"name": entry.name,
|
|
65
|
+
"depth": depth,
|
|
66
|
+
"path": entry.path,
|
|
67
|
+
"children": {"count": len(children), "list": children},
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
else:
|
|
71
|
+
items.append(
|
|
72
|
+
{"type": "file", "name": entry.name, "depth": depth}
|
|
73
|
+
)
|
|
74
|
+
return items
|
|
75
|
+
|
|
76
|
+
return recursive_list(path)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def remove_empty_strings_from_dict(d):
|
|
80
|
+
for key in d:
|
|
81
|
+
if d[key] == "":
|
|
82
|
+
d[key] = None
|
|
83
|
+
return d
|
tgp_ui/__init__.py
ADDED
|
File without changes
|