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.

Files changed (46) hide show
  1. tg_prepare-1.0.0.dist-info/METADATA +19 -0
  2. tg_prepare-1.0.0.dist-info/RECORD +46 -0
  3. tg_prepare-1.0.0.dist-info/WHEEL +5 -0
  4. tg_prepare-1.0.0.dist-info/entry_points.txt +3 -0
  5. tg_prepare-1.0.0.dist-info/licenses/LICENSE +202 -0
  6. tg_prepare-1.0.0.dist-info/top_level.txt +2 -0
  7. tgp_backend/__init__.py +18 -0
  8. tgp_backend/auth.py +71 -0
  9. tgp_backend/cli.py +22 -0
  10. tgp_backend/directories.py +85 -0
  11. tgp_backend/interfaces.py +3 -0
  12. tgp_backend/nextcloud.py +125 -0
  13. tgp_backend/project.py +267 -0
  14. tgp_backend/tgclient.py +92 -0
  15. tgp_backend/util.py +83 -0
  16. tgp_ui/__init__.py +0 -0
  17. tgp_ui/app.py +424 -0
  18. tgp_ui/static/css/bootstrap-icons.min.css +5 -0
  19. tgp_ui/static/css/bootstrap.min.css +7 -0
  20. tgp_ui/static/css/main.css +146 -0
  21. tgp_ui/static/css/simpleXML.css +67 -0
  22. tgp_ui/static/js/bootstrap.bundle.min.js +7 -0
  23. tgp_ui/static/js/jquery.min.js +2 -0
  24. tgp_ui/static/js/jstree.min.js +3 -0
  25. tgp_ui/static/js/main.js +454 -0
  26. tgp_ui/static/js/simpleXML.js +193 -0
  27. tgp_ui/templates/collection.html +194 -0
  28. tgp_ui/templates/empty.html +4 -0
  29. tgp_ui/templates/file_tree.html +39 -0
  30. tgp_ui/templates/file_upload.html +24 -0
  31. tgp_ui/templates/jsfiler.html +6 -0
  32. tgp_ui/templates/layout.html +59 -0
  33. tgp_ui/templates/layout2.html +99 -0
  34. tgp_ui/templates/macros.html +91 -0
  35. tgp_ui/templates/main.html +5 -0
  36. tgp_ui/templates/nxc_file_tree.html +33 -0
  37. tgp_ui/templates/nxc_login.html +25 -0
  38. tgp_ui/templates/nxc_tab.html +15 -0
  39. tgp_ui/templates/project.html +25 -0
  40. tgp_ui/templates/project_nxc.html +37 -0
  41. tgp_ui/templates/projects.html +73 -0
  42. tgp_ui/templates/publish.html +120 -0
  43. tgp_ui/templates/storage.html +49 -0
  44. tgp_ui/templates/tei_browser.html +14 -0
  45. tgp_ui/templates/tei_explorer.html +48 -0
  46. 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
+ }
@@ -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