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.
Files changed (71) hide show
  1. {muninn-6.4.2 → muninn-7.0}/CHANGES +14 -0
  2. {muninn-6.4.2 → muninn-7.0}/LICENSE +1 -1
  3. {muninn-6.4.2 → muninn-7.0}/PKG-INFO +1 -1
  4. {muninn-6.4.2 → muninn-7.0}/muninn/__init__.py +4 -4
  5. {muninn-6.4.2 → muninn-7.0}/muninn/archive.py +99 -29
  6. {muninn-6.4.2 → muninn-7.0}/muninn/config.py +2 -2
  7. {muninn-6.4.2 → muninn-7.0}/muninn/core.py +2 -3
  8. muninn-7.0/muninn/database/__init__.py +3 -0
  9. muninn-7.0/muninn/database/base.py +6 -0
  10. {muninn-6.4.2 → muninn-7.0}/muninn/database/blobgeometry.py +3 -3
  11. {muninn-6.4.2 → muninn-7.0}/muninn/database/ewkb.py +3 -3
  12. {muninn-6.4.2 → muninn-7.0}/muninn/database/postgresql.py +4 -3
  13. {muninn-6.4.2 → muninn-7.0}/muninn/database/sql.py +3 -3
  14. {muninn-6.4.2 → muninn-7.0}/muninn/database/sqlite.py +8 -8
  15. {muninn-6.4.2 → muninn-7.0}/muninn/enum.py +1 -1
  16. {muninn-6.4.2 → muninn-7.0}/muninn/exceptions.py +1 -1
  17. {muninn-6.4.2 → muninn-7.0}/muninn/extension.py +1 -1
  18. {muninn-6.4.2 → muninn-7.0}/muninn/function.py +1 -1
  19. {muninn-6.4.2 → muninn-7.0}/muninn/geometry.py +1 -1
  20. {muninn-6.4.2 → muninn-7.0}/muninn/language.py +3 -3
  21. {muninn-6.4.2 → muninn-7.0}/muninn/remote.py +8 -2
  22. {muninn-6.4.2 → muninn-7.0}/muninn/schema.py +1 -1
  23. {muninn-6.4.2 → muninn-7.0}/muninn/struct.py +1 -1
  24. muninn-7.0/muninn/tools/__init__.py +3 -0
  25. {muninn-6.4.2 → muninn-7.0}/muninn/tools/attach.py +1 -1
  26. {muninn-6.4.2 → muninn-7.0}/muninn/tools/destroy.py +1 -1
  27. {muninn-6.4.2 → muninn-7.0}/muninn/tools/export.py +1 -1
  28. {muninn-6.4.2 → muninn-7.0}/muninn/tools/hash.py +1 -1
  29. {muninn-6.4.2 → muninn-7.0}/muninn/tools/info.py +17 -5
  30. {muninn-6.4.2 → muninn-7.0}/muninn/tools/ingest.py +1 -1
  31. {muninn-6.4.2 → muninn-7.0}/muninn/tools/list_tags.py +1 -1
  32. {muninn-6.4.2 → muninn-7.0}/muninn/tools/prepare.py +1 -1
  33. {muninn-6.4.2 → muninn-7.0}/muninn/tools/pull.py +1 -1
  34. {muninn-6.4.2 → muninn-7.0}/muninn/tools/remove.py +1 -1
  35. {muninn-6.4.2 → muninn-7.0}/muninn/tools/retrieve.py +1 -1
  36. {muninn-6.4.2 → muninn-7.0}/muninn/tools/search.py +1 -1
  37. {muninn-6.4.2 → muninn-7.0}/muninn/tools/strip.py +1 -1
  38. {muninn-6.4.2 → muninn-7.0}/muninn/tools/summary.py +1 -1
  39. muninn-7.0/muninn/tools/sync.py +42 -0
  40. {muninn-6.4.2 → muninn-7.0}/muninn/tools/tag.py +1 -1
  41. {muninn-6.4.2 → muninn-7.0}/muninn/tools/untag.py +1 -1
  42. {muninn-6.4.2 → muninn-7.0}/muninn/tools/update.py +1 -1
  43. {muninn-6.4.2 → muninn-7.0}/muninn/tools/utils.py +1 -1
  44. {muninn-6.4.2 → muninn-7.0}/muninn/util.py +1 -1
  45. {muninn-6.4.2 → muninn-7.0}/muninn/visitor.py +1 -1
  46. {muninn-6.4.2 → muninn-7.0}/muninn.egg-info/PKG-INFO +1 -1
  47. {muninn-6.4.2 → muninn-7.0}/muninn.egg-info/SOURCES.txt +1 -0
  48. {muninn-6.4.2 → muninn-7.0}/muninn.egg-info/entry_points.txt +1 -0
  49. {muninn-6.4.2 → muninn-7.0}/setup.py +2 -1
  50. muninn-6.4.2/muninn/database/__init__.py +0 -3
  51. muninn-6.4.2/muninn/database/base.py +0 -6
  52. muninn-6.4.2/muninn/tools/__init__.py +0 -3
  53. {muninn-6.4.2 → muninn-7.0}/DEVELOPER.md +0 -0
  54. {muninn-6.4.2 → muninn-7.0}/MANIFEST.in +0 -0
  55. {muninn-6.4.2 → muninn-7.0}/README.md +0 -0
  56. {muninn-6.4.2 → muninn-7.0}/muninn/_compat.py +0 -0
  57. {muninn-6.4.2 → muninn-7.0}/muninn/storage/__init__.py +0 -0
  58. {muninn-6.4.2 → muninn-7.0}/muninn/storage/base.py +0 -0
  59. {muninn-6.4.2 → muninn-7.0}/muninn/storage/fs.py +0 -0
  60. {muninn-6.4.2 → muninn-7.0}/muninn/storage/s3.py +0 -0
  61. {muninn-6.4.2 → muninn-7.0}/muninn/storage/swift.py +0 -0
  62. {muninn-6.4.2 → muninn-7.0}/muninn.egg-info/dependency_links.txt +0 -0
  63. {muninn-6.4.2 → muninn-7.0}/muninn.egg-info/top_level.txt +0 -0
  64. {muninn-6.4.2 → muninn-7.0}/setup.cfg +0 -0
  65. {muninn-6.4.2 → muninn-7.0}/test/coverage.sh +0 -0
  66. {muninn-6.4.2 → muninn-7.0}/test/data/README +0 -0
  67. {muninn-6.4.2 → muninn-7.0}/test/data/multi/1.txt +0 -0
  68. {muninn-6.4.2 → muninn-7.0}/test/data/multi/2.txt +0 -0
  69. {muninn-6.4.2 → muninn-7.0}/test/data/pi.txt +0 -0
  70. {muninn-6.4.2 → muninn-7.0}/test/product_type.py.template +0 -0
  71. {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,4 +1,4 @@
1
- Copyright (C) 2014-2024 S[&]T, The Netherlands.
1
+ Copyright (C) 2014-2025 S[&]T, The Netherlands.
2
2
  All rights reserved.
3
3
 
4
4
  Redistribution and use in source and binary forms, with or without
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.2
2
2
  Name: muninn
3
- Version: 6.4.2
3
+ Version: 7.0
4
4
  Summary: Data product catalogue and archive system
5
5
  Home-page: https://github.com/stcorp/muninn
6
6
  Author: S[&]T
@@ -1,11 +1,11 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
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__ = "6.4.2"
8
- __copyright__ = "Copyright (C) 2014-2024 S[&]T, The Netherlands."
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-2024 S[&]T, The Netherlands.
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): # TODO generator?
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) # TODO verify hash?
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) # TODO determine after?
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
- properties.core.hash = util.product_hash(paths, hash_type=hash_type)
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
- properties.core.archive_date = self._database.server_time_utc()
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
- properties.core.remote_url = 'file://' + os.path.realpath(paths[0])
1209
+ updated_properties.core.remote_url = 'file://' + os.path.realpath(paths[0])
1152
1210
  else:
