h5netcdf 1.6.0__py3-none-any.whl → 1.6.2__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 h5netcdf might be problematic. Click here for more details.

h5netcdf/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '1.6.0'
21
- __version_tuple__ = version_tuple = (1, 6, 0)
20
+ __version__ = version = '1.6.2'
21
+ __version_tuple__ = version_tuple = (1, 6, 2)
h5netcdf/core.py CHANGED
@@ -608,6 +608,11 @@ class BaseVariable(BaseObject):
608
608
  class Variable(BaseVariable):
609
609
  @property
610
610
  def chunks(self):
611
+ if self.shape == ():
612
+ # In HSDS, the layout can be chunked even for scalar datasets, but with only a single chunk.
613
+ # Return None for scalar datasets since they shall be handled as non-chunked.
614
+ assert self._h5ds.chunks in (None, (), (1,))
615
+ return None
611
616
  return self._h5ds.chunks
612
617
 
613
618
  @property
@@ -1514,16 +1519,35 @@ class File(Group):
1514
1519
  "No module named 'h5pyd'. h5pyd is required for "
1515
1520
  f"opening urls: {path}"
1516
1521
  )
1522
+ self._preexisting_file = mode in {"r", "r+", "a"}
1523
+ # remap "a" -> "r+" to check file existence
1524
+ # fallback to "w" if not
1525
+ _mode = mode
1526
+ if mode == "a":
1527
+ mode = "r+"
1528
+ self._h5py = h5pyd
1517
1529
  try:
1518
- with h5pyd.File(path, "r", **kwargs) as f: # noqa
1519
- pass
1520
- self._preexisting_file = True
1530
+ self._h5file = self._h5py.File(
1531
+ path, mode, track_order=track_order, **kwargs
1532
+ )
1533
+ self._preexisting_file = mode != "w"
1521
1534
  except OSError:
1522
- self._preexisting_file = False
1523
- self._h5py = h5pyd
1524
- self._h5file = self._h5py.File(
1525
- path, mode, track_order=track_order, **kwargs
1526
- )
1535
+ # if file does not exist, create it
1536
+ if _mode == "a":
1537
+ mode = "w"
1538
+ self._h5file = self._h5py.File(
1539
+ path, mode, track_order=track_order, **kwargs
1540
+ )
1541
+ self._preexisting_file = False
1542
+ msg = (
1543
+ "Append mode for h5pyd now probes with 'r+' first and "
1544
+ "only falls back to 'w' if the file is missing.\n"
1545
+ "To silence this warning use 'r+' (open-existing) or 'w' "
1546
+ "(create-new) directly."
1547
+ )
1548
+ warnings.warn(msg, UserWarning, stacklevel=2)
1549
+ else:
1550
+ raise
1527
1551
  else:
1528
1552
  self._preexisting_file = os.path.exists(path) and mode != "w"
1529
1553
  self._h5py = h5py
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  import tempfile
3
+ import time
3
4
  from pathlib import Path
4
5
  from shutil import rmtree
5
6
 
@@ -16,50 +17,67 @@ except ImportError:
16
17
 
17
18
  @pytest.fixture(scope="session")
18
19
  def hsds_up():
19
- """Provide HDF Highly Scalabale Data Service (HSDS) for h5pyd testing."""
20
- if with_reqd_pkgs:
21
- root_dir = Path(tempfile.mkdtemp(prefix="tmp-hsds-root-"))
22
- bucket_name = "pytest"
23
- os.environ["BUCKET_NAME"] = bucket_name
24
- os.mkdir(
25
- f"{root_dir}/{bucket_name}"
26
- ) # need to create a directory for our bucket
20
+ """Provide HDF Highly Scalable Data Service (HSDS) for h5pyd testing."""
21
+ if not with_reqd_pkgs:
22
+ pytest.skip("Required packages h5pyd and hsds not available")
27
23
 
28
- hs_username = "h5netcdf-pytest"
29
- hs_password = "TestEarlyTestEverything"
24
+ root_dir = Path(tempfile.mkdtemp(prefix="tmp-hsds-root-"))
25
+ bucket_name = "pytest"
26
+ os.environ["BUCKET_NAME"] = bucket_name
27
+ # need to create a directory for our bucket
28
+ (root_dir / bucket_name).mkdir()
30
29
 
