1# This file is part of h5py, a Python interface to the HDF5 library.
2#
3# http://www.h5py.org
4#
5# Copyright 2008-2013 Andrew Collette and contributors
6#
7# License:  Standard 3-clause BSD; see "license.txt" for full license terms
8#           and contributor agreement.
9
10"""
11    Attributes testing module
12
13    Covers all operations which access the .attrs property, with the
14    exception of data read/write and type conversion.  Those operations
15    are tested by module test_attrs_data.
16"""
17
18import numpy as np
19
20from collections.abc import MutableMapping
21
22from .common import TestCase, ut
23
24import h5py
25from h5py import File
26from h5py import h5a,  h5t
27from h5py import AttributeManager
28
29
30class BaseAttrs(TestCase):
31
32    def setUp(self):
33        self.f = File(self.mktemp(), 'w')
34
35    def tearDown(self):
36        if self.f:
37            self.f.close()
38
39class TestRepr(TestCase):
40
41    """ Feature: AttributeManager provide a helpful
42        __repr__ string
43    """
44
45    def test_repr(self):
46        grp = self.f.create_group('grp')
47        grp.attrs.create('att', 1)
48        self.assertIsInstance(repr(grp.attrs), str)
49        grp.id.close()
50        self.assertIsInstance(repr(grp.attrs), str)
51
52
53class TestAccess(BaseAttrs):
54
55    """
56        Feature: Attribute creation/retrieval via special methods
57    """
58
59    def test_create(self):
60        """ Attribute creation by direct assignment """
61        self.f.attrs['a'] = 4.0
62        self.assertEqual(list(self.f.attrs.keys()), ['a'])
63        self.assertEqual(self.f.attrs['a'], 4.0)
64
65    def test_create_2(self):
66        """ Attribute creation by create() method """
67        self.f.attrs.create('a', 4.0)
68        self.assertEqual(list(self.f.attrs.keys()), ['a'])
69        self.assertEqual(self.f.attrs['a'], 4.0)
70
71    def test_modify(self):
72        """ Attributes are modified by direct assignment"""
73        self.f.attrs['a'] = 3
74        self.assertEqual(list(self.f.attrs.keys()), ['a'])
75        self.assertEqual(self.f.attrs['a'], 3)
76        self.f.attrs['a'] = 4
77        self.assertEqual(list(self.f.attrs.keys()), ['a'])
78        self.assertEqual(self.f.attrs['a'], 4)
79
80    def test_modify_2(self):
81        """ Attributes are modified by modify() method """
82        self.f.attrs.modify('a',3)
83        self.assertEqual(list(self.f.attrs.keys()), ['a'])
84        self.assertEqual(self.f.attrs['a'], 3)
85
86        self.f.attrs.modify('a', 4)
87        self.assertEqual(list(self.f.attrs.keys()), ['a'])
88        self.assertEqual(self.f.attrs['a'], 4)
89
90        # If the attribute doesn't exist, create new
91        self.f.attrs.modify('b', 5)
92        self.assertEqual(list(self.f.attrs.keys()), ['a', 'b'])
93        self.assertEqual(self.f.attrs['a'], 4)
94        self.assertEqual(self.f.attrs['b'], 5)
95
96        # Shape of new value is incompatible with the previous
97        new_value = np.arange(5)
98        with self.assertRaises(TypeError):
99            self.f.attrs.modify('b', new_value)
100
101    def test_overwrite(self):
102        """ Attributes are silently overwritten """
103        self.f.attrs['a'] = 4.0
104        self.f.attrs['a'] = 5.0
105        self.assertEqual(self.f.attrs['a'], 5.0)
106
107    def test_rank(self):
108        """ Attribute rank is preserved """
109        self.f.attrs['a'] = (4.0, 5.0)
110        self.assertEqual(self.f.attrs['a'].shape, (2,))
111        self.assertArrayEqual(self.f.attrs['a'], np.array((4.0,5.0)))
112
113    def test_single(self):
114        """ Attributes of shape (1,) don't become scalars """
115        self.f.attrs['a'] = np.ones((1,))
116        out = self.f.attrs['a']
117        self.assertEqual(out.shape, (1,))
118        self.assertEqual(out[()], 1)
119
120    def test_access_exc(self):
121        """ Attempt to access missing item raises KeyError """
122        with self.assertRaises(KeyError):
123            self.f.attrs['a']
124
125    def test_get_id(self):
126        self.f.attrs['a'] = 4.0
127        aid = self.f.attrs.get_id('a')
128        assert isinstance(aid, h5a.AttrID)
129
130        with self.assertRaises(KeyError):
131            self.f.attrs.get_id('b')
132
133class TestDelete(BaseAttrs):
134
135    """
136        Feature: Deletion of attributes using __delitem__
137    """
138
139    def test_delete(self):
140        """ Deletion via "del" """
141        self.f.attrs['a'] = 4.0
142        self.assertIn('a', self.f.attrs)
143        del self.f.attrs['a']
144        self.assertNotIn('a', self.f.attrs)
145
146    def test_delete_exc(self):
147        """ Attempt to delete missing item raises KeyError """
148        with self.assertRaises(KeyError):
149            del self.f.attrs['a']
150
151
152class TestUnicode(BaseAttrs):
153
154    """
155        Feature: Attributes can be accessed via Unicode or byte strings
156    """
157
158    def test_ascii(self):
159        """ Access via pure-ASCII byte string """
160        self.f.attrs[b"ascii"] = 42
161        out = self.f.attrs[b"ascii"]
162        self.assertEqual(out, 42)
163
164    def test_raw(self):
165        """ Access via non-ASCII byte string """
166        name = b"non-ascii\xfe"
167        self.f.attrs[name] = 42
168        out = self.f.attrs[name]
169        self.assertEqual(out, 42)
170
171    def test_unicode(self):
172        """ Access via Unicode string with non-ascii characters """
173        name = "Omega" + chr(0x03A9)
174        self.f.attrs[name] = 42
175        out = self.f.attrs[name]
176        self.assertEqual(out, 42)
177
178
179class TestCreate(BaseAttrs):
180
181    """
182        Options for explicit attribute creation
183    """
184
185    def test_named(self):
186        """ Attributes created from named types link to the source type object
187        """
188        self.f['type'] = np.dtype('u8')
189        self.f.attrs.create('x', 42, dtype=self.f['type'])
190        self.assertEqual(self.f.attrs['x'], 42)
191        aid = h5a.open(self.f.id, b'x')
192        htype = aid.get_type()
193        htype2 = self.f['type'].id
194        self.assertEqual(htype, htype2)
195        self.assertTrue(htype.committed())
196
197    def test_empty(self):
198        # https://github.com/h5py/h5py/issues/1540
199        """ Create attribute with h5py.Empty value
200        """
201        self.f.attrs.create('empty', h5py.Empty('f'))
202        self.assertEqual(self.f.attrs['empty'], h5py.Empty('f'))
203
204        self.f.attrs.create('empty', h5py.Empty(None))
205        self.assertEqual(self.f.attrs['empty'], h5py.Empty(None))
206
207class TestMutableMapping(BaseAttrs):
208    '''Tests if the registration of AttributeManager as a MutableMapping
209    behaves as expected
210    '''
211    def test_resolution(self):
212        assert issubclass(AttributeManager, MutableMapping)
213        assert isinstance(self.f.attrs, MutableMapping)
214
215    def test_validity(self):
216        '''
217        Test that the required functions are implemented.
218        '''
219        AttributeManager.__getitem__
220        AttributeManager.__setitem__
221        AttributeManager.__delitem__
222        AttributeManager.__iter__
223        AttributeManager.__len__
224
225class TestVlen(BaseAttrs):
226    def test_vlen(self):
227        a = np.array([np.arange(3), np.arange(4)],
228            dtype=h5t.vlen_dtype(int))
229        self.f.attrs['a'] = a
230        self.assertArrayEqual(self.f.attrs['a'][0], a[0])
231
232    def test_vlen_s1(self):
233        dt = h5py.vlen_dtype(np.dtype('S1'))
234        a = np.empty((1,), dtype=dt)
235        a[0] = np.array([b'a', b'b'], dtype='S1')
236
237        self.f.attrs.create('test', a)
238        self.assertArrayEqual(self.f.attrs['test'][0], a[0])
239
240
241class TestTrackOrder(BaseAttrs):
242    def fill_attrs(self, track_order):
243        attrs = self.f.create_group('test', track_order=track_order).attrs
244        for i in range(100):
245            attrs[str(i)] = i
246        return attrs
247
248    @ut.skipUnless(h5py.version.hdf5_version_tuple >= (1, 10, 6), 'HDF5 1.10.6 required')
249    # https://forum.hdfgroup.org/t/bug-h5arename-fails-unexpectedly/4881
250    def test_track_order(self):
251        attrs = self.fill_attrs(track_order=True)  # creation order
252        self.assertEqual(list(attrs),
253                         [str(i) for i in range(100)])
254
255    def test_no_track_order(self):
256        attrs = self.fill_attrs(track_order=False)  # name alphanumeric
257        self.assertEqual(list(attrs),
258                         sorted([str(i) for i in range(100)]))
259
260
261class TestDatatype(BaseAttrs):
262
263    def test_datatype(self):
264        self.f['foo'] = np.dtype('f')
265        dt = self.f['foo']
266        self.assertEqual(list(dt.attrs.keys()), [])
267        dt.attrs.create('a', 4.0)
268        self.assertEqual(list(dt.attrs.keys()), ['a'])
269        self.assertEqual(list(dt.attrs.values()), [4.0])
270
271def test_python_int_uint64(writable_file):
272    f = writable_file
273    data = [np.iinfo(np.int64).max, np.iinfo(np.int64).max + 1]
274
275    # Check creating a new attribute
276    f.attrs.create('a', data, dtype=np.uint64)
277    assert f.attrs['a'].dtype == np.dtype(np.uint64)
278    np.testing.assert_array_equal(f.attrs['a'], np.array(data, dtype=np.uint64))
279
280    # Check modifying an existing attribute
281    f.attrs.modify('a', data)
282    np.testing.assert_array_equal(f.attrs['a'], np.array(data, dtype=np.uint64))
283