1153
- properties.core.remote_url = 'file://' + os.path.realpath(os.path.dirname(paths[0])) # TODO test
1154
- properties.core.remote_url = properties.core.remote_url.replace('\\', '/')
1155
- self.update_properties(Struct({'core': {'remote_url': properties.core.remote_url}}),
1156
- properties.core.uuid)
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
- # Update archive date and activate product
1168
- properties.core.active = True
1169
- metadata = {
1170
- 'active': properties.core.active,
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 # TODO different type of return value when single product passed?
1843
+ return failed_products
@@ -1,12 +1,12 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
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-2024 S[&]T, The Netherlands.
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):
@@ -0,0 +1,3 @@
1
+ #
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
+ #
@@ -0,0 +1,6 @@
1
+ #
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
+ #
4
+
5
+ class DatabaseBackend(object):
6
+ pass
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
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-2024 S[&]T, The Netherlands.
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-2024 S[&]T, The Netherlands.
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-2024 S[&]T, The Netherlands.
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-2024 S[&]T, The Netherlands.
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(Text)
43
- table_prefix = optional(Text)
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(object):
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-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
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-2024 S[&]T, The Netherlands.
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-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -0,0 +1,3 @@
1
+ #
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
+ #
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
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", end='')
16
- for archive_name in archive_names:
17
- print("\n " + archive_name)
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("archive", nargs='*', metavar="ARCHIVE", help="archive identifier (default: search archives)")
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
 
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -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()
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
2
+ # Copyright (C) 2014-2025 S[&]T, The Netherlands.
3
3
  #
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.2
2
2
  Name: muninn
3
- Version: 6.4.2
3
+ Version: 7.0
4
4
  Summary: Data product catalogue and archive system
5
5
  Home-page: https://github.com/stcorp/muninn
6
6
  Author: S[&]T
@@ -52,6 +52,7 @@ muninn/tools/retrieve.py
52
52
  muninn/tools/search.py
53
53
  muninn/tools/strip.py
54
54
  muninn/tools/summary.py
55
+ muninn/tools/sync.py
55
56
  muninn/tools/tag.py
56
57
  muninn/tools/untag.py
57
58
  muninn/tools/update.py
@@ -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="6.4.2",
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",
@@ -1,3 +0,0 @@
1
- #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
3
- #
@@ -1,6 +0,0 @@
1
- #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
3
- #
4
-
5
- class DatabaseBackend(object):
6
- pass
@@ -1,3 +0,0 @@
1
- #
2
- # Copyright (C) 2014-2024 S[&]T, The Netherlands.
3
- #
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