31
- kwargs = {}
32
- kwargs["username"] = hs_username
33
- kwargs["password"] = hs_password
34
- kwargs["root_dir"] = str(root_dir)
35
- kwargs["logfile"] = f"{root_dir}/hsds.log"
36
- kwargs["log_level"] = "DEBUG"
37
- kwargs["host"] = "localhost"
38
- kwargs["sn_port"] = 5101
30
+ kwargs = {
31
+ "username": "h5netcdf-pytest",
32
+ "password": "TestEarlyTestEverything",
33
+ "root_dir": str(root_dir),
34
+ "logfile": str(root_dir / "hsds.log"),
35
+ "log_level": "DEBUG",
36
+ "host": "localhost",
37
+ "sn_port": 5101,
38
+ }
39
39
 
40
- try:
41
- hsds = HsdsApp(**kwargs)
40
+ os.environ.update(
41
+ {
42
+ "BUCKET_NAME": bucket_name,
43
+ "HS_USERNAME": kwargs["username"],
44
+ "HS_PASSWORD": kwargs["password"],
45
+ "HS_USE_HTTPS": "False",
46
+ }
47
+ )
42
48
 
43
- hsds.run()
44
- is_up = hsds.ready
49
+ hsds = HsdsApp(**kwargs)
45
50
 
46
- if is_up:
47
- os.environ["HS_ENDPOINT"] = hsds.endpoint
48
- os.environ["HS_USERNAME"] = hs_username
49
- os.environ["HS_PASSWORD"] = hs_password
50
- # make folders expected by pytest
51
- # pytest/home/h5netcdf-pytest
52
- # Folder("/pytest/", mode='w')
53
- Folder("/home/", mode="w")
54
- Folder("/home/h5netcdf-pytest/", mode="w")
55
- except Exception:
56
- is_up = False
51
+ try:
52
+ hsds.run()
53
+ timeout = time.time() + 60
54
+ while not hsds.ready:
55
+ if time.time() > timeout:
56
+ raise TimeoutError("HSDS server did not become ready in time")
57
+ time.sleep(1)
58
+
59
+ os.environ["HS_ENDPOINT"] = hsds.endpoint
60
+ # make folders expected by pytest
61
+ Folder("/home/", mode="w")
62
+ Folder("/home/h5netcdf-pytest/", mode="w")
57
63
 
58
- yield is_up
59
- hsds.check_processes() # this will capture hsds log output
60
- hsds.stop()
64
+ yield True
61
65
 
62
- rmtree(root_dir, ignore_errors=True)
66
+ except Exception as err:
67
+ log_path = kwargs["logfile"]
68
+ if os.path.exists(log_path):
69
+ with open(log_path) as f:
70
+ print("\n=== HSDS Log ===")
71
+ print(f.read())
72
+ else:
73
+ print(f"HSDS log not found at: {log_path}")
74
+ raise err
75
+
76
+ finally:
77
+ try:
78
+ hsds.check_processes()
79
+ hsds.stop()
80
+ except Exception:
81
+ pass
63
82
 
64
- else:
65
- yield False
83
+ rmtree(root_dir, ignore_errors=True)
@@ -3,7 +3,9 @@ import io
3
3
  import random
4
4
  import re
5
5
  import string
6
+ import sys
6
7
  import tempfile
8
+ import weakref
7
9
  from os import environ as env
8
10
 
9
11
  import h5py
@@ -30,6 +32,7 @@ except ImportError:
30
32
 
31
33
 
32
34
  remote_h5 = ("http:", "hdf5:")
35
+ python_version = version.parse(".".join(map(str, sys.version_info[:3])))
33
36
 
34
37
 
35
38
  @pytest.fixture
@@ -37,26 +40,30 @@ def tmp_local_netcdf(tmpdir):
37
40
  return str(tmpdir.join("testfile.nc"))
38
41
 
39
42
 
43
+ @pytest.fixture()
44
+ def setup_h5pyd_config(hsds_up):
45
+ env["HS_ENDPOINT"] = "http://127.0.0.1:5101"
46
+ env["HS_USERNAME"] = "h5netcdf-pytest"
47
+ env["HS_PASSWORD"] = "TestEarlyTestEverything"
48
+ env["HS_USE_HTTPS"] = "False"
49
+
50
+
40
51
  @pytest.fixture(params=["testfile.nc", "hdf5://testfile"])
