muninn 6.4.2__tar.gz → 7.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.
- {muninn-6.4.2 → muninn-7.0}/CHANGES +14 -0
- {muninn-6.4.2 → muninn-7.0}/LICENSE +1 -1
- {muninn-6.4.2 → muninn-7.0}/PKG-INFO +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/__init__.py +4 -4
- {muninn-6.4.2 → muninn-7.0}/muninn/archive.py +99 -29
- {muninn-6.4.2 → muninn-7.0}/muninn/config.py +2 -2
- {muninn-6.4.2 → muninn-7.0}/muninn/core.py +2 -3
- muninn-7.0/muninn/database/__init__.py +3 -0
- muninn-7.0/muninn/database/base.py +6 -0
- {muninn-6.4.2 → muninn-7.0}/muninn/database/blobgeometry.py +3 -3
- {muninn-6.4.2 → muninn-7.0}/muninn/database/ewkb.py +3 -3
- {muninn-6.4.2 → muninn-7.0}/muninn/database/postgresql.py +4 -3
- {muninn-6.4.2 → muninn-7.0}/muninn/database/sql.py +3 -3
- {muninn-6.4.2 → muninn-7.0}/muninn/database/sqlite.py +8 -8
- {muninn-6.4.2 → muninn-7.0}/muninn/enum.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/exceptions.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/extension.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/function.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/geometry.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/language.py +3 -3
- {muninn-6.4.2 → muninn-7.0}/muninn/remote.py +8 -2
- {muninn-6.4.2 → muninn-7.0}/muninn/schema.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/struct.py +1 -1
- muninn-7.0/muninn/tools/__init__.py +3 -0
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/attach.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/destroy.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/export.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/hash.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/info.py +17 -5
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/ingest.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/list_tags.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/prepare.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/pull.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/remove.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/retrieve.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/search.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/strip.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/summary.py +1 -1
- muninn-7.0/muninn/tools/sync.py +42 -0
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/tag.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/untag.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/update.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/tools/utils.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/util.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn/visitor.py +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn.egg-info/PKG-INFO +1 -1
- {muninn-6.4.2 → muninn-7.0}/muninn.egg-info/SOURCES.txt +1 -0
- {muninn-6.4.2 → muninn-7.0}/muninn.egg-info/entry_points.txt +1 -0
- {muninn-6.4.2 → muninn-7.0}/setup.py +2 -1
- muninn-6.4.2/muninn/database/__init__.py +0 -3
- muninn-6.4.2/muninn/database/base.py +0 -6
- muninn-6.4.2/muninn/tools/__init__.py +0 -3
- {muninn-6.4.2 → muninn-7.0}/DEVELOPER.md +0 -0
- {muninn-6.4.2 → muninn-7.0}/MANIFEST.in +0 -0
- {muninn-6.4.2 → muninn-7.0}/README.md +0 -0
- {muninn-6.4.2 → muninn-7.0}/muninn/_compat.py +0 -0
- {muninn-6.4.2 → muninn-7.0}/muninn/storage/__init__.py +0 -0
- {muninn-6.4.2 → muninn-7.0}/muninn/storage/base.py +0 -0
- {muninn-6.4.2 → muninn-7.0}/muninn/storage/fs.py +0 -0
- {muninn-6.4.2 → muninn-7.0}/muninn/storage/s3.py +0 -0
- {muninn-6.4.2 → muninn-7.0}/muninn/storage/swift.py +0 -0
- {muninn-6.4.2 → muninn-7.0}/muninn.egg-info/dependency_links.txt +0 -0
- {muninn-6.4.2 → muninn-7.0}/muninn.egg-info/top_level.txt +0 -0
- {muninn-6.4.2 → muninn-7.0}/setup.cfg +0 -0
- {muninn-6.4.2 → muninn-7.0}/test/coverage.sh +0 -0
- {muninn-6.4.2 → muninn-7.0}/test/data/README +0 -0
- {muninn-6.4.2 → muninn-7.0}/test/data/multi/1.txt +0 -0
- {muninn-6.4.2 → muninn-7.0}/test/data/multi/2.txt +0 -0
- {muninn-6.4.2 → muninn-7.0}/test/data/pi.txt +0 -0
- {muninn-6.4.2 → muninn-7.0}/test/product_type.py.template +0 -0
- {muninn-6.4.2 → muninn-7.0}/test/test.py +0 -0
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
7.0 2025-08-14
|
|
2
|
+
~~~~~~~~~~~~~~
|
|
3
|
+
|
|
4
|
+
* Added catalogue synchronizer functionality to muninn. This introduces
|
|
5
|
+
muninn-sync, archive.sync(), and a new synchronizer extension interface.
|
|
6
|
+
|
|
7
|
+
* Added -n/--name-only option to muninn-info.
|
|
8
|
+
|
|
9
|
+
6.5 2025-03-25
|
|
10
|
+
~~~~~~~~~~~~~~
|
|
11
|
+
|
|
12
|
+
* Added support for optional `auth_args` setting in credentials file for
|
|
13
|
+
oauth2 token requests.
|
|
14
|
+
|
|
1
15
|
6.4.2 2025-02-24
|
|
2
16
|
~~~~~~~~~~~~~~~~
|
|
3
17
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (C) 2014-
|
|
2
|
+
# Copyright (C) 2014-2025 S[&]T, The Netherlands.
|
|
3
3
|
#
|
|
4
4
|
|
|
5
5
|
from __future__ import absolute_import, division, print_function
|
|
6
6
|
|
|
7
|
-
__version__ = "
|
|
8
|
-
__copyright__ = "Copyright (C) 2014-
|
|
7
|
+
__version__ = "7.0.0"
|
|
8
|
+
__copyright__ = "Copyright (C) 2014-2025 S[&]T, The Netherlands."
|
|
9
9
|
|
|
10
10
|
__all__ = ["Error", "InternalError", "Struct", "Archive", "open", "config_path"]
|
|
11
11
|
|
|
@@ -14,7 +14,7 @@ import sys as _sys
|
|
|
14
14
|
|
|
15
15
|
from muninn._compat import urlparse as _urlparse
|
|
16
16
|
from muninn.archive import Archive
|
|
17
|
-
from muninn.exceptions import
|
|
17
|
+
from muninn.exceptions import Error, InternalError, StorageError
|
|
18
18
|
from muninn.struct import Struct
|
|
19
19
|
|
|
20
20
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (C) 2014-
|
|
2
|
+
# Copyright (C) 2014-2025 S[&]T, The Netherlands.
|
|
3
3
|
#
|
|
4
4
|
|
|
5
5
|
from __future__ import absolute_import, division, print_function
|
|
@@ -56,6 +56,7 @@ class _ArchiveConfig(Mapping):
|
|
|
56
56
|
product_type_extensions = _ExtensionList(optional=True)
|
|
57
57
|
remote_backend_extensions = _ExtensionList(optional=True)
|
|
58
58
|
hook_extensions = _ExtensionList(optional=True)
|
|
59
|
+
synchronizers = _ExtensionList(optional=True)
|
|
59
60
|
auth_file = Text(optional=True)
|
|
60
61
|
tempdir = Text(optional=True)
|
|
61
62
|
|
|
@@ -91,6 +92,15 @@ def _load_extension(name):
|
|
|
91
92
|
return sys.modules[name]
|
|
92
93
|
|
|
93
94
|
|
|
95
|
+
def _load_synchronizer_module(name):
|
|
96
|
+
try:
|
|
97
|
+
__import__(name)
|
|
98
|
+
except ImportError as e:
|
|
99
|
+
raise Error("import of syncrhonizer %r failed (%s)" % (name, e))
|
|
100
|
+
|
|
101
|
+
return sys.modules[name]
|
|
102
|
+
|
|
103
|
+
|
|
94
104
|
def _inspect_nargs(func):
|
|
95
105
|
try:
|
|
96
106
|
getargspec = inspect.getfullargspec
|
|
@@ -161,12 +171,12 @@ class Archive(object):
|
|
|
161
171
|
options = config.parse(configuration.get("archive", {}), _ArchiveConfig)
|
|
162
172
|
_ArchiveConfig.validate(options)
|
|
163
173
|
|
|
164
|
-
# Load and create the database backend
|
|
174
|
+
# Load and create the database backend
|
|
165
175
|
database_name = options.pop('database')
|
|
166
176
|
database_module = _load_database_module(database_name)
|
|
167
177
|
database = database_module.create(configuration)
|
|
168
178
|
|
|
169
|
-
# Load and create the storage backend
|
|
179
|
+
# Load and create the storage backend
|
|
170
180
|
storage_name = options.pop('storage')
|
|
171
181
|
if storage_name == 'none':
|
|
172
182
|
storage = None
|
|
@@ -174,18 +184,19 @@ class Archive(object):
|
|
|
174
184
|
storage_module = _load_storage_module(storage_name)
|
|
175
185
|
storage = storage_module.create(configuration, options.get('tempdir', None), options.get('auth_file', None))
|
|
176
186
|
|
|
177
|
-
# Create the archive
|
|
187
|
+
# Create the archive
|
|
178
188
|
namespace_extensions = options.pop("namespace_extensions", [])
|
|
179
189
|
product_type_extensions = options.pop("product_type_extensions", [])
|
|
180
190
|
remote_backend_extensions = options.pop("remote_backend_extensions", [])
|
|
181
191
|
hook_extensions = options.pop("hook_extensions", [])
|
|
192
|
+
synchronizers = options.pop("synchronizers", [])
|
|
182
193
|
archive = Archive(database=database, storage=storage, id=id,
|
|
183
194
|
configuration=configuration, **options)
|
|
184
195
|
|
|
185
|
-
# Register core namespace
|
|
196
|
+
# Register core namespace
|
|
186
197
|
archive.register_namespace("core", Core)
|
|
187
198
|
|
|
188
|
-
# Register custom extensions
|
|
199
|
+
# Register custom extensions
|
|
189
200
|
for name in namespace_extensions:
|
|
190
201
|
extension = _load_extension(name)
|
|
191
202
|
_register_extension(archive, extension, name, 'namespace')
|
|
@@ -202,6 +213,10 @@ class Archive(object):
|
|
|
202
213
|
extension = _load_extension(name)
|
|
203
214
|
_register_extension(archive, extension, name, 'hook_extension')
|
|
204
215
|
|
|
216
|
+
# Register synchronizers
|
|
217
|
+
for synchronizer in synchronizers:
|
|
218
|
+
archive.register_synchronizer(synchronizer)
|
|
219
|
+
|
|
205
220
|
return archive
|
|
206
221
|
|
|
207
222
|
def __init__(self, database, storage, cascade_grace_period=0,
|
|
@@ -217,6 +232,7 @@ class Archive(object):
|
|
|
217
232
|
self._product_type_plugins = {}
|
|
218
233
|
self._remote_backend_plugins = {}
|
|
219
234
|
self._hook_extensions = collections.OrderedDict()
|
|
235
|
+
self._synchronizers = {}
|
|
220
236
|
|
|
221
237
|
self._export_formats = set()
|
|
222
238
|
|
|
@@ -377,6 +393,49 @@ class Archive(object):
|
|
|
377
393
|
"""Return a list of supported hook extensions."""
|
|
378
394
|
return list(self._hook_extensions.keys())
|
|
379
395
|
|
|
396
|
+
def register_synchronizer(self, synchronizer):
|
|
397
|
+
"""Register a synchronizer.
|
|
398
|
+
|
|
399
|
+
Arguments:
|
|
400
|
+
synchronizer -- Synchronizer name
|
|
401
|
+
"""
|
|
402
|
+
if synchronizer in self._synchronizers:
|
|
403
|
+
raise Error("redefinition of synchronizer: \"%s\"" % synchronizer)
|
|
404
|
+
|
|
405
|
+
if "synchronizer:" + synchronizer not in self._configuration:
|
|
406
|
+
raise Error("missing synchronizer \"%s\" configuration" % (synchronizer))
|
|
407
|
+
config_section = self._configuration.get('synchronizer:' + synchronizer)
|
|
408
|
+
|
|
409
|
+
if "module" not in config_section:
|
|
410
|
+
raise Error("missing module option for synchronizer \"%s\"" % (synchronizer))
|
|
411
|
+
module_name = config_section.pop("module")
|
|
412
|
+
module = _load_synchronizer_module(module_name)
|
|
413
|
+
|
|
414
|
+
if not hasattr(module, "synchronizer"):
|
|
415
|
+
raise Error("synchronizer \"%s\" does not implement the synchronizer API" % (synchronizer))
|
|
416
|
+
|
|
417
|
+
plugin = module.synchronizer(config_section)
|
|
418
|
+
if not hasattr(plugin, "sync"):
|
|
419
|
+
raise Error("missing 'sync' method in plugin for synchronizer \"%s\"" % (synchronizer))
|
|
420
|
+
|
|
421
|
+
self._synchronizers[synchronizer] = plugin
|
|
422
|
+
|
|
423
|
+
def synchronizer(self, synchronizer):
|
|
424
|
+
"""Return the synchronizer with the specified name.
|
|
425
|
+
|
|
426
|
+
Arguments:
|
|
427
|
+
synchronizer -- Synchronizer name
|
|
428
|
+
"""
|
|
429
|
+
if synchronizer not in self._synchronizers:
|
|
430
|
+
raise Error("unregistered synchronizer: \"%s\"; registered synchronizers: %s" %
|
|
431
|
+
(synchronizer, util.quoted_list(self._synchronizers.keys())))
|
|
432
|
+
|
|
433
|
+
return self._synchronizers[synchronizer]
|
|
434
|
+
|
|
435
|
+
def synchronizers(self):
|
|
436
|
+
"""Return a list of supported synchronizers"""
|
|
437
|
+
return list(self._synchronizers.keys())
|
|
438
|
+
|
|
380
439
|
def _analyze_paths(self, plugin, paths):
|
|
381
440
|
metadata = plugin.analyze(paths)
|
|
382
441
|
|
|
@@ -442,7 +501,7 @@ class Archive(object):
|
|
|
442
501
|
assert len(products) == 1
|
|
443
502
|
return products[0]
|
|
444
503
|
|
|
445
|
-
def _get_products(self, where, parameters=None, namespaces=None, property_names=None):
|
|
504
|
+
def _get_products(self, where, parameters=None, namespaces=None, property_names=None):
|
|
446
505
|
if isinstance(where, basestring):
|
|
447
506
|
return self.search(where, parameters=parameters, namespaces=namespaces, property_names=property_names)
|
|
448
507
|
|
|
@@ -532,9 +591,6 @@ class Archive(object):
|
|
|
532
591
|
|
|
533
592
|
def _retrieve(self, product, target_path, use_symlinks=False, verify_hash=False):
|
|
534
593
|
if 'archive_path' in product.core:
|
|
535
|
-
# Determine the path of the product in storage.
|
|
536
|
-
product_path = self._product_path(product)
|
|
537
|
-
|
|
538
594
|
# Get the product type specific plugin.
|
|
539
595
|
plugin = self.product_type_plugin(product.core.product_type)
|
|
540
596
|
use_enclosing_directory = plugin.use_enclosing_directory
|
|
@@ -593,7 +649,7 @@ class Archive(object):
|
|
|
593
649
|
else:
|
|
594
650
|
with util.TemporaryDirectory(prefix=".run_for_product-",
|
|
595
651
|
suffix="-%s" % product.core.uuid.hex) as tmp_path:
|
|
596
|
-
retrieve_files = remote.retrieve_function(self, product, True)
|
|
652
|
+
retrieve_files = remote.retrieve_function(self, product, True)
|
|
597
653
|
retrieve_files(tmp_path)
|
|
598
654
|
paths = [os.path.join(tmp_path, basename) for basename in os.listdir(tmp_path)]
|
|
599
655
|
return fn(paths)
|
|
@@ -1079,7 +1135,7 @@ class Archive(object):
|
|
|
1079
1135
|
properties.core.uuid = self.generate_uuid()
|
|
1080
1136
|
properties.core.active = False
|
|
1081
1137
|
properties.core.hash = None
|
|
1082
|
-
properties.core.size = util.product_size(paths)
|
|
1138
|
+
properties.core.size = util.product_size(paths)
|
|
1083
1139
|
properties.core.metadata_date = None
|
|
1084
1140
|
properties.core.archive_date = None
|
|
1085
1141
|
properties.core.archive_path = None
|
|
@@ -1129,6 +1185,8 @@ class Archive(object):
|
|
|
1129
1185
|
|
|
1130
1186
|
self.create_properties(properties, disable_hooks=True)
|
|
1131
1187
|
|
|
1188
|
+
updated_properties = Struct({'core': {}})
|
|
1189
|
+
|
|
1132
1190
|
# Try to determine the product hash and ingest the product into the archive.
|
|
1133
1191
|
try:
|
|
1134
1192
|
# Determine product hash. Since it is an expensive operation, the hash is computed after inserting the
|
|
@@ -1136,24 +1194,26 @@ class Archive(object):
|
|
|
1136
1194
|
hash_type = self._plugin_hash_type(plugin)
|
|
1137
1195
|
if hash_type is not None:
|
|
1138
1196
|
try:
|
|
1139
|
-
|
|
1197
|
+
updated_properties.core.hash = util.product_hash(paths, hash_type=hash_type)
|
|
1140
1198
|
except EnvironmentError as _error:
|
|
1141
1199
|
raise Error("cannot determine product hash [%s]" % (_error,))
|
|
1142
|
-
self.update_properties(Struct({'core': {'hash': properties.core.hash}}), properties.core.uuid)
|
|
1143
1200
|
|
|
1201
|
+
properties.update(updated_properties)
|
|
1144
1202
|
if ingest_product:
|
|
1145
1203
|
use_enclosing_directory = plugin.use_enclosing_directory
|
|
1146
1204
|
self._storage.put(paths, properties, use_enclosing_directory, use_symlinks)
|
|
1147
|
-
|
|
1205
|
+
updated_properties.core.archive_date = self._database.server_time_utc()
|
|
1148
1206
|
|
|
1149
1207
|
elif self._storage is None:
|
|
1150
1208
|
if len(paths) == 1:
|
|
1151
|
-
|
|
1209
|
+
updated_properties.core.remote_url = 'file://' + os.path.realpath(paths[0])
|
|
1152
1210
|
else:
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1211
|
+
parent_path = os.path.dirname(paths[0])
|
|
1212
|
+
for path in paths:
|
|
1213
|
+
if os.path.dirname(path) != parent_path:
|
|
1214
|
+
raise Error("all paths need to have the same parent directory")
|
|
1215
|
+
updated_properties.core.remote_url = 'file://' + os.path.realpath(os.path.dirname(paths[0]))
|
|
1216
|
+
updated_properties.core.remote_url = updated_properties.core.remote_url.replace('\\', '/')
|
|
1157
1217
|
|
|
1158
1218
|
except Exception as e:
|
|
1159
1219
|
if not (isinstance(e, StorageError) and e.anything_stored):
|
|
@@ -1164,15 +1224,12 @@ class Archive(object):
|
|
|
1164
1224
|
else:
|
|
1165
1225
|
raise
|
|
1166
1226
|
|
|
1167
|
-
#
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
'archive_date': properties.core.archive_date,
|
|
1172
|
-
}
|
|
1173
|
-
self.update_properties(Struct({'core': metadata}), properties.core.uuid)
|
|
1227
|
+
# Set additional properties and activate product
|
|
1228
|
+
updated_properties.core.active = True
|
|
1229
|
+
self.update_properties(updated_properties, properties.core.uuid)
|
|
1230
|
+
properties.update(updated_properties)
|
|
1174
1231
|
|
|
1175
|
-
# Set product tags
|
|
1232
|
+
# Set product tags
|
|
1176
1233
|
self._database.tag(properties.core.uuid, tags)
|
|
1177
1234
|
|
|
1178
1235
|
# Verify product hash after copy
|
|
@@ -1660,6 +1717,19 @@ class Archive(object):
|
|
|
1660
1717
|
"""
|
|
1661
1718
|
return self._database.summary(where, parameters, aggregates, group_by, group_by_tag, having, order_by)
|
|
1662
1719
|
|
|
1720
|
+
def sync(self, synchronizer, product_types=None, start=None, end=None, force=False):
|
|
1721
|
+
"""Synchronize the archive product catalogue using a synchronizer plugin.
|
|
1722
|
+
|
|
1723
|
+
Arguments:
|
|
1724
|
+
synchronizer -- The synchronizer plugin
|
|
1725
|
+
product_types -- Product types to be synchronized
|
|
1726
|
+
start -- Start datetime of synchronization range (exclusive)
|
|
1727
|
+
end -- End datetime of synchronization range (exclusive)
|
|
1728
|
+
force
|
|
1729
|
+
"""
|
|
1730
|
+
synchronizer = self.synchronizer(synchronizer)
|
|
1731
|
+
synchronizer.sync(self, product_types, start, end, force)
|
|
1732
|
+
|
|
1663
1733
|
def tag(self, where=None, tags=None, parameters={}):
|
|
1664
1734
|
"""Set one or more tags on one or more product(s).
|
|
1665
1735
|
|
|
@@ -1770,4 +1840,4 @@ class Archive(object):
|
|
|
1770
1840
|
if not self._verify_hash(product):
|
|
1771
1841
|
failed_products.append(product.core.uuid)
|
|
1772
1842
|
|
|
1773
|
-
return failed_products
|
|
1843
|
+
return failed_products
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (C) 2014-
|
|
2
|
+
# Copyright (C) 2014-2025 S[&]T, The Netherlands.
|
|
3
3
|
#
|
|
4
4
|
|
|
5
5
|
from __future__ import absolute_import, division, print_function
|
|
6
6
|
|
|
7
7
|
from muninn._compat import string_types as basestring
|
|
8
8
|
|
|
9
|
-
from muninn.schema import
|
|
9
|
+
from muninn.schema import prefix_message_with_path, join
|
|
10
10
|
from muninn.exceptions import InternalError
|
|
11
11
|
from muninn.visitor import TypeVisitor
|
|
12
12
|
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (C) 2014-
|
|
2
|
+
# Copyright (C) 2014-2025 S[&]T, The Netherlands.
|
|
3
3
|
#
|
|
4
4
|
|
|
5
5
|
from __future__ import absolute_import, division, print_function
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
8
|
|
|
9
|
-
from muninn.schema import
|
|
9
|
+
from muninn.schema import Mapping, Boolean, Geometry, Long, Text, Timestamp, UUID
|
|
10
10
|
from muninn.util import split_path
|
|
11
|
-
from muninn.remote import *
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
class ArchivePath(Text):
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (C) 2014-
|
|
2
|
+
# Copyright (C) 2014-2025 S[&]T, The Netherlands.
|
|
3
3
|
#
|
|
4
4
|
|
|
5
5
|
from __future__ import absolute_import, division, print_function
|
|
@@ -8,8 +8,8 @@ import sys
|
|
|
8
8
|
import struct
|
|
9
9
|
|
|
10
10
|
from muninn.enum import Enum
|
|
11
|
-
from muninn.exceptions import
|
|
12
|
-
from muninn.geometry import
|
|
11
|
+
from muninn.exceptions import Error
|
|
12
|
+
from muninn.geometry import LinearRing, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon
|
|
13
13
|
from muninn.visitor import Visitor
|
|
14
14
|
|
|
15
15
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (C) 2014-
|
|
2
|
+
# Copyright (C) 2014-2025 S[&]T, The Netherlands.
|
|
3
3
|
#
|
|
4
4
|
|
|
5
5
|
from __future__ import absolute_import, division, print_function
|
|
@@ -8,8 +8,8 @@ import codecs
|
|
|
8
8
|
import struct
|
|
9
9
|
|
|
10
10
|
from muninn.enum import Enum
|
|
11
|
-
from muninn.exceptions import
|
|
12
|
-
from muninn.geometry import
|
|
11
|
+
from muninn.exceptions import Error
|
|
12
|
+
from muninn.geometry import LinearRing, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon
|
|
13
13
|
from muninn.visitor import Visitor
|
|
14
14
|
|
|
15
15
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (C) 2014-
|
|
2
|
+
# Copyright (C) 2014-2025 S[&]T, The Netherlands.
|
|
3
3
|
#
|
|
4
4
|
|
|
5
5
|
from __future__ import absolute_import, division, print_function
|
|
6
6
|
|
|
7
7
|
import re
|
|
8
|
+
import datetime
|
|
8
9
|
import functools
|
|
9
10
|
import inspect
|
|
10
11
|
import json
|
|
@@ -29,9 +30,9 @@ import muninn.database.sql as sql
|
|
|
29
30
|
import muninn.database.ewkb as ewkb
|
|
30
31
|
import muninn.geometry as geometry
|
|
31
32
|
|
|
32
|
-
from muninn.exceptions import
|
|
33
|
+
from muninn.exceptions import Error, InternalError
|
|
33
34
|
from muninn.function import Prototype
|
|
34
|
-
from muninn.schema import
|
|
35
|
+
from muninn.schema import Mapping, Namespace, Boolean, Geometry, Integer, JSON, Long, Real, Text, Timestamp, UUID
|
|
35
36
|
from muninn.struct import Struct
|
|
36
37
|
|
|
37
38
|
from muninn._compat import dictkeys, dictvalues, is_python2_unicode
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (C) 2014-
|
|
2
|
+
# Copyright (C) 2014-2025 S[&]T, The Netherlands.
|
|
3
3
|
#
|
|
4
4
|
|
|
5
5
|
from __future__ import absolute_import, division, print_function
|
|
@@ -13,10 +13,10 @@ except ImportError:
|
|
|
13
13
|
|
|
14
14
|
import inspect
|
|
15
15
|
|
|
16
|
-
from muninn.exceptions import
|
|
16
|
+
from muninn.exceptions import Error
|
|
17
17
|
from muninn.function import Prototype
|
|
18
18
|
from muninn.language import parse_and_analyze, Literal, Name, Identifier
|
|
19
|
-
from muninn.schema import
|
|
19
|
+
from muninn.schema import Boolean, Integer, Long, Real, Sequence, Text, Timestamp, UUID
|
|
20
20
|
from muninn.visitor import Visitor
|
|
21
21
|
from muninn.core import Basename, ArchivePath
|
|
22
22
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (C) 2014-
|
|
2
|
+
# Copyright (C) 2014-2025 S[&]T, The Netherlands.
|
|
3
3
|
#
|
|
4
4
|
|
|
5
5
|
from __future__ import absolute_import, division, print_function
|
|
@@ -11,8 +11,6 @@ import functools
|
|
|
11
11
|
import json
|
|
12
12
|
import uuid
|
|
13
13
|
|
|
14
|
-
from .base import DatabaseBackend
|
|
15
|
-
|
|
16
14
|
from muninn._compat import decode, dictkeys, dictvalues, utcnow
|
|
17
15
|
|
|
18
16
|
# Select a version of dbapi2 that's available.
|
|
@@ -24,14 +22,16 @@ try:
|
|
|
24
22
|
except ImportError:
|
|
25
23
|
import sqlite3.dbapi2 as dbapi2
|
|
26
24
|
|
|
25
|
+
from .base import DatabaseBackend
|
|
26
|
+
|
|
27
27
|
import muninn.config as config
|
|
28
28
|
import muninn.database.sql as sql
|
|
29
29
|
import muninn.database.blobgeometry as blobgeometry
|
|
30
30
|
import muninn.geometry as geometry
|
|
31
31
|
|
|
32
|
-
from muninn.exceptions import
|
|
32
|
+
from muninn.exceptions import Error, InternalError
|
|
33
33
|
from muninn.function import Prototype
|
|
34
|
-
from muninn.schema import
|
|
34
|
+
from muninn.schema import Mapping, Namespace, Boolean, Geometry, Integer, JSON, Long, Real, Text, Timestamp, UUID
|
|
35
35
|
from muninn.struct import Struct
|
|
36
36
|
|
|
37
37
|
|
|
@@ -39,8 +39,8 @@ class _SQLiteConfig(Mapping):
|
|
|
39
39
|
_alias = "sqlite"
|
|
40
40
|
|
|
41
41
|
connection_string = Text
|
|
42
|
-
mod_spatialite_path = optional
|
|
43
|
-
table_prefix = optional
|
|
42
|
+
mod_spatialite_path = Text(optional=True)
|
|
43
|
+
table_prefix = Text(optional=True)
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
def create(configuration):
|
|
@@ -224,7 +224,7 @@ class SQLiteConnection(object):
|
|
|
224
224
|
return self._connection.cursor()
|
|
225
225
|
|
|
226
226
|
|
|
227
|
-
class SQLiteBackend(
|
|
227
|
+
class SQLiteBackend(DatabaseBackend):
|
|
228
228
|
def __init__(self, connection_string="", mod_spatialite_path="mod_spatialite", table_prefix=""):
|
|
229
229
|
dbapi2.register_converter("BOOLEAN", lambda x: bool(int(x)))
|
|
230
230
|
dbapi2.register_adapter(bool, lambda x: int(x))
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (C) 2014-
|
|
2
|
+
# Copyright (C) 2014-2025 S[&]T, The Netherlands.
|
|
3
3
|
#
|
|
4
4
|
|
|
5
5
|
from __future__ import absolute_import, division, print_function
|
|
@@ -14,9 +14,9 @@ import uuid
|
|
|
14
14
|
import muninn.geometry as geometry
|
|
15
15
|
|
|
16
16
|
from muninn.enum import Enum
|
|
17
|
-
from muninn.exceptions import
|
|
17
|
+
from muninn.exceptions import Error, InternalError
|
|
18
18
|
from muninn.function import Prototype, FunctionTable
|
|
19
|
-
from muninn.schema import
|
|
19
|
+
from muninn.schema import Namespace, Boolean, Geometry, Integer, Long, Real, Sequence, Text, Timestamp, UUID
|
|
20
20
|
from muninn.visitor import Visitor
|
|
21
21
|
|
|
22
22
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (C) 2014-
|
|
2
|
+
# Copyright (C) 2014-2025 S[&]T, The Netherlands.
|
|
3
3
|
#
|
|
4
4
|
from __future__ import absolute_import, division, print_function
|
|
5
5
|
|
|
@@ -41,10 +41,14 @@ def download_http_oath2(url, target_dir, credentials, timeout, retries):
|
|
|
41
41
|
|
|
42
42
|
assert credentials['grant_type'] == "ResourceOwnerPasswordCredentialsGrant"
|
|
43
43
|
|
|
44
|
+
auth_args = {}
|
|
45
|
+
if 'auth_args' in credentials:
|
|
46
|
+
auth_args = credentials['auth_args']
|
|
47
|
+
|
|
44
48
|
session = OAuth2Session(client=LegacyApplicationClient(client_id=credentials['client_id']))
|
|
45
49
|
session.fetch_token(token_url=credentials['token_url'], username=credentials['username'],
|
|
46
50
|
password=credentials['password'], client_id=credentials['client_id'],
|
|
47
|
-
client_secret=credentials['client_secret'])
|
|
51
|
+
client_secret=credentials['client_secret'], timeout=timeout, **auth_args)
|
|
48
52
|
try:
|
|
49
53
|
while True:
|
|
50
54
|
try:
|
|
@@ -258,6 +262,8 @@ class RemoteBackend(object):
|
|
|
258
262
|
class FileBackend(RemoteBackend):
|
|
259
263
|
def pull(self, archive, product, target_dir):
|
|
260
264
|
source_path = urlparse(product.core.remote_url).path
|
|
265
|
+
if not os.path.exists(source_path):
|
|
266
|
+
raise DownloadError("%s does not exist" % product.core.remote_url)
|
|
261
267
|
target_path = os.path.join(target_dir, os.path.basename(source_path))
|
|
262
268
|
util.copy_path(source_path, target_path)
|
|
263
269
|
return self.auto_extract(target_path, product)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright (C) 2014-
|
|
2
|
+
# Copyright (C) 2014-2025 S[&]T, The Netherlands.
|
|
3
3
|
#
|
|
4
4
|
|
|
5
5
|
from __future__ import absolute_import, division, print_function
|
|
@@ -12,11 +12,15 @@ from muninn.tools.utils import create_parser, parse_args_and_run
|
|
|
12
12
|
def run(args):
|
|
13
13
|
archive_names = args.archive or muninn.list_archives()
|
|
14
14
|
|
|
15
|
-
print("ARCHIVES"
|
|
16
|
-
for archive_name in archive_names:
|
|
17
|
-
|
|
15
|
+
print("ARCHIVES")
|
|
16
|
+
for index, archive_name in enumerate(sorted(archive_names)):
|
|
17
|
+
if index > 0 and not args.name_only:
|
|
18
|
+
print()
|
|
19
|
+
print(" " + archive_name)
|
|
18
20
|
try:
|
|
19
21
|
with muninn.open(archive_name) as archive:
|
|
22
|
+
if args.name_only:
|
|
23
|
+
continue
|
|
20
24
|
print(" NAMESPACES")
|
|
21
25
|
for namespace in sorted(archive.namespaces()):
|
|
22
26
|
print(" %s" % namespace)
|
|
@@ -37,6 +41,11 @@ def run(args):
|
|
|
37
41
|
print("\n REMOTE BACKENDS")
|
|
38
42
|
for remote_backend in sorted(archive.remote_backends()):
|
|
39
43
|
print(" %s" % remote_backend)
|
|
44
|
+
|
|
45
|
+
if archive.synchronizers():
|
|
46
|
+
print("\n SYNCHRONIZERS")
|
|
47
|
+
for synchronizer in sorted(archive.synchronizers()):
|
|
48
|
+
print(" %s" % synchronizer)
|
|
40
49
|
except Exception:
|
|
41
50
|
print(' (could not open archive)')
|
|
42
51
|
|
|
@@ -45,7 +54,10 @@ def run(args):
|
|
|
45
54
|
|
|
46
55
|
def main():
|
|
47
56
|
parser = create_parser(description="Display generic archive information")
|
|
48
|
-
parser.add_argument("
|
|
57
|
+
parser.add_argument("-n", "--name-only", action="store_true",
|
|
58
|
+
help="only show archive name")
|
|
59
|
+
parser.add_argument("archive", nargs='*', metavar="ARCHIVE",
|
|
60
|
+
help="archive identifier (default: search and show all archives)")
|
|
49
61
|
return parse_args_and_run(parser, run)
|
|
50
62
|
|
|
51
63
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (C) 2014-2025 S[&]T, The Netherlands.
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
from __future__ import absolute_import, division, print_function
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
import muninn
|
|
11
|
+
|
|
12
|
+
from muninn.tools.utils import create_parser, parse_args_and_run
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def valid_date(date: str) -> datetime:
|
|
16
|
+
date_fmt = '%Y-%m-%d'
|
|
17
|
+
try:
|
|
18
|
+
return datetime.strptime(date, date_fmt)
|
|
19
|
+
except ValueError:
|
|
20
|
+
raise argparse.ArgumentTypeError(f"invalid date: {date} (expected '{date_fmt}')")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def run(args):
|
|
24
|
+
with muninn.open(args.archive) as archive:
|
|
25
|
+
archive.sync(args.synchronizer, args.product_types, args.start, args.end, args.force)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main():
|
|
29
|
+
parser = create_parser(description="Synchronize a muninn archive")
|
|
30
|
+
parser.add_argument("archive", metavar="ARCHIVE", help="archive identifier")
|
|
31
|
+
parser.add_argument("synchronizer", help="name of the synchronizer to use")
|
|
32
|
+
parser.add_argument("-p", "--product-types", nargs="*",
|
|
33
|
+
help="the product types to be synchronized")
|
|
34
|
+
parser.add_argument('--start', type=valid_date, help='start datetime of synchronization range')
|
|
35
|
+
parser.add_argument('--end', type=valid_date, help='end datetime of synchronization range')
|
|
36
|
+
parser.add_argument("-f", "--force", action="store_true", default=False,
|
|
37
|
+
help="force update, even if entries have not changed")
|
|
38
|
+
return parse_args_and_run(parser, run)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if __name__ == '__main__':
|
|
42
|
+
main()
|
|
@@ -13,6 +13,7 @@ muninn-retrieve = muninn.tools.retrieve:main
|
|
|
13
13
|
muninn-search = muninn.tools.search:main
|
|
14
14
|
muninn-strip = muninn.tools.strip:main
|
|
15
15
|
muninn-summary = muninn.tools.summary:main
|
|
16
|
+
muninn-sync = muninn.tools.sync:main
|
|
16
17
|
muninn-tag = muninn.tools.tag:main
|
|
17
18
|
muninn-untag = muninn.tools.untag:main
|
|
18
19
|
muninn-update = muninn.tools.update:main
|
|
@@ -15,7 +15,7 @@ requirements = []
|
|
|
15
15
|
|
|
16
16
|
setup(
|
|
17
17
|
name="muninn",
|
|
18
|
-
version="
|
|
18
|
+
version="7.0",
|
|
19
19
|
description="Data product catalogue and archive system",
|
|
20
20
|
url="https://github.com/stcorp/muninn",
|
|
21
21
|
author="S[&]T",
|
|
@@ -36,6 +36,7 @@ setup(
|
|
|
36
36
|
"muninn-search = muninn.tools.search:main",
|
|
37
37
|
"muninn-strip = muninn.tools.strip:main",
|
|
38
38
|
"muninn-summary = muninn.tools.summary:main",
|
|
39
|
+
"muninn-sync = muninn.tools.sync:main",
|
|
39
40
|
"muninn-tag = muninn.tools.tag:main",
|
|
40
41
|
"muninn-untag = muninn.tools.untag:main",
|
|
41
42
|
"muninn-update = muninn.tools.update:main",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|