muninn 6.4__tar.gz → 6.4.2__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 (67) hide show
  1. {muninn-6.4 → muninn-6.4.2}/CHANGES +11 -0
  2. {muninn-6.4 → muninn-6.4.2}/PKG-INFO +3 -1
  3. {muninn-6.4 → muninn-6.4.2}/muninn/__init__.py +1 -1
  4. {muninn-6.4 → muninn-6.4.2}/muninn/_compat.py +9 -2
  5. {muninn-6.4 → muninn-6.4.2}/muninn/archive.py +1 -1
  6. {muninn-6.4 → muninn-6.4.2}/muninn/database/postgresql.py +6 -4
  7. {muninn-6.4 → muninn-6.4.2}/muninn/database/sqlite.py +30 -6
  8. {muninn-6.4 → muninn-6.4.2}/muninn/language.py +3 -3
  9. {muninn-6.4 → muninn-6.4.2}/muninn/schema.py +5 -2
  10. {muninn-6.4 → muninn-6.4.2}/muninn/tools/ingest.py +1 -1
  11. {muninn-6.4 → muninn-6.4.2}/muninn/tools/update.py +2 -5
  12. {muninn-6.4 → muninn-6.4.2}/muninn.egg-info/PKG-INFO +3 -1
  13. {muninn-6.4 → muninn-6.4.2}/setup.py +3 -1
  14. {muninn-6.4 → muninn-6.4.2}/test/test.py +12 -1
  15. {muninn-6.4 → muninn-6.4.2}/DEVELOPER.md +0 -0
  16. {muninn-6.4 → muninn-6.4.2}/LICENSE +0 -0
  17. {muninn-6.4 → muninn-6.4.2}/MANIFEST.in +0 -0
  18. {muninn-6.4 → muninn-6.4.2}/README.md +0 -0
  19. {muninn-6.4 → muninn-6.4.2}/muninn/config.py +0 -0
  20. {muninn-6.4 → muninn-6.4.2}/muninn/core.py +0 -0
  21. {muninn-6.4 → muninn-6.4.2}/muninn/database/__init__.py +0 -0
  22. {muninn-6.4 → muninn-6.4.2}/muninn/database/base.py +0 -0
  23. {muninn-6.4 → muninn-6.4.2}/muninn/database/blobgeometry.py +0 -0
  24. {muninn-6.4 → muninn-6.4.2}/muninn/database/ewkb.py +0 -0
  25. {muninn-6.4 → muninn-6.4.2}/muninn/database/sql.py +0 -0
  26. {muninn-6.4 → muninn-6.4.2}/muninn/enum.py +0 -0
  27. {muninn-6.4 → muninn-6.4.2}/muninn/exceptions.py +0 -0
  28. {muninn-6.4 → muninn-6.4.2}/muninn/extension.py +0 -0
  29. {muninn-6.4 → muninn-6.4.2}/muninn/function.py +0 -0
  30. {muninn-6.4 → muninn-6.4.2}/muninn/geometry.py +0 -0
  31. {muninn-6.4 → muninn-6.4.2}/muninn/remote.py +0 -0
  32. {muninn-6.4 → muninn-6.4.2}/muninn/storage/__init__.py +0 -0
  33. {muninn-6.4 → muninn-6.4.2}/muninn/storage/base.py +0 -0
  34. {muninn-6.4 → muninn-6.4.2}/muninn/storage/fs.py +0 -0
  35. {muninn-6.4 → muninn-6.4.2}/muninn/storage/s3.py +0 -0
  36. {muninn-6.4 → muninn-6.4.2}/muninn/storage/swift.py +0 -0
  37. {muninn-6.4 → muninn-6.4.2}/muninn/struct.py +0 -0
  38. {muninn-6.4 → muninn-6.4.2}/muninn/tools/__init__.py +0 -0
  39. {muninn-6.4 → muninn-6.4.2}/muninn/tools/attach.py +0 -0
  40. {muninn-6.4 → muninn-6.4.2}/muninn/tools/destroy.py +0 -0
  41. {muninn-6.4 → muninn-6.4.2}/muninn/tools/export.py +0 -0
  42. {muninn-6.4 → muninn-6.4.2}/muninn/tools/hash.py +0 -0
  43. {muninn-6.4 → muninn-6.4.2}/muninn/tools/info.py +0 -0
  44. {muninn-6.4 → muninn-6.4.2}/muninn/tools/list_tags.py +0 -0
  45. {muninn-6.4 → muninn-6.4.2}/muninn/tools/prepare.py +0 -0
  46. {muninn-6.4 → muninn-6.4.2}/muninn/tools/pull.py +0 -0
  47. {muninn-6.4 → muninn-6.4.2}/muninn/tools/remove.py +0 -0
  48. {muninn-6.4 → muninn-6.4.2}/muninn/tools/retrieve.py +0 -0
  49. {muninn-6.4 → muninn-6.4.2}/muninn/tools/search.py +0 -0
  50. {muninn-6.4 → muninn-6.4.2}/muninn/tools/strip.py +0 -0
  51. {muninn-6.4 → muninn-6.4.2}/muninn/tools/summary.py +0 -0
  52. {muninn-6.4 → muninn-6.4.2}/muninn/tools/tag.py +0 -0
  53. {muninn-6.4 → muninn-6.4.2}/muninn/tools/untag.py +0 -0
  54. {muninn-6.4 → muninn-6.4.2}/muninn/tools/utils.py +0 -0
  55. {muninn-6.4 → muninn-6.4.2}/muninn/util.py +0 -0
  56. {muninn-6.4 → muninn-6.4.2}/muninn/visitor.py +0 -0
  57. {muninn-6.4 → muninn-6.4.2}/muninn.egg-info/SOURCES.txt +0 -0
  58. {muninn-6.4 → muninn-6.4.2}/muninn.egg-info/dependency_links.txt +0 -0
  59. {muninn-6.4 → muninn-6.4.2}/muninn.egg-info/entry_points.txt +0 -0
  60. {muninn-6.4 → muninn-6.4.2}/muninn.egg-info/top_level.txt +0 -0
  61. {muninn-6.4 → muninn-6.4.2}/setup.cfg +0 -0
  62. {muninn-6.4 → muninn-6.4.2}/test/coverage.sh +0 -0
  63. {muninn-6.4 → muninn-6.4.2}/test/data/README +0 -0
  64. {muninn-6.4 → muninn-6.4.2}/test/data/multi/1.txt +0 -0
  65. {muninn-6.4 → muninn-6.4.2}/test/data/multi/2.txt +0 -0
  66. {muninn-6.4 → muninn-6.4.2}/test/data/pi.txt +0 -0
  67. {muninn-6.4 → muninn-6.4.2}/test/product_type.py.template +0 -0