41
- def tmp_local_or_remote_netcdf(request, tmpdir, hsds_up):
42
- if request.param.startswith(remote_h5):
43
- if without_h5pyd:
44
- pytest.skip("h5pyd package not available")
45
- elif not hsds_up:
46
- pytest.skip("HSDS service not running")
47
- rnd = "".join(random.choice(string.ascii_uppercase) for _ in range(5))
48
- return (
49
- "hdf5://"
50
- + "home"
51
- + "/"
52
- + env["HS_USERNAME"]
53
- + "/"
54
- + "testfile"
55
- + rnd
56
- + ".nc"
57
- )
52
+ def tmp_local_or_remote_netcdf(request, tmpdir):
53
+ param = request.param
54
+ if param.startswith(remote_h5):
55
+ try:
56
+ hsds_up = request.getfixturevalue("hsds_up")
57
+ except pytest.skip.Exception:
58
+ pytest.skip("HSDS not available")
59
+
60
+ if not hsds_up:
61
+ pytest.skip("HSDS fixture returned False (not running)")
62
+
63
+ rnd = "".join(random.choices(string.ascii_uppercase, k=5))
64
+ return f"hdf5://home/{env['HS_USERNAME']}/testfile{rnd}.nc"
58
65
  else:
59
- return str(tmpdir.join(request.param))
66
+ return str(tmpdir.join(param))
60
67
 
61
68
 
62
69
  @pytest.fixture(params=[True, False])
@@ -1300,6 +1307,24 @@ def test_overwrite_existing_file(tmp_local_netcdf):
1300
1307
  assert ds.attrs._h5attrs.get("_NCProperties", False)
1301
1308
 
1302
1309
 
1310
+ def test_overwrite_existing_remote_file(tmp_local_or_remote_netcdf):
1311
+ # create file with legacyapi
1312
+ with legacyapi.Dataset(tmp_local_or_remote_netcdf, "w") as ds:
1313
+ ds.createDimension("x", 10)
1314
+
1315
+ # check attribute
1316
+ with h5netcdf.File(tmp_local_or_remote_netcdf, "r") as ds:
1317
+ assert ds.attrs._h5attrs.get("_NCProperties", False)
1318
+
1319
+ # overwrite file with new api
1320
+ with h5netcdf.File(tmp_local_or_remote_netcdf, "w") as ds:
1321
+ ds.dimensions["x"] = 10
1322
+
1323
+ # check attribute
1324
+ with h5netcdf.File(tmp_local_or_remote_netcdf, "r") as ds:
1325
+ assert ds.attrs._h5attrs.get("_NCProperties", False)
1326
+
1327
+
1303
1328
  def test_scales_on_append(tmp_local_netcdf):
1304
1329
  # create file with _NCProperties attribute
1305
1330
  with netCDF4.Dataset(tmp_local_netcdf, "w") as ds:
@@ -1555,7 +1580,38 @@ def test_no_circular_references(tmp_local_or_remote_netcdf):
1555
1580
  refs = gc.get_referrers(ds)
1556
1581
  for ref in refs:
1557
1582
  print(ref)
1558
- assert len(refs) == 1
1583
+ if python_version >= version.parse("3.14"):
1584
+ assert len(refs) == 0
1585
+ else:
1586
+ assert len(refs) == 1
1587
+
1588
+
1589
+ def test_no_circular_references_py314(tmp_local_or_remote_netcdf):
1590
+ # https://github.com/h5py/h5py/issues/2019
1591
+ with h5netcdf.File(tmp_local_or_remote_netcdf, "w") as ds:
1592
+ ds.dimensions["x"] = 2
1593
+ ds.dimensions["y"] = 2
1594
+
1595
+ # clean up everything
1596
+ gc.collect()
1597
+ gc.garbage.clear()
1598
+
1599
+ # use weakref to hold on object
1600
+ file_ref = None
1601
+ with h5netcdf.File(tmp_local_or_remote_netcdf, "r") as ds:
1602
+ file_ref = weakref.ref(ds)
1603
+
1604
+ # clean up
1605
+ gc.collect()
1606
+
1607
+ # check garbage list
1608
+ if file_ref() is not None:
1609
+ print("Uncollectable object:", file_ref())
1610
+ print("Potential GC garbage:")
1611
+ for obj in gc.garbage:
1612
+ print(repr(obj))
1613
+
1614
+ assert file_ref() is None or "<Closed h5netcdf.File>"
1559
1615
 
1560
1616
 
1561
1617
  def test_expanded_variables_netcdf4(tmp_local_netcdf, netcdf_write_module):
@@ -1693,7 +1749,7 @@ def test_track_order_specification(tmp_local_netcdf):
1693
1749
  # While netcdf4-c has historically only allowed track_order to be True
1694
1750
  # There doesn't seem to be a good reason for this
1695
1751
  # https://github.com/Unidata/netcdf-c/issues/2054 historically, h5netcdf
1696
- # has not specified this parameter (leaving it implicitely as False)
1752
+ # has not specified this parameter (leaving it implicitly as False)
1697
1753
  # We want to make sure we allow both here
1698
1754
  with h5netcdf.File(tmp_local_netcdf, "w", track_order=False):
1699
1755
  pass
@@ -2757,3 +2813,35 @@ def test_h5pyd_driver(hsds_up):
2757
2813
  with h5netcdf.File(fname, "w", driver="h5pyd") as ds:
2758
2814
  assert ds._h5py == h5pyd
2759
2815
  assert isinstance(ds._h5file, h5pyd.File)
2816
+
2817
+
2818
+ def test_h5pyd_nonchunked_scalars(hsds_up):
2819
+ if without_h5pyd:
2820
+ pytest.skip("h5pyd package not available")
2821
+ elif not hsds_up:
2822
+ pytest.skip("HSDS service not running")
2823
+ rnd = "".join(random.choice(string.ascii_uppercase) for _ in range(5))
2824
+ fname = f"hdf5://testfile{rnd}.nc"
2825
+ with h5pyd.File(fname, "w") as ds:
2826
+ ds.create_dataset("foo", data=b"1234")
2827
+ with h5netcdf.File(fname, "r", driver="h5pyd") as ds:
2828
+ # HSDS stores this as a chunked dataset, but only with a single chunk
2829
+ assert ds["foo"]._h5ds.chunks == (1,)
2830
+ # However, since it is a scalar dataset, we should not expose the chunking
2831
+ assert ds["foo"].chunks is None
2832
+
2833
+
2834
+ def test_h5pyd_append(hsds_up):
2835
+ if without_h5pyd:
2836
+ pytest.skip("h5pyd package not available")
2837
+ elif not hsds_up:
2838
+ pytest.skip("HSDS service not running")
2839
+ rnd = "".join(random.choice(string.ascii_uppercase) for _ in range(5))
2840
+ fname = f"hdf5://testfile{rnd}.nc"
2841
+
2842
+ with pytest.warns(UserWarning, match="Append mode for h5pyd"):
2843
+ with h5netcdf.File(fname, "a", driver="h5pyd") as ds:
2844
+ assert not ds._preexisting_file
2845
+
2846
+ with h5netcdf.File(fname, "a", driver="h5pyd") as ds:
2847
+ assert ds._preexisting_file
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: h5netcdf
3
- Version: 1.6.0
3
+ Version: 1.6.2
4
4
  Summary: netCDF4 via h5py
5
5
  Author-email: Stephan Hoyer <shoyer@gmail.com>, Kai Mühlbauer <kmuehlbauer@wradlib.org>
6
6
  Maintainer-email: h5netcdf developers <devteam@h5netcdf.org>
@@ -56,6 +56,7 @@ Requires-Dist: packaging
56
56
  Provides-Extra: test
57
57
  Requires-Dist: netCDF4; extra == "test"
58
58
  Requires-Dist: pytest; extra == "test"
59
+ Dynamic: license-file
59
60
 
60
61
  h5netcdf
61
62
  ========
@@ -318,7 +319,7 @@ The following describes the behavior of h5netcdf with respect to order tracking
318
319
  for a few key versions:
319
320
 