@@ -1,3 +1,14 @@
1
+ 6.4.2 2025-02-24
2
+ ~~~~~~~~~~~~~~~~
3
+
4
+ * Fixed issue with loading spatialite for versions of sqlite that were
5
+ build to disallow DQS (double-quoted string literals).
6
+
7
+ 6.4.1 2024-08-14
8
+ ~~~~~~~~~~~~~~~~
9
+
10
+ * Fixed issue where only 'dict' values could be stored as a JSON property.
11
+
1
12
  6.4 2024-03-14
2
13
  ~~~~~~~~~~~~~~
3
14
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.2
2
2
  Name: muninn
3
- Version: 6.4
3
+ Version: 6.4.2
4
4
  Summary: Data product catalogue and archive system
5
5
  Home-page: https://github.com/stcorp/muninn
6
6
  Author: S[&]T
@@ -17,6 +17,8 @@ Classifier: Programming Language :: Python :: 3.8
17
17
  Classifier: Programming Language :: Python :: 3.9
18
18
  Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
20
22
  Classifier: License :: OSI Approved :: BSD License
21
23
  Classifier: Operating System :: OS Independent
22
24
  Classifier: Topic :: Scientific/Engineering
@@ -4,7 +4,7 @@
4
4
 
5
5
  from __future__ import absolute_import, division, print_function
6
6
 
7
- __version__ = "6.4"
7
+ __version__ = "6.4.2"
8
8
  __copyright__ = "Copyright (C) 2014-2024 S[&]T, The Netherlands."
9
9
 
10
10
  __all__ = ["Error", "InternalError", "Struct", "Archive", "open", "config_path"]