320
321
  - Version 0.12.0 and earlier, the ``track_order`` parameter`order was missing
321
- and thus order tracking was implicitely set to ``False``.
322
+ and thus order tracking was implicitly set to ``False``.
322
323
  - Version 0.13.0 enabled order tracking by setting the parameter
323
324
  ``track_order`` to ``True`` by default without deprecation.
324
325
  - Versions 0.13.1 to 1.0.2 set ``track_order`` to ``False`` due to a bug in a
@@ -0,0 +1,16 @@
1
+ h5netcdf/__init__.py,sha256=Y0EBCcmlJctwl1kCmj7yLijTVy9AioBTr2091vInAtw,456
2
+ h5netcdf/_version.py,sha256=0l_x32-zDcY9aQqQSs0LtsRx42EjkEfyXdUn9HtFgXU,511
3
+ h5netcdf/attrs.py,sha256=4IvV4ULLWkz4igFsvu9S2LB745wgUKrIdIuSeO5kpX8,3581
4
+ h5netcdf/core.py,sha256=AnPeV5XE_04QZWPSM7WjrddaKfkkdPtvbIMEKf3cSIs,64154
5
+ h5netcdf/dimensions.py,sha256=2g0p9DOAC0hhQ94spIAjWeKC1qyhzzO0s15xCFYSscM,7803
6
+ h5netcdf/legacyapi.py,sha256=MIZlht5Ad4hDFF1Slz2vXmKkgbv7Fhhf2YwNIe16Lfk,7682
7
+ h5netcdf/utils.py,sha256=6E-HAIE0ONMyL4SxI3oUyQvrDgDWifR5EPde91V9rT0,674
8
+ h5netcdf/tests/conftest.py,sha256=4fLa2qoB8br2UpokaOn1-mjHgqVUgVV0G3QLIUzfbZo,2133
9
+ h5netcdf/tests/pytest.ini,sha256=ruJxrLdCIA4bCPVuPQjxsLSlvVxuIsIakK6iQOmz-ak,107
10
+ h5netcdf/tests/test_h5netcdf.py,sha256=Pap1X7YQNRGyU5wH17ZDmHZaPLdRkQwB7BrKQeUjImE,110737
11
+ h5netcdf-1.6.2.dist-info/licenses/AUTHORS.txt,sha256=LTKzUh9o4Wc_oT3aFC48cyDCCP6tdm6VEV_6RrNy4uo,272
12
+ h5netcdf-1.6.2.dist-info/licenses/LICENSE,sha256=Xer1Jg8iL_n9Da0xt0S99blk6tsg9tee_JdgH1rWTjs,1505
13
+ h5netcdf-1.6.2.dist-info/METADATA,sha256=-FuPQxJcnu21jRLDgd6Dqx1Gu39smy8UrAoDImCGAn4,13387
14
+ h5netcdf-1.6.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ h5netcdf-1.6.2.dist-info/top_level.txt,sha256=Fb_KIpOE6MBqjSvxV1Ay7oYce1mdmQ1pO9JQJPDeGqg,9
16
+ h5netcdf-1.6.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,16 +0,0 @@
1
- h5netcdf/__init__.py,sha256=Y0EBCcmlJctwl1kCmj7yLijTVy9AioBTr2091vInAtw,456
2
- h5netcdf/_version.py,sha256=5FGJNp9Lkk9uOxeCjXpoCGBF79Ar6LGPOR7-atBqb_4,511
3
- h5netcdf/attrs.py,sha256=4IvV4ULLWkz4igFsvu9S2LB745wgUKrIdIuSeO5kpX8,3581
4
- h5netcdf/core.py,sha256=fU4uOfrGf0n-C6QmtNw-SJXv1WFmmue0dekba_6HUW4,62819
5
- h5netcdf/dimensions.py,sha256=2g0p9DOAC0hhQ94spIAjWeKC1qyhzzO0s15xCFYSscM,7803
6
- h5netcdf/legacyapi.py,sha256=MIZlht5Ad4hDFF1Slz2vXmKkgbv7Fhhf2YwNIe16Lfk,7682
7
- h5netcdf/utils.py,sha256=6E-HAIE0ONMyL4SxI3oUyQvrDgDWifR5EPde91V9rT0,674
8
- h5netcdf/tests/conftest.py,sha256=l6bOQyqe4gcdKHSNNijeWlFYTpEZse4QEUWbUntAIf4,1825
9
- h5netcdf/tests/pytest.ini,sha256=ruJxrLdCIA4bCPVuPQjxsLSlvVxuIsIakK6iQOmz-ak,107
10
- h5netcdf/tests/test_h5netcdf.py,sha256=fB-4-FxhuvqibPBFQzrS-niAZDLzXr7oIauRGqYQWSU,107713
11
- h5netcdf-1.6.0.dist-info/AUTHORS.txt,sha256=LTKzUh9o4Wc_oT3aFC48cyDCCP6tdm6VEV_6RrNy4uo,272
12
- h5netcdf-1.6.0.dist-info/LICENSE,sha256=Xer1Jg8iL_n9Da0xt0S99blk6tsg9tee_JdgH1rWTjs,1505
13
- h5netcdf-1.6.0.dist-info/METADATA,sha256=Lnov_mrz4uS67oVyS16m1zXhVz4nO55NqLOPaIQDGzY,13366
14
- h5netcdf-1.6.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
15
- h5netcdf-1.6.0.dist-info/top_level.txt,sha256=Fb_KIpOE6MBqjSvxV1Ay7oYce1mdmQ1pO9JQJPDeGqg,9
16
- h5netcdf-1.6.0.dist-info/RECORD,,