@@ -5,6 +5,7 @@ Inspired by six, future, and jinja2
5
5
  '''
6
6
  import sys
7
7
  import operator
8
+ import datetime
8
9
 
9
10
 
10
11
  PY3 = sys.version_info[0] == 3
@@ -33,6 +34,9 @@ if PY3:
33
34
  def decode(s):
34
35
  return s.decode('utf-8')
35
36
 
37
+ def utcnow():
38
+ return datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
39
+
36
40
  itervalues = operator.methodcaller("values")
37
41
 
38
42
  imap = map
@@ -55,12 +59,15 @@ else:
55
59
  def path_utf8(path):
56
60
  return path.decode(sys.getfilesystemencoding()).encode('utf-8')
57
61
 
58
- def decode(s):
62
+ def encode(s):
59
63
  return s
60
64
 
61
- def encode(s):
65
+ def decode(s):
62
66
  return s
63
67
 
68
+ def utcnow():
69
+ return datetime.datetime.utcnow()
70
+
64
71
  dictkeys = operator.methodcaller("keys")
65
72
  dictvalues = operator.methodcaller("values")
66
73
  itervalues = operator.methodcaller("itervalues")
@@ -413,7 +413,7 @@ class Archive(object):
413
413
  if len(set(basenames)) < len(basenames):
414
414
  raise Error("basename of each part should be unique for multi-part products")
415
415
 
416
- return [path.replace('\\', '/') for path in paths]
416
+ return sorted([path.replace('\\', '/') for path in paths])
417
417
 
418
418
  def _extract_hash_type(self, hash_value):
419
419
  prefix, middle, _ = hash_value.partition(':')
@@ -481,8 +481,9 @@ class PostgresqlBackend(DatabaseBackend):
481
481
  fields.append("uuid")
482
482
  parameters.append(uuid)
483
483
 
484
- # TODO generic conversion? more overlap between sqlite.py and postgresql.py?
485
- parameters = [json.dumps(p) if isinstance(p, dict) else p for p in parameters]
484
+ schema = self._namespace_schema(name)
485
+ parameters = [json.dumps(p) if f != "uuid" and issubclass(schema[f], JSON) else p for f, p in
486
+ zip(fields, parameters)]
486
487
 
487
488
  # Build and execute INSERT query.
488
489
  query = "INSERT INTO %s (%s) VALUES (%s)" % (self._table_name(name), ", ".join(fields),
@@ -775,11 +776,12 @@ class PostgresqlBackend(DatabaseBackend):
775
776
  del fields[uuid_index]
776
777
  del parameters[uuid_index]
777
778
 
779
+ schema = self._namespace_schema(name)
780
+ parameters = [json.dumps(p) if issubclass(schema[f], JSON) else p for f, p in zip(fields, parameters)]
781
+
778
782
  # Append the uuid (value) at the end of the list of parameters (will be used in the WHERE clause).
779
783
  parameters.append(uuid)
780
784
 
781
- parameters = [json.dumps(p) if isinstance(p, dict) else p for p in parameters]
782
-
783
785
  # Build and execute UPDATE query.
784
786
  set_clause = ", ".join(["%s = %s" % (field, self._placeholder()) for field in fields])
785
787
  query = "UPDATE %s SET %s WHERE uuid = %s" % (self._table_name(name), set_clause, self._placeholder())
@@ -13,7 +13,7 @@ import uuid
13
13
 
14
14
  from .base import DatabaseBackend
15
15
 
16
- from muninn._compat import dictkeys, dictvalues
16
+ from muninn._compat import decode, dictkeys, dictvalues, utcnow
17
17
 
18
18
  # Select a version of dbapi2 that's available.
19
19
  # Since we need the spatialite module, we need a version of sqlite3 that supports extensions.
@@ -96,6 +96,24 @@ def _cast_geometry(blob):
96
96
  return blobgeometry.decode_blob_geometry(blob)
97
97
 
98
98
 
99
+ def _adapt_datetime(val):
100
+ return val.isoformat(" ")
101
+
102
+
103
+ def _cast_datetime(val):
104
+ datepart, timepart = decode(val).split(" ")
105
+ year, month, day = map(int, datepart.split("-"))
106
+ timepart_full = timepart.split(".")
107
+ hours, minutes, seconds = map(int, timepart_full[0].split(":"))
108
+ if len(timepart_full) == 2:
109
+ microseconds = int('{:0<6.6}'.format(timepart_full[1]))
110
+ else:
111
+ microseconds = 0
112
+
113
+ val = datetime.datetime(year, month, day, hours, minutes, seconds, microseconds)
114
+ return val
115
+
116
+
99
117
  class SQLiteConnection(object):
100
118
  """Wrapper for a sqlite database connection that defers (re)connection until an attempt is made to use the
101
119
  connection.
@@ -150,7 +168,7 @@ class SQLiteConnection(object):
150
168
  # load the spatialite extension
151
169
  try:
152
170
  self._connection.enable_load_extension(True)
153
- self._connection.execute("select load_extension(\"%s\");" % (self._mod_spatialite))
171
+ self._connection.execute("select load_extension('%s');" % (self._mod_spatialite))
154
172
  except Exception as e:
155
173
  raise Error("loading mod_spatialite extension failed (mod_spatialite_path='%s'): %s" %
156
174
  (self._mod_spatialite, str(e)))
@@ -222,6 +240,9 @@ class SQLiteBackend(object):
222
240
  dbapi2.register_adapter(geometry.MultiLineString, _adapt_geometry)
223
241
  dbapi2.register_adapter(geometry.MultiPolygon, _adapt_geometry)
224
242
 
243
+ dbapi2.register_converter("TIMESTAMP", _cast_datetime)
244
+ dbapi2.register_adapter(datetime.datetime, _adapt_datetime)
245
+
225
246
  self._connection_string = connection_string
226
247
  self._connection = SQLiteConnection(connection_string, mod_spatialite_path, self)
227
248
 
@@ -438,7 +459,9 @@ class SQLiteBackend(object):
438
459
  fields.append("uuid")
439
460
  parameters.append(uuid)
440
461
 
441
- parameters = [json.dumps(p) if isinstance(p, dict) else p for p in parameters]
462
+ schema = self._namespace_schema(name)
463
+ parameters = [json.dumps(p) if f != "uuid" and issubclass(schema[f], JSON) else p for f, p in
464
+ zip(fields, parameters)]
442
465
 
443
466
  # Build and execute INSERT query.
444
467
  query = "INSERT INTO %s (%s) VALUES (%s)" % (self._table_name(name), ", ".join(fields),
@@ -733,11 +756,12 @@ class SQLiteBackend(object):
733
756
  del fields[uuid_index]
734
757
  del parameters[uuid_index]
735
758
 
759
+ schema = self._namespace_schema(name)
760
+ parameters = [json.dumps(p) if issubclass(schema[f], JSON) else p for f, p in zip(fields, parameters)]
761
+
736
762
  # Append the uuid (value) at the end of the list of parameters (will be used in the WHERE clause).
737
763
  parameters.append(uuid)
738
764
 
739
- parameters = [json.dumps(p) if isinstance(p, dict) else p for p in parameters]
740
-
741
765
  # Build and execute UPDATE query.
742
766
  set_clause = ", ".join(["%s = %s" % (field, self._placeholder()) for field in fields])
743
767
  query = "UPDATE %s SET %s WHERE uuid = %s" % (self._table_name(name), set_clause, self._placeholder())
@@ -875,7 +899,7 @@ class SQLiteBackend(object):
875
899
 
876
900
  @translate_sqlite_errors
877
901
  def server_time_utc(self):
878
- return datetime.datetime.utcnow()
902
+ return utcnow()
879
903
 
880
904
  @translate_sqlite_errors
881
905
  def source_products(self, uuid):
@@ -613,7 +613,7 @@ def _literal_type(literal):
613
613
  else:
614
614
  return type
615
615
 
616
- raise Error("unable to determine type of literal value: %r" % literal)
616
+ raise Error("unable to determine type of literal value: %r" % (literal,))
617
617
 
618
618
 
619
619
  class SemanticAnalysis(Visitor):
@@ -788,7 +788,7 @@ class Identifier(object):
788
788
  self.muninn_type = Long
789
789
 
790
790
  elif not re.match(r'[\w]+\.[\w.]+', canonical_identifier):
791
- raise Error("cannot resolve identifier: %r" % canonical_identifier)
791
+ raise Error("cannot resolve identifier: %r" % (canonical_identifier,))
792
792
 
793
793
  else:
794
794
  segments = canonical_identifier.split('.')
@@ -808,7 +808,7 @@ class Identifier(object):
808
808
  self.namespace, self.identifier, self.subscript = segments
809
809
 
810
810
  else:
811
- raise Error("cannot resolve identifier: %r" % canonical_identifier)
811
+ raise Error("cannot resolve identifier: %r" % (canonical_identifier,))
812
812
 
813
813
  # check if namespace is valid
814
814
  if self.namespace not in namespace_schemas:
@@ -5,9 +5,10 @@
5
5
  from __future__ import absolute_import, division, print_function
6
6
 
7
7
  import datetime
8
+ import json
9
+ import uuid
8
10
  from muninn._compat import long
9
11
  from muninn._compat import string_types as basestring
10
- import uuid
11
12
 
12
13
  from muninn import geometry
13
14
 
@@ -96,7 +97,9 @@ class Timestamp(Type):
96
97
  class JSON(Type):
97
98
  @classmethod
98
99
  def validate(cls, value):
99
- if type(value) is not dict:
100
+ try:
101
+ json.dumps(value)
102
+ except Exception:
100
103
  raise ValueError("invalid value %r for type %r" % (value, cls.name()))
101
104
 
102
105
 
@@ -31,7 +31,7 @@ class CheckProductListAction(argparse.Action):
31
31
 
32
32
  def expand_stem(stem):
33
33
  try:
34
- return glob.glob(stem + "*")
34
+ return sorted(glob.glob(stem + "*"))
35
35
  except EnvironmentError as error:
36
36
  raise Error("unable to expand stem \"%s\" [%s]" % (stem, error))
37
37
 
@@ -75,11 +75,8 @@ def update(args):
75
75
 
76
76
  processor = UpdateProcessor(args)
77
77
  with muninn.open(args.archive) as archive:
78
- if args.action in ['ingest', 'pull']:
79
- # we only need the uuid and the product_name
80
- products = archive.search(expression, property_names=['uuid', 'product_name'])
81
- else:
82
- products = archive.search(expression, namespaces=archive.namespaces())
78
+ # we only need the uuid and the product_name
79
+ products = archive.search(expression, property_names=['uuid', 'product_name'])
83
80
  if args.parallel:
84
81
  if args.processes is not None:
85
82
  pool = multiprocessing.Pool(args.processes)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.2
2
2
  Name: muninn
3
- Version: 6.4
3
+ Version: 6.4.2
4
4
  Summary: Data product catalogue and archive system
5
5
  Home-page: https://github.com/stcorp/muninn
6
6
  Author: S[&]T
@@ -17,6 +17,8 @@ Classifier: Programming Language :: Python :: 3.8
17
17
  Classifier: Programming Language :: Python :: 3.9
18
18
  Classifier: Programming Language :: Python :: 3.10
19
19
  Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
20
22
  Classifier: License :: OSI Approved :: BSD License
21
23
  Classifier: Operating System :: OS Independent
22
24
  Classifier: Topic :: Scientific/Engineering
@@ -15,7 +15,7 @@ requirements = []
15
15
 
16
16
  setup(
17
17
  name="muninn",
18
- version="6.4",
18
+ version="6.4.2",
19
19
  description="Data product catalogue and archive system",
20
20
  url="https://github.com/stcorp/muninn",
21
21
  author="S[&]T",
@@ -51,6 +51,8 @@ setup(
51
51
  "Programming Language :: Python :: 3.9",
52
52
  "Programming Language :: Python :: 3.10",
53
53
  "Programming Language :: Python :: 3.11",
54
+ "Programming Language :: Python :: 3.12",
55
+ "Programming Language :: Python :: 3.13",
54
56
  "License :: OSI Approved :: BSD License",
55
57
  "Operating System :: OS Independent",
56
58
  "Topic :: Scientific/Engineering",
@@ -26,7 +26,11 @@ import botocore
26
26
  logging.getLogger("boto3").setLevel(logging.CRITICAL)
27
27
 
28
28
  warnings.filterwarnings('ignore', 'distutils version classes', DeprecationWarning, 'swiftclient.client')
29
- import swiftclient
29
+ try:
30
+ import swiftclient
31
+ except ImportError:
32
+ # allow to run tests where the swift backend is not enabled
33
+ pass
30
34
  logging.getLogger("swiftclient").setLevel(logging.CRITICAL)
31
35
 
32
36
  sys.path.insert(0, '..')
@@ -1233,6 +1237,13 @@ class TestArchive:
1233
1237
  assert myjson['bla'] == 14
1234
1238
  assert myjson['mylist'] == [1, 2, 3]
1235
1239
 
1240
+ # also test that we can store other json objects than 'dict'
1241
+ props = muninn.Struct({'mynamespace': {'myjson': [1, 2, 3]}})
1242
+ archive.update_properties(props, product.core.uuid)
1243
+ props = archive.retrieve_properties(uuid=product.core.uuid, namespaces=['mynamespace'])
1244
+ myjson = props.mynamespace.myjson
1245
+ assert myjson == [1, 2, 3]
1246
+
1236
1247
  def test_verify_hash(self, archive):
1237
1248
  product = self._ingest_file(archive)
1238
1249
 
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
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
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