From 1f6aa5d47cd50e54b3aad85d2bac667f50f98915 Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:25:17 -0700 Subject: [PATCH 01/82] ignore sphinx cache warnings (#1105) --- docs/source/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index caff737e7..9781933f5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -93,6 +93,8 @@ ('py:class', 'unittest.case.TestCase'), ] +suppress_warnings = ["config.cache"] + # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] From 3146c97b68bf8149b7635f5505969fdfd01439af Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Fri, 26 Apr 2024 15:37:57 -0500 Subject: [PATCH 02/82] Workflows to support python 3.9 and 3.8 with Mac OS 13 (#1104) --- .github/workflows/run_all_tests.yml | 6 +++--- .github/workflows/run_tests.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run_all_tests.yml b/.github/workflows/run_all_tests.yml index d5f8afc7f..67a645037 100644 --- a/.github/workflows/run_all_tests.yml +++ b/.github/workflows/run_all_tests.yml @@ -41,8 +41,8 @@ jobs: - { name: windows-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: windows-latest } - { name: windows-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: windows-latest } - { name: windows-python3.12-prerelease , test-tox-env: pytest-py312-prerelease , python-ver: "3.12", os: windows-latest } - - { name: macos-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: macos-latest } - - { name: macos-python3.9 , test-tox-env: pytest-py39-pinned , python-ver: "3.9" , os: macos-latest } + - { name: macos-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: macos-13 } + - { name: macos-python3.9 , test-tox-env: pytest-py39-pinned , python-ver: "3.9" , os: macos-13 } - { name: macos-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: macos-latest } - { name: macos-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: macos-latest } - { name: macos-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: macos-latest } @@ -105,7 +105,7 @@ jobs: - { name: windows-gallery-python3.11-optional , test-tox-env: gallery-py311-optional-pinned , python-ver: "3.11", os: windows-latest } - { name: windows-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: windows-latest } - { name: windows-gallery-python3.12-prerelease, test-tox-env: gallery-py312-prerelease , python-ver: "3.12", os: windows-latest } - - { name: macos-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: macos-latest } + - { name: macos-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: macos-13 } - { name: macos-gallery-python3.11-optional , test-tox-env: gallery-py311-optional-pinned , python-ver: "3.11", os: macos-latest } - { name: macos-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: macos-latest } - { name: macos-gallery-python3.12-prerelease , test-tox-env: gallery-py312-prerelease , python-ver: "3.12", os: macos-latest } diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 049cec2e5..d800d86f1 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -28,7 +28,7 @@ jobs: - { name: linux-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: ubuntu-latest , upload-wheels: true } - { name: windows-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: windows-latest } - { name: windows-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: windows-latest } - - { name: macos-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: macos-latest } + - { name: macos-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: macos-13 } - { name: macos-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: macos-latest } steps: - name: Checkout repo with submodules From 126bdb100c6d5ce3e2dadd375de9d32524219404 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Wed, 1 May 2024 16:34:34 -0700 Subject: [PATCH 03/82] Don't install in editable/develop mode during testing (#1107) --- .github/workflows/run_all_tests.yml | 2 +- .github/workflows/run_coverage.yml | 8 ++++---- .github/workflows/run_hdmf_zarr_tests.yml | 3 +-- .github/workflows/run_pynwb_tests.yml | 3 +-- .github/workflows/run_tests.yml | 2 +- CHANGELOG.md | 1 + pyproject.toml | 11 +++++------ tox.ini | 1 - 8 files changed, 14 insertions(+), 17 deletions(-) diff --git a/.github/workflows/run_all_tests.yml b/.github/workflows/run_all_tests.yml index 67a645037..def51537f 100644 --- a/.github/workflows/run_all_tests.yml +++ b/.github/workflows/run_all_tests.yml @@ -233,7 +233,7 @@ jobs: - name: Install run dependencies run: | - pip install -e . + pip install . pip list - name: Conda reporting diff --git a/.github/workflows/run_coverage.yml b/.github/workflows/run_coverage.yml index 7a18e5068..a72a05e73 100644 --- a/.github/workflows/run_coverage.yml +++ b/.github/workflows/run_coverage.yml @@ -55,18 +55,18 @@ jobs: - name: Install package run: | - python -m pip install -e . # must install in editable mode for coverage to find sources + python -m pip install . python -m pip list - name: Run tests and generate coverage report run: | - pytest --cov - python -m coverage xml # codecov uploader requires xml format - python -m coverage report -m + # coverage is configured in pyproject.toml + pytest --cov --cov-report=xml --cov-report=term # codecov uploader requires xml format - name: Upload coverage to Codecov uses: codecov/codecov-action@v4 with: fail_ci_if_error: true + file: ./coverage.xml env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/run_hdmf_zarr_tests.yml b/.github/workflows/run_hdmf_zarr_tests.yml index ecfdeaeeb..5e76711af 100644 --- a/.github/workflows/run_hdmf_zarr_tests.yml +++ b/.github/workflows/run_hdmf_zarr_tests.yml @@ -32,8 +32,7 @@ jobs: git clone https://github.com/hdmf-dev/hdmf-zarr.git --recurse-submodules cd hdmf-zarr python -m pip install -r requirements-dev.txt # do not install the pinned install requirements - # must install in editable mode for coverage to find sources - python -m pip install -e . # this will install a different version of hdmf from the current one + python -m pip install . # this will install a different version of hdmf from the current one cd .. python -m pip uninstall -y hdmf # uninstall the other version of hdmf python -m pip install . # reinstall current branch of hdmf diff --git a/.github/workflows/run_pynwb_tests.yml b/.github/workflows/run_pynwb_tests.yml index bf3f32343..1a714ed9f 100644 --- a/.github/workflows/run_pynwb_tests.yml +++ b/.github/workflows/run_pynwb_tests.yml @@ -32,8 +32,7 @@ jobs: git clone https://github.com/NeurodataWithoutBorders/pynwb.git --recurse-submodules cd pynwb python -m pip install -r requirements-dev.txt # do not install the pinned install requirements - # must install in editable mode for coverage to find sources - python -m pip install -e . # this will install a different version of hdmf from the current one + python -m pip install . # this will install a different version of hdmf from the current one cd .. python -m pip uninstall -y hdmf # uninstall the other version of hdmf python -m pip install . # reinstall current branch of hdmf diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index d800d86f1..2723e03d0 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -243,7 +243,7 @@ jobs: - name: Install run dependencies run: | - pip install -e . + pip install . pip list - name: Conda reporting diff --git a/CHANGELOG.md b/CHANGELOG.md index 00eeeb5dd..5d5a2cc62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Updated `_field_config` to take `type_map` as an argument for APIs. @mavaylon1 [#1094](https://github.com/hdmf-dev/hdmf/pull/1094) - Added `TypeConfigurator` to automatically wrap fields with `TermSetWrapper` according to a configuration file. @mavaylon1 [#1016](https://github.com/hdmf-dev/hdmf/pull/1016) - Updated `TermSetWrapper` to support validating a single field within a compound array. @mavaylon1 [#1061](https://github.com/hdmf-dev/hdmf/pull/1061) +- Updated testing to not install in editable mode and not run `coverage` by default. @rly [#1107](https://github.com/hdmf-dev/hdmf/pull/1107) ## HDMF 3.13.0 (March 20, 2024) diff --git a/pyproject.toml b/pyproject.toml index b60ae6943..e5584b581 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,7 +77,6 @@ packages = ["src/hdmf"] # verbose = 1 [tool.pytest.ini_options] -addopts = "--cov --cov-report html" norecursedirs = "tests/unit/helpers" [tool.codespell] @@ -86,17 +85,17 @@ ignore-words-list = "datas" [tool.coverage.run] branch = true -source = ["src/"] -omit = [ - "src/hdmf/_due.py", - "src/hdmf/testing/*", -] +source = ["hdmf"] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "@abstract" ] +omit = [ + "*/hdmf/_due.py", + "*/hdmf/testing/*", +] # [tool.black] # line-length = 120 diff --git a/tox.ini b/tox.ini index aeb743c45..75b011aa0 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,6 @@ requires = pip >= 22.0 [testenv] download = True -usedevelop = True setenv = PYTHONDONTWRITEBYTECODE = 1 VIRTUALENV_PIP = 23.3.1 From 6377b43ae39d16015c940845dc5aae4a6874e6e6 Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Tue, 14 May 2024 17:51:48 -0700 Subject: [PATCH 04/82] Post init option for class generator (#1089) --- CHANGELOG.md | 1 + src/hdmf/build/classgenerator.py | 32 ++++++- src/hdmf/build/manager.py | 15 ++- src/hdmf/common/__init__.py | 7 +- tests/unit/build_tests/test_classgenerator.py | 91 +++++++++++++++++-- 5 files changed, 133 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d5a2cc62..909ef5253 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Added `TypeConfigurator` to automatically wrap fields with `TermSetWrapper` according to a configuration file. @mavaylon1 [#1016](https://github.com/hdmf-dev/hdmf/pull/1016) - Updated `TermSetWrapper` to support validating a single field within a compound array. @mavaylon1 [#1061](https://github.com/hdmf-dev/hdmf/pull/1061) - Updated testing to not install in editable mode and not run `coverage` by default. @rly [#1107](https://github.com/hdmf-dev/hdmf/pull/1107) +- Add `post_init_method` parameter when generating classes to perform post-init functionality, i.e., validation. @mavaylon1 [#1089](https://github.com/hdmf-dev/hdmf/pull/1089) ## HDMF 3.13.0 (March 20, 2024) diff --git a/src/hdmf/build/classgenerator.py b/src/hdmf/build/classgenerator.py index d2e7d4fc0..a3336b98e 100644 --- a/src/hdmf/build/classgenerator.py +++ b/src/hdmf/build/classgenerator.py @@ -1,5 +1,6 @@ from copy import deepcopy from datetime import datetime, date +from collections.abc import Callable import numpy as np @@ -35,6 +36,8 @@ def register_generator(self, **kwargs): {'name': 'spec', 'type': BaseStorageSpec, 'doc': ''}, {'name': 'parent_cls', 'type': type, 'doc': ''}, {'name': 'attr_names', 'type': dict, 'doc': ''}, + {'name': 'post_init_method', 'type': Callable, 'default': None, + 'doc': 'The function used as a post_init method to validate the class generation.'}, {'name': 'type_map', 'type': 'hdmf.build.manager.TypeMap', 'doc': ''}, returns='the class for the given namespace and data_type', rtype=type) def generate_class(self, **kwargs): @@ -42,8 +45,10 @@ def generate_class(self, **kwargs): If no class has been associated with the ``data_type`` from ``namespace``, a class will be dynamically created and returned. """ - data_type, spec, parent_cls, attr_names, type_map = getargs('data_type', 'spec', 'parent_cls', 'attr_names', - 'type_map', kwargs) + data_type, spec, parent_cls, attr_names, type_map, post_init_method = getargs('data_type', 'spec', + 'parent_cls', 'attr_names', + 'type_map', + 'post_init_method', kwargs) not_inherited_fields = dict() for k, field_spec in attr_names.items(): @@ -82,6 +87,8 @@ def generate_class(self, **kwargs): + str(e) + " Please define that type before defining '%s'." % name) cls = ExtenderMeta(data_type, tuple(bases), classdict) + cls.post_init_method = post_init_method + return cls @@ -316,8 +323,19 @@ def set_init(cls, classdict, bases, docval_args, not_inherited_fields, name): elif attr_name not in attrs_not_to_set: attrs_to_set.append(attr_name) - @docval(*docval_args, allow_positional=AllowPositional.WARNING) + # We want to use the skip_post_init of the current class and not the parent class + for item in docval_args: + if item['name'] == 'skip_post_init': + docval_args.remove(item) + + @docval(*docval_args, + {'name': 'skip_post_init', 'type': bool, 'default': False, + 'doc': 'bool to skip post_init'}, + allow_positional=AllowPositional.WARNING) def __init__(self, **kwargs): + skip_post_init = popargs('skip_post_init', kwargs) + + original_kwargs = dict(kwargs) if name is not None: # force container name to be the fixed name in the spec kwargs.update(name=name) @@ -343,6 +361,9 @@ def __init__(self, **kwargs): for f in fixed_value_attrs_to_set: self.fields[f] = getattr(not_inherited_fields[f], 'value') + if self.post_init_method is not None and not skip_post_init: + self.post_init_method(**original_kwargs) + classdict['__init__'] = __init__ @@ -417,6 +438,7 @@ def set_init(cls, classdict, bases, docval_args, not_inherited_fields, name): def __init__(self, **kwargs): # store the values passed to init for each MCI attribute so that they can be added # after calling __init__ + original_kwargs = dict(kwargs) new_kwargs = list() for field_clsconf in classdict['__clsconf__']: attr_name = field_clsconf['attr'] @@ -437,6 +459,7 @@ def __init__(self, **kwargs): kwargs[attr_name] = list() # call the parent class init without the MCI attribute + kwargs['skip_post_init'] = True previous_init(self, **kwargs) # call the add method for each MCI attribute @@ -444,5 +467,8 @@ def __init__(self, **kwargs): add_method = getattr(self, new_kwarg['add_method_name']) add_method(new_kwarg['value']) + if self.post_init_method is not None: + self.post_init_method(**original_kwargs) + # override __init__ classdict['__init__'] = __init__ diff --git a/src/hdmf/build/manager.py b/src/hdmf/build/manager.py index a26de3279..25b9b81bd 100644 --- a/src/hdmf/build/manager.py +++ b/src/hdmf/build/manager.py @@ -1,6 +1,7 @@ import logging from collections import OrderedDict, deque from copy import copy +from collections.abc import Callable from .builders import DatasetBuilder, GroupBuilder, LinkBuilder, Builder, BaseBuilder from .classgenerator import ClassGenerator, CustomClassGenerator, MCIClassGenerator @@ -498,11 +499,14 @@ def get_container_cls(self, **kwargs): created and returned. """ # NOTE: this internally used function get_container_cls will be removed in favor of get_dt_container_cls + # Deprecated: Will be removed by HDMF 4.0 namespace, data_type, autogen = getargs('namespace', 'data_type', 'autogen', kwargs) return self.get_dt_container_cls(data_type, namespace, autogen) @docval({"name": "data_type", "type": str, "doc": "the data type to create a AbstractContainer class for"}, {"name": "namespace", "type": str, "doc": "the namespace containing the data_type", "default": None}, + {'name': 'post_init_method', 'type': Callable, 'default': None, + 'doc': 'The function used as a post_init method to validate the class generation.'}, {"name": "autogen", "type": bool, "doc": "autogenerate class if one does not exist", "default": True}, returns='the class for the given namespace and data_type', rtype=type) def get_dt_container_cls(self, **kwargs): @@ -513,7 +517,8 @@ def get_dt_container_cls(self, **kwargs): Replaces get_container_cls but namespace is optional. If namespace is unknown, it will be looked up from all namespaces. """ - namespace, data_type, autogen = getargs('namespace', 'data_type', 'autogen', kwargs) + namespace, data_type, post_init_method, autogen = getargs('namespace', 'data_type', + 'post_init_method','autogen', kwargs) # namespace is unknown, so look it up if namespace is None: @@ -527,12 +532,18 @@ def get_dt_container_cls(self, **kwargs): raise ValueError("Namespace could not be resolved.") cls = self.__get_container_cls(namespace, data_type) + if cls is None and autogen: # dynamically generate a class spec = self.__ns_catalog.get_spec(namespace, data_type) self.__check_dependent_types(spec, namespace) parent_cls = self.__get_parent_cls(namespace, data_type, spec) attr_names = self.__default_mapper_cls.get_attr_names(spec) - cls = self.__class_generator.generate_class(data_type, spec, parent_cls, attr_names, self) + cls = self.__class_generator.generate_class(data_type=data_type, + spec=spec, + parent_cls=parent_cls, + attr_names=attr_names, + post_init_method=post_init_method, + type_map=self) self.register_container_type(namespace, data_type, cls) return cls diff --git a/src/hdmf/common/__init__.py b/src/hdmf/common/__init__.py index 248ca1095..4d724d1d1 100644 --- a/src/hdmf/common/__init__.py +++ b/src/hdmf/common/__init__.py @@ -3,6 +3,7 @@ ''' import os.path from copy import deepcopy +from collections.abc import Callable CORE_NAMESPACE = 'hdmf-common' EXP_NAMESPACE = 'hdmf-experimental' @@ -136,12 +137,14 @@ def available_namespaces(): @docval({'name': 'data_type', 'type': str, 'doc': 'the data_type to get the Container class for'}, {'name': 'namespace', 'type': str, 'doc': 'the namespace the data_type is defined in'}, + {'name': 'post_init_method', 'type': Callable, 'default': None, + 'doc': 'The function used as a post_init method to validate the class generation.'}, is_method=False) def get_class(**kwargs): """Get the class object of the Container subclass corresponding to a given neurdata_type. """ - data_type, namespace = getargs('data_type', 'namespace', kwargs) - return __TYPE_MAP.get_dt_container_cls(data_type, namespace) + data_type, namespace, post_init_method = getargs('data_type', 'namespace', 'post_init_method', kwargs) + return __TYPE_MAP.get_dt_container_cls(data_type, namespace, post_init_method) @docval({'name': 'extensions', 'type': (str, TypeMap, list), diff --git a/tests/unit/build_tests/test_classgenerator.py b/tests/unit/build_tests/test_classgenerator.py index 0c117820b..52fdc4839 100644 --- a/tests/unit/build_tests/test_classgenerator.py +++ b/tests/unit/build_tests/test_classgenerator.py @@ -2,6 +2,7 @@ import os import shutil import tempfile +from warnings import warn from hdmf.build import TypeMap, CustomClassGenerator from hdmf.build.classgenerator import ClassGenerator, MCIClassGenerator @@ -82,6 +83,79 @@ def test_no_generators(self): self.assertTrue(hasattr(cls, '__init__')) +class TestPostInitGetClass(TestCase): + def setUp(self): + def post_init_method(self, **kwargs): + attr1 = kwargs['attr1'] + if attr1<10: + msg = "attr1 should be >=10" + warn(msg) + self.post_init=post_init_method + + def test_post_init(self): + spec = GroupSpec( + doc='A test group specification with a data type', + data_type_def='Baz', + attributes=[ + AttributeSpec(name='attr1', doc='a int attribute', dtype='int') + ] + ) + + spec_catalog = SpecCatalog() + spec_catalog.register_spec(spec, 'test.yaml') + namespace = SpecNamespace( + doc='a test namespace', + name=CORE_NAMESPACE, + schema=[{'source': 'test.yaml'}], + version='0.1.0', + catalog=spec_catalog + ) + namespace_catalog = NamespaceCatalog() + namespace_catalog.add_namespace(CORE_NAMESPACE, namespace) + type_map = TypeMap(namespace_catalog) + + cls = type_map.get_dt_container_cls('Baz', CORE_NAMESPACE, self.post_init) + + with self.assertWarns(Warning): + cls(name='instance', attr1=9) + + def test_multi_container_post_init(self): + bar_spec = GroupSpec( + doc='A test group specification with a data type', + data_type_def='Bar', + datasets=[ + DatasetSpec( + doc='a dataset', + dtype='int', + name='data', + attributes=[AttributeSpec(name='attr2', doc='an integer attribute', dtype='int')] + ) + ], + attributes=[AttributeSpec(name='attr1', doc='a string attribute', dtype='text')]) + + multi_spec = GroupSpec(doc='A test extension that contains a multi', + data_type_def='Multi', + groups=[GroupSpec(data_type_inc=bar_spec, doc='test multi', quantity='*')], + attributes=[AttributeSpec(name='attr1', doc='a float attribute', dtype='float')]) + + spec_catalog = SpecCatalog() + spec_catalog.register_spec(bar_spec, 'test.yaml') + spec_catalog.register_spec(multi_spec, 'test.yaml') + namespace = SpecNamespace( + doc='a test namespace', + name=CORE_NAMESPACE, + schema=[{'source': 'test.yaml'}], + version='0.1.0', + catalog=spec_catalog + ) + namespace_catalog = NamespaceCatalog() + namespace_catalog.add_namespace(CORE_NAMESPACE, namespace) + type_map = TypeMap(namespace_catalog) + Multi = type_map.get_dt_container_cls('Multi', CORE_NAMESPACE, self.post_init) + + with self.assertWarns(Warning): + Multi(name='instance', attr1=9.1) + class TestDynamicContainer(TestCase): def setUp(self): @@ -109,13 +183,15 @@ def test_dynamic_container_creation(self): AttributeSpec('attr4', 'another float attribute', 'float')]) self.spec_catalog.register_spec(baz_spec, 'extension.yaml') cls = self.type_map.get_dt_container_cls('Baz', CORE_NAMESPACE) - expected_args = {'name', 'data', 'attr1', 'attr2', 'attr3', 'attr4'} + expected_args = {'name', 'data', 'attr1', 'attr2', 'attr3', 'attr4', 'skip_post_init'} received_args = set() + for x in get_docval(cls.__init__): if x['name'] != 'foo': received_args.add(x['name']) with self.subTest(name=x['name']): - self.assertNotIn('default', x) + if x['name'] != 'skip_post_init': + self.assertNotIn('default', x) self.assertSetEqual(expected_args, received_args) self.assertEqual(cls.__name__, 'Baz') self.assertTrue(issubclass(cls, Bar)) @@ -135,7 +211,7 @@ def test_dynamic_container_creation_defaults(self): AttributeSpec('attr4', 'another float attribute', 'float')]) self.spec_catalog.register_spec(baz_spec, 'extension.yaml') cls = self.type_map.get_dt_container_cls('Baz', CORE_NAMESPACE) - expected_args = {'name', 'data', 'attr1', 'attr2', 'attr3', 'attr4', 'foo'} + expected_args = {'name', 'data', 'attr1', 'attr2', 'attr3', 'attr4', 'foo', 'skip_post_init'} received_args = set(map(lambda x: x['name'], get_docval(cls.__init__))) self.assertSetEqual(expected_args, received_args) self.assertEqual(cls.__name__, 'Baz') @@ -285,13 +361,14 @@ def __init__(self, **kwargs): AttributeSpec('attr4', 'another float attribute', 'float')]) self.spec_catalog.register_spec(baz_spec, 'extension.yaml') cls = self.type_map.get_dt_container_cls('Baz', CORE_NAMESPACE) - expected_args = {'name', 'data', 'attr2', 'attr3', 'attr4'} + expected_args = {'name', 'data', 'attr2', 'attr3', 'attr4', 'skip_post_init'} received_args = set() for x in get_docval(cls.__init__): if x['name'] != 'foo': received_args.add(x['name']) with self.subTest(name=x['name']): - self.assertNotIn('default', x) + if x['name'] != 'skip_post_init': + self.assertNotIn('default', x) self.assertSetEqual(expected_args, received_args) self.assertTrue(issubclass(cls, FixedAttrBar)) inst = cls(name="My Baz", data=[1, 2, 3, 4], attr2=1000, attr3=98.6, attr4=1.0) @@ -445,7 +522,7 @@ def setUp(self): def test_init_docval(self): cls = self.type_map.get_dt_container_cls('Baz', CORE_NAMESPACE) # generate the class - expected_args = {'name'} # 'attr1' should not be included + expected_args = {'name', 'skip_post_init'} # 'attr1' should not be included received_args = set() for x in get_docval(cls.__init__): received_args.add(x['name']) @@ -518,6 +595,8 @@ def test_gen_parent_class(self): {'name': 'my_baz1', 'doc': 'A composition inside with a fixed name', 'type': baz1_cls}, {'name': 'my_baz2', 'doc': 'A composition inside with a fixed name', 'type': baz2_cls}, {'name': 'my_baz1_link', 'doc': 'A composition inside without a fixed name', 'type': baz1_cls}, + {'name': 'skip_post_init', 'type': bool, 'default': False, + 'doc': 'bool to skip post_init'} )) def test_init_fields(self): From d390c14b370ce231f58eea22101a7d5208b1a0d6 Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Wed, 15 May 2024 12:37:06 -0700 Subject: [PATCH 05/82] Update pyproject.toml (#1115) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e5584b581..67b13350b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ classifiers = [ dependencies = [ "h5py>=2.10", "jsonschema>=2.6.0", - "numpy>=1.18", + 'numpy>=1.18, <2.0', # pin below 2.0 until HDMF supports numpy 2.0 "pandas>=1.0.5", "ruamel.yaml>=0.16", "scipy>=1.4", From a497da35ca8d6c747f10b0d2b51d690ca345e035 Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Fri, 17 May 2024 11:56:55 -0700 Subject: [PATCH 06/82] Fix warning (#1116) * Fix warning * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * breakpoint * Delete docs/gallery/expanded_example_dynamic_term_set.yaml * Delete docs/gallery/schemasheets/nwb_static_enums.yaml * Update CHANGELOG.md * Update .gitignore --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .gitignore | 2 + CHANGELOG.md | 3 + .../expanded_example_dynamic_term_set.yaml | 2073 ----------------- .../schemasheets/nwb_static_enums.yaml | 52 - src/hdmf/container.py | 12 +- 5 files changed, 11 insertions(+), 2131 deletions(-) delete mode 100644 docs/gallery/expanded_example_dynamic_term_set.yaml delete mode 100644 docs/gallery/schemasheets/nwb_static_enums.yaml diff --git a/.gitignore b/.gitignore index 8257bc927..d75abc985 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ /docs/source/hdmf*.rst /docs/gallery/*.hdf5 /docs/gallery/*.sqlite +/docs/gallery/expanded_example_dynamic_term_set.yaml +/docs/gallery/schemasheets/nwb_static_enums.yaml # Auto-generated files after running tutorials mylab.*.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 909ef5253..47cb8c8d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ - Updated testing to not install in editable mode and not run `coverage` by default. @rly [#1107](https://github.com/hdmf-dev/hdmf/pull/1107) - Add `post_init_method` parameter when generating classes to perform post-init functionality, i.e., validation. @mavaylon1 [#1089](https://github.com/hdmf-dev/hdmf/pull/1089) +### Bug Fixes +- Fixed `TermSetWrapper` warning raised during the setters. @mavaylon1 [#1116](https://github.com/hdmf-dev/hdmf/pull/1116) + ## HDMF 3.13.0 (March 20, 2024) ### Enhancements diff --git a/docs/gallery/expanded_example_dynamic_term_set.yaml b/docs/gallery/expanded_example_dynamic_term_set.yaml deleted file mode 100644 index a2631696a..000000000 --- a/docs/gallery/expanded_example_dynamic_term_set.yaml +++ /dev/null @@ -1,2073 +0,0 @@ -id: https://w3id.org/linkml/examples/nwb_dynamic_enums -title: dynamic enums example -name: nwb_dynamic_enums -description: this schema demonstrates the use of dynamic enums - -prefixes: - linkml: https://w3id.org/linkml/ - CL: http://purl.obolibrary.org/obo/CL_ - -imports: -- linkml:types - -default_range: string - -# ======================== # -# CLASSES # -# ======================== # -classes: - BrainSample: - slots: - - cell_type - -# ======================== # -# SLOTS # -# ======================== # -slots: - cell_type: - required: true - range: NeuronTypeEnum - -# ======================== # -# ENUMS # -# ======================== # -enums: - NeuronTypeEnum: - reachable_from: - source_ontology: obo:cl - source_nodes: - - CL:0000540 ## neuron - include_self: false - relationship_types: - - rdfs:subClassOf - permissible_values: - CL:0000705: - text: CL:0000705 - description: R6 photoreceptor cell - meaning: CL:0000705 - CL:4023108: - text: CL:4023108 - description: oxytocin-secreting magnocellular cell - meaning: CL:4023108 - CL:0004240: - text: CL:0004240 - description: WF1 amacrine cell - meaning: CL:0004240 - CL:0004242: - text: CL:0004242 - description: WF3-1 amacrine cell - meaning: CL:0004242 - CL:1000380: - text: CL:1000380 - description: type 1 vestibular sensory cell of epithelium of macula of saccule - of membranous labyrinth - meaning: CL:1000380 - CL:4023128: - text: CL:4023128 - description: rostral periventricular region of the third ventricle KNDy neuron - meaning: CL:4023128 - CL:0003020: - text: CL:0003020 - description: retinal ganglion cell C outer - meaning: CL:0003020 - CL:4023094: - text: CL:4023094 - description: tufted pyramidal neuron - meaning: CL:4023094 - CL:4023057: - text: CL:4023057 - description: cerebellar inhibitory GABAergic interneuron - meaning: CL:4023057 - CL:2000049: - text: CL:2000049 - description: primary motor cortex pyramidal cell - meaning: CL:2000049 - CL:0000119: - text: CL:0000119 - description: cerebellar Golgi cell - meaning: CL:0000119 - CL:0004227: - text: CL:0004227 - description: flat bistratified amacrine cell - meaning: CL:0004227 - CL:1000606: - text: CL:1000606 - description: kidney nerve cell - meaning: CL:1000606 - CL:1001582: - text: CL:1001582 - description: lateral ventricle neuron - meaning: CL:1001582 - CL:0000165: - text: CL:0000165 - description: neuroendocrine cell - meaning: CL:0000165 - CL:0000555: - text: CL:0000555 - description: neuronal brush cell - meaning: CL:0000555 - CL:0004231: - text: CL:0004231 - description: recurving diffuse amacrine cell - meaning: CL:0004231 - CL:0000687: - text: CL:0000687 - description: R1 photoreceptor cell - meaning: CL:0000687 - CL:0001031: - text: CL:0001031 - description: cerebellar granule cell - meaning: CL:0001031 - CL:0003026: - text: CL:0003026 - description: retinal ganglion cell D1 - meaning: CL:0003026 - CL:4033035: - text: CL:4033035 - description: giant bipolar cell - meaning: CL:4033035 - CL:4023009: - text: CL:4023009 - description: extratelencephalic-projecting glutamatergic cortical neuron - meaning: CL:4023009 - CL:0010022: - text: CL:0010022 - description: cardiac neuron - meaning: CL:0010022 - CL:0000287: - text: CL:0000287 - description: eye photoreceptor cell - meaning: CL:0000287 - CL:0000488: - text: CL:0000488 - description: visible light photoreceptor cell - meaning: CL:0000488 - CL:0003046: - text: CL:0003046 - description: M13 retinal ganglion cell - meaning: CL:0003046 - CL:4023169: - text: CL:4023169 - description: trigeminal neuron - meaning: CL:4023169 - CL:0005007: - text: CL:0005007 - description: Kolmer-Agduhr neuron - meaning: CL:0005007 - CL:0005008: - text: CL:0005008 - description: macular hair cell - meaning: CL:0005008 - CL:4023027: - text: CL:4023027 - description: L5 T-Martinotti sst GABAergic cortical interneuron (Mmus) - meaning: CL:4023027 - CL:4033032: - text: CL:4033032 - description: diffuse bipolar 6 cell - meaning: CL:4033032 - CL:0008021: - text: CL:0008021 - description: anterior lateral line ganglion neuron - meaning: CL:0008021 - CL:4023028: - text: CL:4023028 - description: L5 non-Martinotti sst GABAergic cortical interneuron (Mmus) - meaning: CL:4023028 - CL:4023063: - text: CL:4023063 - description: medial ganglionic eminence derived interneuron - meaning: CL:4023063 - CL:4023032: - text: CL:4023032 - description: ON retinal ganglion cell - meaning: CL:4023032 - CL:0003039: - text: CL:0003039 - description: M8 retinal ganglion cell - meaning: CL:0003039 - CL:0000757: - text: CL:0000757 - description: type 5 cone bipolar cell (sensu Mus) - meaning: CL:0000757 - CL:0000609: - text: CL:0000609 - description: vestibular hair cell - meaning: CL:0000609 - CL:0004219: - text: CL:0004219 - description: A2 amacrine cell - meaning: CL:0004219 - CL:4030028: - text: CL:4030028 - description: glycinergic amacrine cell - meaning: CL:4030028 - CL:0002450: - text: CL:0002450 - description: tether cell - meaning: CL:0002450 - CL:0002374: - text: CL:0002374 - description: ear hair cell - meaning: CL:0002374 - CL:0004124: - text: CL:0004124 - description: retinal ganglion cell C1 - meaning: CL:0004124 - CL:0004115: - text: CL:0004115 - description: retinal ganglion cell B - meaning: CL:0004115 - CL:1000384: - text: CL:1000384 - description: type 2 vestibular sensory cell of epithelium of macula of saccule - of membranous labyrinth - meaning: CL:1000384 - CL:2000037: - text: CL:2000037 - description: posterior lateral line neuromast hair cell - meaning: CL:2000037 - CL:0000673: - text: CL:0000673 - description: Kenyon cell - meaning: CL:0000673 - CL:4023052: - text: CL:4023052 - description: Betz upper motor neuron - meaning: CL:4023052 - CL:0004243: - text: CL:0004243 - description: WF3-2 amacrine cell - meaning: CL:0004243 - CL:1000222: - text: CL:1000222 - description: stomach neuroendocrine cell - meaning: CL:1000222 - CL:0002310: - text: CL:0002310 - description: mammosomatotroph - meaning: CL:0002310 - CL:4023066: - text: CL:4023066 - description: horizontal pyramidal neuron - meaning: CL:4023066 - CL:0000379: - text: CL:0000379 - description: sensory processing neuron - meaning: CL:0000379 - CL:0011006: - text: CL:0011006 - description: Lugaro cell - meaning: CL:0011006 - CL:0004216: - text: CL:0004216 - description: type 5b cone bipolar cell - meaning: CL:0004216 - CL:0004126: - text: CL:0004126 - description: retinal ganglion cell C2 outer - meaning: CL:0004126 - CL:0000108: - text: CL:0000108 - description: cholinergic neuron - meaning: CL:0000108 - CL:0011103: - text: CL:0011103 - description: sympathetic neuron - meaning: CL:0011103 - CL:4023107: - text: CL:4023107 - description: reticulospinal neuron - meaning: CL:4023107 - CL:4023002: - text: CL:4023002 - description: dynamic beta motor neuron - meaning: CL:4023002 - CL:4030048: - text: CL:4030048 - description: striosomal D1 medium spiny neuron - meaning: CL:4030048 - CL:4023163: - text: CL:4023163 - description: spherical bushy cell - meaning: CL:4023163 - CL:4023061: - text: CL:4023061 - description: hippocampal CA4 neuron - meaning: CL:4023061 - CL:0000532: - text: CL:0000532 - description: CAP motoneuron - meaning: CL:0000532 - CL:0000526: - text: CL:0000526 - description: afferent neuron - meaning: CL:0000526 - CL:0003003: - text: CL:0003003 - description: G2 retinal ganglion cell - meaning: CL:0003003 - CL:0000530: - text: CL:0000530 - description: primary neuron (sensu Teleostei) - meaning: CL:0000530 - CL:4023045: - text: CL:4023045 - description: medulla-projecting glutamatergic neuron of the primary motor - cortex - meaning: CL:4023045 - CL:3000004: - text: CL:3000004 - description: peripheral sensory neuron - meaning: CL:3000004 - CL:0000544: - text: CL:0000544 - description: slowly adapting mechanoreceptor cell - meaning: CL:0000544 - CL:4030047: - text: CL:4030047 - description: matrix D2 medium spiny neuron - meaning: CL:4030047 - CL:0004220: - text: CL:0004220 - description: flag amacrine cell - meaning: CL:0004220 - CL:4023125: - text: CL:4023125 - description: KNDy neuron - meaning: CL:4023125 - CL:0004228: - text: CL:0004228 - description: broad diffuse amacrine cell - meaning: CL:0004228 - CL:4023122: - text: CL:4023122 - description: oxytocin receptor sst GABAergic cortical interneuron - meaning: CL:4023122 - CL:1000379: - text: CL:1000379 - description: type 1 vestibular sensory cell of epithelium of macula of utricle - of membranous labyrinth - meaning: CL:1000379 - CL:0011111: - text: CL:0011111 - description: gonadotropin-releasing hormone neuron - meaning: CL:0011111 - CL:0003042: - text: CL:0003042 - description: M9-OFF retinal ganglion cell - meaning: CL:0003042 - CL:0003030: - text: CL:0003030 - description: M3 retinal ganglion cell - meaning: CL:0003030 - CL:0003011: - text: CL:0003011 - description: G8 retinal ganglion cell - meaning: CL:0003011 - CL:0000202: - text: CL:0000202 - description: auditory hair cell - meaning: CL:0000202 - CL:0002271: - text: CL:0002271 - description: type EC1 enteroendocrine cell - meaning: CL:0002271 - CL:4023013: - text: CL:4023013 - description: corticothalamic-projecting glutamatergic cortical neuron - meaning: CL:4023013 - CL:4023114: - text: CL:4023114 - description: calyx vestibular afferent neuron - meaning: CL:4023114 - CL:0003045: - text: CL:0003045 - description: M12 retinal ganglion cell - meaning: CL:0003045 - CL:0002487: - text: CL:0002487 - description: cutaneous/subcutaneous mechanoreceptor cell - meaning: CL:0002487 - CL:4030053: - text: CL:4030053 - description: Island of Calleja granule cell - meaning: CL:4030053 - CL:0000490: - text: CL:0000490 - description: photopic photoreceptor cell - meaning: CL:0000490 - CL:2000023: - text: CL:2000023 - description: spinal cord ventral column interneuron - meaning: CL:2000023 - CL:1000381: - text: CL:1000381 - description: type 1 vestibular sensory cell of epithelium of crista of ampulla - of semicircular duct of membranous labyrinth - meaning: CL:1000381 - CL:0003013: - text: CL:0003013 - description: G10 retinal ganglion cell - meaning: CL:0003013 - CL:0000602: - text: CL:0000602 - description: pressoreceptor cell - meaning: CL:0000602 - CL:4023039: - text: CL:4023039 - description: amygdala excitatory neuron - meaning: CL:4023039 - CL:4030043: - text: CL:4030043 - description: matrix D1 medium spiny neuron - meaning: CL:4030043 - CL:0000105: - text: CL:0000105 - description: pseudounipolar neuron - meaning: CL:0000105 - CL:0004137: - text: CL:0004137 - description: retinal ganglion cell A2 inner - meaning: CL:0004137 - CL:1001436: - text: CL:1001436 - description: hair-tylotrich neuron - meaning: CL:1001436 - CL:1001503: - text: CL:1001503 - description: olfactory bulb tufted cell - meaning: CL:1001503 - CL:0000406: - text: CL:0000406 - description: CNS short range interneuron - meaning: CL:0000406 - CL:2000087: - text: CL:2000087 - description: dentate gyrus of hippocampal formation basket cell - meaning: CL:2000087 - CL:0000534: - text: CL:0000534 - description: primary interneuron (sensu Teleostei) - meaning: CL:0000534 - CL:0000246: - text: CL:0000246 - description: Mauthner neuron - meaning: CL:0000246 - CL:0003027: - text: CL:0003027 - description: retinal ganglion cell D2 - meaning: CL:0003027 - CL:0000752: - text: CL:0000752 - description: cone retinal bipolar cell - meaning: CL:0000752 - CL:0000410: - text: CL:0000410 - description: CNS long range interneuron - meaning: CL:0000410 - CL:0009000: - text: CL:0009000 - description: sensory neuron of spinal nerve - meaning: CL:0009000 - CL:0000754: - text: CL:0000754 - description: type 2 cone bipolar cell (sensu Mus) - meaning: CL:0000754 - CL:0002309: - text: CL:0002309 - description: corticotroph - meaning: CL:0002309 - CL:0010009: - text: CL:0010009 - description: camera-type eye photoreceptor cell - meaning: CL:0010009 - CL:4023069: - text: CL:4023069 - description: medial ganglionic eminence derived GABAergic cortical interneuron - meaning: CL:4023069 - CL:0000102: - text: CL:0000102 - description: polymodal neuron - meaning: CL:0000102 - CL:0000694: - text: CL:0000694 - description: R3 photoreceptor cell - meaning: CL:0000694 - CL:0004183: - text: CL:0004183 - description: retinal ganglion cell B3 - meaning: CL:0004183 - CL:0000693: - text: CL:0000693 - description: neurogliaform cell - meaning: CL:0000693 - CL:0000760: - text: CL:0000760 - description: type 8 cone bipolar cell (sensu Mus) - meaning: CL:0000760 - CL:4023001: - text: CL:4023001 - description: static beta motor neuron - meaning: CL:4023001 - CL:1000424: - text: CL:1000424 - description: chromaffin cell of paraaortic body - meaning: CL:1000424 - CL:0000120: - text: CL:0000120 - description: granule cell - meaning: CL:0000120 - CL:0002312: - text: CL:0002312 - description: somatotroph - meaning: CL:0002312 - CL:0000107: - text: CL:0000107 - description: autonomic neuron - meaning: CL:0000107 - CL:2000047: - text: CL:2000047 - description: brainstem motor neuron - meaning: CL:2000047 - CL:4023080: - text: CL:4023080 - description: stellate L6 intratelencephalic projecting glutamatergic neuron - of the primary motor cortex (Mmus) - meaning: CL:4023080 - CL:0000848: - text: CL:0000848 - description: microvillous olfactory receptor neuron - meaning: CL:0000848 - CL:0004213: - text: CL:0004213 - description: type 3a cone bipolar cell - meaning: CL:0004213 - CL:0000116: - text: CL:0000116 - description: pioneer neuron - meaning: CL:0000116 - CL:4023187: - text: CL:4023187 - description: koniocellular cell - meaning: CL:4023187 - CL:4023116: - text: CL:4023116 - description: type 2 spiral ganglion neuron - meaning: CL:4023116 - CL:0008015: - text: CL:0008015 - description: inhibitory motor neuron - meaning: CL:0008015 - CL:0003048: - text: CL:0003048 - description: L cone cell - meaning: CL:0003048 - CL:1000082: - text: CL:1000082 - description: stretch receptor cell - meaning: CL:1000082 - CL:0003031: - text: CL:0003031 - description: M3-ON retinal ganglion cell - meaning: CL:0003031 - CL:1001474: - text: CL:1001474 - description: medium spiny neuron - meaning: CL:1001474 - CL:0000745: - text: CL:0000745 - description: retina horizontal cell - meaning: CL:0000745 - CL:0002515: - text: CL:0002515 - description: interrenal norepinephrine type cell - meaning: CL:0002515 - CL:2000027: - text: CL:2000027 - description: cerebellum basket cell - meaning: CL:2000027 - CL:0004225: - text: CL:0004225 - description: spider amacrine cell - meaning: CL:0004225 - CL:4023031: - text: CL:4023031 - description: L4 sst GABAergic cortical interneuron (Mmus) - meaning: CL:4023031 - CL:0008038: - text: CL:0008038 - description: alpha motor neuron - meaning: CL:0008038 - CL:4033030: - text: CL:4033030 - description: diffuse bipolar 3b cell - meaning: CL:4033030 - CL:0000336: - text: CL:0000336 - description: adrenal medulla chromaffin cell - meaning: CL:0000336 - CL:0000751: - text: CL:0000751 - description: rod bipolar cell - meaning: CL:0000751 - CL:0008037: - text: CL:0008037 - description: gamma motor neuron - meaning: CL:0008037 - CL:0003028: - text: CL:0003028 - description: M1 retinal ganglion cell - meaning: CL:0003028 - CL:0003016: - text: CL:0003016 - description: G11-OFF retinal ganglion cell - meaning: CL:0003016 - CL:0004239: - text: CL:0004239 - description: wavy bistratified amacrine cell - meaning: CL:0004239 - CL:4023168: - text: CL:4023168 - description: somatosensory neuron - meaning: CL:4023168 - CL:4023018: - text: CL:4023018 - description: pvalb GABAergic cortical interneuron - meaning: CL:4023018 - CL:0004138: - text: CL:0004138 - description: retinal ganglion cell A2 - meaning: CL:0004138 - CL:0000750: - text: CL:0000750 - description: OFF-bipolar cell - meaning: CL:0000750 - CL:0000709: - text: CL:0000709 - description: R8 photoreceptor cell - meaning: CL:0000709 - CL:0004214: - text: CL:0004214 - description: type 3b cone bipolar cell - meaning: CL:0004214 - CL:0003047: - text: CL:0003047 - description: M14 retinal ganglion cell - meaning: CL:0003047 - CL:0015000: - text: CL:0015000 - description: cranial motor neuron - meaning: CL:0015000 - CL:0003036: - text: CL:0003036 - description: M7 retinal ganglion cell - meaning: CL:0003036 - CL:0000397: - text: CL:0000397 - description: ganglion interneuron - meaning: CL:0000397 - CL:1001509: - text: CL:1001509 - description: glycinergic neuron - meaning: CL:1001509 - CL:4023038: - text: CL:4023038 - description: L6b glutamatergic cortical neuron - meaning: CL:4023038 - CL:0000112: - text: CL:0000112 - description: columnar neuron - meaning: CL:0000112 - CL:0002517: - text: CL:0002517 - description: interrenal epinephrin secreting cell - meaning: CL:0002517 - CL:1000383: - text: CL:1000383 - description: type 2 vestibular sensory cell of epithelium of macula of utricle - of membranous labyrinth - meaning: CL:1000383 - CL:0004116: - text: CL:0004116 - description: retinal ganglion cell C - meaning: CL:0004116 - CL:4023113: - text: CL:4023113 - description: bouton vestibular afferent neuron - meaning: CL:4023113 - CL:0003034: - text: CL:0003034 - description: M5 retinal ganglion cell - meaning: CL:0003034 - CL:0011005: - text: CL:0011005 - description: GABAergic interneuron - meaning: CL:0011005 - CL:0011105: - text: CL:0011105 - description: dopamanergic interplexiform cell - meaning: CL:0011105 - CL:0000749: - text: CL:0000749 - description: ON-bipolar cell - meaning: CL:0000749 - CL:0000498: - text: CL:0000498 - description: inhibitory interneuron - meaning: CL:0000498 - CL:4023071: - text: CL:4023071 - description: L5/6 cck cortical GABAergic interneuron (Mmus) - meaning: CL:4023071 - CL:1000245: - text: CL:1000245 - description: posterior lateral line ganglion neuron - meaning: CL:1000245 - CL:0004139: - text: CL:0004139 - description: retinal ganglion cell A2 outer - meaning: CL:0004139 - CL:0000531: - text: CL:0000531 - description: primary sensory neuron (sensu Teleostei) - meaning: CL:0000531 - CL:0004125: - text: CL:0004125 - description: retinal ganglion cell C2 inner - meaning: CL:0004125 - CL:4023064: - text: CL:4023064 - description: caudal ganglionic eminence derived interneuron - meaning: CL:4023064 - CL:4030049: - text: CL:4030049 - description: striosomal D2 medium spiny neuron - meaning: CL:4030049 - CL:0017002: - text: CL:0017002 - description: prostate neuroendocrine cell - meaning: CL:0017002 - CL:0000756: - text: CL:0000756 - description: type 4 cone bipolar cell (sensu Mus) - meaning: CL:0000756 - CL:0000707: - text: CL:0000707 - description: R7 photoreceptor cell - meaning: CL:0000707 - CL:0000700: - text: CL:0000700 - description: dopaminergic neuron - meaning: CL:0000700 - CL:0003002: - text: CL:0003002 - description: G1 retinal ganglion cell - meaning: CL:0003002 - CL:1000001: - text: CL:1000001 - description: retrotrapezoid nucleus neuron - meaning: CL:1000001 - CL:4023007: - text: CL:4023007 - description: L2/3 bipolar vip GABAergic cortical interneuron (Mmus) - meaning: CL:4023007 - CL:0000528: - text: CL:0000528 - description: nitrergic neuron - meaning: CL:0000528 - CL:0000639: - text: CL:0000639 - description: basophil cell of pars distalis of adenohypophysis - meaning: CL:0000639 - CL:0000849: - text: CL:0000849 - description: crypt olfactory receptor neuron - meaning: CL:0000849 - CL:0011110: - text: CL:0011110 - description: histaminergic neuron - meaning: CL:0011110 - CL:0005025: - text: CL:0005025 - description: visceromotor neuron - meaning: CL:0005025 - CL:0003001: - text: CL:0003001 - description: bistratified retinal ganglion cell - meaning: CL:0003001 - CL:0004241: - text: CL:0004241 - description: WF2 amacrine cell - meaning: CL:0004241 - CL:4023019: - text: CL:4023019 - description: L5/6 cck, vip cortical GABAergic interneuron (Mmus) - meaning: CL:4023019 - CL:4023040: - text: CL:4023040 - description: L2/3-6 intratelencephalic projecting glutamatergic cortical neuron - meaning: CL:4023040 - CL:1001435: - text: CL:1001435 - description: periglomerular cell - meaning: CL:1001435 - CL:4023127: - text: CL:4023127 - description: arcuate nucleus of hypothalamus KNDy neuron - meaning: CL:4023127 - CL:0003007: - text: CL:0003007 - description: G4-OFF retinal ganglion cell - meaning: CL:0003007 - CL:0000101: - text: CL:0000101 - description: sensory neuron - meaning: CL:0000101 - CL:2000097: - text: CL:2000097 - description: midbrain dopaminergic neuron - meaning: CL:2000097 - CL:4023095: - text: CL:4023095 - description: untufted pyramidal neuron - meaning: CL:4023095 - CL:0003004: - text: CL:0003004 - description: G3 retinal ganglion cell - meaning: CL:0003004 - CL:0000527: - text: CL:0000527 - description: efferent neuron - meaning: CL:0000527 - CL:1000382: - text: CL:1000382 - description: type 2 vestibular sensory cell of stato-acoustic epithelium - meaning: CL:1000382 - CL:4033019: - text: CL:4033019 - description: ON-blue cone bipolar cell - meaning: CL:4033019 - CL:0000589: - text: CL:0000589 - description: cochlear inner hair cell - meaning: CL:0000589 - CL:4023160: - text: CL:4023160 - description: cartwheel cell - meaning: CL:4023160 - CL:1001437: - text: CL:1001437 - description: hair-down neuron - meaning: CL:1001437 - CL:0011102: - text: CL:0011102 - description: parasympathetic neuron - meaning: CL:0011102 - CL:2000029: - text: CL:2000029 - description: central nervous system neuron - meaning: CL:2000029 - CL:4023115: - text: CL:4023115 - description: type 1 spiral ganglion neuron - meaning: CL:4023115 - CL:0002311: - text: CL:0002311 - description: mammotroph - meaning: CL:0002311 - CL:0003025: - text: CL:0003025 - description: retinal ganglion cell C3 - meaning: CL:0003025 - CL:4030050: - text: CL:4030050 - description: D1/D2-hybrid medium spiny neuron - meaning: CL:4030050 - CL:4023118: - text: CL:4023118 - description: L5/6 non-Martinotti sst GABAergic cortical interneuron (Mmus) - meaning: CL:4023118 - CL:4023110: - text: CL:4023110 - description: amygdala pyramidal neuron - meaning: CL:4023110 - CL:0002273: - text: CL:0002273 - description: type ECL enteroendocrine cell - meaning: CL:0002273 - CL:0003050: - text: CL:0003050 - description: S cone cell - meaning: CL:0003050 - CL:4023121: - text: CL:4023121 - description: sst chodl GABAergic cortical interneuron - meaning: CL:4023121 - CL:4023020: - text: CL:4023020 - description: dynamic gamma motor neuron - meaning: CL:4023020 - CL:0004246: - text: CL:0004246 - description: monostratified cell - meaning: CL:0004246 - CL:0000495: - text: CL:0000495 - description: blue sensitive photoreceptor cell - meaning: CL:0000495 - CL:0000029: - text: CL:0000029 - description: neural crest derived neuron - meaning: CL:0000029 - CL:0004001: - text: CL:0004001 - description: local interneuron - meaning: CL:0004001 - CL:0000551: - text: CL:0000551 - description: unimodal nocireceptor - meaning: CL:0000551 - CL:0003006: - text: CL:0003006 - description: G4-ON retinal ganglion cell - meaning: CL:0003006 - CL:4023011: - text: CL:4023011 - description: lamp5 GABAergic cortical interneuron - meaning: CL:4023011 - CL:4023109: - text: CL:4023109 - description: vasopressin-secreting magnocellular cell - meaning: CL:4023109 - CL:0000121: - text: CL:0000121 - description: Purkinje cell - meaning: CL:0000121 - CL:0000678: - text: CL:0000678 - description: commissural neuron - meaning: CL:0000678 - CL:0004252: - text: CL:0004252 - description: medium field retinal amacrine cell - meaning: CL:0004252 - CL:0000103: - text: CL:0000103 - description: bipolar neuron - meaning: CL:0000103 - CL:4033036: - text: CL:4033036 - description: OFFx cell - meaning: CL:4033036 - CL:4023014: - text: CL:4023014 - description: L5 vip cortical GABAergic interneuron (Mmus) - meaning: CL:4023014 - CL:0008031: - text: CL:0008031 - description: cortical interneuron - meaning: CL:0008031 - CL:0008010: - text: CL:0008010 - description: cranial somatomotor neuron - meaning: CL:0008010 - CL:0000637: - text: CL:0000637 - description: chromophil cell of anterior pituitary gland - meaning: CL:0000637 - CL:0003014: - text: CL:0003014 - description: G11 retinal ganglion cell - meaning: CL:0003014 - CL:4033029: - text: CL:4033029 - description: diffuse bipolar 3a cell - meaning: CL:4033029 - CL:0002611: - text: CL:0002611 - description: neuron of the dorsal spinal cord - meaning: CL:0002611 - CL:0010010: - text: CL:0010010 - description: cerebellar stellate cell - meaning: CL:0010010 - CL:1000465: - text: CL:1000465 - description: chromaffin cell of ovary - meaning: CL:1000465 - CL:0000761: - text: CL:0000761 - description: type 9 cone bipolar cell (sensu Mus) - meaning: CL:0000761 - CL:0004226: - text: CL:0004226 - description: monostratified amacrine cell - meaning: CL:0004226 - CL:0004253: - text: CL:0004253 - description: wide field retinal amacrine cell - meaning: CL:0004253 - CL:4023075: - text: CL:4023075 - description: L6 tyrosine hydroxylase sst GABAergic cortical interneuron (Mmus) - meaning: CL:4023075 - CL:4023068: - text: CL:4023068 - description: thalamic excitatory neuron - meaning: CL:4023068 - CL:1000377: - text: CL:1000377 - description: dense-core granulated cell of epithelium of trachea - meaning: CL:1000377 - CL:4023089: - text: CL:4023089 - description: nest basket cell - meaning: CL:4023089 - CL:4023189: - text: CL:4023189 - description: parasol ganglion cell of retina - meaning: CL:4023189 - CL:0000856: - text: CL:0000856 - description: neuromast hair cell - meaning: CL:0000856 - CL:4023025: - text: CL:4023025 - description: long-range projecting sst GABAergic cortical interneuron (Mmus) - meaning: CL:4023025 - CL:0003043: - text: CL:0003043 - description: M10 retinal ganglion cell - meaning: CL:0003043 - CL:4023000: - text: CL:4023000 - description: beta motor neuron - meaning: CL:4023000 - CL:4023048: - text: CL:4023048 - description: L4/5 intratelencephalic projecting glutamatergic neuron of the - primary motor cortex - meaning: CL:4023048 - CL:0000855: - text: CL:0000855 - description: sensory hair cell - meaning: CL:0000855 - CL:4023070: - text: CL:4023070 - description: caudal ganglionic eminence derived GABAergic cortical interneuron - meaning: CL:4023070 - CL:0002070: - text: CL:0002070 - description: type I vestibular sensory cell - meaning: CL:0002070 - CL:2000028: - text: CL:2000028 - description: cerebellum glutamatergic neuron - meaning: CL:2000028 - CL:0000533: - text: CL:0000533 - description: primary motor neuron (sensu Teleostei) - meaning: CL:0000533 - CL:4023083: - text: CL:4023083 - description: chandelier cell - meaning: CL:4023083 - CL:2000034: - text: CL:2000034 - description: anterior lateral line neuromast hair cell - meaning: CL:2000034 - CL:0003015: - text: CL:0003015 - description: G11-ON retinal ganglion cell - meaning: CL:0003015 - CL:0000204: - text: CL:0000204 - description: acceleration receptive cell - meaning: CL:0000204 - CL:4033031: - text: CL:4033031 - description: diffuse bipolar 4 cell - meaning: CL:4033031 - CL:0003024: - text: CL:0003024 - description: retinal ganglion cell C inner - meaning: CL:0003024 - CL:4023074: - text: CL:4023074 - description: mammillary body neuron - meaning: CL:4023074 - CL:2000089: - text: CL:2000089 - description: dentate gyrus granule cell - meaning: CL:2000089 - CL:4033028: - text: CL:4033028 - description: diffuse bipolar 2 cell - meaning: CL:4033028 - CL:0000110: - text: CL:0000110 - description: peptidergic neuron - meaning: CL:0000110 - CL:4033002: - text: CL:4033002 - description: neuroendocrine cell of epithelium of crypt of Lieberkuhn - meaning: CL:4033002 - CL:4033027: - text: CL:4033027 - description: diffuse bipolar 1 cell - meaning: CL:4033027 - CL:3000003: - text: CL:3000003 - description: sympathetic cholinergic neuron - meaning: CL:3000003 - CL:4023158: - text: CL:4023158 - description: octopus cell of the mammalian cochlear nucleus - meaning: CL:4023158 - CL:0000118: - text: CL:0000118 - description: basket cell - meaning: CL:0000118 - CL:0004223: - text: CL:0004223 - description: AB diffuse-1 amacrine cell - meaning: CL:0004223 - CL:4030054: - text: CL:4030054 - description: RXFP1-positive interface island D1-medium spiny neuron - meaning: CL:4030054 - CL:0002610: - text: CL:0002610 - description: raphe nuclei neuron - meaning: CL:0002610 - CL:4023026: - text: CL:4023026 - description: direct pathway medium spiny neuron - meaning: CL:4023026 - CL:4023016: - text: CL:4023016 - description: vip GABAergic cortical interneuron - meaning: CL:4023016 - CL:0004237: - text: CL:0004237 - description: fountain amacrine cell - meaning: CL:0004237 - CL:0003035: - text: CL:0003035 - description: M6 retinal ganglion cell - meaning: CL:0003035 - CL:1001611: - text: CL:1001611 - description: cerebellar neuron - meaning: CL:1001611 - CL:0000591: - text: CL:0000591 - description: warmth sensing thermoreceptor cell - meaning: CL:0000591 - CL:0002613: - text: CL:0002613 - description: striatum neuron - meaning: CL:0002613 - CL:0000496: - text: CL:0000496 - description: green sensitive photoreceptor cell - meaning: CL:0000496 - CL:0007011: - text: CL:0007011 - description: enteric neuron - meaning: CL:0007011 - CL:2000056: - text: CL:2000056 - description: Meynert cell - meaning: CL:2000056 - CL:0003040: - text: CL:0003040 - description: M9 retinal ganglion cell - meaning: CL:0003040 - CL:0004250: - text: CL:0004250 - description: bistratified retinal amacrine cell - meaning: CL:0004250 - CL:0003029: - text: CL:0003029 - description: M2 retinal ganglion cell - meaning: CL:0003029 - CL:4023017: - text: CL:4023017 - description: sst GABAergic cortical interneuron - meaning: CL:4023017 - CL:0008028: - text: CL:0008028 - description: visual system neuron - meaning: CL:0008028 - CL:0008039: - text: CL:0008039 - description: lower motor neuron - meaning: CL:0008039 - CL:2000086: - text: CL:2000086 - description: neocortex basket cell - meaning: CL:2000086 - CL:4023023: - text: CL:4023023 - description: L5,6 neurogliaform lamp5 GABAergic cortical interneuron (Mmus) - meaning: CL:4023023 - CL:0000697: - text: CL:0000697 - description: R4 photoreceptor cell - meaning: CL:0000697 - CL:2000088: - text: CL:2000088 - description: Ammon's horn basket cell - meaning: CL:2000088 - CL:0004232: - text: CL:0004232 - description: starburst amacrine cell - meaning: CL:0004232 - CL:4023041: - text: CL:4023041 - description: L5 extratelencephalic projecting glutamatergic cortical neuron - meaning: CL:4023041 - CL:0004121: - text: CL:0004121 - description: retinal ganglion cell B2 - meaning: CL:0004121 - CL:0000748: - text: CL:0000748 - description: retinal bipolar neuron - meaning: CL:0000748 - CL:4023164: - text: CL:4023164 - description: globular bushy cell - meaning: CL:4023164 - CL:0000536: - text: CL:0000536 - description: secondary motor neuron (sensu Teleostei) - meaning: CL:0000536 - CL:1000466: - text: CL:1000466 - description: chromaffin cell of right ovary - meaning: CL:1000466 - CL:0011001: - text: CL:0011001 - description: spinal cord motor neuron - meaning: CL:0011001 - CL:0000755: - text: CL:0000755 - description: type 3 cone bipolar cell (sensu Mus) - meaning: CL:0000755 - CL:0004238: - text: CL:0004238 - description: asymmetric bistratified amacrine cell - meaning: CL:0004238 - CL:0004161: - text: CL:0004161 - description: 510 nm-cone - meaning: CL:0004161 - CL:0000198: - text: CL:0000198 - description: pain receptor cell - meaning: CL:0000198 - CL:0003038: - text: CL:0003038 - description: M7-OFF retinal ganglion cell - meaning: CL:0003038 - CL:0003033: - text: CL:0003033 - description: M4 retinal ganglion cell - meaning: CL:0003033 - CL:0012001: - text: CL:0012001 - description: neuron of the forebrain - meaning: CL:0012001 - CL:0011104: - text: CL:0011104 - description: interplexiform cell - meaning: CL:0011104 - CL:0003049: - text: CL:0003049 - description: M cone cell - meaning: CL:0003049 - CL:2000032: - text: CL:2000032 - description: peripheral nervous system neuron - meaning: CL:2000032 - CL:0011100: - text: CL:0011100 - description: galanergic neuron - meaning: CL:0011100 - CL:0008025: - text: CL:0008025 - description: noradrenergic neuron - meaning: CL:0008025 - CL:0000122: - text: CL:0000122 - description: stellate neuron - meaning: CL:0000122 - CL:0003005: - text: CL:0003005 - description: G4 retinal ganglion cell - meaning: CL:0003005 - CL:0000699: - text: CL:0000699 - description: paraganglial type 1 cell - meaning: CL:0000699 - CL:4033050: - text: CL:4033050 - description: catecholaminergic neuron - meaning: CL:4033050 - CL:1001502: - text: CL:1001502 - description: mitral cell - meaning: CL:1001502 - CL:0002069: - text: CL:0002069 - description: type II vestibular sensory cell - meaning: CL:0002069 - CL:4023065: - text: CL:4023065 - description: meis2 expressing cortical GABAergic cell - meaning: CL:4023065 - CL:4023077: - text: CL:4023077 - description: bitufted neuron - meaning: CL:4023077 - CL:0000847: - text: CL:0000847 - description: ciliated olfactory receptor neuron - meaning: CL:0000847 - CL:4023188: - text: CL:4023188 - description: midget ganglion cell of retina - meaning: CL:4023188 - CL:2000090: - text: CL:2000090 - description: dentate gyrus of hippocampal formation stellate cell - meaning: CL:2000090 - CL:0000568: - text: CL:0000568 - description: amine precursor uptake and decarboxylation cell - meaning: CL:0000568 - CL:1000426: - text: CL:1000426 - description: chromaffin cell of adrenal gland - meaning: CL:1000426 - CL:0000100: - text: CL:0000100 - description: motor neuron - meaning: CL:0000100 - CL:0011109: - text: CL:0011109 - description: hypocretin-secreting neuron - meaning: CL:0011109 - CL:4023171: - text: CL:4023171 - description: trigeminal motor neuron - meaning: CL:4023171 - CL:1001434: - text: CL:1001434 - description: olfactory bulb interneuron - meaning: CL:1001434 - CL:0000494: - text: CL:0000494 - description: UV sensitive photoreceptor cell - meaning: CL:0000494 - CL:0004117: - text: CL:0004117 - description: retinal ganglion cell A - meaning: CL:0004117 - CL:0000205: - text: CL:0000205 - description: thermoreceptor cell - meaning: CL:0000205 - CL:0004217: - text: CL:0004217 - description: H1 horizontal cell - meaning: CL:0004217 - CL:0000200: - text: CL:0000200 - description: touch receptor cell - meaning: CL:0000200 - CL:4023111: - text: CL:4023111 - description: cerebral cortex pyramidal neuron - meaning: CL:4023111 - CL:4032001: - text: CL:4032001 - description: reelin GABAergic cortical interneuron - meaning: CL:4032001 - CL:4023076: - text: CL:4023076 - description: Martinotti neuron - meaning: CL:4023076 - CL:0000753: - text: CL:0000753 - description: type 1 cone bipolar cell (sensu Mus) - meaning: CL:0000753 - CL:1001451: - text: CL:1001451 - description: sensory neuron of dorsal root ganglion - meaning: CL:1001451 - CL:4023021: - text: CL:4023021 - description: static gamma motor neuron - meaning: CL:4023021 - CL:0002066: - text: CL:0002066 - description: Feyrter cell - meaning: CL:0002066 - CL:0000598: - text: CL:0000598 - description: pyramidal neuron - meaning: CL:0000598 - CL:0000702: - text: CL:0000702 - description: R5 photoreceptor cell - meaning: CL:0000702 - CL:0008049: - text: CL:0008049 - description: Betz cell - meaning: CL:0008049 - CL:0001033: - text: CL:0001033 - description: hippocampal granule cell - meaning: CL:0001033 - CL:0000587: - text: CL:0000587 - description: cold sensing thermoreceptor cell - meaning: CL:0000587 - CL:4023161: - text: CL:4023161 - description: unipolar brush cell - meaning: CL:4023161 - CL:2000031: - text: CL:2000031 - description: lateral line ganglion neuron - meaning: CL:2000031 - CL:4023119: - text: CL:4023119 - description: displaced amacrine cell - meaning: CL:4023119 - CL:1001569: - text: CL:1001569 - description: hippocampal interneuron - meaning: CL:1001569 - CL:4023130: - text: CL:4023130 - description: kisspeptin neuron - meaning: CL:4023130 - CL:4023090: - text: CL:4023090 - description: small basket cell - meaning: CL:4023090 - CL:4023033: - text: CL:4023033 - description: OFF retinal ganglion cell - meaning: CL:4023033 - CL:4023112: - text: CL:4023112 - description: vestibular afferent neuron - meaning: CL:4023112 - CL:0004234: - text: CL:0004234 - description: diffuse multistratified amacrine cell - meaning: CL:0004234 - CL:0002082: - text: CL:0002082 - description: type II cell of adrenal medulla - meaning: CL:0002082 - CL:0010011: - text: CL:0010011 - description: cerebral cortex GABAergic interneuron - meaning: CL:0010011 - CL:4030052: - text: CL:4030052 - description: nucleus accumbens shell and olfactory tubercle D2 medium spiny - neuron - meaning: CL:4030052 - CL:0000604: - text: CL:0000604 - description: retinal rod cell - meaning: CL:0000604 - CL:4030027: - text: CL:4030027 - description: GABAergic amacrine cell - meaning: CL:4030027 - CL:1001561: - text: CL:1001561 - description: vomeronasal sensory neuron - meaning: CL:1001561 - CL:0000210: - text: CL:0000210 - description: photoreceptor cell - meaning: CL:0000210 - CL:4023012: - text: CL:4023012 - description: near-projecting glutamatergic cortical neuron - meaning: CL:4023012 - CL:4023087: - text: CL:4023087 - description: fan Martinotti neuron - meaning: CL:4023087 - CL:0000028: - text: CL:0000028 - description: CNS neuron (sensu Nematoda and Protostomia) - meaning: CL:0000028 - CL:0000006: - text: CL:0000006 - description: neuronal receptor cell - meaning: CL:0000006 - CL:0004247: - text: CL:0004247 - description: bistratified cell - meaning: CL:0004247 - CL:0010012: - text: CL:0010012 - description: cerebral cortex neuron - meaning: CL:0010012 - CL:0004245: - text: CL:0004245 - description: indoleamine-accumulating amacrine cell - meaning: CL:0004245 - CL:0004224: - text: CL:0004224 - description: AB diffuse-2 amacrine cell - meaning: CL:0004224 - CL:0003009: - text: CL:0003009 - description: G6 retinal ganglion cell - meaning: CL:0003009 - CL:0000679: - text: CL:0000679 - description: glutamatergic neuron - meaning: CL:0000679 - CL:0000166: - text: CL:0000166 - description: chromaffin cell - meaning: CL:0000166 - CL:4023088: - text: CL:4023088 - description: large basket cell - meaning: CL:4023088 - CL:4030057: - text: CL:4030057 - description: eccentric medium spiny neuron - meaning: CL:4030057 - CL:4023024: - text: CL:4023024 - description: neurogliaform lamp5 GABAergic cortical interneuron (Mmus) - meaning: CL:4023024 - CL:0005024: - text: CL:0005024 - description: somatomotor neuron - meaning: CL:0005024 - CL:4023049: - text: CL:4023049 - description: L5 intratelencephalic projecting glutamatergic neuron of the - primary motor cortex - meaning: CL:4023049 - CL:0000573: - text: CL:0000573 - description: retinal cone cell - meaning: CL:0000573 - CL:4023123: - text: CL:4023123 - description: hypothalamus kisspeptin neuron - meaning: CL:4023123 - CL:0000376: - text: CL:0000376 - description: humidity receptor cell - meaning: CL:0000376 - CL:0004235: - text: CL:0004235 - description: AB broad diffuse-1 amacrine cell - meaning: CL:0004235 - CL:0000106: - text: CL:0000106 - description: unipolar neuron - meaning: CL:0000106 - CL:0001032: - text: CL:0001032 - description: cortical granule cell - meaning: CL:0001032 - CL:0000561: - text: CL:0000561 - description: amacrine cell - meaning: CL:0000561 - CL:4023093: - text: CL:4023093 - description: stellate pyramidal neuron - meaning: CL:4023093 - CL:0000247: - text: CL:0000247 - description: Rohon-Beard neuron - meaning: CL:0000247 - CL:0003008: - text: CL:0003008 - description: G5 retinal ganglion cell - meaning: CL:0003008 - CL:0000203: - text: CL:0000203 - description: gravity sensitive cell - meaning: CL:0000203 - CL:0003037: - text: CL:0003037 - description: M7-ON retinal ganglion cell - meaning: CL:0003037 - CL:0004221: - text: CL:0004221 - description: flag A amacrine cell - meaning: CL:0004221 - CL:0000638: - text: CL:0000638 - description: acidophil cell of pars distalis of adenohypophysis - meaning: CL:0000638 - CL:0004229: - text: CL:0004229 - description: A2-like amacrine cell - meaning: CL:0004229 - CL:4023120: - text: CL:4023120 - description: cochlea auditory hair cell - meaning: CL:4023120 - CL:0008032: - text: CL:0008032 - description: rosehip neuron - meaning: CL:0008032 - CL:0008027: - text: CL:0008027 - description: rod bipolar cell (sensu Mus) - meaning: CL:0008027 - CL:0000497: - text: CL:0000497 - description: red sensitive photoreceptor cell - meaning: CL:0000497 - CL:4023062: - text: CL:4023062 - description: dentate gyrus neuron - meaning: CL:4023062 - CL:0002516: - text: CL:0002516 - description: interrenal chromaffin cell - meaning: CL:0002516 - CL:0004119: - text: CL:0004119 - description: retinal ganglion cell B1 - meaning: CL:0004119 - CL:4030039: - text: CL:4030039 - description: von Economo neuron - meaning: CL:4030039 - CL:4023036: - text: CL:4023036 - description: chandelier pvalb GABAergic cortical interneuron - meaning: CL:4023036 - CL:0000117: - text: CL:0000117 - description: CNS neuron (sensu Vertebrata) - meaning: CL:0000117 - CL:4023015: - text: CL:4023015 - description: sncg GABAergic cortical interneuron - meaning: CL:4023015 - CL:4033033: - text: CL:4033033 - description: flat midget bipolar cell - meaning: CL:4033033 - CL:0000626: - text: CL:0000626 - description: olfactory granule cell - meaning: CL:0000626 - CL:0004218: - text: CL:0004218 - description: H2 horizontal cell - meaning: CL:0004218 - CL:0004233: - text: CL:0004233 - description: DAPI-3 amacrine cell - meaning: CL:0004233 - CL:0003021: - text: CL:0003021 - description: retinal ganglion cell C4 - meaning: CL:0003021 - CL:0000489: - text: CL:0000489 - description: scotopic photoreceptor cell - meaning: CL:0000489 - CL:4023159: - text: CL:4023159 - description: double bouquet cell - meaning: CL:4023159 - CL:0002612: - text: CL:0002612 - description: neuron of the ventral spinal cord - meaning: CL:0002612 - CL:0000476: - text: CL:0000476 - description: thyrotroph - meaning: CL:0000476 - CL:4033034: - text: CL:4033034 - description: invaginating midget bipolar cell - meaning: CL:4033034 - CL:4023029: - text: CL:4023029 - description: indirect pathway medium spiny neuron - meaning: CL:4023029 - CL:0004236: - text: CL:0004236 - description: AB broad diffuse-2 amacrine cell - meaning: CL:0004236 - CL:0003017: - text: CL:0003017 - description: retinal ganglion cell B3 outer - meaning: CL:0003017 - CL:0000759: - text: CL:0000759 - description: type 7 cone bipolar cell (sensu Mus) - meaning: CL:0000759 - CL:0000740: - text: CL:0000740 - description: retinal ganglion cell - meaning: CL:0000740 - CL:0004120: - text: CL:0004120 - description: retinal ganglion cell A1 - meaning: CL:0004120 - CL:3000002: - text: CL:3000002 - description: sympathetic noradrenergic neuron - meaning: CL:3000002 - CL:0003023: - text: CL:0003023 - description: retinal ganglion cell C6 - meaning: CL:0003023 - CL:0000690: - text: CL:0000690 - description: R2 photoreceptor cell - meaning: CL:0000690 - CL:4023047: - text: CL:4023047 - description: L2/3 intratelencephalic projecting glutamatergic neuron of the - primary motor cortex - meaning: CL:4023047 - CL:4023022: - text: CL:4023022 - description: canopy lamp5 GABAergic cortical interneuron (Mmus) - meaning: CL:4023022 - CL:4023060: - text: CL:4023060 - description: hippocampal CA1-3 neuron - meaning: CL:4023060 - CL:0000758: - text: CL:0000758 - description: type 6 cone bipolar cell (sensu Mus) - meaning: CL:0000758 - CL:0000535: - text: CL:0000535 - description: secondary neuron (sensu Teleostei) - meaning: CL:0000535 - CL:4023055: - text: CL:4023055 - description: corticothalamic VAL/VM projecting glutamatergic neuron of the - primary motor cortex - meaning: CL:4023055 - CL:1000467: - text: CL:1000467 - description: chromaffin cell of left ovary - meaning: CL:1000467 - CL:0011002: - text: CL:0011002 - description: lateral motor column neuron - meaning: CL:0011002 - CL:0004244: - text: CL:0004244 - description: WF4 amacrine cell - meaning: CL:0004244 - CL:1000223: - text: CL:1000223 - description: lung neuroendocrine cell - meaning: CL:1000223 - CL:1000385: - text: CL:1000385 - description: type 2 vestibular sensory cell of epithelium of crista of ampulla - of semicircular duct of membranous labyrinth - meaning: CL:1000385 - CL:0000691: - text: CL:0000691 - description: stellate interneuron - meaning: CL:0000691 - CL:4023008: - text: CL:4023008 - description: intratelencephalic-projecting glutamatergic cortical neuron - meaning: CL:4023008 - CL:4023044: - text: CL:4023044 - description: non-medulla, extratelencephalic-projecting glutamatergic neuron - of the primary motor cortex - meaning: CL:4023044 - CL:0000850: - text: CL:0000850 - description: serotonergic neuron - meaning: CL:0000850 - CL:0000695: - text: CL:0000695 - description: Cajal-Retzius cell - meaning: CL:0000695 - CL:0003051: - text: CL:0003051 - description: UV cone cell - meaning: CL:0003051 - CL:0000402: - text: CL:0000402 - description: CNS interneuron - meaning: CL:0000402 - CL:0005023: - text: CL:0005023 - description: branchiomotor neuron - meaning: CL:0005023 - CL:4023043: - text: CL:4023043 - description: L5/6 near-projecting glutamatergic neuron of the primary motor - cortex - meaning: CL:4023043 - CL:0004162: - text: CL:0004162 - description: 360 nm-cone - meaning: CL:0004162 - CL:0011003: - text: CL:0011003 - description: magnocellular neurosecretory cell - meaning: CL:0011003 - CL:0004230: - text: CL:0004230 - description: diffuse bistratified amacrine cell - meaning: CL:0004230 - CL:1001505: - text: CL:1001505 - description: parvocellular neurosecretory cell - meaning: CL:1001505 - CL:0011106: - text: CL:0011106 - description: GABAnergic interplexiform cell - meaning: CL:0011106 - CL:0000437: - text: CL:0000437 - description: gonadtroph - meaning: CL:0000437 - CL:4023010: - text: CL:4023010 - description: alpha7 GABAergic cortical interneuron (Mmus) - meaning: CL:4023010 - CL:4023046: - text: CL:4023046 - description: L6b subplate glutamatergic neuron of the primary motor cortex - meaning: CL:4023046 - CL:0000109: - text: CL:0000109 - description: adrenergic neuron - meaning: CL:0000109 - CL:0011000: - text: CL:0011000 - description: dorsal horn interneuron - meaning: CL:0011000 - CL:0000251: - text: CL:0000251 - description: extramedullary cell - meaning: CL:0000251 - CL:0003044: - text: CL:0003044 - description: M11 retinal ganglion cell - meaning: CL:0003044 - CL:4023053: - text: CL:4023053 - description: spinal interneuron synapsing Betz cell - meaning: CL:4023053 - CL:1000378: - text: CL:1000378 - description: type 1 vestibular sensory cell of stato-acoustic epithelium - meaning: CL:1000378 - CL:4023124: - text: CL:4023124 - description: dentate gyrus kisspeptin neuron - meaning: CL:4023124 - CL:1000427: - text: CL:1000427 - description: adrenal cortex chromaffin cell - meaning: CL:1000427 - CL:0000207: - text: CL:0000207 - description: olfactory receptor cell - meaning: CL:0000207 - CL:4023162: - text: CL:4023162 - description: bushy cell - meaning: CL:4023162 - CL:2000019: - text: CL:2000019 - description: compound eye photoreceptor cell - meaning: CL:2000019 - CL:4023086: - text: CL:4023086 - description: T Martinotti neuron - meaning: CL:4023086 - CL:0003012: - text: CL:0003012 - description: G9 retinal ganglion cell - meaning: CL:0003012 - CL:0002270: - text: CL:0002270 - description: type EC2 enteroendocrine cell - meaning: CL:0002270 - CL:2000024: - text: CL:2000024 - description: spinal cord medial motor column neuron - meaning: CL:2000024 - CL:0003022: - text: CL:0003022 - description: retinal ganglion cell C5 - meaning: CL:0003022 - CL:0000104: - text: CL:0000104 - description: multipolar neuron - meaning: CL:0000104 - CL:4023050: - text: CL:4023050 - description: L6 intratelencephalic projecting glutamatergic neuron of the - primary motor cortex - meaning: CL:4023050 - CL:4023030: - text: CL:4023030 - description: L2/3/5 fan Martinotti sst GABAergic cortical interneuron (Mmus) - meaning: CL:4023030 - CL:0000741: - text: CL:0000741 - description: spinal accessory motor neuron - meaning: CL:0000741 - CL:4033010: - text: CL:4033010 - description: neuroendocrine cell of epithelium of lobar bronchus - meaning: CL:4033010 - CL:1000425: - text: CL:1000425 - description: chromaffin cell of paraganglion - meaning: CL:1000425 - CL:4030051: - text: CL:4030051 - description: nucleus accumbens shell and olfactory tubercle D1 medium spiny - neuron - meaning: CL:4030051 - CL:0000567: - text: CL:0000567 - description: polymodal nocireceptor - meaning: CL:0000567 - CL:0004215: - text: CL:0004215 - description: type 5a cone bipolar cell - meaning: CL:0004215 - CL:0003032: - text: CL:0003032 - description: M3-OFF retinal ganglion cell - meaning: CL:0003032 - CL:4023079: - text: CL:4023079 - description: midbrain-derived inhibitory neuron - meaning: CL:4023079 - CL:0000099: - text: CL:0000099 - description: interneuron - meaning: CL:0000099 - CL:0000253: - text: CL:0000253 - description: eurydendroid cell - meaning: CL:0000253 - CL:0008013: - text: CL:0008013 - description: cranial visceromotor neuron - meaning: CL:0008013 - CL:0005000: - text: CL:0005000 - description: spinal cord interneuron - meaning: CL:0005000 - CL:0004222: - text: CL:0004222 - description: flag B amacrine cell - meaning: CL:0004222 - CL:0000617: - text: CL:0000617 - description: GABAergic neuron - meaning: CL:0000617 - CL:0003010: - text: CL:0003010 - description: G7 retinal ganglion cell - meaning: CL:0003010 - CL:0000577: - text: CL:0000577 - description: type EC enteroendocrine cell - meaning: CL:0000577 - CL:0003018: - text: CL:0003018 - description: retinal ganglion cell B3 inner - meaning: CL:0003018 - CL:0002083: - text: CL:0002083 - description: type I cell of adrenal medulla - meaning: CL:0002083 - CL:4023081: - text: CL:4023081 - description: inverted L6 intratelencephalic projecting glutamatergic neuron - of the primary motor cortex (Mmus) - meaning: CL:4023081 - CL:0004251: - text: CL:0004251 - description: narrow field retinal amacrine cell - meaning: CL:0004251 - CL:4023092: - text: CL:4023092 - description: inverted pyramidal neuron - meaning: CL:4023092 - CL:0002608: - text: CL:0002608 - description: hippocampal neuron - meaning: CL:0002608 - CL:0008048: - text: CL:0008048 - description: upper motor neuron - meaning: CL:0008048 - CL:0011113: - text: CL:0011113 - description: spiral ganglion neuron - meaning: CL:0011113 - CL:0000601: - text: CL:0000601 - description: cochlear outer hair cell - meaning: CL:0000601 - CL:0003041: - text: CL:0003041 - description: M9-ON retinal ganglion cell - meaning: CL:0003041 - CL:4023042: - text: CL:4023042 - description: L6 corticothalamic-projecting glutamatergic cortical neuron - meaning: CL:4023042 - CL:0000199: - text: CL:0000199 - description: mechanoreceptor cell - meaning: CL:0000199 - CL:1001571: - text: CL:1001571 - description: hippocampal pyramidal neuron - meaning: CL:1001571 - CL:2000048: - text: CL:2000048 - description: anterior horn motor neuron - meaning: CL:2000048 - CL:4023170: - text: CL:4023170 - description: trigeminal sensory neuron - meaning: CL:4023170 - CL:0002614: - text: CL:0002614 - description: neuron of the substantia nigra - meaning: CL:0002614 diff --git a/docs/gallery/schemasheets/nwb_static_enums.yaml b/docs/gallery/schemasheets/nwb_static_enums.yaml deleted file mode 100644 index 222205959..000000000 --- a/docs/gallery/schemasheets/nwb_static_enums.yaml +++ /dev/null @@ -1,52 +0,0 @@ -classes: - BrainSample: - slot_usage: - cell_type: {} - slots: - - cell_type -default_prefix: TEMP -default_range: string -description: this schema demonstrates the use of static enums -enums: - NeuronOrGlialCellTypeEnum: - description: Enumeration to capture various cell types found in the brain. - permissible_values: - ASTROCYTE: - description: Characteristic star-shaped glial cells in the brain and spinal - cord. - meaning: CL:0000127 - INTERNEURON: - description: Neurons whose axons (and dendrites) are limited to a single brain - area. - meaning: CL:0000099 - MICROGLIAL_CELL: - description: Microglia are the resident immune cells of the brain and constantly - patrol the cerebral microenvironment to respond to pathogens and damage. - meaning: CL:0000129 - MOTOR_NEURON: - description: Neurons whose cell body is located in the motor cortex, brainstem - or the spinal cord, and whose axon (fiber) projects to the spinal cord or - outside of the spinal cord to directly or indirectly control effector organs, - mainly muscles and glands. - meaning: CL:0000100 - OLIGODENDROCYTE: - description: Type of neuroglia whose main functions are to provide support - and insulation to axons within the central nervous system (CNS) of jawed - vertebrates. - meaning: CL:0000128 - PYRAMIDAL_NEURON: - description: Neurons with a pyramidal shaped cell body (soma) and two distinct - dendritic trees. - meaning: CL:0000598 -id: https://w3id.org/linkml/examples/nwb_static_enums -imports: -- linkml:types -name: nwb_static_enums -prefixes: - CL: http://purl.obolibrary.org/obo/CL_ - TEMP: https://example.org/TEMP/ - linkml: https://w3id.org/linkml/ -slots: - cell_type: - required: true -title: static enums example diff --git a/src/hdmf/container.py b/src/hdmf/container.py index ca2c5252b..287809406 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -112,12 +112,6 @@ def _field_config(self, arg_name, val, type_map): itself is only one file. When a user loads custom configs, the config is appended/modified. The modifications are not written to file, avoiding permanent modifications. """ - # If the val has been manually wrapped then skip checking the config for the attr - if isinstance(val, TermSetWrapper): - msg = "Field value already wrapped with TermSetWrapper." - warn(msg) - return val - configurator = type_map.type_config if len(configurator.path)>0: @@ -127,6 +121,12 @@ def _field_config(self, arg_name, val, type_map): else: return val + # If the val has been manually wrapped then skip checking the config for the attr + if isinstance(val, TermSetWrapper): + msg = "Field value already wrapped with TermSetWrapper." + warn(msg) + return val + # check to see that the namespace for the container is in the config if self.namespace not in termset_config['namespaces']: msg = "%s not found within loaded configuration." % self.namespace From 8a2658f223ea08830333e5abeb3d823952972d1b Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Mon, 20 May 2024 15:40:18 -0700 Subject: [PATCH 07/82] Config Typemap generalize (#1117) * Config Typemap generalize * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/hdmf/common/__init__.py | 23 +++++++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47cb8c8d6..f14ce7c8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Updated `TermSetWrapper` to support validating a single field within a compound array. @mavaylon1 [#1061](https://github.com/hdmf-dev/hdmf/pull/1061) - Updated testing to not install in editable mode and not run `coverage` by default. @rly [#1107](https://github.com/hdmf-dev/hdmf/pull/1107) - Add `post_init_method` parameter when generating classes to perform post-init functionality, i.e., validation. @mavaylon1 [#1089](https://github.com/hdmf-dev/hdmf/pull/1089) +- Updated loading, unloading, and getting the `TypeConfigurator` to support a `TypeMap` parameter. @mavaylon1 [#1117](https://github.com/hdmf-dev/hdmf/pull/1117) ### Bug Fixes - Fixed `TermSetWrapper` warning raised during the setters. @mavaylon1 [#1116](https://github.com/hdmf-dev/hdmf/pull/1116) diff --git a/src/hdmf/common/__init__.py b/src/hdmf/common/__init__.py index 4d724d1d1..5c9d9a3b7 100644 --- a/src/hdmf/common/__init__.py +++ b/src/hdmf/common/__init__.py @@ -22,6 +22,7 @@ global __TYPE_MAP @docval({'name': 'config_path', 'type': str, 'doc': 'Path to the configuration file.'}, + {'name': 'type_map', 'type': TypeMap, 'doc': 'The TypeMap.', 'default': None}, is_method=False) def load_type_config(**kwargs): """ @@ -29,23 +30,33 @@ def load_type_config(**kwargs): NOTE: This config is global and shared across all type maps. """ config_path = kwargs['config_path'] - __TYPE_MAP.type_config.load_type_config(config_path) + type_map = kwargs['type_map'] or get_type_map() -def get_loaded_type_config(): + type_map.type_config.load_type_config(config_path) + +@docval({'name': 'type_map', 'type': TypeMap, 'doc': 'The TypeMap.', 'default': None}, + is_method=False) +def get_loaded_type_config(**kwargs): """ This method returns the entire config file. """ - if __TYPE_MAP.type_config.config is None: + type_map = kwargs['type_map'] or get_type_map() + + if type_map.type_config.config is None: msg = "No configuration is loaded." raise ValueError(msg) else: - return __TYPE_MAP.type_config.config + return type_map.type_config.config -def unload_type_config(): +@docval({'name': 'type_map', 'type': TypeMap, 'doc': 'The TypeMap.', 'default': None}, + is_method=False) +def unload_type_config(**kwargs): """ Unload the configuration file. """ - return __TYPE_MAP.type_config.unload_type_config() + type_map = kwargs['type_map'] or get_type_map() + + return type_map.type_config.unload_type_config() # a function to register a container classes with the global map @docval({'name': 'data_type', 'type': str, 'doc': 'the data_type to get the spec for'}, From e6e6c5bbd418270aa19c7d510a1123c973cec167 Mon Sep 17 00:00:00 2001 From: Cody Baker <51133164+CodyCBakerPhD@users.noreply.github.com> Date: Mon, 20 May 2024 19:03:38 -0400 Subject: [PATCH 08/82] Expose progress bar class control (#1110) * expose progress bar class control * update types * grab progress bar class from kwargs * fix * swap back to callable but from typing * swap from typing to collections * Update CHANGELOG.md * add test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- CHANGELOG.md | 1 + src/hdmf/data_utils.py | 35 +++++++++++++++---- .../test_core_GenericDataChunkIterator.py | 29 ++++++++++++++- 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f14ce7c8d..bf2134756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Updated `TermSetWrapper` to support validating a single field within a compound array. @mavaylon1 [#1061](https://github.com/hdmf-dev/hdmf/pull/1061) - Updated testing to not install in editable mode and not run `coverage` by default. @rly [#1107](https://github.com/hdmf-dev/hdmf/pull/1107) - Add `post_init_method` parameter when generating classes to perform post-init functionality, i.e., validation. @mavaylon1 [#1089](https://github.com/hdmf-dev/hdmf/pull/1089) +- Exposed `progress_bar_class` to the `GenericDataChunkIterator` for more custom control over display of progress while iterating. @codycbakerphd [#1110](https://github.com/hdmf-dev/hdmf/pull/1110) - Updated loading, unloading, and getting the `TypeConfigurator` to support a `TypeMap` parameter. @mavaylon1 [#1117](https://github.com/hdmf-dev/hdmf/pull/1117) ### Bug Fixes diff --git a/src/hdmf/data_utils.py b/src/hdmf/data_utils.py index 23f0b4019..0e83bde2d 100644 --- a/src/hdmf/data_utils.py +++ b/src/hdmf/data_utils.py @@ -1,9 +1,9 @@ import copy import math from abc import ABCMeta, abstractmethod -from collections.abc import Iterable +from collections.abc import Iterable, Callable from warnings import warn -from typing import Tuple, Callable +from typing import Tuple from itertools import product, chain import h5py @@ -179,9 +179,15 @@ class GenericDataChunkIterator(AbstractDataChunkIterator): doc="Display a progress bar with iteration rate and estimated completion time.", default=False, ), + dict( + name="progress_bar_class", + type=Callable, + doc="The progress bar class to use. Defaults to tqdm.tqdm if the TQDM package is installed.", + default=None, + ), dict( name="progress_bar_options", - type=None, + type=dict, doc="Dictionary of keyword arguments to be passed directly to tqdm.", default=None, ), @@ -199,8 +205,23 @@ def __init__(self, **kwargs): HDF5 recommends chunk size in the range of 2 to 16 MB for optimal cloud performance. https://youtu.be/rcS5vt-mKok?t=621 """ - buffer_gb, buffer_shape, chunk_mb, chunk_shape, self.display_progress, progress_bar_options = getargs( - "buffer_gb", "buffer_shape", "chunk_mb", "chunk_shape", "display_progress", "progress_bar_options", kwargs + ( + buffer_gb, + buffer_shape, + chunk_mb, + chunk_shape, + self.display_progress, + progress_bar_class, + progress_bar_options, + ) = getargs( + "buffer_gb", + "buffer_shape", + "chunk_mb", + "chunk_shape", + "display_progress", + "progress_bar_class", + "progress_bar_options", + kwargs, ) self.progress_bar_options = progress_bar_options or dict() @@ -277,11 +298,13 @@ def __init__(self, **kwargs): try: from tqdm import tqdm + progress_bar_class = progress_bar_class or tqdm + if "total" in self.progress_bar_options: warn("Option 'total' in 'progress_bar_options' is not allowed to be over-written! Ignoring.") self.progress_bar_options.pop("total") - self.progress_bar = tqdm(total=self.num_buffers, **self.progress_bar_options) + self.progress_bar = progress_bar_class(total=self.num_buffers, **self.progress_bar_options) except ImportError: warn( "You must install tqdm to use the progress bar feature (pip install tqdm)! " diff --git a/tests/unit/utils_test/test_core_GenericDataChunkIterator.py b/tests/unit/utils_test/test_core_GenericDataChunkIterator.py index debac9cab..2117eb6d0 100644 --- a/tests/unit/utils_test/test_core_GenericDataChunkIterator.py +++ b/tests/unit/utils_test/test_core_GenericDataChunkIterator.py @@ -4,7 +4,7 @@ from pathlib import Path from tempfile import mkdtemp from shutil import rmtree -from typing import Tuple, Iterable, Callable +from typing import Tuple, Iterable, Callable, Union from sys import version_info import h5py @@ -408,6 +408,33 @@ def test_progress_bar(self): first_line = file.read() self.assertIn(member=desc, container=first_line) + @unittest.skipIf(not TQDM_INSTALLED, "optional tqdm module is not installed") + def test_progress_bar_class(self): + import tqdm + + class MyCustomProgressBar(tqdm.tqdm): + def update(self, n: int = 1) -> Union[bool, None]: + displayed = super().update(n) + print(f"Custom injection on step {n}") # noqa: T201 + + return displayed + + out_text_file = self.test_dir / "test_progress_bar_class.txt" + desc = "Testing progress bar..." + with open(file=out_text_file, mode="w") as file: + iterator = self.TestNumpyArrayDataChunkIterator( + array=self.test_array, + display_progress=True, + progress_bar_class=MyCustomProgressBar, + progress_bar_options=dict(file=file, desc=desc), + ) + j = 0 + for buffer in iterator: + j += 1 # dummy operation; must be silent for proper updating of bar + with open(file=out_text_file, mode="r") as file: + first_line = file.read() + self.assertIn(member=desc, container=first_line) + @unittest.skipIf(not TQDM_INSTALLED, "optional tqdm module is installed") def test_progress_bar_no_options(self): dci = self.TestNumpyArrayDataChunkIterator(array=self.test_array, display_progress=True) From 77319108a6074a4f3b6317b92fa935dce21dc1f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 23:09:18 +0000 Subject: [PATCH 09/82] Bump tqdm from 4.41.0 to 4.66.3 (#1109) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Ly --- requirements-opt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-opt.txt b/requirements-opt.txt index 53fd11e3a..c1d34220b 100644 --- a/requirements-opt.txt +++ b/requirements-opt.txt @@ -1,5 +1,5 @@ # pinned dependencies that are optional. used to reproduce an entire development environment to use HDMF -tqdm==4.66.2 +tqdm==4.66.3 zarr==2.17.1 linkml-runtime==1.7.4; python_version >= "3.9" schemasheets==0.2.1; python_version >= "3.9" From 6a0f9d8184387f73c6abed65d919d2aafc03f832 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 23:24:09 +0000 Subject: [PATCH 10/82] [pre-commit.ci] pre-commit autoupdate (#1082) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ryan Ly --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 786a3e4b7..3f80639c7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ # NOTE: run `pre-commit autoupdate` to update hooks to latest version repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-yaml - id: end-of-file-fixer @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.3.3 + rev: v0.4.4 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From c18d1b3d108ee8798c977a1c994fc528b4fa05b0 Mon Sep 17 00:00:00 2001 From: Cody Baker <51133164+CodyCBakerPhD@users.noreply.github.com> Date: Mon, 20 May 2024 22:09:54 -0400 Subject: [PATCH 11/82] Expose AWS Region to HDF5IO (#1040) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ryan Ly --- .github/workflows/run_all_tests.yml | 8 +-- .github/workflows/run_coverage.yml | 55 ++++++++++++++++++ .github/workflows/run_tests.yml | 4 +- CHANGELOG.md | 1 + src/hdmf/backends/hdf5/h5tools.py | 38 ++++++++++--- tests/unit/test_io_hdf5_streaming.py | 56 +++++++++++++++++++ tests/unit/utils_test/test_core_DataIO.py | 13 ++++- .../test_core_GenericDataChunkIterator.py | 1 - 8 files changed, 159 insertions(+), 17 deletions(-) diff --git a/.github/workflows/run_all_tests.yml b/.github/workflows/run_all_tests.yml index def51537f..8df190d55 100644 --- a/.github/workflows/run_all_tests.yml +++ b/.github/workflows/run_all_tests.yml @@ -197,7 +197,7 @@ jobs: run: | tox -e wheelinstall --installpkg dist/*.tar.gz - run-gallery-ros3-tests: + run-ros3-tests: name: ${{ matrix.name }} runs-on: ${{ matrix.os }} defaults: @@ -210,9 +210,9 @@ jobs: fail-fast: false matrix: include: - - { name: linux-gallery-python3.12-ros3 , python-ver: "3.12", os: ubuntu-latest } - - { name: windows-gallery-python3.12-ros3 , python-ver: "3.12", os: windows-latest } - - { name: macos-gallery-python3.12-ros3 , python-ver: "3.12", os: macos-latest } + - { name: linux-python3.12-ros3 , python-ver: "3.12", os: ubuntu-latest } + - { name: windows-python3.12-ros3 , python-ver: "3.12", os: windows-latest } + - { name: macos-python3.12-ros3 , python-ver: "3.12", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 diff --git a/.github/workflows/run_coverage.yml b/.github/workflows/run_coverage.yml index a72a05e73..bd2eeb921 100644 --- a/.github/workflows/run_coverage.yml +++ b/.github/workflows/run_coverage.yml @@ -70,3 +70,58 @@ jobs: file: ./coverage.xml env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + run-ros3-coverage: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash -l {0} # necessary for conda + concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.name }} + cancel-in-progress: true + strategy: + fail-fast: false + matrix: + include: + - { name: linux-python3.12-ros3 , python-ver: "3.12", os: ubuntu-latest } + steps: + - name: Checkout repo with submodules + uses: actions/checkout@v4 + with: + submodules: 'recursive' + fetch-depth: 0 # tags are required to determine the version + + - name: Set up Conda + uses: conda-incubator/setup-miniconda@v3 + with: + auto-update-conda: true + activate-environment: ros3 + environment-file: environment-ros3.yml + python-version: ${{ matrix.python-ver }} + channels: conda-forge + auto-activate-base: false + mamba-version: "*" + + - name: Install run dependencies + run: | + pip install . + pip list + + - name: Conda reporting + run: | + conda info + conda config --show-sources + conda list --show-channel-urls + + - name: Run ros3 tests # TODO include gallery tests after they are written + run: | + pytest --cov --cov-report=xml --cov-report=term tests/unit/test_io_hdf5_streaming.py + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + file: ./coverage.xml + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 2723e03d0..5e0b3bff2 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -209,7 +209,7 @@ jobs: --token ${{ secrets.BOT_GITHUB_TOKEN }} \ --re-upload - run-gallery-ros3-tests: + run-ros3-tests: name: ${{ matrix.name }} runs-on: ${{ matrix.os }} defaults: @@ -222,7 +222,7 @@ jobs: fail-fast: false matrix: include: - - { name: linux-gallery-python3.12-ros3 , python-ver: "3.12", os: ubuntu-latest } + - { name: linux-python3.12-ros3 , python-ver: "3.12", os: ubuntu-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index bf2134756..8a1d0b2c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - Updated `TermSetWrapper` to support validating a single field within a compound array. @mavaylon1 [#1061](https://github.com/hdmf-dev/hdmf/pull/1061) - Updated testing to not install in editable mode and not run `coverage` by default. @rly [#1107](https://github.com/hdmf-dev/hdmf/pull/1107) - Add `post_init_method` parameter when generating classes to perform post-init functionality, i.e., validation. @mavaylon1 [#1089](https://github.com/hdmf-dev/hdmf/pull/1089) +- Exposed `aws_region` to `HDF5IO` and downstream passes to `h5py.File`. @codycbakerphd [#1040](https://github.com/hdmf-dev/hdmf/pull/1040) - Exposed `progress_bar_class` to the `GenericDataChunkIterator` for more custom control over display of progress while iterating. @codycbakerphd [#1110](https://github.com/hdmf-dev/hdmf/pull/1110) - Updated loading, unloading, and getting the `TypeConfigurator` to support a `TypeMap` parameter. @mavaylon1 [#1117](https://github.com/hdmf-dev/hdmf/pull/1117) diff --git a/src/hdmf/backends/hdf5/h5tools.py b/src/hdmf/backends/hdf5/h5tools.py index 05ce36e13..0604881bb 100644 --- a/src/hdmf/backends/hdf5/h5tools.py +++ b/src/hdmf/backends/hdf5/h5tools.py @@ -62,15 +62,21 @@ def can_read(path): {'name': 'file', 'type': [File, "S3File", "RemFile"], 'doc': 'a pre-existing h5py.File, S3File, or RemFile object', 'default': None}, {'name': 'driver', 'type': str, 'doc': 'driver for h5py to use when opening HDF5 file', 'default': None}, + { + 'name': 'aws_region', + 'type': str, + 'doc': 'If driver is ros3, then specify the aws region of the url.', + 'default': None + }, {'name': 'herd_path', 'type': str, 'doc': 'The path to read/write the HERD file', 'default': None},) def __init__(self, **kwargs): """Open an HDF5 file for IO. """ self.logger = logging.getLogger('%s.%s' % (self.__class__.__module__, self.__class__.__qualname__)) - path, manager, mode, comm, file_obj, driver, herd_path = popargs('path', 'manager', 'mode', + path, manager, mode, comm, file_obj, driver, aws_region, herd_path = popargs('path', 'manager', 'mode', 'comm', 'file', 'driver', - 'herd_path', + 'aws_region', 'herd_path', kwargs) self.__open_links = [] # keep track of other files opened from links in this file @@ -91,6 +97,7 @@ def __init__(self, **kwargs): elif isinstance(manager, TypeMap): manager = BuildManager(manager) self.__driver = driver + self.__aws_region = aws_region self.__comm = comm self.__mode = mode self.__file = file_obj @@ -116,6 +123,10 @@ def _file(self): def driver(self): return self.__driver + @property + def aws_region(self): + return self.__aws_region + @classmethod def __check_path_file_obj(cls, path, file_obj): if isinstance(path, Path): @@ -133,13 +144,17 @@ def __check_path_file_obj(cls, path, file_obj): return path @classmethod - def __resolve_file_obj(cls, path, file_obj, driver): + def __resolve_file_obj(cls, path, file_obj, driver, aws_region=None): + """Helper function to return a File when loading or getting namespaces from a file.""" path = cls.__check_path_file_obj(path, file_obj) if file_obj is None: file_kwargs = dict() if driver is not None: file_kwargs.update(driver=driver) + + if aws_region is not None: + file_kwargs.update(aws_region=bytes(aws_region, "ascii")) file_obj = File(path, 'r', **file_kwargs) return file_obj @@ -150,6 +165,8 @@ def __resolve_file_obj(cls, path, file_obj, driver): {'name': 'namespaces', 'type': list, 'doc': 'the namespaces to load', 'default': None}, {'name': 'file', 'type': File, 'doc': 'a pre-existing h5py.File object', 'default': None}, {'name': 'driver', 'type': str, 'doc': 'driver for h5py to use when opening HDF5 file', 'default': None}, + {'name': 'aws_region', 'type': str, 'doc': 'If driver is ros3, then specify the aws region of the url.', + 'default': None}, returns=("dict mapping the names of the loaded namespaces to a dict mapping included namespace names and " "the included data types"), rtype=dict) @@ -162,10 +179,10 @@ def load_namespaces(cls, **kwargs): :raises ValueError: if both `path` and `file` are supplied but `path` is not the same as the path of `file`. """ - namespace_catalog, path, namespaces, file_obj, driver = popargs( - 'namespace_catalog', 'path', 'namespaces', 'file', 'driver', kwargs) + namespace_catalog, path, namespaces, file_obj, driver, aws_region = popargs( + 'namespace_catalog', 'path', 'namespaces', 'file', 'driver', 'aws_region', kwargs) - open_file_obj = cls.__resolve_file_obj(path, file_obj, driver) + open_file_obj = cls.__resolve_file_obj(path, file_obj, driver, aws_region=aws_region) if file_obj is None: # need to close the file object that we just opened with open_file_obj: return cls.__load_namespaces(namespace_catalog, namespaces, open_file_obj) @@ -214,6 +231,8 @@ def __check_specloc(cls, file_obj): @docval({'name': 'path', 'type': (str, Path), 'doc': 'the path to the HDF5 file', 'default': None}, {'name': 'file', 'type': File, 'doc': 'a pre-existing h5py.File object', 'default': None}, {'name': 'driver', 'type': str, 'doc': 'driver for h5py to use when opening HDF5 file', 'default': None}, + {'name': 'aws_region', 'type': str, 'doc': 'If driver is ros3, then specify the aws region of the url.', + 'default': None}, returns="dict mapping names to versions of the namespaces in the file", rtype=dict) def get_namespaces(cls, **kwargs): """Get the names and versions of the cached namespaces from a file. @@ -227,9 +246,9 @@ def get_namespaces(cls, **kwargs): :raises ValueError: if both `path` and `file` are supplied but `path` is not the same as the path of `file`. """ - path, file_obj, driver = popargs('path', 'file', 'driver', kwargs) + path, file_obj, driver, aws_region = popargs('path', 'file', 'driver', 'aws_region', kwargs) - open_file_obj = cls.__resolve_file_obj(path, file_obj, driver) + open_file_obj = cls.__resolve_file_obj(path, file_obj, driver, aws_region=aws_region) if file_obj is None: # need to close the file object that we just opened with open_file_obj: return cls.__get_namespaces(open_file_obj) @@ -756,6 +775,9 @@ def open(self): if self.driver is not None: kwargs.update(driver=self.driver) + if self.driver == "ros3" and self.aws_region is not None: + kwargs.update(aws_region=bytes(self.aws_region, "ascii")) + self.__file = File(self.source, open_flag, **kwargs) def close(self, close_links=True): diff --git a/tests/unit/test_io_hdf5_streaming.py b/tests/unit/test_io_hdf5_streaming.py index d1c9d1ab3..d82c9c5c3 100644 --- a/tests/unit/test_io_hdf5_streaming.py +++ b/tests/unit/test_io_hdf5_streaming.py @@ -2,7 +2,9 @@ import os import urllib.request import h5py +import warnings +from hdmf.backends.hdf5.h5tools import HDF5IO from hdmf.build import TypeMap, BuildManager from hdmf.common import get_hdf5io, get_type_map from hdmf.spec import GroupSpec, DatasetSpec, SpecNamespace, NamespaceBuilder, NamespaceCatalog @@ -10,6 +12,7 @@ from hdmf.utils import docval, get_docval + class TestRos3(TestCase): """Test reading an HDMF file using HDF5 ROS3 streaming. @@ -77,6 +80,8 @@ def setUp(self): self.manager = BuildManager(type_map) + warnings.filterwarnings(action="ignore", message="Ignoring cached namespace .*") + def tearDown(self): if os.path.exists(self.ns_filename): os.remove(self.ns_filename) @@ -89,6 +94,57 @@ def test_basic_read(self): with get_hdf5io(s3_path, "r", manager=self.manager, driver="ros3") as io: io.read() + def test_basic_read_with_aws_region(self): + s3_path = "https://dandiarchive.s3.amazonaws.com/blobs/11e/c89/11ec8933-1456-4942-922b-94e5878bb991" + + with get_hdf5io(s3_path, "r", manager=self.manager, driver="ros3", aws_region="us-east-2") as io: + io.read() + + def test_basic_read_s3_with_aws_region(self): + # NOTE: if an s3 path is used with ros3 driver, aws_region must be specified + s3_path = "s3://dandiarchive/blobs/11e/c89/11ec8933-1456-4942-922b-94e5878bb991" + + with get_hdf5io(s3_path, "r", manager=self.manager, driver="ros3", aws_region="us-east-2") as io: + io.read() + assert io.aws_region == "us-east-2" + + def test_get_namespaces(self): + s3_path = "https://dandiarchive.s3.amazonaws.com/blobs/11e/c89/11ec8933-1456-4942-922b-94e5878bb991" + + namespaces = HDF5IO.get_namespaces(s3_path, driver="ros3") + self.assertEqual(namespaces, {'core': '2.3.0', 'hdmf-common': '1.5.0', 'hdmf-experimental': '0.1.0'}) + + def test_get_namespaces_with_aws_region(self): + s3_path = "https://dandiarchive.s3.amazonaws.com/blobs/11e/c89/11ec8933-1456-4942-922b-94e5878bb991" + + namespaces = HDF5IO.get_namespaces(s3_path, driver="ros3", aws_region="us-east-2") + self.assertEqual(namespaces, {'core': '2.3.0', 'hdmf-common': '1.5.0', 'hdmf-experimental': '0.1.0'}) + + def test_get_namespaces_s3_with_aws_region(self): + s3_path = "s3://dandiarchive/blobs/11e/c89/11ec8933-1456-4942-922b-94e5878bb991" + + namespaces = HDF5IO.get_namespaces(s3_path, driver="ros3", aws_region="us-east-2") + self.assertEqual(namespaces, {'core': '2.3.0', 'hdmf-common': '1.5.0', 'hdmf-experimental': '0.1.0'}) + + def test_load_namespaces(self): + s3_path = "https://dandiarchive.s3.amazonaws.com/blobs/11e/c89/11ec8933-1456-4942-922b-94e5878bb991" + + HDF5IO.load_namespaces(self.manager.namespace_catalog, path=s3_path, driver="ros3") + assert set(self.manager.namespace_catalog.namespaces) == set(["core", "hdmf-common", "hdmf-experimental"]) + + def test_load_namespaces_with_aws_region(self): + s3_path = "https://dandiarchive.s3.amazonaws.com/blobs/11e/c89/11ec8933-1456-4942-922b-94e5878bb991" + + HDF5IO.load_namespaces(self.manager.namespace_catalog, path=s3_path, driver="ros3", aws_region="us-east-2") + assert set(self.manager.namespace_catalog.namespaces) == set(["core", "hdmf-common", "hdmf-experimental"]) + + def test_load_namespaces_s3_with_aws_region(self): + s3_path = "s3://dandiarchive/blobs/11e/c89/11ec8933-1456-4942-922b-94e5878bb991" + + HDF5IO.load_namespaces(self.manager.namespace_catalog, path=s3_path, driver="ros3", aws_region="us-east-2") + assert set(self.manager.namespace_catalog.namespaces) == set(["core", "hdmf-common", "hdmf-experimental"]) + + # Util functions and classes to enable loading of the NWB namespace -- see pynwb/src/pynwb/spec.py diff --git a/tests/unit/utils_test/test_core_DataIO.py b/tests/unit/utils_test/test_core_DataIO.py index 778dd2617..4c2ffac15 100644 --- a/tests/unit/utils_test/test_core_DataIO.py +++ b/tests/unit/utils_test/test_core_DataIO.py @@ -4,6 +4,7 @@ from hdmf.container import Data from hdmf.data_utils import DataIO from hdmf.testing import TestCase +import warnings class DataIOTests(TestCase): @@ -36,7 +37,9 @@ def test_set_dataio(self): dataio = DataIO() data = np.arange(30).reshape(5, 2, 3) container = Data('wrapped_data', data) - container.set_dataio(dataio) + msg = "Data.set_dataio() is deprecated. Please use Data.set_data_io() instead." + with self.assertWarnsWith(DeprecationWarning, msg): + container.set_dataio(dataio) self.assertIs(dataio.data, data) self.assertIs(dataio, container.data) @@ -48,7 +51,13 @@ def test_set_dataio_data_already_set(self): data = np.arange(30).reshape(5, 2, 3) container = Data('wrapped_data', data) with self.assertRaisesWith(ValueError, "cannot overwrite 'data' on DataIO"): - container.set_dataio(dataio) + with warnings.catch_warnings(record=True): + warnings.filterwarnings( + action='ignore', + category=DeprecationWarning, + message="Data.set_dataio() is deprecated. Please use Data.set_data_io() instead.", + ) + container.set_dataio(dataio) def test_dataio_options(self): """ diff --git a/tests/unit/utils_test/test_core_GenericDataChunkIterator.py b/tests/unit/utils_test/test_core_GenericDataChunkIterator.py index 2117eb6d0..cb1a727a4 100644 --- a/tests/unit/utils_test/test_core_GenericDataChunkIterator.py +++ b/tests/unit/utils_test/test_core_GenericDataChunkIterator.py @@ -410,7 +410,6 @@ def test_progress_bar(self): @unittest.skipIf(not TQDM_INSTALLED, "optional tqdm module is not installed") def test_progress_bar_class(self): - import tqdm class MyCustomProgressBar(tqdm.tqdm): def update(self, n: int = 1) -> Union[bool, None]: From 9387e85058fb44ea1702ad888e9df547c41fc324 Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Mon, 20 May 2024 20:34:43 -0700 Subject: [PATCH 12/82] Release 3.14 (#1118) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a1d0b2c5..89c937e80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # HDMF Changelog -## HDMF 3.14.0 (Upcoming) +## HDMF 3.14.0 (May 20, 2024) ### Enhancements - Updated `_field_config` to take `type_map` as an argument for APIs. @mavaylon1 [#1094](https://github.com/hdmf-dev/hdmf/pull/1094) From 2505a0e3b310a7c096dd9e23f262255f410e7551 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Tue, 4 Jun 2024 22:19:13 -0700 Subject: [PATCH 13/82] Fix issue with resolving attribute specs with same name (#1122) * Fix issue with resolving attribute specs with same name * fix codespell --- CHANGELOG.md | 9 +- pyproject.toml | 2 +- src/hdmf/data_utils.py | 2 +- src/hdmf/spec/spec.py | 143 ++++++++++++----------- tests/unit/spec_tests/test_group_spec.py | 121 ++++++++++++++++--- 5 files changed, 186 insertions(+), 91 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89c937e80..5ed9984db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # HDMF Changelog +## HDMF 3.14.1 (June 3, 2024) + +### Bug Fixes +- Fixed issue with resolving attribute specs that have the same name at different levels of a spec hierarchy. + @rly [#1122](https://github.com/hdmf-dev/hdmf/pull/1122) + + ## HDMF 3.14.0 (May 20, 2024) ### Enhancements @@ -548,7 +555,7 @@ the fields (i.e., when the constructor sets some fields to fixed values). @rly Each sub-table is itself a DynamicTable that is aligned with the main table by row index. Each subtable defines a sub-category in the main table effectively creating a table with sub-headings to organize columns. @oruebel (#551) -- Add tutoral for new `AlignedDynamicTable` type. @oruebel (#571) +- Add tutorial for new `AlignedDynamicTable` type. @oruebel (#571) - Equality check for `DynamicTable` now also checks that the name and description of the table are the same. @rly (#566) ### Internal improvements diff --git a/pyproject.toml b/pyproject.toml index 67b13350b..f0a0b7fb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ norecursedirs = "tests/unit/helpers" [tool.codespell] skip = "htmlcov,.git,.mypy_cache,.pytest_cache,.coverage,*.pdf,*.svg,venvs,.tox,hdmf-common-schema,./docs/_build/*,*.ipynb" -ignore-words-list = "datas" +ignore-words-list = "datas,assertIn" [tool.coverage.run] branch = true diff --git a/src/hdmf/data_utils.py b/src/hdmf/data_utils.py index 0e83bde2d..c03665caa 100644 --- a/src/hdmf/data_utils.py +++ b/src/hdmf/data_utils.py @@ -938,7 +938,7 @@ class ShapeValidatorResult: {'name': 'message', 'type': str, 'doc': 'Message describing the result of the shape validation', 'default': None}, {'name': 'ignored', 'type': tuple, - 'doc': 'Axes that have been ignored in the validaton process', 'default': tuple(), 'shape': (None,)}, + 'doc': 'Axes that have been ignored in the validation process', 'default': tuple(), 'shape': (None,)}, {'name': 'unmatched', 'type': tuple, 'doc': 'List of axes that did not match during shape validation', 'default': tuple(), 'shape': (None,)}, {'name': 'error', 'type': str, 'doc': 'Error that may have occurred. One of ERROR_TYPE', 'default': None}, diff --git a/src/hdmf/spec/spec.py b/src/hdmf/spec/spec.py index 585fc6494..1a6e8d987 100644 --- a/src/hdmf/spec/spec.py +++ b/src/hdmf/spec/spec.py @@ -385,7 +385,7 @@ def resolve_spec(self, **kwargs): self.set_attribute(attribute) self.__resolved = True - @docval({'name': 'spec', 'type': (Spec, str), 'doc': 'the specification to check'}) + @docval({'name': 'spec', 'type': Spec, 'doc': 'the specification to check'}) def is_inherited_spec(self, **kwargs): ''' Return True if this spec was inherited from the parent type, False otherwise. @@ -393,13 +393,11 @@ def is_inherited_spec(self, **kwargs): Returns False if the spec is not found. ''' spec = getargs('spec', kwargs) - if isinstance(spec, Spec): - spec = spec.name - if spec in self.__attributes: - return self.is_inherited_attribute(spec) + if spec.parent is self and spec.name in self.__attributes: + return self.is_inherited_attribute(spec.name) return False - @docval({'name': 'spec', 'type': (Spec, str), 'doc': 'the specification to check'}) + @docval({'name': 'spec', 'type': Spec, 'doc': 'the specification to check'}) def is_overridden_spec(self, **kwargs): ''' Return True if this spec overrides a specification from the parent type, False otherwise. @@ -407,10 +405,8 @@ def is_overridden_spec(self, **kwargs): Returns False if the spec is not found. ''' spec = getargs('spec', kwargs) - if isinstance(spec, Spec): - spec = spec.name - if spec in self.__attributes: - return self.is_overridden_attribute(spec) + if spec.parent is self and spec.name in self.__attributes: + return self.is_overridden_attribute(spec.name) return False @docval({'name': 'name', 'type': str, 'doc': 'the name of the attribute to check'}) @@ -1011,85 +1007,92 @@ def is_overridden_link(self, **kwargs): raise ValueError("Link '%s' not found in spec" % name) return name in self.__overridden_links - @docval({'name': 'spec', 'type': (Spec, str), 'doc': 'the specification to check'}) + @docval({'name': 'spec', 'type': Spec, 'doc': 'the specification to check'}) def is_inherited_spec(self, **kwargs): ''' Returns 'True' if specification was inherited from a parent type ''' spec = getargs('spec', kwargs) - if isinstance(spec, Spec): - name = spec.name - if name is None and hasattr(spec, 'data_type_def'): - name = spec.data_type_def - if name is None: # NOTE: this will return the target type for LinkSpecs - name = spec.data_type_inc - if name is None: # pragma: no cover - # this should not be possible - raise ValueError('received Spec with wildcard name but no data_type_inc or data_type_def') - spec = name + spec_name = spec.name + if spec_name is None and hasattr(spec, 'data_type_def'): + spec_name = spec.data_type_def + if spec_name is None: # NOTE: this will return the target type for LinkSpecs + spec_name = spec.data_type_inc + if spec_name is None: # pragma: no cover + # this should not be possible + raise ValueError('received Spec with wildcard name but no data_type_inc or data_type_def') # if the spec has a name, it will be found in __links/__groups/__datasets before __data_types/__target_types - if spec in self.__links: - return self.is_inherited_link(spec) - elif spec in self.__groups: - return self.is_inherited_group(spec) - elif spec in self.__datasets: - return self.is_inherited_dataset(spec) - elif spec in self.__data_types: + if spec_name in self.__links: + return self.is_inherited_link(spec_name) + elif spec_name in self.__groups: + return self.is_inherited_group(spec_name) + elif spec_name in self.__datasets: + return self.is_inherited_dataset(spec_name) + elif spec_name in self.__data_types: # NOTE: the same data type can be both an unnamed data type and an unnamed target type - return self.is_inherited_type(spec) - elif spec in self.__target_types: - return self.is_inherited_target_type(spec) + return self.is_inherited_type(spec_name) + elif spec_name in self.__target_types: + return self.is_inherited_target_type(spec_name) else: + # attribute spec if super().is_inherited_spec(spec): return True else: - for s in self.__datasets: - if self.is_inherited_dataset(s): - if self.__datasets[s].get_attribute(spec) is not None: - return True - for s in self.__groups: - if self.is_inherited_group(s): - if self.__groups[s].get_attribute(spec) is not None: - return True + parent_name = spec.parent.name + if parent_name is None: + parent_name = spec.parent.data_type + if isinstance(spec.parent, DatasetSpec): + if parent_name in self.__datasets: + if self.is_inherited_dataset(parent_name): + if self.__datasets[parent_name].get_attribute(spec_name) is not None: + return True + else: + if parent_name in self.__groups: + if self.is_inherited_group(parent_name): + if self.__groups[parent_name].get_attribute(spec_name) is not None: + return True return False - @docval({'name': 'spec', 'type': (Spec, str), 'doc': 'the specification to check'}) + @docval({'name': 'spec', 'type': Spec, 'doc': 'the specification to check'}) def is_overridden_spec(self, **kwargs): # noqa: C901 ''' Returns 'True' if specification overrides a specification from the parent type ''' spec = getargs('spec', kwargs) - if isinstance(spec, Spec): - name = spec.name - if name is None: - if isinstance(spec, LinkSpec): # unnamed LinkSpec cannot be overridden - return False - if spec.is_many(): # this is a wildcard spec, so it cannot be overridden - return False - name = spec.data_type_def - if name is None: # NOTE: this will return the target type for LinkSpecs - name = spec.data_type_inc - if name is None: # pragma: no cover - # this should not happen - raise ValueError('received Spec with wildcard name but no data_type_inc or data_type_def') - spec = name + spec_name = spec.name + if spec_name is None: + if isinstance(spec, LinkSpec): # unnamed LinkSpec cannot be overridden + return False + if spec.is_many(): # this is a wildcard spec, so it cannot be overridden + return False + spec_name = spec.data_type_def + if spec_name is None: # NOTE: this will return the target type for LinkSpecs + spec_name = spec.data_type_inc + if spec_name is None: # pragma: no cover + # this should not happen + raise ValueError('received Spec with wildcard name but no data_type_inc or data_type_def') # if the spec has a name, it will be found in __links/__groups/__datasets before __data_types/__target_types - if spec in self.__links: - return self.is_overridden_link(spec) - elif spec in self.__groups: - return self.is_overridden_group(spec) - elif spec in self.__datasets: - return self.is_overridden_dataset(spec) - elif spec in self.__data_types: - return self.is_overridden_type(spec) + if spec_name in self.__links: + return self.is_overridden_link(spec_name) + elif spec_name in self.__groups: + return self.is_overridden_group(spec_name) + elif spec_name in self.__datasets: + return self.is_overridden_dataset(spec_name) + elif spec_name in self.__data_types: + return self.is_overridden_type(spec_name) else: if super().is_overridden_spec(spec): # check if overridden attribute return True else: - for s in self.__datasets: - if self.is_overridden_dataset(s): - if self.__datasets[s].is_overridden_spec(spec): - return True - for s in self.__groups: - if self.is_overridden_group(s): - if self.__groups[s].is_overridden_spec(spec): - return True + parent_name = spec.parent.name + if parent_name is None: + parent_name = spec.parent.data_type + if isinstance(spec.parent, DatasetSpec): + if parent_name in self.__datasets: + if self.is_overridden_dataset(parent_name): + if self.__datasets[parent_name].is_overridden_spec(spec): + return True + else: + if parent_name in self.__groups: + if self.is_overridden_group(parent_name): + if self.__groups[parent_name].is_overridden_spec(spec): + return True return False @docval({'name': 'spec', 'type': (BaseStorageSpec, str), 'doc': 'the specification to check'}) diff --git a/tests/unit/spec_tests/test_group_spec.py b/tests/unit/spec_tests/test_group_spec.py index 9c117fa1f..00a937538 100644 --- a/tests/unit/spec_tests/test_group_spec.py +++ b/tests/unit/spec_tests/test_group_spec.py @@ -365,26 +365,22 @@ def test_resolved(self): self.assertTrue(self.inc_group_spec.resolved) def test_is_inherited_spec(self): - self.assertFalse(self.def_group_spec.is_inherited_spec('attribute1')) - self.assertFalse(self.def_group_spec.is_inherited_spec('attribute2')) - self.assertTrue(self.inc_group_spec.is_inherited_spec( - AttributeSpec('attribute1', 'my first attribute', 'text') - )) - self.assertTrue(self.inc_group_spec.is_inherited_spec('attribute1')) - self.assertTrue(self.inc_group_spec.is_inherited_spec('attribute2')) - self.assertFalse(self.inc_group_spec.is_inherited_spec('attribute3')) - self.assertFalse(self.inc_group_spec.is_inherited_spec('attribute4')) + self.assertFalse(self.def_group_spec.is_inherited_spec(self.def_group_spec.attributes[0])) + self.assertFalse(self.def_group_spec.is_inherited_spec(self.def_group_spec.attributes[1])) + + attr_spec_map = {attr.name: attr for attr in self.inc_group_spec.attributes} + self.assertTrue(self.inc_group_spec.is_inherited_spec(attr_spec_map["attribute1"])) + self.assertTrue(self.inc_group_spec.is_inherited_spec(attr_spec_map["attribute2"])) + self.assertFalse(self.inc_group_spec.is_inherited_spec(attr_spec_map["attribute3"])) def test_is_overridden_spec(self): - self.assertFalse(self.def_group_spec.is_overridden_spec('attribute1')) - self.assertFalse(self.def_group_spec.is_overridden_spec('attribute2')) - self.assertFalse(self.inc_group_spec.is_overridden_spec( - AttributeSpec('attribute1', 'my first attribute', 'text') - )) - self.assertFalse(self.inc_group_spec.is_overridden_spec('attribute1')) - self.assertTrue(self.inc_group_spec.is_overridden_spec('attribute2')) - self.assertFalse(self.inc_group_spec.is_overridden_spec('attribute3')) - self.assertFalse(self.inc_group_spec.is_overridden_spec('attribute4')) + self.assertFalse(self.def_group_spec.is_overridden_spec(self.def_group_spec.attributes[0])) + self.assertFalse(self.def_group_spec.is_overridden_spec(self.def_group_spec.attributes[0])) + + attr_spec_map = {attr.name: attr for attr in self.inc_group_spec.attributes} + self.assertFalse(self.inc_group_spec.is_overridden_spec(attr_spec_map["attribute1"])) + self.assertTrue(self.inc_group_spec.is_overridden_spec(attr_spec_map["attribute2"])) + self.assertFalse(self.inc_group_spec.is_overridden_spec(attr_spec_map["attribute3"])) def test_is_inherited_attribute(self): self.assertFalse(self.def_group_spec.is_inherited_attribute('attribute1')) @@ -405,6 +401,95 @@ def test_is_overridden_attribute(self): self.inc_group_spec.is_overridden_attribute('attribute4') +class TestResolveGroupSameAttributeName(TestCase): + # https://github.com/hdmf-dev/hdmf/issues/1121 + + def test_is_inherited_two_different_datasets(self): + self.def_group_spec = GroupSpec( + doc='A test group', + data_type_def='MyGroup', + datasets=[ + DatasetSpec( + name='dset1', + doc="dset1", + dtype='int', + attributes=[AttributeSpec('attr1', 'MyGroup.dset1.attr1', 'text')] + ), + ] + ) + self.inc_group_spec = GroupSpec( + doc='A test subgroup', + data_type_def='SubGroup', + data_type_inc='MyGroup', + datasets=[ + DatasetSpec( + name='dset2', + doc="dset2", + dtype='int', + attributes=[AttributeSpec('attr1', 'SubGroup.dset2.attr1', 'text')] + ), + ] + ) + self.inc_group_spec.resolve_spec(self.def_group_spec) + + self.assertFalse(self.def_group_spec.is_inherited_spec(self.def_group_spec.datasets[0].attributes[0])) + + dset_spec_map = {dset.name: dset for dset in self.inc_group_spec.datasets} + self.assertFalse(self.inc_group_spec.is_inherited_spec(dset_spec_map["dset2"].attributes[0])) + self.assertTrue(self.inc_group_spec.is_inherited_spec(dset_spec_map["dset1"].attributes[0])) + + def test_is_inherited_different_groups_and_datasets(self): + self.def_group_spec = GroupSpec( + doc='A test group', + data_type_def='MyGroup', + attributes=[AttributeSpec('attr1', 'MyGroup.attr1', 'text')], # <-- added from above + datasets=[ + DatasetSpec( + name='dset1', + doc="dset1", + dtype='int', + attributes=[AttributeSpec('attr1', 'MyGroup.dset1.attr1', 'text')] + ), + ] + ) + self.inc_group_spec = GroupSpec( + doc='A test subgroup', + data_type_def='SubGroup', + data_type_inc='MyGroup', + attributes=[AttributeSpec('attr1', 'SubGroup.attr1', 'text')], # <-- added from above + datasets=[ + DatasetSpec( + name='dset2', + doc="dset2", + dtype='int', + attributes=[AttributeSpec('attr1', 'SubGroup.dset2.attr1', 'text')] + ), + ] + ) + self.inc_group_spec.resolve_spec(self.def_group_spec) + + self.assertFalse(self.def_group_spec.is_inherited_spec(self.def_group_spec.datasets[0].attributes[0])) + + dset_spec_map = {dset.name: dset for dset in self.inc_group_spec.datasets} + self.assertFalse(self.inc_group_spec.is_inherited_spec(dset_spec_map["dset2"].attributes[0])) + self.assertTrue(self.inc_group_spec.is_inherited_spec(dset_spec_map["dset1"].attributes[0])) + self.assertTrue(self.inc_group_spec.is_inherited_spec(self.inc_group_spec.attributes[0])) + + self.inc_group_spec2 = GroupSpec( + doc='A test subsubgroup', + data_type_def='SubSubGroup', + data_type_inc='SubGroup', + ) + self.inc_group_spec2.resolve_spec(self.inc_group_spec) + + dset_spec_map = {dset.name: dset for dset in self.inc_group_spec2.datasets} + self.assertTrue(self.inc_group_spec2.is_inherited_spec(dset_spec_map["dset1"].attributes[0])) + self.assertTrue(self.inc_group_spec2.is_inherited_spec(dset_spec_map["dset2"].attributes[0])) + self.assertTrue(self.inc_group_spec2.is_inherited_spec(self.inc_group_spec2.attributes[0])) + + + + class GroupSpecWithLinksTest(TestCase): def test_constructor(self): From 5ad0beb327310e1fc44a2539bae12f8e632890b4 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Wed, 5 Jun 2024 14:28:17 -0700 Subject: [PATCH 14/82] Delete MANIFEST.in and exclude artifacts from sdist and wheel (#1119) * Delete MANIFEST.in * Exclude files from sdist and wheel * Update changelog --- CHANGELOG.md | 6 +++--- MANIFEST.in | 5 ----- pyproject.toml | 15 ++++++++++++++- 3 files changed, 17 insertions(+), 9 deletions(-) delete mode 100644 MANIFEST.in diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ed9984db..4f11fa143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,12 @@ # HDMF Changelog -## HDMF 3.14.1 (June 3, 2024) +## HDMF 3.14.1 (Upcoming) -### Bug Fixes +### Bug fixes +- Excluded unnecessary artifacts from sdist and wheel. @rly [#1119](https://github.com/hdmf-dev/hdmf/pull/1119) - Fixed issue with resolving attribute specs that have the same name at different levels of a spec hierarchy. @rly [#1122](https://github.com/hdmf-dev/hdmf/pull/1122) - ## HDMF 3.14.0 (May 20, 2024) ### Enhancements diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 9b77b2ac8..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -include license.txt Legal.txt src/hdmf/_due.py -include requirements.txt requirements-dev.txt requirements-doc.txt requirements-min.txt requirements-opt.txt -include test_gallery.py tox.ini -graft tests -global-exclude *.py[cod] diff --git a/pyproject.toml b/pyproject.toml index f0a0b7fb7..3a0034087 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,10 +64,23 @@ source = "vcs" version-file = "src/hdmf/_version.py" [tool.hatch.build.targets.sdist] -exclude = [".git_archival.txt"] +exclude = [ + ".git*", + ".codecov.yml", + ".readthedocs.yaml", + ".mailmap", + ".pre-commit-config.yaml", +] [tool.hatch.build.targets.wheel] packages = ["src/hdmf"] +exclude = [ + ".git*", + ".codecov.yml", + ".readthedocs.yaml", + ".mailmap", + ".pre-commit-config.yaml", +] # [tool.mypy] # no_incremental = true # needed b/c mypy and ruamel.yaml do not play nice. https://github.com/python/mypy/issues/12664 From 4d131a0d93f26f483484ae776a33a6d018e0d391 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 16:11:31 -0700 Subject: [PATCH 15/82] [pre-commit.ci] pre-commit autoupdate (#1120) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ryan Ly --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3f80639c7..3f4d13e28 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.4 + rev: v0.4.7 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate @@ -26,7 +26,7 @@ repos: # hooks: # - id: interrogate - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell additional_dependencies: From 543935f164d5e8beb77bb4d854b1f1a8b1b4b18b Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Thu, 6 Jun 2024 08:13:47 -0700 Subject: [PATCH 16/82] Update CHANGELOG.md (#1124) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f11fa143..f022ef0e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # HDMF Changelog -## HDMF 3.14.1 (Upcoming) +## HDMF 3.14.1 (June 6, 2024) ### Bug fixes - Excluded unnecessary artifacts from sdist and wheel. @rly [#1119](https://github.com/hdmf-dev/hdmf/pull/1119) From ece2c27d9c828f4ecb5e2ee3b66dd49e547f7b4c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 22:26:04 -0700 Subject: [PATCH 17/82] [pre-commit.ci] pre-commit autoupdate (#1127) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3f4d13e28..7684975ab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.7 + rev: v0.4.8 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From d756abeb98ef89fde37a71ed64ec84de627f8bfb Mon Sep 17 00:00:00 2001 From: Cody Baker <51133164+CodyCBakerPhD@users.noreply.github.com> Date: Wed, 12 Jun 2024 12:41:51 -0400 Subject: [PATCH 18/82] Fix iterator increment (#1128) --- CHANGELOG.md | 7 +++++++ src/hdmf/data_utils.py | 10 +++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f022ef0e7..ae753b98b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # HDMF Changelog +## HDMF 3.14.2 (???) + +### Bug fixes +- Fix iterator increment causing an extra +1 added after the end of completion. @CodyCBakerPhD [#1128](https://github.com/hdmf-dev/hdmf/pull/1128) + + + ## HDMF 3.14.1 (June 6, 2024) ### Bug fixes diff --git a/src/hdmf/data_utils.py b/src/hdmf/data_utils.py index c03665caa..f4ac6541f 100644 --- a/src/hdmf/data_utils.py +++ b/src/hdmf/data_utils.py @@ -386,14 +386,18 @@ def __next__(self): :returns: DataChunk object with the data and selection of the current buffer. :rtype: DataChunk """ - if self.display_progress: - self.progress_bar.update(n=1) try: buffer_selection = next(self.buffer_selection_generator) + + # Only update after successful iteration + if self.display_progress: + self.progress_bar.update(n=1) + return DataChunk(data=self._get_data(selection=buffer_selection), selection=buffer_selection) except StopIteration: + # Allow text to be written to new lines after completion if self.display_progress: - self.progress_bar.write("\n") # Allows text to be written to new lines after completion + self.progress_bar.write("\n") raise StopIteration def __reduce__(self) -> Tuple[Callable, Iterable]: From 7426275cacc769a10ffca89836765df1355ba9db Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 23:40:49 -0400 Subject: [PATCH 19/82] [pre-commit.ci] pre-commit autoupdate (#1131) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.8 → v0.4.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.8...v0.4.9) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7684975ab..0f486273b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.8 + rev: v0.4.9 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From 539ecf47ad1ad70e23666f7a7d750d2d84535632 Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Fri, 28 Jun 2024 16:23:06 -0700 Subject: [PATCH 20/82] Zarr append for datasets (non-reference) (#1136) * Zarr append * Update CHANGELOG.md * Update pyproject.toml * Update pyproject.toml * Update pyproject.toml * Update pyproject.toml * Update pyproject.toml * Update pyproject.toml * Update validator.py * Update testcase.py * Update objectmapper.py * Update h5tools.py * Update h5tools.py * Update h5tools.py * Update src/hdmf/validate/validator.py Co-authored-by: Steph Prince <40640337+stephprince@users.noreply.github.com> --------- Co-authored-by: Steph Prince <40640337+stephprince@users.noreply.github.com> --- CHANGELOG.md | 5 +++-- pyproject.toml | 8 ++++---- src/hdmf/backends/hdf5/h5tools.py | 2 +- src/hdmf/build/objectmapper.py | 2 +- src/hdmf/data_utils.py | 5 ++++- src/hdmf/testing/testcase.py | 2 +- src/hdmf/validate/validator.py | 2 +- tests/unit/utils_test/test_data_utils.py | 14 ++++++++++++++ 8 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 tests/unit/utils_test/test_data_utils.py diff --git a/CHANGELOG.md b/CHANGELOG.md index ae753b98b..5f5db4918 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,12 @@ # HDMF Changelog -## HDMF 3.14.2 (???) +## HDMF 3.14.2 (Upcoming) ### Bug fixes - Fix iterator increment causing an extra +1 added after the end of completion. @CodyCBakerPhD [#1128](https://github.com/hdmf-dev/hdmf/pull/1128) - +### Enhancements +- Support appending to zarr arrays. @mavaylon1 [#1136](https://github.com/hdmf-dev/hdmf/pull/1136) ## HDMF 3.14.1 (June 6, 2024) diff --git a/pyproject.toml b/pyproject.toml index 3a0034087..a089113c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,12 +36,12 @@ dependencies = [ "pandas>=1.0.5", "ruamel.yaml>=0.16", "scipy>=1.4", + "zarr >= 2.12.0", "importlib-resources; python_version < '3.9'", # TODO: remove when minimum python version is 3.9 ] dynamic = ["version"] [project.optional-dependencies] -zarr = ["zarr>=2.12.0"] tqdm = ["tqdm>=4.41.0"] termset = ["linkml-runtime>=1.5.5; python_version >= '3.9'", "schemasheets>=0.1.23; python_version >= '3.9'", @@ -117,7 +117,7 @@ omit = [ # force-exclude = "src/hdmf/common/hdmf-common-schema|docs/gallery" [tool.ruff] -select = ["E", "F", "T100", "T201", "T203"] +lint.select = ["E", "F", "T100", "T201", "T203"] exclude = [ ".git", ".tox", @@ -132,11 +132,11 @@ exclude = [ ] line-length = 120 -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "docs/gallery/*" = ["E402", "T201"] "src/*/__init__.py" = ["F401"] "setup.py" = ["T201"] "test_gallery.py" = ["T201"] -[tool.ruff.mccabe] +[tool.ruff.lint.mccabe] max-complexity = 17 diff --git a/src/hdmf/backends/hdf5/h5tools.py b/src/hdmf/backends/hdf5/h5tools.py index 0604881bb..8135d75e7 100644 --- a/src/hdmf/backends/hdf5/h5tools.py +++ b/src/hdmf/backends/hdf5/h5tools.py @@ -728,7 +728,7 @@ def __read_dataset(self, h5obj, name=None): def _check_str_dtype(self, h5obj): dtype = h5obj.dtype if dtype.kind == 'O': - if dtype.metadata.get('vlen') == str and H5PY_3: + if dtype.metadata.get('vlen') is str and H5PY_3: return StrDataset(h5obj, None) return h5obj diff --git a/src/hdmf/build/objectmapper.py b/src/hdmf/build/objectmapper.py index fed678d41..52fbfec49 100644 --- a/src/hdmf/build/objectmapper.py +++ b/src/hdmf/build/objectmapper.py @@ -1164,7 +1164,7 @@ def __get_subspec_values(self, builder, spec, manager): if not isinstance(builder, DatasetBuilder): # pragma: no cover raise ValueError("__get_subspec_values - must pass DatasetBuilder with DatasetSpec") if (spec.shape is None and getattr(builder.data, 'shape', None) == (1,) and - type(builder.data[0]) != np.void): + type(builder.data[0]) is not np.void): # if a scalar dataset is expected and a 1-element non-compound dataset is given, then read the dataset builder['data'] = builder.data[0] # use dictionary reference instead of .data to bypass error ret[spec] = self.__check_ref_resolver(builder.data) diff --git a/src/hdmf/data_utils.py b/src/hdmf/data_utils.py index f4ac6541f..71f5bdf6d 100644 --- a/src/hdmf/data_utils.py +++ b/src/hdmf/data_utils.py @@ -5,17 +5,20 @@ from warnings import warn from typing import Tuple from itertools import product, chain +from zarr import Array as ZarrArray import h5py import numpy as np from .utils import docval, getargs, popargs, docval_macro, get_data_shape - def append_data(data, arg): if isinstance(data, (list, DataIO)): data.append(arg) return data + elif isinstance(data, ZarrArray): + data.append([arg], axis=0) + return data elif type(data).__name__ == 'TermSetWrapper': # circular import data.append(arg) return data diff --git a/src/hdmf/testing/testcase.py b/src/hdmf/testing/testcase.py index 798df6fe4..1be4bcecd 100644 --- a/src/hdmf/testing/testcase.py +++ b/src/hdmf/testing/testcase.py @@ -174,7 +174,7 @@ def _assert_array_equal(self, :param message: custom additional message to show when assertions as part of this assert are failing """ array_data_types = tuple([i for i in get_docval_macro('array_data') - if (i != list and i != tuple and i != AbstractDataChunkIterator)]) + if (i is not list and i is not tuple and i is not AbstractDataChunkIterator)]) # We construct array_data_types this way to avoid explicit dependency on h5py, Zarr and other # I/O backends. Only list and tuple do not support [()] slicing, and AbstractDataChunkIterator # should never occur here. The effective value of array_data_types is then: diff --git a/src/hdmf/validate/validator.py b/src/hdmf/validate/validator.py index bdfc15f8f..e39011d9f 100644 --- a/src/hdmf/validate/validator.py +++ b/src/hdmf/validate/validator.py @@ -164,7 +164,7 @@ def get_type(data, builder_dtype=None): # Empty array else: # Empty string array - if data.dtype.metadata["vlen"] == str: + if data.dtype.metadata["vlen"] is str: return "utf", None # Undetermined variable length data type. else: # pragma: no cover diff --git a/tests/unit/utils_test/test_data_utils.py b/tests/unit/utils_test/test_data_utils.py new file mode 100644 index 000000000..2e0df7ba8 --- /dev/null +++ b/tests/unit/utils_test/test_data_utils.py @@ -0,0 +1,14 @@ +from hdmf.data_utils import append_data +from hdmf.testing import TestCase + +import numpy as np +from numpy.testing import assert_array_equal +import zarr + +class TestAppendData(TestCase): + + def test_append_data_zarr(self): + zarr_array = zarr.array([1,2,3]) + new = append_data(zarr_array, 4) + + assert_array_equal(new[:], np.array([1,2,3,4])) From 5e13d64efabedb039ed4eb49abd4a9c9e848f72a Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Mon, 1 Jul 2024 12:33:29 -0400 Subject: [PATCH 21/82] Warn when unexpected keys in specs (#1134) * Warn when unexpected keys in specs * Update changelog --- CHANGELOG.md | 7 ++++--- src/hdmf/spec/spec.py | 4 ++++ tests/unit/spec_tests/test_attribute_spec.py | 12 ++++++++++++ tests/unit/spec_tests/test_dataset_spec.py | 12 ++++++++++++ tests/unit/spec_tests/test_group_spec.py | 10 ++++++++++ tests/unit/spec_tests/test_link_spec.py | 12 ++++++++++++ 6 files changed, 54 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f5db4918..74e048a19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,13 @@ ## HDMF 3.14.2 (Upcoming) -### Bug fixes -- Fix iterator increment causing an extra +1 added after the end of completion. @CodyCBakerPhD [#1128](https://github.com/hdmf-dev/hdmf/pull/1128) - ### Enhancements +- Warn when unexpected keys are present in specs. @rly [#1134](https://github.com/hdmf-dev/hdmf/pull/1134) - Support appending to zarr arrays. @mavaylon1 [#1136](https://github.com/hdmf-dev/hdmf/pull/1136) +### Bug fixes +- Fix iterator increment causing an extra +1 added after the end of completion. @CodyCBakerPhD [#1128](https://github.com/hdmf-dev/hdmf/pull/1128) + ## HDMF 3.14.1 (June 6, 2024) ### Bug fixes diff --git a/src/hdmf/spec/spec.py b/src/hdmf/spec/spec.py index 1a6e8d987..6d7d29e49 100644 --- a/src/hdmf/spec/spec.py +++ b/src/hdmf/spec/spec.py @@ -93,9 +93,13 @@ def build_spec(cls, spec_dict): vargs = cls.build_const_args(spec_dict) kwargs = dict() # iterate through the Spec docval and construct kwargs based on matching values in spec_dict + unused_vargs = list(vargs) for x in get_docval(cls.__init__): if x['name'] in vargs: kwargs[x['name']] = vargs.get(x['name']) + unused_vargs.remove(x['name']) + if unused_vargs: + warn(f'Unexpected keys {unused_vargs} in spec {spec_dict}') return cls(**kwargs) diff --git a/tests/unit/spec_tests/test_attribute_spec.py b/tests/unit/spec_tests/test_attribute_spec.py index 15102e728..bac8e12a3 100644 --- a/tests/unit/spec_tests/test_attribute_spec.py +++ b/tests/unit/spec_tests/test_attribute_spec.py @@ -91,3 +91,15 @@ def test_build_spec_no_doc(self): msg = "AttributeSpec.__init__: missing argument 'doc'" with self.assertRaisesWith(TypeError, msg): AttributeSpec.build_spec(spec_dict) + + def test_build_warn_extra_args(self): + spec_dict = { + 'name': 'attribute1', + 'doc': 'test attribute', + 'dtype': 'int', + 'quantity': '?', + } + msg = ("Unexpected keys ['quantity'] in spec {'name': 'attribute1', 'doc': 'test attribute', " + "'dtype': 'int', 'quantity': '?'}") + with self.assertWarnsWith(UserWarning, msg): + AttributeSpec.build_spec(spec_dict) diff --git a/tests/unit/spec_tests/test_dataset_spec.py b/tests/unit/spec_tests/test_dataset_spec.py index 0309aced4..008e8c6fc 100644 --- a/tests/unit/spec_tests/test_dataset_spec.py +++ b/tests/unit/spec_tests/test_dataset_spec.py @@ -245,3 +245,15 @@ def test_data_type_property_value(self): group = GroupSpec('A group', name='group', data_type_inc=data_type_inc, data_type_def=data_type_def) self.assertEqual(group.data_type, data_type) + + def test_build_warn_extra_args(self): + spec_dict = { + 'name': 'dataset1', + 'doc': 'test dataset', + 'dtype': 'int', + 'required': True, + } + msg = ("Unexpected keys ['required'] in spec {'name': 'dataset1', 'doc': 'test dataset', " + "'dtype': 'int', 'required': True}") + with self.assertWarnsWith(UserWarning, msg): + DatasetSpec.build_spec(spec_dict) diff --git a/tests/unit/spec_tests/test_group_spec.py b/tests/unit/spec_tests/test_group_spec.py index 00a937538..31c00cfbb 100644 --- a/tests/unit/spec_tests/test_group_spec.py +++ b/tests/unit/spec_tests/test_group_spec.py @@ -314,6 +314,16 @@ def test_get_namespace_spec(self): expected = AttributeSpec('namespace', 'the namespace for the data type of this object', 'text', required=False) self.assertDictEqual(GroupSpec.get_namespace_spec(), expected) + def test_build_warn_extra_args(self): + spec_dict = { + 'name': 'group1', + 'doc': 'test group', + 'required': True, + } + msg = "Unexpected keys ['required'] in spec {'name': 'group1', 'doc': 'test group', 'required': True}" + with self.assertWarnsWith(UserWarning, msg): + GroupSpec.build_spec(spec_dict) + class TestNotAllowedConfig(TestCase): diff --git a/tests/unit/spec_tests/test_link_spec.py b/tests/unit/spec_tests/test_link_spec.py index e6c680b7c..38e10886b 100644 --- a/tests/unit/spec_tests/test_link_spec.py +++ b/tests/unit/spec_tests/test_link_spec.py @@ -67,3 +67,15 @@ def test_required_is_many(self): ) self.assertEqual(spec.required, req) self.assertEqual(spec.is_many(), many) + + def test_build_warn_extra_args(self): + spec_dict = { + 'name': 'link1', + 'doc': 'test link', + 'target_type': 'TestType', + 'required': True, + } + msg = ("Unexpected keys ['required'] in spec {'name': 'link1', 'doc': 'test link', " + "'target_type': 'TestType', 'required': True}") + with self.assertWarnsWith(UserWarning, msg): + LinkSpec.build_spec(spec_dict) From 8917eaffb7a10bb97233e484b78c801cfb57161e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:53:00 +0000 Subject: [PATCH 22/82] Bump actions/add-to-project from 0.6.1 to 1.0.1 (#1097) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Ly --- .github/workflows/project_action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/project_action.yml b/.github/workflows/project_action.yml index 5d141d1d1..0f0d8f3ce 100644 --- a/.github/workflows/project_action.yml +++ b/.github/workflows/project_action.yml @@ -20,7 +20,7 @@ jobs: - name: Add to Developer Board env: TOKEN: ${{ steps.generate_token.outputs.token }} - uses: actions/add-to-project@v0.6.1 + uses: actions/add-to-project@v1.0.1 with: project-url: https://github.com/orgs/hdmf-dev/projects/7 github-token: ${{ env.TOKEN }} @@ -28,7 +28,7 @@ jobs: - name: Add to Community Board env: TOKEN: ${{ steps.generate_token.outputs.token }} - uses: actions/add-to-project@v0.6.1 + uses: actions/add-to-project@v1.0.1 with: project-url: https://github.com/orgs/hdmf-dev/projects/8 github-token: ${{ env.TOKEN }} From 62edbe44892d10d199393eae3f6c7645a2689ea4 Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Fri, 5 Jul 2024 12:40:28 -0700 Subject: [PATCH 23/82] Make Zarr optional for testing (#1141) --- pyproject.toml | 1 - src/hdmf/data_utils.py | 13 ++++++++---- tests/unit/utils_test/test_data_utils.py | 25 +++++++++++++++++++++++- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a089113c0..6ef9850a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,6 @@ dependencies = [ "pandas>=1.0.5", "ruamel.yaml>=0.16", "scipy>=1.4", - "zarr >= 2.12.0", "importlib-resources; python_version < '3.9'", # TODO: remove when minimum python version is 3.9 ] dynamic = ["version"] diff --git a/src/hdmf/data_utils.py b/src/hdmf/data_utils.py index 71f5bdf6d..798a40973 100644 --- a/src/hdmf/data_utils.py +++ b/src/hdmf/data_utils.py @@ -5,7 +5,12 @@ from warnings import warn from typing import Tuple from itertools import product, chain -from zarr import Array as ZarrArray + +try: + from zarr import Array as ZarrArray + ZARR_INSTALLED = True +except ImportError: + ZARR_INSTALLED = False import h5py import numpy as np @@ -16,9 +21,6 @@ def append_data(data, arg): if isinstance(data, (list, DataIO)): data.append(arg) return data - elif isinstance(data, ZarrArray): - data.append([arg], axis=0) - return data elif type(data).__name__ == 'TermSetWrapper': # circular import data.append(arg) return data @@ -33,6 +35,9 @@ def append_data(data, arg): data.resize(shape) data[-1] = arg return data + elif ZARR_INSTALLED and isinstance(data, ZarrArray): + data.append([arg], axis=0) + return data else: msg = "Data cannot append to object of type '%s'" % type(data) raise ValueError(msg) diff --git a/tests/unit/utils_test/test_data_utils.py b/tests/unit/utils_test/test_data_utils.py index 2e0df7ba8..b5a5e50e7 100644 --- a/tests/unit/utils_test/test_data_utils.py +++ b/tests/unit/utils_test/test_data_utils.py @@ -3,9 +3,32 @@ import numpy as np from numpy.testing import assert_array_equal -import zarr + +try: + import zarr + ZARR_INSTALLED = True +except ImportError: + ZARR_INSTALLED = False + + +class MyIterable: + def __init__(self, data): + self.data = data + class TestAppendData(TestCase): + def test_append_exception(self): + data = MyIterable([1, 2, 3, 4, 5]) + with self.assertRaises(ValueError): + append_data(data, 4) + + +class TestZarrAppendData(TestCase): + + def setUp(self): + if not ZARR_INSTALLED: + self.skipTest("optional Zarr package is not installed") + def test_append_data_zarr(self): zarr_array = zarr.array([1,2,3]) From eb67626c1c251a9c64458f77a6d893a14209fc88 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Sun, 7 Jul 2024 19:09:57 -0400 Subject: [PATCH 24/82] Add support for numpy 2, update all deps (#1139) --- CHANGELOG.md | 1 + environment-ros3.yml | 12 ++++++------ pyproject.toml | 2 +- requirements-dev.txt | 17 +++++++++-------- requirements-opt.txt | 8 ++++---- requirements.txt | 12 +++++++----- src/hdmf/build/objectmapper.py | 2 +- src/hdmf/common/table.py | 2 +- tests/unit/test_io_hdf5.py | 4 ++-- tests/unit/utils_test/test_docval.py | 11 +++++++++-- 10 files changed, 41 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74e048a19..74d7bd477 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Enhancements - Warn when unexpected keys are present in specs. @rly [#1134](https://github.com/hdmf-dev/hdmf/pull/1134) - Support appending to zarr arrays. @mavaylon1 [#1136](https://github.com/hdmf-dev/hdmf/pull/1136) +- Add support for numpy 2. @rly [#1139](https://github.com/hdmf-dev/hdmf/pull/1139) ### Bug fixes - Fix iterator increment causing an extra +1 added after the end of completion. @CodyCBakerPhD [#1128](https://github.com/hdmf-dev/hdmf/pull/1128) diff --git a/environment-ros3.yml b/environment-ros3.yml index 458b899ba..34c37cc01 100644 --- a/environment-ros3.yml +++ b/environment-ros3.yml @@ -5,11 +5,11 @@ channels: - defaults dependencies: - python==3.12 - - h5py==3.10.0 - - matplotlib==3.8.0 - - numpy==1.26.0 - - pandas==2.1.2 + - h5py==3.11.0 + - matplotlib==3.8.4 + - numpy==2.0.0 + - pandas==2.2.2 - python-dateutil==2.8.2 - - pytest==7.4.3 - - pytest-cov==4.1.0 + - pytest==8.1.2 # regression introduced in pytest 8.2.*, will be fixed in 8.3.0 + - pytest-cov==5.0.0 - setuptools diff --git a/pyproject.toml b/pyproject.toml index 6ef9850a6..86e52a137 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ classifiers = [ dependencies = [ "h5py>=2.10", "jsonschema>=2.6.0", - 'numpy>=1.18, <2.0', # pin below 2.0 until HDMF supports numpy 2.0 + 'numpy>=1.18', "pandas>=1.0.5", "ruamel.yaml>=0.16", "scipy>=1.4", diff --git a/requirements-dev.txt b/requirements-dev.txt index 1d856e4e7..95cf0797e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,12 +2,13 @@ # compute coverage, and create test environments. note that depending on the version of python installed, different # versions of requirements may be installed due to package incompatibilities. # -black==24.3.0 -codespell==2.2.6 -coverage==7.3.2 -pre-commit==3.5.0 -pytest==7.4.3 -pytest-cov==4.1.0 +black==24.4.2 +codespell==2.3.0 +coverage==7.5.4 +pre-commit==3.7.1; python_version >= "3.9" +pre-commit==3.5.0; python_version < "3.9" +pytest==8.1.2 # regression introduced in pytest 8.2.*, will be fixed in 8.3.0 +pytest-cov==5.0.0 python-dateutil==2.8.2 -ruff==0.1.3 -tox==4.11.3 +ruff==0.5.0 +tox==4.15.1 diff --git a/requirements-opt.txt b/requirements-opt.txt index c1d34220b..4831d1949 100644 --- a/requirements-opt.txt +++ b/requirements-opt.txt @@ -1,6 +1,6 @@ # pinned dependencies that are optional. used to reproduce an entire development environment to use HDMF -tqdm==4.66.3 -zarr==2.17.1 -linkml-runtime==1.7.4; python_version >= "3.9" +tqdm==4.66.4 +zarr==2.18.2 +linkml-runtime==1.7.7; python_version >= "3.9" schemasheets==0.2.1; python_version >= "3.9" -oaklib==0.5.32; python_version >= "3.9" +oaklib==0.6.10; python_version >= "3.9" diff --git a/requirements.txt b/requirements.txt index 5182d5c2e..30a596ada 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,10 @@ # pinned dependencies to reproduce an entire development environment to use HDMF -h5py==3.10.0 +h5py==3.11.0 importlib-resources==6.1.0; python_version < "3.9" # TODO: remove when minimum python version is 3.9 -jsonschema==4.19.1 -numpy==1.26.1 -pandas==2.1.2 +jsonschema==4.22.0 +numpy==1.26.4 # TODO: numpy 2.0.0 is supported by hdmf but incompatible with pandas and scipy +pandas==2.2.2; python_version >= "3.9" +pandas==2.1.2; python_version < "3.8" # TODO: remove when minimum python version is 3.9 ruamel.yaml==0.18.2 -scipy==1.11.3 +scipy==1.14.0; python_version >= "3.10" +scipy==1.11.3; python_version < "3.10" diff --git a/src/hdmf/build/objectmapper.py b/src/hdmf/build/objectmapper.py index 52fbfec49..b0bd7d594 100644 --- a/src/hdmf/build/objectmapper.py +++ b/src/hdmf/build/objectmapper.py @@ -299,7 +299,7 @@ def __check_edgecases(cls, spec, value, spec_dtype): # noqa: C901 cls.__check_convert_numeric(value.dtype.type) if np.issubdtype(value.dtype, np.str_): ret_dtype = 'utf8' - elif np.issubdtype(value.dtype, np.string_): + elif np.issubdtype(value.dtype, np.bytes_): ret_dtype = 'ascii' elif np.issubdtype(value.dtype, np.dtype('O')): # Only variable-length strings should ever appear as generic objects. diff --git a/src/hdmf/common/table.py b/src/hdmf/common/table.py index 3b67ff19d..2e90b0cdf 100644 --- a/src/hdmf/common/table.py +++ b/src/hdmf/common/table.py @@ -235,7 +235,7 @@ def __eq__(self, other): if isinstance(search_ids, int): search_ids = [search_ids] # Find all matching locations - return np.in1d(self.data, search_ids).nonzero()[0] + return np.isin(self.data, search_ids).nonzero()[0] def _validate_new_data(self, data): # NOTE this may not cover all the many AbstractDataChunkIterator edge cases diff --git a/tests/unit/test_io_hdf5.py b/tests/unit/test_io_hdf5.py index 0dae1fbbe..29b7f2d7f 100644 --- a/tests/unit/test_io_hdf5.py +++ b/tests/unit/test_io_hdf5.py @@ -121,10 +121,10 @@ def __assert_helper(self, a, b): # if strings, convert before comparing if b_array: if b_sub.dtype.char in ('S', 'U'): - a_sub = [np.string_(s) for s in a_sub] + a_sub = [np.bytes_(s) for s in a_sub] else: if a_sub.dtype.char in ('S', 'U'): - b_sub = [np.string_(s) for s in b_sub] + b_sub = [np.bytes_(s) for s in b_sub] equal = np.array_equal(a_sub, b_sub) else: equal = a_sub == b_sub diff --git a/tests/unit/utils_test/test_docval.py b/tests/unit/utils_test/test_docval.py index 154a5c4b0..c766dcf46 100644 --- a/tests/unit/utils_test/test_docval.py +++ b/tests/unit/utils_test/test_docval.py @@ -736,8 +736,12 @@ def method(self, **kwargs): self.assertEqual(method(self, np.uint(1)), np.uint(1)) self.assertEqual(method(self, np.uint(2)), np.uint(2)) + # the string rep of uint changes from numpy 1 to 2 ("1" to "np.uint64(1)"), so do not hardcode the string + uint_str1 = np.uint(1).__repr__() + uint_str2 = np.uint(2).__repr__() + msg = ("TestDocValidator.test_enum_uint..method: " - "forbidden value for 'arg1' (got 3, expected (1, 2))") + "forbidden value for 'arg1' (got 3, expected (%s, %s))" % (uint_str1, uint_str2)) with self.assertRaisesWith(ValueError, msg): method(self, np.uint(3)) @@ -767,8 +771,11 @@ def method(self, **kwargs): self.assertEqual(method(self, 'true'), 'true') self.assertEqual(method(self, np.uint(1)), np.uint(1)) + # the string rep of uint changes from numpy 1 to 2 ("1" to "np.uint64(1)"), so do not hardcode the string + uint_str = np.uint(1).__repr__() + msg = ("TestDocValidator.test_enum_bool_mixed..method: " - "forbidden value for 'arg1' (got 0, expected (True, 1, 1.0, 'true', 1))") + "forbidden value for 'arg1' (got 0, expected (True, 1, 1.0, 'true', %s))" % uint_str) with self.assertRaisesWith(ValueError, msg): method(self, 0) From 639d0caa1951b63f271542a97473d50e53a5faaf Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Mon, 8 Jul 2024 00:00:46 -0400 Subject: [PATCH 25/82] Allow "value" in DatasetSpec (#1143) --- CHANGELOG.md | 1 + src/hdmf/spec/spec.py | 11 ++++++++++- tests/unit/spec_tests/test_dataset_spec.py | 4 ++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74d7bd477..3edd7c36d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Enhancements - Warn when unexpected keys are present in specs. @rly [#1134](https://github.com/hdmf-dev/hdmf/pull/1134) - Support appending to zarr arrays. @mavaylon1 [#1136](https://github.com/hdmf-dev/hdmf/pull/1136) +- Support specifying "value" key in DatasetSpec. @rly [#1143](https://github.com/hdmf-dev/hdmf/pull/1143) - Add support for numpy 2. @rly [#1139](https://github.com/hdmf-dev/hdmf/pull/1139) ### Bug fixes diff --git a/src/hdmf/spec/spec.py b/src/hdmf/spec/spec.py index 6d7d29e49..358cc3256 100644 --- a/src/hdmf/spec/spec.py +++ b/src/hdmf/spec/spec.py @@ -648,6 +648,7 @@ def build_const_args(cls, spec_dict): {'name': 'linkable', 'type': bool, 'doc': 'whether or not this group can be linked', 'default': True}, {'name': 'quantity', 'type': (str, int), 'doc': 'the required number of allowed instance', 'default': 1}, {'name': 'default_value', 'type': None, 'doc': 'a default value for this dataset', 'default': None}, + {'name': 'value', 'type': None, 'doc': 'a fixed value for this dataset', 'default': None}, {'name': 'data_type_def', 'type': str, 'doc': 'the data type this specification represents', 'default': None}, {'name': 'data_type_inc', 'type': (str, 'DatasetSpec'), 'doc': 'the data type this specification extends', 'default': None}, @@ -662,7 +663,8 @@ class DatasetSpec(BaseStorageSpec): @docval(*_dataset_args) def __init__(self, **kwargs): - doc, shape, dims, dtype, default_value = popargs('doc', 'shape', 'dims', 'dtype', 'default_value', kwargs) + doc, shape, dims, dtype = popargs('doc', 'shape', 'dims', 'dtype', kwargs) + default_value, value = popargs('default_value', 'value', kwargs) if shape is not None: self['shape'] = shape if dims is not None: @@ -685,6 +687,8 @@ def __init__(self, **kwargs): super().__init__(doc, **kwargs) if default_value is not None: self['default_value'] = default_value + if value is not None: + self['value'] = value if self.name is not None: valid_quant_vals = [1, 'zero_or_one', ZERO_OR_ONE] if self.quantity not in valid_quant_vals: @@ -762,6 +766,11 @@ def default_value(self): '''The default value of the dataset or None if not specified''' return self.get('default_value', None) + @property + def value(self): + '''The fixed value of the dataset or None if not specified''' + return self.get('value', None) + @classmethod def dtype_spec_cls(cls): ''' The class to use when constructing DtypeSpec objects diff --git a/tests/unit/spec_tests/test_dataset_spec.py b/tests/unit/spec_tests/test_dataset_spec.py index 008e8c6fc..c9db14635 100644 --- a/tests/unit/spec_tests/test_dataset_spec.py +++ b/tests/unit/spec_tests/test_dataset_spec.py @@ -246,6 +246,10 @@ def test_data_type_property_value(self): data_type_inc=data_type_inc, data_type_def=data_type_def) self.assertEqual(group.data_type, data_type) + def test_constructor_value(self): + spec = DatasetSpec(doc='my first dataset', dtype='int', name='dataset1', value=42) + assert spec.value == 42 + def test_build_warn_extra_args(self): spec_dict = { 'name': 'dataset1', From dfb1df79bce3e34c52af7996429c3f561d96390a Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Mon, 8 Jul 2024 08:32:07 -0700 Subject: [PATCH 26/82] Update CHANGELOG.md (#1145) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3edd7c36d..0624a5f3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # HDMF Changelog -## HDMF 3.14.2 (Upcoming) +## HDMF 3.14.2 (July 7, 2024) ### Enhancements - Warn when unexpected keys are present in specs. @rly [#1134](https://github.com/hdmf-dev/hdmf/pull/1134) From 77be0cc6d23a8ddfa3b96120f9d7599a5614044a Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Fri, 26 Jul 2024 10:25:31 -0700 Subject: [PATCH 27/82] Add doc to H5DataIO.dataset setter (#1154) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Oliver Ruebel --- src/hdmf/backends/hdf5/h5_utils.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/hdmf/backends/hdf5/h5_utils.py b/src/hdmf/backends/hdf5/h5_utils.py index 8654e2b4b..aa68272c9 100644 --- a/src/hdmf/backends/hdf5/h5_utils.py +++ b/src/hdmf/backends/hdf5/h5_utils.py @@ -546,10 +546,31 @@ def __init__(self, **kwargs): @property def dataset(self): + """Get the cached h5py.Dataset.""" return self.__dataset @dataset.setter def dataset(self, val): + """Cache the h5py.Dataset written with the stored IO settings. + + This attribute can be used to cache a written, empty dataset and fill it in later. + This allows users to access the handle to the dataset *without* having to close + and reopen a file. + + For example:: + + dataio = H5DataIO(shape=(5,), dtype=int) + foo = Foo('foo1', dataio, "I am foo1", 17, 3.14) + bucket = FooBucket('bucket1', [foo]) + foofile = FooFile(buckets=[bucket]) + + io = HDF5IO(self.path, manager=self.manager, mode='w') + # write the object to disk, including initializing an empty int dataset with shape (5,) + io.write(foofile) + + foo.my_data.dataset[:] = [0, 1, 2, 3, 4] + io.close() + """ if self.__dataset is not None: raise ValueError("Cannot overwrite H5DataIO.dataset") self.__dataset = val From 4c32820f8cf8adb3fbad27b8553ec2842548e571 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Fri, 26 Jul 2024 14:42:30 -0700 Subject: [PATCH 28/82] Write dimension labels to DatasetBuilder on build (#1081) Co-authored-by: Matthew Avaylon --- CHANGELOG.md | 7 + src/hdmf/build/builders.py | 16 +- src/hdmf/build/objectmapper.py | 99 +++++- src/hdmf/build/warnings.py | 7 + .../build_tests/mapper_tests/test_build.py | 286 +++++++++++++++++- 5 files changed, 404 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0624a5f3a..bb80f5336 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # HDMF Changelog +## HDMF 3.14.3 (Upcoming) + +### Enhancements +- Added new attribute "dimension_labels" on `DatasetBuilder` which specifies the names of the dimensions used in the +dataset based on the shape of the dataset data and the dimension names in the spec for the data type. This attribute +is available on build (during the write process), but not on read of a dataset from a file. @rly [#1081](https://github.com/hdmf-dev/hdmf/pull/1081) + ## HDMF 3.14.2 (July 7, 2024) ### Enhancements diff --git a/src/hdmf/build/builders.py b/src/hdmf/build/builders.py index 73c683bbd..cb658b6d4 100644 --- a/src/hdmf/build/builders.py +++ b/src/hdmf/build/builders.py @@ -330,6 +330,10 @@ class DatasetBuilder(BaseBuilder): 'doc': 'The datatype of this dataset.', 'default': None}, {'name': 'attributes', 'type': dict, 'doc': 'A dictionary of attributes to create in this dataset.', 'default': dict()}, + {'name': 'dimension_labels', 'type': tuple, + 'doc': ('A list of labels for each dimension of this dataset from the spec. Currently this is ' + 'supplied only on build.'), + 'default': None}, {'name': 'maxshape', 'type': (int, tuple), 'doc': 'The shape of this dataset. Use None for scalars.', 'default': None}, {'name': 'chunks', 'type': bool, 'doc': 'Whether or not to chunk this dataset.', 'default': False}, @@ -337,11 +341,14 @@ class DatasetBuilder(BaseBuilder): {'name': 'source', 'type': str, 'doc': 'The source of the data in this builder.', 'default': None}) def __init__(self, **kwargs): """ Create a Builder object for a dataset """ - name, data, dtype, attributes, maxshape, chunks, parent, source = getargs( - 'name', 'data', 'dtype', 'attributes', 'maxshape', 'chunks', 'parent', 'source', kwargs) + name, data, dtype, attributes, dimension_labels, maxshape, chunks, parent, source = getargs( + 'name', 'data', 'dtype', 'attributes', 'dimension_labels', 'maxshape', 'chunks', 'parent', 'source', + kwargs + ) super().__init__(name, attributes, parent, source) self['data'] = data self['attributes'] = _copy.copy(attributes) + self.__dimension_labels = dimension_labels self.__chunks = chunks self.__maxshape = maxshape if isinstance(data, BaseBuilder): @@ -361,6 +368,11 @@ def data(self, val): raise AttributeError("Cannot overwrite data.") self['data'] = val + @property + def dimension_labels(self): + """Labels for each dimension of this dataset from the spec.""" + return self.__dimension_labels + @property def chunks(self): """Whether or not this dataset is chunked.""" diff --git a/src/hdmf/build/objectmapper.py b/src/hdmf/build/objectmapper.py index b0bd7d594..b5815ee2c 100644 --- a/src/hdmf/build/objectmapper.py +++ b/src/hdmf/build/objectmapper.py @@ -10,14 +10,15 @@ from .errors import (BuildError, OrphanContainerBuildError, ReferenceTargetNotBuiltError, ContainerConfigurationError, ConstructError) from .manager import Proxy, BuildManager -from .warnings import MissingRequiredBuildWarning, DtypeConversionWarning, IncorrectQuantityBuildWarning +from .warnings import (MissingRequiredBuildWarning, DtypeConversionWarning, IncorrectQuantityBuildWarning, + IncorrectDatasetShapeBuildWarning) from ..container import AbstractContainer, Data, DataRegion from ..term_set import TermSetWrapper from ..data_utils import DataIO, AbstractDataChunkIterator from ..query import ReferenceResolver from ..spec import Spec, AttributeSpec, DatasetSpec, GroupSpec, LinkSpec, RefSpec from ..spec.spec import BaseStorageSpec -from ..utils import docval, getargs, ExtenderMeta, get_docval +from ..utils import docval, getargs, ExtenderMeta, get_docval, get_data_shape _const_arg = '__constructor_arg' @@ -721,19 +722,34 @@ def build(self, **kwargs): if not isinstance(container, Data): msg = "'container' must be of type Data with DatasetSpec" raise ValueError(msg) - spec_dtype, spec_shape, spec = self.__check_dset_spec(self.spec, spec_ext) + spec_dtype, spec_shape, spec_dims, spec = self.__check_dset_spec(self.spec, spec_ext) + dimension_labels = self.__get_dimension_labels_from_spec(container.data, spec_shape, spec_dims) if isinstance(spec_dtype, RefSpec): self.logger.debug("Building %s '%s' as a dataset of references (source: %s)" % (container.__class__.__name__, container.name, repr(source))) # create dataset builder with data=None as a placeholder. fill in with refs later - builder = DatasetBuilder(name, data=None, parent=parent, source=source, dtype=spec_dtype.reftype) + builder = DatasetBuilder( + name, + data=None, + parent=parent, + source=source, + dtype=spec_dtype.reftype, + dimension_labels=dimension_labels, + ) manager.queue_ref(self.__set_dataset_to_refs(builder, spec_dtype, spec_shape, container, manager)) elif isinstance(spec_dtype, list): # a compound dataset self.logger.debug("Building %s '%s' as a dataset of compound dtypes (source: %s)" % (container.__class__.__name__, container.name, repr(source))) # create dataset builder with data=None, dtype=None as a placeholder. fill in with refs later - builder = DatasetBuilder(name, data=None, parent=parent, source=source, dtype=spec_dtype) + builder = DatasetBuilder( + name, + data=None, + parent=parent, + source=source, + dtype=spec_dtype, + dimension_labels=dimension_labels, + ) manager.queue_ref(self.__set_compound_dataset_to_refs(builder, spec, spec_dtype, container, manager)) else: @@ -744,7 +760,14 @@ def build(self, **kwargs): % (container.__class__.__name__, container.name, repr(source))) # an unspecified dtype and we were given references # create dataset builder with data=None as a placeholder. fill in with refs later - builder = DatasetBuilder(name, data=None, parent=parent, source=source, dtype='object') + builder = DatasetBuilder( + name, + data=None, + parent=parent, + source=source, + dtype="object", + dimension_labels=dimension_labels, + ) manager.queue_ref(self.__set_untyped_dataset_to_refs(builder, container, manager)) else: # a dataset that has no references, pass the conversion off to the convert_dtype method @@ -760,7 +783,14 @@ def build(self, **kwargs): except Exception as ex: msg = 'could not resolve dtype for %s \'%s\'' % (type(container).__name__, container.name) raise Exception(msg) from ex - builder = DatasetBuilder(name, bldr_data, parent=parent, source=source, dtype=dtype) + builder = DatasetBuilder( + name, + data=bldr_data, + parent=parent, + source=source, + dtype=dtype, + dimension_labels=dimension_labels, + ) # Add attributes from the specification extension to the list of attributes all_attrs = self.__spec.attributes + getattr(spec_ext, 'attributes', tuple()) @@ -779,14 +809,67 @@ def __check_dset_spec(self, orig, ext): """ dtype = orig.dtype shape = orig.shape + dims = orig.dims spec = orig if ext is not None: if ext.dtype is not None: dtype = ext.dtype if ext.shape is not None: shape = ext.shape + dims = ext.dims spec = ext - return dtype, shape, spec + return dtype, shape, dims, spec + + def __get_dimension_labels_from_spec(self, data, spec_shape, spec_dims) -> tuple: + if spec_shape is None or spec_dims is None: + return None + data_shape = get_data_shape(data) + # if shape is a list of allowed shapes, find the index of the shape that matches the data + if isinstance(spec_shape[0], list): + match_shape_inds = list() + for i, s in enumerate(spec_shape): + # skip this shape if it has a different number of dimensions from the data + if len(s) != len(data_shape): + continue + # check each dimension. None means any length is allowed + match = True + for j, d in enumerate(data_shape): + if s[j] is not None and s[j] != d: + match = False + break + if match: + match_shape_inds.append(i) + # use the most specific match -- the one with the fewest Nones + if match_shape_inds: + if len(match_shape_inds) == 1: + return tuple(spec_dims[match_shape_inds[0]]) + else: + count_nones = [len([x for x in spec_shape[k] if x is None]) for k in match_shape_inds] + index_min_count = count_nones.index(min(count_nones)) + best_match_ind = match_shape_inds[index_min_count] + return tuple(spec_dims[best_match_ind]) + else: + # no matches found + msg = "Shape of data does not match any allowed shapes in spec '%s'" % self.spec.path + warnings.warn(msg, IncorrectDatasetShapeBuildWarning) + return None + else: + if len(data_shape) != len(spec_shape): + msg = "Shape of data does not match shape in spec '%s'" % self.spec.path + warnings.warn(msg, IncorrectDatasetShapeBuildWarning) + return None + # check each dimension. None means any length is allowed + match = True + for j, d in enumerate(data_shape): + if spec_shape[j] is not None and spec_shape[j] != d: + match = False + break + if not match: + msg = "Shape of data does not match shape in spec '%s'" % self.spec.path + warnings.warn(msg, IncorrectDatasetShapeBuildWarning) + return None + # shape is a single list of allowed dimension lengths + return tuple(spec_dims) def __is_reftype(self, data): if (isinstance(data, AbstractDataChunkIterator) or diff --git a/src/hdmf/build/warnings.py b/src/hdmf/build/warnings.py index 3d5f02126..6a6ea6986 100644 --- a/src/hdmf/build/warnings.py +++ b/src/hdmf/build/warnings.py @@ -15,6 +15,13 @@ class IncorrectQuantityBuildWarning(BuildWarning): pass +class IncorrectDatasetShapeBuildWarning(BuildWarning): + """ + Raised when a dataset has a shape that is not allowed by the spec. + """ + pass + + class MissingRequiredBuildWarning(BuildWarning): """ Raised when a required field is missing. diff --git a/tests/unit/build_tests/mapper_tests/test_build.py b/tests/unit/build_tests/mapper_tests/test_build.py index b90ad6f1a..28cc9518e 100644 --- a/tests/unit/build_tests/mapper_tests/test_build.py +++ b/tests/unit/build_tests/mapper_tests/test_build.py @@ -4,7 +4,7 @@ from hdmf import Container, Data, TermSet, TermSetWrapper from hdmf.common import VectorData, get_type_map from hdmf.build import ObjectMapper, BuildManager, TypeMap, GroupBuilder, DatasetBuilder -from hdmf.build.warnings import DtypeConversionWarning +from hdmf.build.warnings import DtypeConversionWarning, IncorrectDatasetShapeBuildWarning from hdmf.spec import GroupSpec, AttributeSpec, DatasetSpec, SpecCatalog, SpecNamespace, NamespaceCatalog, Spec from hdmf.testing import TestCase from hdmf.utils import docval, getargs @@ -650,3 +650,287 @@ def test_build_incorrect_dtype(self): msg = "could not resolve dtype for BarData 'my_bar'" with self.assertRaisesWith(Exception, msg): self.manager.build(bar_data_holder_inst, source='test.h5') + + +class BuildDatasetShapeMixin(TestCase, metaclass=ABCMeta): + + def setUp(self): + self.set_up_specs() + spec_catalog = SpecCatalog() + spec_catalog.register_spec(self.bar_data_spec, 'test.yaml') + spec_catalog.register_spec(self.bar_data_holder_spec, 'test.yaml') + namespace = SpecNamespace( + doc='a test namespace', + name=CORE_NAMESPACE, + schema=[{'source': 'test.yaml'}], + version='0.1.0', + catalog=spec_catalog + ) + namespace_catalog = NamespaceCatalog() + namespace_catalog.add_namespace(CORE_NAMESPACE, namespace) + type_map = TypeMap(namespace_catalog) + type_map.register_container_type(CORE_NAMESPACE, 'BarData', BarData) + type_map.register_container_type(CORE_NAMESPACE, 'BarDataHolder', BarDataHolder) + type_map.register_map(BarData, ExtBarDataMapper) + type_map.register_map(BarDataHolder, ObjectMapper) + self.manager = BuildManager(type_map) + + def set_up_specs(self): + shape, dims = self.get_base_shape_dims() + self.bar_data_spec = DatasetSpec( + doc='A test dataset specification with a data type', + data_type_def='BarData', + dtype='int', + shape=shape, + dims=dims, + ) + self.bar_data_holder_spec = GroupSpec( + doc='A container of multiple extended BarData objects', + data_type_def='BarDataHolder', + datasets=[self.get_dataset_inc_spec()], + ) + + @abstractmethod + def get_base_shape_dims(self): + pass + + @abstractmethod + def get_dataset_inc_spec(self): + pass + + +class TestBuildDatasetOneOptionBadShapeUnspecified1(BuildDatasetShapeMixin): + """Test dataset spec shape = 2D any length, data = 1D. Should raise warning and set dimension_labels to None.""" + + def get_base_shape_dims(self): + return [None, None], ['a', 'b'] + + def get_dataset_inc_spec(self): + dataset_inc_spec = DatasetSpec( + doc='A BarData', + data_type_inc='BarData', + quantity='*', + ) + return dataset_inc_spec + + def test_build(self): + """ + Test build of BarDataHolder which contains a BarData. + """ + # NOTE: attr1 doesn't map to anything but is required in the test container class + bar_data_inst = BarData(name='my_bar', data=[1, 2, 3], attr1='a string') + bar_data_holder_inst = BarDataHolder( + name='my_bar_holder', + bar_datas=[bar_data_inst], + ) + + msg = "Shape of data does not match shape in spec 'BarData'" + with self.assertWarnsWith(IncorrectDatasetShapeBuildWarning, msg): + builder = self.manager.build(bar_data_holder_inst, source='test.h5') + assert builder.datasets['my_bar'].dimension_labels is None + + +class TestBuildDatasetOneOptionBadShapeUnspecified2(BuildDatasetShapeMixin): + """Test dataset spec shape = (any, 2), data = (3, 1). Should raise warning and set dimension_labels to None.""" + + def get_base_shape_dims(self): + return [None, 2], ['a', 'b'] + + def get_dataset_inc_spec(self): + dataset_inc_spec = DatasetSpec( + doc='A BarData', + data_type_inc='BarData', + quantity='*', + ) + return dataset_inc_spec + + def test_build(self): + """ + Test build of BarDataHolder which contains a BarData. + """ + # NOTE: attr1 doesn't map to anything but is required in the test container class + bar_data_inst = BarData(name='my_bar', data=[[1], [2], [3]], attr1='a string') + bar_data_holder_inst = BarDataHolder( + name='my_bar_holder', + bar_datas=[bar_data_inst], + ) + + msg = "Shape of data does not match shape in spec 'BarData'" + with self.assertWarnsWith(IncorrectDatasetShapeBuildWarning, msg): + builder = self.manager.build(bar_data_holder_inst, source='test.h5') + assert builder.datasets['my_bar'].dimension_labels is None + + +class TestBuildDatasetTwoOptionsBadShapeUnspecified(BuildDatasetShapeMixin): + """Test dataset spec shape = (any, 2) or (any, 3), data = (3, 1). + Should raise warning and set dimension_labels to None. + """ + + def get_base_shape_dims(self): + return [[None, 2], [None, 3]], [['a', 'b1'], ['a', 'b2']] + + def get_dataset_inc_spec(self): + dataset_inc_spec = DatasetSpec( + doc='A BarData', + data_type_inc='BarData', + quantity='*', + ) + return dataset_inc_spec + + def test_build(self): + """ + Test build of BarDataHolder which contains a BarData. + """ + # NOTE: attr1 doesn't map to anything but is required in the test container class + bar_data_inst = BarData(name='my_bar', data=[[1], [2], [3]], attr1='a string') + bar_data_holder_inst = BarDataHolder( + name='my_bar_holder', + bar_datas=[bar_data_inst], + ) + + msg = "Shape of data does not match any allowed shapes in spec 'BarData'" + with self.assertWarnsWith(IncorrectDatasetShapeBuildWarning, msg): + builder = self.manager.build(bar_data_holder_inst, source='test.h5') + assert builder.datasets['my_bar'].dimension_labels is None + + +class TestBuildDatasetDimensionLabelsUnspecified(BuildDatasetShapeMixin): + + def get_base_shape_dims(self): + return None, None + + def get_dataset_inc_spec(self): + dataset_inc_spec = DatasetSpec( + doc='A BarData', + data_type_inc='BarData', + quantity='*', + ) + return dataset_inc_spec + + def test_build(self): + """ + Test build of BarDataHolder which contains a BarData. + """ + # NOTE: attr1 doesn't map to anything but is required in the test container class + bar_data_inst = BarData(name='my_bar', data=[[1, 2, 3], [4, 5, 6]], attr1='a string') + bar_data_holder_inst = BarDataHolder( + name='my_bar_holder', + bar_datas=[bar_data_inst], + ) + + builder = self.manager.build(bar_data_holder_inst, source='test.h5') + assert builder.datasets['my_bar'].dimension_labels is None + + +class TestBuildDatasetDimensionLabelsOneOption(BuildDatasetShapeMixin): + + def get_base_shape_dims(self): + return [None, None], ['a', 'b'] + + def get_dataset_inc_spec(self): + dataset_inc_spec = DatasetSpec( + doc='A BarData', + data_type_inc='BarData', + quantity='*', + ) + return dataset_inc_spec + + def test_build(self): + """ + Test build of BarDataHolder which contains a BarData. + """ + # NOTE: attr1 doesn't map to anything but is required in the test container class + bar_data_inst = BarData(name='my_bar', data=[[1, 2, 3], [4, 5, 6]], attr1='a string') + bar_data_holder_inst = BarDataHolder( + name='my_bar_holder', + bar_datas=[bar_data_inst], + ) + + builder = self.manager.build(bar_data_holder_inst, source='test.h5') + assert builder.datasets['my_bar'].dimension_labels == ('a', 'b') + + +class TestBuildDatasetDimensionLabelsTwoOptionsOneMatch(BuildDatasetShapeMixin): + + def get_base_shape_dims(self): + return [[None], [None, None]], [['a'], ['a', 'b']] + + def get_dataset_inc_spec(self): + dataset_inc_spec = DatasetSpec( + doc='A BarData', + data_type_inc='BarData', + quantity='*', + ) + return dataset_inc_spec + + def test_build(self): + """ + Test build of BarDataHolder which contains a BarData. + """ + # NOTE: attr1 doesn't map to anything but is required in the test container class + bar_data_inst = BarData(name='my_bar', data=[[1, 2, 3], [4, 5, 6]], attr1='a string') + bar_data_holder_inst = BarDataHolder( + name='my_bar_holder', + bar_datas=[bar_data_inst], + ) + + builder = self.manager.build(bar_data_holder_inst, source='test.h5') + assert builder.datasets['my_bar'].dimension_labels == ('a', 'b') + + +class TestBuildDatasetDimensionLabelsTwoOptionsTwoMatches(BuildDatasetShapeMixin): + + def get_base_shape_dims(self): + return [[None, None], [None, 3]], [['a', 'b1'], ['a', 'b2']] + + def get_dataset_inc_spec(self): + dataset_inc_spec = DatasetSpec( + doc='A BarData', + data_type_inc='BarData', + quantity='*', + ) + return dataset_inc_spec + + def test_build(self): + """ + Test build of BarDataHolder which contains a BarData. + """ + # NOTE: attr1 doesn't map to anything but is required in the test container class + bar_data_inst = BarData(name='my_bar', data=[[1, 2, 3], [4, 5, 6]], attr1='a string') + bar_data_holder_inst = BarDataHolder( + name='my_bar_holder', + bar_datas=[bar_data_inst], + ) + + builder = self.manager.build(bar_data_holder_inst, source='test.h5') + assert builder.datasets['my_bar'].dimension_labels == ('a', 'b2') + + +class TestBuildDatasetDimensionLabelsOneOptionRefined(BuildDatasetShapeMixin): + + def get_base_shape_dims(self): + return [None, None], ['a', 'b1'] + + def get_dataset_inc_spec(self): + dataset_inc_spec = DatasetSpec( + doc='A BarData', + data_type_inc='BarData', + quantity='*', + shape=[None, 3], + dims=['a', 'b2'], + ) + return dataset_inc_spec + + def test_build(self): + """ + Test build of BarDataHolder which contains a BarData. + """ + # NOTE: attr1 doesn't map to anything but is required in the test container class + bar_data_inst = BarData(name='my_bar', data=[[1, 2, 3], [4, 5, 6]], attr1='a string') + bar_data_holder_inst = BarDataHolder( + name='my_bar_holder', + bar_datas=[bar_data_inst], + ) + + builder = self.manager.build(bar_data_holder_inst, source='test.h5') + assert builder.datasets['my_bar'].dimension_labels == ('a', 'b2') From 50aad2fe4a3e34f4d7b941e7f8aa668ad0672535 Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Mon, 29 Jul 2024 08:59:29 -0700 Subject: [PATCH 29/82] Update CHANGELOG.md (#1156) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb80f5336..4d77fbdad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # HDMF Changelog -## HDMF 3.14.3 (Upcoming) +## HDMF 3.14.3 (July 29, 2024) ### Enhancements - Added new attribute "dimension_labels" on `DatasetBuilder` which specifies the names of the dimensions used in the From a98e5e98cbfb6e824fb57eb5e7eac1dc6cd4532b Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Wed, 31 Jul 2024 10:08:55 -0700 Subject: [PATCH 30/82] Minimum Support for Appending References in Hdmf-Zarr (#1157) * Minimum Support * Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ src/hdmf/data_utils.py | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d77fbdad..4a6369094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # HDMF Changelog +## HDMF 3.14.4 (Upcoming) + +### Enhancements +- Added support to append to a dataset of references for HDMF-Zarr. @mavaylon1 [#1157](https://github.com/hdmf-dev/hdmf/pull/1157) + ## HDMF 3.14.3 (July 29, 2024) ### Enhancements diff --git a/src/hdmf/data_utils.py b/src/hdmf/data_utils.py index 798a40973..91400da84 100644 --- a/src/hdmf/data_utils.py +++ b/src/hdmf/data_utils.py @@ -18,7 +18,8 @@ from .utils import docval, getargs, popargs, docval_macro, get_data_shape def append_data(data, arg): - if isinstance(data, (list, DataIO)): + from hdmf.backends.hdf5.h5_utils import HDMFDataset + if isinstance(data, (list, DataIO, HDMFDataset)): data.append(arg) return data elif type(data).__name__ == 'TermSetWrapper': # circular import From 3cc7db39d3e7f8e21fbaaa2282c14cebd758d20e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:01:11 -0700 Subject: [PATCH 31/82] [pre-commit.ci] pre-commit autoupdate (#1133) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f486273b..9011c9dc3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.9 + rev: v0.5.6 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From 49a60df21016fb4da83d64e84e7bc99dac03e94e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:54:11 -0700 Subject: [PATCH 32/82] [pre-commit.ci] pre-commit autoupdate (#1169) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9011c9dc3..0ad399734 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.6 + rev: v0.5.7 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From b0f068ee956870ce640dabf118260a97b18b1564 Mon Sep 17 00:00:00 2001 From: Jeremy Magland Date: Mon, 19 Aug 2024 00:34:38 -0400 Subject: [PATCH 33/82] speed up loading of namespaces: skip register type when already registered when loading namespace (#1102) * skip register type when already registered when loading namespace * update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md * Update namespace.py --------- Co-authored-by: Matthew Avaylon --- CHANGELOG.md | 1 + src/hdmf/spec/namespace.py | 20 ++++++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a6369094..c83a7ac3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Added new attribute "dimension_labels" on `DatasetBuilder` which specifies the names of the dimensions used in the dataset based on the shape of the dataset data and the dimension names in the spec for the data type. This attribute is available on build (during the write process), but not on read of a dataset from a file. @rly [#1081](https://github.com/hdmf-dev/hdmf/pull/1081) +- Speed up loading namespaces by skipping register_type when already registered. @magland [#1102](https://github.com/hdmf-dev/hdmf/pull/1102) ## HDMF 3.14.2 (July 7, 2024) diff --git a/src/hdmf/spec/namespace.py b/src/hdmf/spec/namespace.py index a2ae0bd37..f0417175f 100644 --- a/src/hdmf/spec/namespace.py +++ b/src/hdmf/spec/namespace.py @@ -466,15 +466,19 @@ def __load_namespace(self, namespace, reader, resolve=True): return included_types def __register_type(self, ndt, inc_ns, catalog, registered_types): - spec = inc_ns.get_spec(ndt) - spec_file = inc_ns.catalog.get_spec_source_file(ndt) - self.__register_dependent_types(spec, inc_ns, catalog, registered_types) - if isinstance(spec, DatasetSpec): - built_spec = self.dataset_spec_cls.build_spec(spec) + if ndt in registered_types: + # already registered + pass else: - built_spec = self.group_spec_cls.build_spec(spec) - registered_types.add(ndt) - catalog.register_spec(built_spec, spec_file) + spec = inc_ns.get_spec(ndt) + spec_file = inc_ns.catalog.get_spec_source_file(ndt) + self.__register_dependent_types(spec, inc_ns, catalog, registered_types) + if isinstance(spec, DatasetSpec): + built_spec = self.dataset_spec_cls.build_spec(spec) + else: + built_spec = self.group_spec_cls.build_spec(spec) + registered_types.add(ndt) + catalog.register_spec(built_spec, spec_file) def __register_dependent_types(self, spec, inc_ns, catalog, registered_types): """Ensure that classes for all types used by this type are registered From 875712be786974606730db4e36668138bb1e09fe Mon Sep 17 00:00:00 2001 From: Jeremy Magland Date: Mon, 19 Aug 2024 09:14:50 -0400 Subject: [PATCH 34/82] return shallow copy in build_const_args (#1103) Co-authored-by: Matthew Avaylon --- CHANGELOG.md | 1 + src/hdmf/spec/spec.py | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c83a7ac3b..5d904fc5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ dataset based on the shape of the dataset data and the dimension names in the spec for the data type. This attribute is available on build (during the write process), but not on read of a dataset from a file. @rly [#1081](https://github.com/hdmf-dev/hdmf/pull/1081) - Speed up loading namespaces by skipping register_type when already registered. @magland [#1102](https://github.com/hdmf-dev/hdmf/pull/1102) +- Speed up namespace loading: return a shallow copy rather than a deep copy in build_const_args. @magland [#1103](https://github.com/hdmf-dev/hdmf/pull/1103) ## HDMF 3.14.2 (July 7, 2024) diff --git a/src/hdmf/spec/spec.py b/src/hdmf/spec/spec.py index 358cc3256..64af0171f 100644 --- a/src/hdmf/spec/spec.py +++ b/src/hdmf/spec/spec.py @@ -1,7 +1,6 @@ import re from abc import ABCMeta from collections import OrderedDict -from copy import deepcopy from warnings import warn from ..utils import docval, getargs, popargs, get_docval @@ -84,7 +83,7 @@ class ConstructableDict(dict, metaclass=ABCMeta): def build_const_args(cls, spec_dict): ''' Build constructor arguments for this ConstructableDict class from a dictionary ''' # main use cases are when spec_dict is a ConstructableDict or a spec dict read from a file - return deepcopy(spec_dict) + return spec_dict.copy() @classmethod def build_spec(cls, spec_dict): From 2f339d14bf50d1a58bbd6f88b389a42523e4f191 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Mon, 19 Aug 2024 06:20:49 -0700 Subject: [PATCH 35/82] Improve "already exists" error message (#1165) * Improve "already exists" error message * Fix test, improve msg * Update changelog --------- Co-authored-by: Matthew Avaylon --- CHANGELOG.md | 1 + src/hdmf/container.py | 4 +++- tests/unit/test_multicontainerinterface.py | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d904fc5c..63106bcd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Enhancements - Added support to append to a dataset of references for HDMF-Zarr. @mavaylon1 [#1157](https://github.com/hdmf-dev/hdmf/pull/1157) +- Improved "already exists" error message when adding a container to a `MultiContainerInterface`. @rly [#1165](https://github.com/hdmf-dev/hdmf/pull/1165) ## HDMF 3.14.3 (July 29, 2024) diff --git a/src/hdmf/container.py b/src/hdmf/container.py index 287809406..67d8bcc2d 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -1142,7 +1142,9 @@ def _func(self, **kwargs): # still need to mark self as modified self.set_modified() if tmp.name in d: - msg = "'%s' already exists in %s '%s'" % (tmp.name, cls.__name__, self.name) + msg = (f"Cannot add {tmp.__class__} '{tmp.name}' at 0x{id(tmp)} to dict attribute '{attr_name}' in " + f"{cls} '{self.name}'. {d[tmp.name].__class__} '{tmp.name}' at 0x{id(d[tmp.name])} " + f"already exists in '{attr_name}' and has the same name.") raise ValueError(msg) d[tmp.name] = tmp return container diff --git a/tests/unit/test_multicontainerinterface.py b/tests/unit/test_multicontainerinterface.py index c705d0a6e..6da81c2cc 100644 --- a/tests/unit/test_multicontainerinterface.py +++ b/tests/unit/test_multicontainerinterface.py @@ -198,7 +198,10 @@ def test_add_single_dup(self): """Test that adding a container to the attribute dict correctly adds the container.""" obj1 = Container('obj1') foo = Foo(obj1) - msg = "'obj1' already exists in Foo 'Foo'" + msg = (f"Cannot add 'obj1' at 0x{id(obj1)} to dict attribute " + "'containers' in 'Foo'. " + f" 'obj1' at 0x{id(obj1)} already exists in 'containers' " + "and has the same name.") with self.assertRaisesWith(ValueError, msg): foo.add_container(obj1) From 316ec4bc41363b9e7f5035dfd9fd8fd329cb6392 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Mon, 19 Aug 2024 06:30:05 -0700 Subject: [PATCH 36/82] Adjust stacklevel to point to user code (#1166) * Adjust stacklevel to point to user code * Add changelog * Fix typo --------- Co-authored-by: Matthew Avaylon --- CHANGELOG.md | 1 + src/hdmf/backends/hdf5/h5_utils.py | 8 ++++---- src/hdmf/backends/hdf5/h5tools.py | 2 +- src/hdmf/common/resources.py | 2 +- src/hdmf/common/table.py | 10 +++++----- src/hdmf/container.py | 2 +- src/hdmf/spec/namespace.py | 8 ++++---- src/hdmf/spec/spec.py | 2 +- 8 files changed, 18 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63106bcd8..c1af6aab8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Enhancements - Added support to append to a dataset of references for HDMF-Zarr. @mavaylon1 [#1157](https://github.com/hdmf-dev/hdmf/pull/1157) +- Adjusted stacklevel of warnings to point to user code when possible. @rly [#1166](https://github.com/hdmf-dev/hdmf/pull/1166) - Improved "already exists" error message when adding a container to a `MultiContainerInterface`. @rly [#1165](https://github.com/hdmf-dev/hdmf/pull/1165) ## HDMF 3.14.3 (July 29, 2024) diff --git a/src/hdmf/backends/hdf5/h5_utils.py b/src/hdmf/backends/hdf5/h5_utils.py index aa68272c9..e484a43c2 100644 --- a/src/hdmf/backends/hdf5/h5_utils.py +++ b/src/hdmf/backends/hdf5/h5_utils.py @@ -501,7 +501,7 @@ def __init__(self, **kwargs): # Check for possible collision with other parameters if not isinstance(getargs('data', kwargs), Dataset) and self.__link_data: self.__link_data = False - warnings.warn('link_data parameter in H5DataIO will be ignored', stacklevel=2) + warnings.warn('link_data parameter in H5DataIO will be ignored', stacklevel=3) # Call the super constructor and consume the data parameter super().__init__(**kwargs) # Construct the dict with the io args, ignoring all options that were set to None @@ -525,7 +525,7 @@ def __init__(self, **kwargs): self.__iosettings.pop('compression', None) if 'compression_opts' in self.__iosettings: warnings.warn('Compression disabled by compression=False setting. ' + - 'compression_opts parameter will, therefore, be ignored.', stacklevel=2) + 'compression_opts parameter will, therefore, be ignored.', stacklevel=3) self.__iosettings.pop('compression_opts', None) # Validate the compression options used self._check_compression_options() @@ -540,7 +540,7 @@ def __init__(self, **kwargs): if isinstance(self.data, Dataset): for k in self.__iosettings.keys(): warnings.warn("%s in H5DataIO will be ignored with H5DataIO.data being an HDF5 dataset" % k, - stacklevel=2) + stacklevel=3) self.__dataset = None @@ -618,7 +618,7 @@ def _check_compression_options(self): if self.__iosettings['compression'] not in ['gzip', h5py_filters.h5z.FILTER_DEFLATE]: warnings.warn(str(self.__iosettings['compression']) + " compression may not be available " "on all installations of HDF5. Use of gzip is recommended to ensure portability of " - "the generated HDF5 files.", stacklevel=3) + "the generated HDF5 files.", stacklevel=4) @staticmethod def filter_available(filter, allow_plugin_filters): diff --git a/src/hdmf/backends/hdf5/h5tools.py b/src/hdmf/backends/hdf5/h5tools.py index 8135d75e7..da07a6a5c 100644 --- a/src/hdmf/backends/hdf5/h5tools.py +++ b/src/hdmf/backends/hdf5/h5tools.py @@ -344,7 +344,7 @@ def copy_file(self, **kwargs): warnings.warn("The copy_file class method is no longer supported and may be removed in a future version of " "HDMF. Please use the export method or h5py.File.copy method instead.", category=DeprecationWarning, - stacklevel=2) + stacklevel=3) source_filename, dest_filename, expand_external, expand_refs, expand_soft = getargs('source_filename', 'dest_filename', diff --git a/src/hdmf/common/resources.py b/src/hdmf/common/resources.py index fdca4bb81..1fc731ef5 100644 --- a/src/hdmf/common/resources.py +++ b/src/hdmf/common/resources.py @@ -628,7 +628,7 @@ def add_ref(self, **kwargs): if entity_uri is not None: entity_uri = entity.entity_uri msg = 'This entity already exists. Ignoring new entity uri' - warn(msg, stacklevel=2) + warn(msg, stacklevel=3) ################# # Validate Object diff --git a/src/hdmf/common/table.py b/src/hdmf/common/table.py index 2e90b0cdf..b4530c7b7 100644 --- a/src/hdmf/common/table.py +++ b/src/hdmf/common/table.py @@ -717,7 +717,7 @@ def add_row(self, **kwargs): warn(("Data has elements with different lengths and therefore cannot be coerced into an " "N-dimensional array. Use the 'index' argument when creating a column to add rows " "with different lengths."), - stacklevel=2) + stacklevel=3) def __eq__(self, other): """Compare if the two DynamicTables contain the same data. @@ -776,7 +776,7 @@ def add_column(self, **kwargs): # noqa: C901 if isinstance(index, VectorIndex): warn("Passing a VectorIndex in for index may lead to unexpected behavior. This functionality will be " - "deprecated in a future version of HDMF.", category=FutureWarning, stacklevel=2) + "deprecated in a future version of HDMF.", category=FutureWarning, stacklevel=3) if name in self.__colids: # column has already been added msg = "column '%s' already exists in %s '%s'" % (name, self.__class__.__name__, self.name) @@ -793,7 +793,7 @@ def add_column(self, **kwargs): # noqa: C901 "Please ensure the new column complies with the spec. " "This will raise an error in a future version of HDMF." % (name, self.__class__.__name__, spec_table)) - warn(msg, stacklevel=2) + warn(msg, stacklevel=3) index_bool = index or not isinstance(index, bool) spec_index = self.__uninit_cols[name].get('index', False) @@ -803,7 +803,7 @@ def add_column(self, **kwargs): # noqa: C901 "Please ensure the new column complies with the spec. " "This will raise an error in a future version of HDMF." % (name, self.__class__.__name__, spec_index)) - warn(msg, stacklevel=2) + warn(msg, stacklevel=3) spec_col_cls = self.__uninit_cols[name].get('class', VectorData) if col_cls != spec_col_cls: @@ -841,7 +841,7 @@ def add_column(self, **kwargs): # noqa: C901 warn(("Data has elements with different lengths and therefore cannot be coerced into an " "N-dimensional array. Use the 'index' argument when adding a column of data with " "different lengths."), - stacklevel=2) + stacklevel=3) # Check that we are asked to create an index if (isinstance(index, bool) or isinstance(index, int)) and index > 0 and len(data) > 0: diff --git a/src/hdmf/container.py b/src/hdmf/container.py index 67d8bcc2d..3772cd634 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -894,7 +894,7 @@ def set_dataio(self, **kwargs): warn( "Data.set_dataio() is deprecated. Please use Data.set_data_io() instead.", DeprecationWarning, - stacklevel=2, + stacklevel=3, ) dataio = getargs('dataio', kwargs) dataio.data = self.__data diff --git a/src/hdmf/spec/namespace.py b/src/hdmf/spec/namespace.py index f0417175f..57232bd25 100644 --- a/src/hdmf/spec/namespace.py +++ b/src/hdmf/spec/namespace.py @@ -50,13 +50,13 @@ def __init__(self, **kwargs): self['full_name'] = full_name if version == str(SpecNamespace.UNVERSIONED): # the unversioned version may be written to file as a string and read from file as a string - warn("Loaded namespace '%s' is unversioned. Please notify the extension author." % name, stacklevel=2) + warn(f"Loaded namespace '{name}' is unversioned. Please notify the extension author.") version = SpecNamespace.UNVERSIONED if version is None: # version is required on write -- see YAMLSpecWriter.write_namespace -- but can be None on read in order to # be able to read older files with extensions that are missing the version key. - warn(("Loaded namespace '%s' is missing the required key 'version'. Version will be set to '%s'. " - "Please notify the extension author.") % (name, SpecNamespace.UNVERSIONED), stacklevel=2) + warn(f"Loaded namespace '{name}' is missing the required key 'version'. Version will be set to " + f"'{SpecNamespace.UNVERSIONED}'. Please notify the extension author.") version = SpecNamespace.UNVERSIONED self['version'] = version if date is not None: @@ -533,7 +533,7 @@ def load_namespaces(self, **kwargs): if ns['version'] != self.__namespaces.get(ns['name'])['version']: # warn if the cached namespace differs from the already loaded namespace warn("Ignoring cached namespace '%s' version %s because version %s is already loaded." - % (ns['name'], ns['version'], self.__namespaces.get(ns['name'])['version']), stacklevel=2) + % (ns['name'], ns['version'], self.__namespaces.get(ns['name'])['version'])) else: to_load.append(ns) # now load specs into namespace diff --git a/src/hdmf/spec/spec.py b/src/hdmf/spec/spec.py index 64af0171f..e10d5e43e 100644 --- a/src/hdmf/spec/spec.py +++ b/src/hdmf/spec/spec.py @@ -321,7 +321,7 @@ def __init__(self, **kwargs): default_name = getargs('default_name', kwargs) if default_name: if name is not None: - warn("found 'default_name' with 'name' - ignoring 'default_name'", stacklevel=2) + warn("found 'default_name' with 'name' - ignoring 'default_name'") else: self['default_name'] = default_name self.__attributes = dict() From d50db924b3dd9ad6487f9dc8a063452d96ae807e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 10:12:27 -0700 Subject: [PATCH 37/82] [pre-commit.ci] pre-commit autoupdate (#1174) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0ad399734..c76f12bef 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.7 + rev: v0.6.1 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From 2b167aedc8a8f58afd75d3d0c750f6d620dc663d Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:48:42 -0700 Subject: [PATCH 38/82] Add support to write multidimensional string arrays (#1173) * add condition for multidim string arrays * add tests for multidim string array build * update condition when defining hdf5 dataset shape * add test to write multidim string array * update CHANGELOG.md * fix text decoding in test * add recursive string type for arrays of arbitrary dim * add test for compound data type with strings * add tests for multidim str attributes * fix line lengths * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update compound dtype test --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ryan Ly --- CHANGELOG.md | 1 + src/hdmf/backends/hdf5/h5tools.py | 2 +- src/hdmf/build/objectmapper.py | 10 +- tests/unit/build_tests/test_classgenerator.py | 7 +- tests/unit/build_tests/test_io_map.py | 119 +++++++++++++++++- tests/unit/test_io_hdf5_h5tools.py | 27 +++- 6 files changed, 153 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1af6aab8..549eccc7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Added support to append to a dataset of references for HDMF-Zarr. @mavaylon1 [#1157](https://github.com/hdmf-dev/hdmf/pull/1157) - Adjusted stacklevel of warnings to point to user code when possible. @rly [#1166](https://github.com/hdmf-dev/hdmf/pull/1166) - Improved "already exists" error message when adding a container to a `MultiContainerInterface`. @rly [#1165](https://github.com/hdmf-dev/hdmf/pull/1165) +- Added support to write multidimensional string arrays. @stephprince [#1173](https://github.com/hdmf-dev/hdmf/pull/1173) ## HDMF 3.14.3 (July 29, 2024) diff --git a/src/hdmf/backends/hdf5/h5tools.py b/src/hdmf/backends/hdf5/h5tools.py index da07a6a5c..ffdc4eab6 100644 --- a/src/hdmf/backends/hdf5/h5tools.py +++ b/src/hdmf/backends/hdf5/h5tools.py @@ -1469,7 +1469,7 @@ def __list_fill__(cls, parent, name, data, options=None): data_shape = io_settings.pop('shape') elif hasattr(data, 'shape'): data_shape = data.shape - elif isinstance(dtype, np.dtype): + elif isinstance(dtype, np.dtype) and len(dtype) > 1: # check if compound dtype data_shape = (len(data),) else: data_shape = get_data_shape(data) diff --git a/src/hdmf/build/objectmapper.py b/src/hdmf/build/objectmapper.py index b5815ee2c..d6e1de15a 100644 --- a/src/hdmf/build/objectmapper.py +++ b/src/hdmf/build/objectmapper.py @@ -598,11 +598,17 @@ def __get_data_type(cls, spec): def __convert_string(self, value, spec): """Convert string types to the specified dtype.""" + def __apply_string_type(value, string_type): + if isinstance(value, (list, tuple, np.ndarray, DataIO)): + return [__apply_string_type(item, string_type) for item in value] + else: + return string_type(value) + ret = value if isinstance(spec, AttributeSpec): if 'text' in spec.dtype: if spec.shape is not None or spec.dims is not None: - ret = list(map(str, value)) + ret = __apply_string_type(value, str) else: ret = str(value) elif isinstance(spec, DatasetSpec): @@ -618,7 +624,7 @@ def string_type(x): return x.isoformat() # method works for both date and datetime if string_type is not None: if spec.shape is not None or spec.dims is not None: - ret = list(map(string_type, value)) + ret = __apply_string_type(value, string_type) else: ret = string_type(value) # copy over any I/O parameters if they were specified diff --git a/tests/unit/build_tests/test_classgenerator.py b/tests/unit/build_tests/test_classgenerator.py index 52fdc4839..3c9fda283 100644 --- a/tests/unit/build_tests/test_classgenerator.py +++ b/tests/unit/build_tests/test_classgenerator.py @@ -180,10 +180,11 @@ def test_dynamic_container_creation(self): baz_spec = GroupSpec('A test extension with no Container class', data_type_def='Baz', data_type_inc=self.bar_spec, attributes=[AttributeSpec('attr3', 'a float attribute', 'float'), - AttributeSpec('attr4', 'another float attribute', 'float')]) + AttributeSpec('attr4', 'another float attribute', 'float'), + AttributeSpec('attr_array', 'an array attribute', 'text', shape=(None,)),]) self.spec_catalog.register_spec(baz_spec, 'extension.yaml') cls = self.type_map.get_dt_container_cls('Baz', CORE_NAMESPACE) - expected_args = {'name', 'data', 'attr1', 'attr2', 'attr3', 'attr4', 'skip_post_init'} + expected_args = {'name', 'data', 'attr1', 'attr2', 'attr3', 'attr4', 'attr_array', 'skip_post_init'} received_args = set() for x in get_docval(cls.__init__): @@ -211,7 +212,7 @@ def test_dynamic_container_creation_defaults(self): AttributeSpec('attr4', 'another float attribute', 'float')]) self.spec_catalog.register_spec(baz_spec, 'extension.yaml') cls = self.type_map.get_dt_container_cls('Baz', CORE_NAMESPACE) - expected_args = {'name', 'data', 'attr1', 'attr2', 'attr3', 'attr4', 'foo', 'skip_post_init'} + expected_args = {'name', 'data', 'attr1', 'attr2', 'attr3', 'attr4', 'attr_array', 'foo', 'skip_post_init'} received_args = set(map(lambda x: x['name'], get_docval(cls.__init__))) self.assertSetEqual(expected_args, received_args) self.assertEqual(cls.__name__, 'Baz') diff --git a/tests/unit/build_tests/test_io_map.py b/tests/unit/build_tests/test_io_map.py index 63f397682..e095ef318 100644 --- a/tests/unit/build_tests/test_io_map.py +++ b/tests/unit/build_tests/test_io_map.py @@ -9,6 +9,7 @@ from hdmf.testing import TestCase from abc import ABCMeta, abstractmethod import unittest +import numpy as np from tests.unit.helpers.utils import CORE_NAMESPACE, create_test_type_map @@ -20,24 +21,27 @@ class Bar(Container): {'name': 'attr1', 'type': str, 'doc': 'an attribute'}, {'name': 'attr2', 'type': int, 'doc': 'another attribute'}, {'name': 'attr3', 'type': float, 'doc': 'a third attribute', 'default': 3.14}, + {'name': 'attr_array', 'type': 'array_data', 'doc': 'another attribute', 'default': (1, 2, 3)}, {'name': 'foo', 'type': 'Foo', 'doc': 'a group', 'default': None}) def __init__(self, **kwargs): - name, data, attr1, attr2, attr3, foo = getargs('name', 'data', 'attr1', 'attr2', 'attr3', 'foo', kwargs) + name, data, attr1, attr2, attr3, attr_array, foo = getargs('name', 'data', 'attr1', 'attr2', 'attr3', + 'attr_array', 'foo', kwargs) super().__init__(name=name) self.__data = data self.__attr1 = attr1 self.__attr2 = attr2 self.__attr3 = attr3 + self.__attr_array = attr_array self.__foo = foo if self.__foo is not None and self.__foo.parent is None: self.__foo.parent = self def __eq__(self, other): - attrs = ('name', 'data', 'attr1', 'attr2', 'attr3', 'foo') + attrs = ('name', 'data', 'attr1', 'attr2', 'attr3', 'attr_array', 'foo') return all(getattr(self, a) == getattr(other, a) for a in attrs) def __str__(self): - attrs = ('name', 'data', 'attr1', 'attr2', 'attr3', 'foo') + attrs = ('name', 'data', 'attr1', 'attr2', 'attr3', 'attr_array', 'foo') return ','.join('%s=%s' % (a, getattr(self, a)) for a in attrs) @property @@ -60,6 +64,10 @@ def attr2(self): def attr3(self): return self.__attr3 + @property + def attr_array(self): + return self.__attr_array + @property def foo(self): return self.__foo @@ -333,12 +341,15 @@ def test_build_1d(self): datasets=[DatasetSpec('an example dataset', 'text', name='data', shape=(None,), attributes=[AttributeSpec( 'attr2', 'an example integer attribute', 'int')])], - attributes=[AttributeSpec('attr1', 'an example string attribute', 'text')]) + attributes=[AttributeSpec('attr1', 'an example string attribute', 'text'), + AttributeSpec('attr_array', 'an example array attribute', 'text', + shape=(None,))]) type_map = self.customSetUp(bar_spec) type_map.register_map(Bar, BarMapper) - bar_inst = Bar('my_bar', ['a', 'b', 'c', 'd'], 'value1', 10) + bar_inst = Bar('my_bar', ['a', 'b', 'c', 'd'], 'value1', 10, attr_array=['a', 'b', 'c', 'd']) builder = type_map.build(bar_inst) - self.assertEqual(builder.get('data').data, ['a', 'b', 'c', 'd']) + np.testing.assert_array_equal(builder.get('data').data, np.array(['a', 'b', 'c', 'd'])) + np.testing.assert_array_equal(builder.get('attr_array'), np.array(['a', 'b', 'c', 'd'])) def test_build_scalar(self): bar_spec = GroupSpec('A test group specification with a data type', @@ -353,6 +364,102 @@ def test_build_scalar(self): builder = type_map.build(bar_inst) self.assertEqual(builder.get('data').data, "['a', 'b', 'c', 'd']") + def test_build_2d_lol(self): + bar_spec = GroupSpec( + doc='A test group specification with a data type', + data_type_def='Bar', + datasets=[ + DatasetSpec( + doc='an example dataset', + dtype='text', + name='data', + shape=(None, None), + attributes=[AttributeSpec(name='attr2', doc='an example integer attribute', dtype='int')], + ) + ], + attributes=[AttributeSpec(name='attr_array', doc='an example array attribute', dtype='text', + shape=(None, None))], + ) + type_map = self.customSetUp(bar_spec) + type_map.register_map(Bar, BarMapper) + str_lol_2d = [['aa', 'bb'], ['cc', 'dd']] + bar_inst = Bar('my_bar', str_lol_2d, 'value1', 10, attr_array=str_lol_2d) + builder = type_map.build(bar_inst) + self.assertEqual(builder.get('data').data, str_lol_2d) + self.assertEqual(builder.get('attr_array'), str_lol_2d) + + def test_build_2d_ndarray(self): + bar_spec = GroupSpec( + doc='A test group specification with a data type', + data_type_def='Bar', + datasets=[ + DatasetSpec( + doc='an example dataset', + dtype='text', + name='data', + shape=(None, None), + attributes=[AttributeSpec(name='attr2', doc='an example integer attribute', dtype='int')], + ) + ], + attributes=[AttributeSpec(name='attr_array', doc='an example array attribute', dtype='text', + shape=(None, None))], + ) + type_map = self.customSetUp(bar_spec) + type_map.register_map(Bar, BarMapper) + str_array_2d = np.array([['aa', 'bb'], ['cc', 'dd']]) + bar_inst = Bar('my_bar', str_array_2d, 'value1', 10, attr_array=str_array_2d) + builder = type_map.build(bar_inst) + np.testing.assert_array_equal(builder.get('data').data, str_array_2d) + np.testing.assert_array_equal(builder.get('attr_array'), str_array_2d) + + def test_build_3d_lol(self): + bar_spec = GroupSpec( + doc='A test group specification with a data type', + data_type_def='Bar', + datasets=[ + DatasetSpec( + doc='an example dataset', + dtype='text', + name='data', + shape=(None, None, None), + attributes=[AttributeSpec(name='attr2', doc='an example integer attribute', dtype='int')], + ) + ], + attributes=[AttributeSpec(name='attr_array', doc='an example array attribute', dtype='text', + shape=(None, None, None))], + ) + type_map = self.customSetUp(bar_spec) + type_map.register_map(Bar, BarMapper) + str_lol_3d = [[['aa', 'bb'], ['cc', 'dd']], [['ee', 'ff'], ['gg', 'hh']]] + bar_inst = Bar('my_bar', str_lol_3d, 'value1', 10, attr_array=str_lol_3d) + builder = type_map.build(bar_inst) + self.assertEqual(builder.get('data').data, str_lol_3d) + self.assertEqual(builder.get('attr_array'), str_lol_3d) + + def test_build_3d_ndarray(self): + bar_spec = GroupSpec( + doc='A test group specification with a data type', + data_type_def='Bar', + datasets=[ + DatasetSpec( + doc='an example dataset', + dtype='text', + name='data', + shape=(None, None, None), + attributes=[AttributeSpec(name='attr2', doc='an example integer attribute', dtype='int')], + ) + ], + attributes=[AttributeSpec(name='attr_array', doc='an example array attribute', dtype='text', + shape=(None, None, None))], + ) + type_map = self.customSetUp(bar_spec) + type_map.register_map(Bar, BarMapper) + str_array_3d = np.array([[['aa', 'bb'], ['cc', 'dd']], [['ee', 'ff'], ['gg', 'hh']]]) + bar_inst = Bar('my_bar', str_array_3d, 'value1', 10, attr_array=str_array_3d) + builder = type_map.build(bar_inst) + np.testing.assert_array_equal(builder.get('data').data, str_array_3d) + np.testing.assert_array_equal(builder.get('attr_array'), str_array_3d) + def test_build_dataio(self): bar_spec = GroupSpec('A test group specification with a data type', data_type_def='Bar', diff --git a/tests/unit/test_io_hdf5_h5tools.py b/tests/unit/test_io_hdf5_h5tools.py index 5a4fd5a32..b004a6c54 100644 --- a/tests/unit/test_io_hdf5_h5tools.py +++ b/tests/unit/test_io_hdf5_h5tools.py @@ -24,7 +24,7 @@ from hdmf.data_utils import DataChunkIterator, GenericDataChunkIterator, InvalidDataIOError from hdmf.spec.catalog import SpecCatalog from hdmf.spec.namespace import NamespaceCatalog, SpecNamespace -from hdmf.spec.spec import GroupSpec +from hdmf.spec.spec import GroupSpec, DtypeSpec from hdmf.testing import TestCase, remove_test_file from hdmf.common.resources import HERD from hdmf.term_set import TermSet, TermSetWrapper @@ -164,6 +164,31 @@ def test_write_dataset_list(self): dset = self.f['test_dataset'] self.assertTrue(np.all(dset[:] == a)) + def test_write_dataset_lol_strings(self): + a = [['aa', 'bb'], ['cc', 'dd']] + self.io.write_dataset(self.f, DatasetBuilder('test_dataset', a, attributes={})) + dset = self.f['test_dataset'] + decoded_dset = [[item.decode('utf-8') if isinstance(item, bytes) else item for item in sublist] + for sublist in dset[:]] + self.assertTrue(decoded_dset == a) + + def test_write_dataset_list_compound_datatype(self): + a = np.array([(1, 2, 0.5), (3, 4, 0.5)], dtype=[('x', 'int'), ('y', 'int'), ('z', 'float')]) + dset_builder = DatasetBuilder( + name='test_dataset', + data=a.tolist(), + attributes={}, + dtype=[ + DtypeSpec('x', doc='x', dtype='int'), + DtypeSpec('y', doc='y', dtype='int'), + DtypeSpec('z', doc='z', dtype='float'), + ], + ) + self.io.write_dataset(self.f, dset_builder) + dset = self.f['test_dataset'] + for field in a.dtype.names: + self.assertTrue(np.all(dset[field][:] == a[field])) + def test_write_dataset_list_compress_gzip(self): a = H5DataIO(np.arange(30).reshape(5, 2, 3), compression='gzip', From acc3d78cc5a828ddd384cca814ef60167ae92682 Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Wed, 21 Aug 2024 22:14:24 -0700 Subject: [PATCH 39/82] Write scalar datasets with compound data type (#1176) * add support for scalar compound datasets * add scalar compound dset io and validation tests * update CHANGELOG.md * Update tests/unit/test_io_hdf5_h5tools.py Co-authored-by: Ryan Ly * update container repr conditionals --------- Co-authored-by: Ryan Ly --- CHANGELOG.md | 3 +++ src/hdmf/backends/hdf5/h5tools.py | 4 ++++ src/hdmf/container.py | 6 +----- src/hdmf/validate/validator.py | 13 ++++++++---- tests/unit/test_io_hdf5_h5tools.py | 21 ++++++++++++++++++++ tests/unit/validator_tests/test_validate.py | 22 +++++++++++++++++++++ 6 files changed, 60 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 549eccc7a..f3c15392b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ - Improved "already exists" error message when adding a container to a `MultiContainerInterface`. @rly [#1165](https://github.com/hdmf-dev/hdmf/pull/1165) - Added support to write multidimensional string arrays. @stephprince [#1173](https://github.com/hdmf-dev/hdmf/pull/1173) +### Bug fixes +- Fixed issue where scalar datasets with a compound data type were being written as non-scalar datasets @stephprince [#1176](https://github.com/hdmf-dev/hdmf/pull/1176) + ## HDMF 3.14.3 (July 29, 2024) ### Enhancements diff --git a/src/hdmf/backends/hdf5/h5tools.py b/src/hdmf/backends/hdf5/h5tools.py index ffdc4eab6..4db6463dc 100644 --- a/src/hdmf/backends/hdf5/h5tools.py +++ b/src/hdmf/backends/hdf5/h5tools.py @@ -698,6 +698,8 @@ def __read_dataset(self, h5obj, name=None): d = ReferenceBuilder(target_builder) kwargs['data'] = d kwargs['dtype'] = d.dtype + elif h5obj.dtype.kind == 'V': # scalar compound data type + kwargs['data'] = np.array(scalar, dtype=h5obj.dtype) else: kwargs["data"] = scalar else: @@ -1227,6 +1229,8 @@ def _filler(): return # If the compound data type contains only regular data (i.e., no references) then we can write it as usual + elif len(np.shape(data)) == 0: + dset = self.__scalar_fill__(parent, name, data, options) else: dset = self.__list_fill__(parent, name, data, options) # Write a dataset containing references, i.e., a region or object reference. diff --git a/src/hdmf/container.py b/src/hdmf/container.py index 3772cd634..88a083599 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -629,12 +629,8 @@ def __repr__(self): template += "\nFields:\n" for k in sorted(self.fields): # sorted to enable tests v = self.fields[k] - # if isinstance(v, DataIO) or not hasattr(v, '__len__') or len(v) > 0: if hasattr(v, '__len__'): - if isinstance(v, (np.ndarray, list, tuple)): - if len(v) > 0: - template += " {}: {}\n".format(k, self.__smart_str(v, 1)) - elif v: + if isinstance(v, (np.ndarray, list, tuple)) or v: template += " {}: {}\n".format(k, self.__smart_str(v, 1)) else: template += " {}: {}\n".format(k, v) diff --git a/src/hdmf/validate/validator.py b/src/hdmf/validate/validator.py index e39011d9f..2668da1ec 100644 --- a/src/hdmf/validate/validator.py +++ b/src/hdmf/validate/validator.py @@ -134,7 +134,7 @@ def get_type(data, builder_dtype=None): elif isinstance(data, ReferenceResolver): return data.dtype, None # Numpy nd-array data - elif isinstance(data, np.ndarray): + elif isinstance(data, np.ndarray) and len(data.dtype) <= 1: if data.size > 0: return get_type(data[0], builder_dtype) else: @@ -147,11 +147,14 @@ def get_type(data, builder_dtype=None): # Case for h5py.Dataset and other I/O specific array types else: # Compound dtype - if builder_dtype and isinstance(builder_dtype, list): + if builder_dtype and len(builder_dtype) > 1: dtypes = [] string_formats = [] for i in range(len(builder_dtype)): - dtype, string_format = get_type(data[0][i]) + if len(np.shape(data)) == 0: + dtype, string_format = get_type(data[()][i]) + else: + dtype, string_format = get_type(data[0][i]) dtypes.append(dtype) string_formats.append(string_format) return dtypes, string_formats @@ -438,7 +441,9 @@ def validate(self, **kwargs): except EmptyArrayError: # do not validate dtype of empty array. HDMF does not yet set dtype when writing a list/tuple pass - if isinstance(builder.dtype, list): + if builder.dtype is not None and len(builder.dtype) > 1 and len(np.shape(builder.data)) == 0: + shape = () # scalar compound dataset + elif isinstance(builder.dtype, list): shape = (len(builder.data), ) # only 1D datasets with compound types are supported else: shape = get_data_shape(data) diff --git a/tests/unit/test_io_hdf5_h5tools.py b/tests/unit/test_io_hdf5_h5tools.py index b004a6c54..73aa89788 100644 --- a/tests/unit/test_io_hdf5_h5tools.py +++ b/tests/unit/test_io_hdf5_h5tools.py @@ -144,6 +144,16 @@ def test_write_dataset_string(self): read_a = read_a.decode('utf-8') self.assertEqual(read_a, a) + def test_write_dataset_scalar_compound(self): + cmpd_dtype = np.dtype([('x', np.int32), ('y', np.float64)]) + a = np.array((1, 0.1), dtype=cmpd_dtype) + self.io.write_dataset(self.f, DatasetBuilder('test_dataset', a, + dtype=[DtypeSpec('x', doc='x', dtype='int32'), + DtypeSpec('y', doc='y', dtype='float64')])) + dset = self.f['test_dataset'] + self.assertTupleEqual(dset.shape, ()) + self.assertEqual(dset[()].tolist(), a.tolist()) + ########################################## # write_dataset tests: TermSetWrapper ########################################## @@ -787,6 +797,17 @@ def test_read_str(self): self.assertEqual(str(bldr['test_dataset'].data), '') + def test_read_scalar_compound(self): + cmpd_dtype = np.dtype([('x', np.int32), ('y', np.float64)]) + a = np.array((1, 0.1), dtype=cmpd_dtype) + self.io.write_dataset(self.f, DatasetBuilder('test_dataset', a, + dtype=[DtypeSpec('x', doc='x', dtype='int32'), + DtypeSpec('y', doc='y', dtype='float64')])) + self.io.close() + with HDF5IO(self.path, 'r') as io: + bldr = io.read_builder() + np.testing.assert_array_equal(bldr['test_dataset'].data[()], a) + class TestRoundTrip(TestCase): diff --git a/tests/unit/validator_tests/test_validate.py b/tests/unit/validator_tests/test_validate.py index 95ff5d98e..dd79cfce5 100644 --- a/tests/unit/validator_tests/test_validate.py +++ b/tests/unit/validator_tests/test_validate.py @@ -501,6 +501,28 @@ def test_np_bool_for_bool(self): results = self.vmap.validate(bar_builder) self.assertEqual(len(results), 0) + def test_scalar_compound_dtype(self): + """Test that validator allows scalar compound dtype data where a compound dtype is specified.""" + spec_catalog = SpecCatalog() + dtype = [DtypeSpec('x', doc='x', dtype='int'), DtypeSpec('y', doc='y', dtype='float')] + spec = GroupSpec('A test group specification with a data type', + data_type_def='Bar', + datasets=[DatasetSpec('an example dataset', dtype, name='data',)], + attributes=[AttributeSpec('attr1', 'an example attribute', 'text',)]) + spec_catalog.register_spec(spec, 'test2.yaml') + self.namespace = SpecNamespace( + 'a test namespace', CORE_NAMESPACE, [{'source': 'test2.yaml'}], version='0.1.0', catalog=spec_catalog) + self.vmap = ValidatorMap(self.namespace) + + value = np.array((1, 2.2), dtype=[('x', 'int'), ('y', 'float')]) + bar_builder = GroupBuilder('my_bar', + attributes={'data_type': 'Bar', 'attr1': 'test'}, + datasets=[DatasetBuilder(name='data', + data=value, + dtype=[DtypeSpec('x', doc='x', dtype='int'), + DtypeSpec('y', doc='y', dtype='float'),],),]) + results = self.vmap.validate(bar_builder) + self.assertEqual(len(results), 0) class Test1DArrayValidation(TestCase): From e0bedca13f167d55a4be5657044c4c6697de95ca Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Thu, 22 Aug 2024 08:45:29 -0700 Subject: [PATCH 40/82] Append a Dataset of References (#1135) --- CHANGELOG.md | 1 + docs/source/install_developers.rst | 2 +- docs/source/install_users.rst | 2 +- src/hdmf/backends/hdf5/h5_utils.py | 16 +++++++++- src/hdmf/backends/hdf5/h5tools.py | 9 ++++++ src/hdmf/build/objectmapper.py | 6 ++++ src/hdmf/query.py | 6 ++++ tests/unit/test_io_hdf5_h5tools.py | 51 ++++++++++++++++++++++++++++++ 8 files changed, 90 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3c15392b..66a3474d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Adjusted stacklevel of warnings to point to user code when possible. @rly [#1166](https://github.com/hdmf-dev/hdmf/pull/1166) - Improved "already exists" error message when adding a container to a `MultiContainerInterface`. @rly [#1165](https://github.com/hdmf-dev/hdmf/pull/1165) - Added support to write multidimensional string arrays. @stephprince [#1173](https://github.com/hdmf-dev/hdmf/pull/1173) +- Add support for appending to a dataset of references. @mavaylon1 [#1135](https://github.com/hdmf-dev/hdmf/pull/1135) ### Bug fixes - Fixed issue where scalar datasets with a compound data type were being written as non-scalar datasets @stephprince [#1176](https://github.com/hdmf-dev/hdmf/pull/1176) diff --git a/docs/source/install_developers.rst b/docs/source/install_developers.rst index d043a351a..04e351c41 100644 --- a/docs/source/install_developers.rst +++ b/docs/source/install_developers.rst @@ -73,7 +73,7 @@ environment by using the ``conda remove --name hdmf-venv --all`` command. For advanced users, we recommend using Mambaforge_, a faster version of the conda package manager that includes conda-forge as a default channel. -.. _Anaconda: https://www.anaconda.com/products/distribution +.. _Anaconda: https://www.anaconda.com/download .. _Mambaforge: https://github.com/conda-forge/miniforge Install from GitHub diff --git a/docs/source/install_users.rst b/docs/source/install_users.rst index 8102651ff..49fbe07b2 100644 --- a/docs/source/install_users.rst +++ b/docs/source/install_users.rst @@ -29,4 +29,4 @@ You can also install HDMF using ``conda`` by running the following command in a conda install -c conda-forge hdmf -.. _Anaconda Distribution: https://www.anaconda.com/products/distribution +.. _Anaconda Distribution: https://www.anaconda.com/download diff --git a/src/hdmf/backends/hdf5/h5_utils.py b/src/hdmf/backends/hdf5/h5_utils.py index e484a43c2..278735fbc 100644 --- a/src/hdmf/backends/hdf5/h5_utils.py +++ b/src/hdmf/backends/hdf5/h5_utils.py @@ -17,7 +17,7 @@ import logging from ...array import Array -from ...data_utils import DataIO, AbstractDataChunkIterator +from ...data_utils import DataIO, AbstractDataChunkIterator, append_data from ...query import HDMFDataset, ReferenceResolver, ContainerResolver, BuilderResolver from ...region import RegionSlicer from ...spec import SpecWriter, SpecReader @@ -108,6 +108,20 @@ def ref(self): def shape(self): return self.dataset.shape + def append(self, arg): + # Get Builder + builder = self.io.manager.get_builder(arg) + if builder is None: + raise ValueError( + "The container being appended to the dataset has not yet been built. " + "Please write the container to the file, then open the modified file, and " + "append the read container to the dataset." + ) + + # Get HDF5 Reference + ref = self.io._create_ref(builder) + append_data(self.dataset, ref) + class DatasetOfReferences(H5Dataset, ReferenceResolver, metaclass=ABCMeta): """ diff --git a/src/hdmf/backends/hdf5/h5tools.py b/src/hdmf/backends/hdf5/h5tools.py index 4db6463dc..da7f78a91 100644 --- a/src/hdmf/backends/hdf5/h5tools.py +++ b/src/hdmf/backends/hdf5/h5tools.py @@ -1518,6 +1518,7 @@ def __get_ref(self, **kwargs): self.logger.debug("Getting reference for %s '%s'" % (container.__class__.__name__, container.name)) builder = self.manager.build(container) path = self.__get_path(builder) + self.logger.debug("Getting reference at path '%s'" % path) if isinstance(container, RegionBuilder): region = container.region @@ -1529,6 +1530,14 @@ def __get_ref(self, **kwargs): else: return self.__file[path].ref + @docval({'name': 'container', 'type': (Builder, Container, ReferenceBuilder), 'doc': 'the object to reference', + 'default': None}, + {'name': 'region', 'type': (slice, list, tuple), 'doc': 'the region reference indexing object', + 'default': None}, + returns='the reference', rtype=Reference) + def _create_ref(self, **kwargs): + return self.__get_ref(**kwargs) + def __is_ref(self, dtype): if isinstance(dtype, DtypeSpec): return self.__is_ref(dtype.dtype) diff --git a/src/hdmf/build/objectmapper.py b/src/hdmf/build/objectmapper.py index d6e1de15a..3e8d835f1 100644 --- a/src/hdmf/build/objectmapper.py +++ b/src/hdmf/build/objectmapper.py @@ -10,8 +10,11 @@ from .errors import (BuildError, OrphanContainerBuildError, ReferenceTargetNotBuiltError, ContainerConfigurationError, ConstructError) from .manager import Proxy, BuildManager + from .warnings import (MissingRequiredBuildWarning, DtypeConversionWarning, IncorrectQuantityBuildWarning, IncorrectDatasetShapeBuildWarning) +from hdmf.backends.hdf5.h5_utils import H5DataIO + from ..container import AbstractContainer, Data, DataRegion from ..term_set import TermSetWrapper from ..data_utils import DataIO, AbstractDataChunkIterator @@ -978,6 +981,9 @@ def __get_ref_builder(self, builder, dtype, shape, container, build_manager): for d in container.data: target_builder = self.__get_target_builder(d, build_manager, builder) bldr_data.append(ReferenceBuilder(target_builder)) + if isinstance(container.data, H5DataIO): + # This is here to support appending a dataset of references. + bldr_data = H5DataIO(bldr_data, **container.data.get_io_params()) else: self.logger.debug("Setting %s '%s' data to reference builder" % (builder.__class__.__name__, builder.name)) diff --git a/src/hdmf/query.py b/src/hdmf/query.py index 835b295c5..9693b0b1c 100644 --- a/src/hdmf/query.py +++ b/src/hdmf/query.py @@ -163,6 +163,12 @@ def __next__(self): def next(self): return self.dataset.next() + def append(self, arg): + """ + Override this method to support appending to backend-specific datasets + """ + pass # pragma: no cover + class ReferenceResolver(metaclass=ABCMeta): """ diff --git a/tests/unit/test_io_hdf5_h5tools.py b/tests/unit/test_io_hdf5_h5tools.py index 73aa89788..1f0c2eb4c 100644 --- a/tests/unit/test_io_hdf5_h5tools.py +++ b/tests/unit/test_io_hdf5_h5tools.py @@ -3004,6 +3004,57 @@ def test_append_data(self): self.assertEqual(f['foofile_data'].file.filename, self.paths[1]) self.assertIsInstance(f.attrs['foo_ref_attr'], h5py.Reference) + def test_append_dataset_of_references(self): + """Test that exporting a written container with a dataset of references works.""" + bazs = [] + num_bazs = 1 + for i in range(num_bazs): + bazs.append(Baz(name='baz%d' % i)) + array_bazs=np.array(bazs) + wrapped_bazs = H5DataIO(array_bazs, maxshape=(None,)) + baz_data = BazData(name='baz_data1', data=wrapped_bazs) + bucket = BazBucket(name='bucket1', bazs=bazs.copy(), baz_data=baz_data) + + with HDF5IO(self.paths[0], manager=get_baz_buildmanager(), mode='w') as write_io: + write_io.write(bucket) + + with HDF5IO(self.paths[0], manager=get_baz_buildmanager(), mode='a') as append_io: + read_bucket1 = append_io.read() + new_baz = Baz(name='new') + read_bucket1.add_baz(new_baz) + append_io.write(read_bucket1) + + with HDF5IO(self.paths[0], manager=get_baz_buildmanager(), mode='a') as ref_io: + read_bucket1 = ref_io.read() + DoR = read_bucket1.baz_data.data + DoR.append(read_bucket1.bazs['new']) + + with HDF5IO(self.paths[0], manager=get_baz_buildmanager(), mode='r') as read_io: + read_bucket1 = read_io.read() + self.assertEqual(len(read_bucket1.baz_data.data), 2) + self.assertIs(read_bucket1.baz_data.data[1], read_bucket1.bazs["new"]) + + def test_append_dataset_of_references_orphaned_target(self): + bazs = [] + num_bazs = 1 + for i in range(num_bazs): + bazs.append(Baz(name='baz%d' % i)) + array_bazs=np.array(bazs) + wrapped_bazs = H5DataIO(array_bazs, maxshape=(None,)) + baz_data = BazData(name='baz_data1', data=wrapped_bazs) + bucket = BazBucket(name='bucket1', bazs=bazs.copy(), baz_data=baz_data) + + with HDF5IO(self.paths[0], manager=get_baz_buildmanager(), mode='w') as write_io: + write_io.write(bucket) + + with HDF5IO(self.paths[0], manager=get_baz_buildmanager(), mode='a') as ref_io: + read_bucket1 = ref_io.read() + new_baz = Baz(name='new') + read_bucket1.add_baz(new_baz) + DoR = read_bucket1.baz_data.data + with self.assertRaises(ValueError): + DoR.append(read_bucket1.bazs['new']) + def test_append_external_link_data(self): """Test that exporting a written container after adding a link with link_data=True creates external links.""" foo1 = Foo('foo1', [1, 2, 3, 4, 5], "I am foo1", 17, 3.14) From abb6fe5745957082c8297be9f8d28941ae69afd8 Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Thu, 22 Aug 2024 14:54:27 -0700 Subject: [PATCH 41/82] Update CHANGELOG.md (#1178) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 66a3474d0..d18bf235a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # HDMF Changelog -## HDMF 3.14.4 (Upcoming) +## HDMF 3.14.4 (August 22, 2024) ### Enhancements - Added support to append to a dataset of references for HDMF-Zarr. @mavaylon1 [#1157](https://github.com/hdmf-dev/hdmf/pull/1157) From d378dec53c3be69cbd03695960e2478cd1f1f455 Mon Sep 17 00:00:00 2001 From: Chadwick Boulay Date: Wed, 28 Aug 2024 12:05:25 -0400 Subject: [PATCH 42/82] Fix #1148 : Add passthrough on non-DCI H5DataIO to support its use in pynwb TimeSeries. (#1149) * Add passthrough on non-DCI H5DataIO to support its use in pynwb TimeSeries. Fixes #1148. * CHANGELOG update for #1149 * Add another maxshape fallback (self.shape) * Incorporated @stephprince suggestions on #1149. --------- Co-authored-by: Steph Prince <40640337+stephprince@users.noreply.github.com> --- CHANGELOG.md | 1 + src/hdmf/backends/hdf5/h5_utils.py | 13 ++++++++++++- tests/unit/test_io_hdf5_h5tools.py | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d18bf235a..fc9974a14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Bug fixes - Fixed issue where scalar datasets with a compound data type were being written as non-scalar datasets @stephprince [#1176](https://github.com/hdmf-dev/hdmf/pull/1176) +- Fixed H5DataIO not exposing `maxshape` on non-dci dsets. @cboulay [#1149](https://github.com/hdmf-dev/hdmf/pull/1149) ## HDMF 3.14.3 (July 29, 2024) diff --git a/src/hdmf/backends/hdf5/h5_utils.py b/src/hdmf/backends/hdf5/h5_utils.py index 278735fbc..2d7187721 100644 --- a/src/hdmf/backends/hdf5/h5_utils.py +++ b/src/hdmf/backends/hdf5/h5_utils.py @@ -21,7 +21,7 @@ from ...query import HDMFDataset, ReferenceResolver, ContainerResolver, BuilderResolver from ...region import RegionSlicer from ...spec import SpecWriter, SpecReader -from ...utils import docval, getargs, popargs, get_docval +from ...utils import docval, getargs, popargs, get_docval, get_data_shape class HDF5IODataChunkIteratorQueue(deque): @@ -672,3 +672,14 @@ def valid(self): if isinstance(self.data, Dataset) and not self.data.id.valid: return False return super().valid + + @property + def maxshape(self): + if 'maxshape' in self.io_settings: + return self.io_settings['maxshape'] + elif hasattr(self.data, 'maxshape'): + return self.data.maxshape + elif hasattr(self, "shape"): + return self.shape + else: + return get_data_shape(self.data) diff --git a/tests/unit/test_io_hdf5_h5tools.py b/tests/unit/test_io_hdf5_h5tools.py index 1f0c2eb4c..131e4a6de 100644 --- a/tests/unit/test_io_hdf5_h5tools.py +++ b/tests/unit/test_io_hdf5_h5tools.py @@ -607,6 +607,12 @@ def test_pass_through_of_chunk_shape_generic_data_chunk_iterator(self): ############################################# # H5DataIO general ############################################# + def test_pass_through_of_maxshape_on_h5dataset(self): + k = 10 + self.io.write_dataset(self.f, DatasetBuilder('test_dataset', np.arange(k), attributes={})) + dset = H5DataIO(self.f['test_dataset']) + self.assertEqual(dset.maxshape, (k,)) + def test_warning_on_non_gzip_compression(self): # Make sure no warning is issued when using gzip with warnings.catch_warnings(record=True) as w: @@ -3763,6 +3769,14 @@ def test_dataio_shape_then_data(self): with self.assertRaisesRegex(ValueError, "Setting data when dtype and shape are not None is not supported"): dataio.data = list() + def test_dataio_maxshape(self): + dataio = H5DataIO(data=np.arange(10), maxshape=(None,)) + self.assertEqual(dataio.maxshape, (None,)) + + def test_dataio_maxshape_from_data(self): + dataio = H5DataIO(data=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + self.assertEqual(dataio.maxshape, (10,)) + def test_hdf5io_can_read(): assert not HDF5IO.can_read("not_a_file") From 1fc621240e2463fd5f5b8f370888fa36d2134b6c Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Thu, 29 Aug 2024 19:50:32 -0700 Subject: [PATCH 43/82] Fix resolution of extension classes that have references (#1183) * Fix resolution of extension classes that have references * Update changelog * Remove unnecessary if * Update CHANGELOG.md Co-authored-by: Oliver Ruebel --------- Co-authored-by: Oliver Ruebel --- CHANGELOG.md | 2 + src/hdmf/build/manager.py | 17 +- tests/unit/build_tests/test_classgenerator.py | 180 +++++++++++++++++- tests/unit/build_tests/test_io_manager.py | 2 +- 4 files changed, 196 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc9974a14..97d89e320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ ### Bug fixes - Fixed issue where scalar datasets with a compound data type were being written as non-scalar datasets @stephprince [#1176](https://github.com/hdmf-dev/hdmf/pull/1176) - Fixed H5DataIO not exposing `maxshape` on non-dci dsets. @cboulay [#1149](https://github.com/hdmf-dev/hdmf/pull/1149) +- Fixed generation of classes in an extension that contain attributes or datasets storing references to other types defined in the extension. + @rly [#1183](https://github.com/hdmf-dev/hdmf/pull/1183) ## HDMF 3.14.3 (July 29, 2024) diff --git a/src/hdmf/build/manager.py b/src/hdmf/build/manager.py index 25b9b81bd..967c34010 100644 --- a/src/hdmf/build/manager.py +++ b/src/hdmf/build/manager.py @@ -7,7 +7,7 @@ from .classgenerator import ClassGenerator, CustomClassGenerator, MCIClassGenerator from ..container import AbstractContainer, Container, Data from ..term_set import TypeConfigurator -from ..spec import DatasetSpec, GroupSpec, NamespaceCatalog +from ..spec import DatasetSpec, GroupSpec, NamespaceCatalog, RefSpec from ..spec.spec import BaseStorageSpec from ..utils import docval, getargs, ExtenderMeta, get_docval @@ -480,6 +480,7 @@ def load_namespaces(self, **kwargs): load_namespaces here has the advantage of being able to keep track of type dependencies across namespaces. ''' deps = self.__ns_catalog.load_namespaces(**kwargs) + # register container types for each dependent type in each dependent namespace for new_ns, ns_deps in deps.items(): for src_ns, types in ns_deps.items(): for dt in types: @@ -529,7 +530,7 @@ def get_dt_container_cls(self, **kwargs): namespace = ns_key break if namespace is None: - raise ValueError("Namespace could not be resolved.") + raise ValueError(f"Namespace could not be resolved for data type '{data_type}'.") cls = self.__get_container_cls(namespace, data_type) @@ -549,6 +550,8 @@ def get_dt_container_cls(self, **kwargs): def __check_dependent_types(self, spec, namespace): """Ensure that classes for all types used by this type exist in this namespace and generate them if not. + + `spec` should be a GroupSpec or DatasetSpec in the `namespace` """ def __check_dependent_types_helper(spec, namespace): if isinstance(spec, (GroupSpec, DatasetSpec)): @@ -564,6 +567,16 @@ def __check_dependent_types_helper(spec, namespace): if spec.data_type_inc is not None: self.get_dt_container_cls(spec.data_type_inc, namespace) + + # handle attributes that have a reference dtype + for attr_spec in spec.attributes: + if isinstance(attr_spec.dtype, RefSpec): + self.get_dt_container_cls(attr_spec.dtype.target_type, namespace) + # handle datasets that have a reference dtype + if isinstance(spec, DatasetSpec): + if isinstance(spec.dtype, RefSpec): + self.get_dt_container_cls(spec.dtype.target_type, namespace) + # recurse into nested types if isinstance(spec, GroupSpec): for child_spec in (spec.groups + spec.datasets + spec.links): __check_dependent_types_helper(child_spec, namespace) diff --git a/tests/unit/build_tests/test_classgenerator.py b/tests/unit/build_tests/test_classgenerator.py index 3c9fda283..42a55b470 100644 --- a/tests/unit/build_tests/test_classgenerator.py +++ b/tests/unit/build_tests/test_classgenerator.py @@ -7,7 +7,9 @@ from hdmf.build import TypeMap, CustomClassGenerator from hdmf.build.classgenerator import ClassGenerator, MCIClassGenerator from hdmf.container import Container, Data, MultiContainerInterface, AbstractContainer -from hdmf.spec import GroupSpec, AttributeSpec, DatasetSpec, SpecCatalog, SpecNamespace, NamespaceCatalog, LinkSpec +from hdmf.spec import ( + GroupSpec, AttributeSpec, DatasetSpec, SpecCatalog, SpecNamespace, NamespaceCatalog, LinkSpec, RefSpec +) from hdmf.testing import TestCase from hdmf.utils import get_docval, docval @@ -734,9 +736,18 @@ def _build_separate_namespaces(self): GroupSpec(data_type_inc='Bar', doc='a bar', quantity='?') ] ) + moo_spec = DatasetSpec( + doc='A test dataset that is a 1D array of object references of Baz', + data_type_def='Moo', + shape=(None,), + dtype=RefSpec( + reftype='object', + target_type='Baz' + ) + ) create_load_namespace_yaml( namespace_name='ndx-test', - specs=[baz_spec], + specs=[baz_spec, moo_spec], output_dir=self.test_dir, incl_types={ CORE_NAMESPACE: ['Bar'], @@ -828,6 +839,171 @@ def test_get_class_include_from_separate_ns_4(self): self._check_classes(baz_cls, bar_cls, bar_cls2, qux_cls, qux_cls2) +class TestGetClassObjectReferences(TestCase): + + def setUp(self): + self.test_dir = tempfile.mkdtemp() + if os.path.exists(self.test_dir): # start clean + self.tearDown() + os.mkdir(self.test_dir) + self.type_map = TypeMap() + + def tearDown(self): + shutil.rmtree(self.test_dir) + + def test_get_class_include_dataset_of_references(self): + """Test that get_class resolves datasets of object references.""" + qux_spec = DatasetSpec( + doc='A test extension', + data_type_def='Qux' + ) + moo_spec = DatasetSpec( + doc='A test dataset that is a 1D array of object references of Qux', + data_type_def='Moo', + shape=(None,), + dtype=RefSpec( + reftype='object', + target_type='Qux' + ), + ) + + create_load_namespace_yaml( + namespace_name='ndx-test', + specs=[qux_spec, moo_spec], + output_dir=self.test_dir, + incl_types={}, + type_map=self.type_map + ) + # no types should be resolved to start + assert self.type_map.get_container_classes('ndx-test') == [] + + self.type_map.get_dt_container_cls('Moo', 'ndx-test') + # now, Moo and Qux should be resolved + assert len(self.type_map.get_container_classes('ndx-test')) == 2 + assert "Moo" in [c.__name__ for c in self.type_map.get_container_classes('ndx-test')] + assert "Qux" in [c.__name__ for c in self.type_map.get_container_classes('ndx-test')] + + def test_get_class_include_attribute_object_reference(self): + """Test that get_class resolves data types with an attribute that is an object reference.""" + qux_spec = DatasetSpec( + doc='A test extension', + data_type_def='Qux' + ) + woo_spec = DatasetSpec( + doc='A test dataset that has a scalar object reference to a Qux', + data_type_def='Woo', + attributes=[ + AttributeSpec( + name='attr1', + doc='a string attribute', + dtype=RefSpec(reftype='object', target_type='Qux') + ), + ] + ) + create_load_namespace_yaml( + namespace_name='ndx-test', + specs=[qux_spec, woo_spec], + output_dir=self.test_dir, + incl_types={}, + type_map=self.type_map + ) + # no types should be resolved to start + assert self.type_map.get_container_classes('ndx-test') == [] + + self.type_map.get_dt_container_cls('Woo', 'ndx-test') + # now, Woo and Qux should be resolved + assert len(self.type_map.get_container_classes('ndx-test')) == 2 + assert "Woo" in [c.__name__ for c in self.type_map.get_container_classes('ndx-test')] + assert "Qux" in [c.__name__ for c in self.type_map.get_container_classes('ndx-test')] + + def test_get_class_include_nested_object_reference(self): + """Test that get_class resolves nested datasets that are object references.""" + qux_spec = DatasetSpec( + doc='A test extension', + data_type_def='Qux' + ) + spam_spec = DatasetSpec( + doc='A test extension', + data_type_def='Spam', + shape=(None,), + dtype=RefSpec( + reftype='object', + target_type='Qux' + ), + ) + goo_spec = GroupSpec( + doc='A test dataset that has a nested dataset (Spam) that has a scalar object reference to a Qux', + data_type_def='Goo', + datasets=[ + DatasetSpec( + doc='a dataset', + data_type_inc='Spam', + ), + ], + ) + + create_load_namespace_yaml( + namespace_name='ndx-test', + specs=[qux_spec, spam_spec, goo_spec], + output_dir=self.test_dir, + incl_types={}, + type_map=self.type_map + ) + # no types should be resolved to start + assert self.type_map.get_container_classes('ndx-test') == [] + + self.type_map.get_dt_container_cls('Goo', 'ndx-test') + # now, Goo, Spam, and Qux should be resolved + assert len(self.type_map.get_container_classes('ndx-test')) == 3 + assert "Goo" in [c.__name__ for c in self.type_map.get_container_classes('ndx-test')] + assert "Spam" in [c.__name__ for c in self.type_map.get_container_classes('ndx-test')] + assert "Qux" in [c.__name__ for c in self.type_map.get_container_classes('ndx-test')] + + def test_get_class_include_nested_attribute_object_reference(self): + """Test that get_class resolves nested datasets that have an attribute that is an object reference.""" + qux_spec = DatasetSpec( + doc='A test extension', + data_type_def='Qux' + ) + bam_spec = DatasetSpec( + doc='A test extension', + data_type_def='Bam', + attributes=[ + AttributeSpec( + name='attr1', + doc='a string attribute', + dtype=RefSpec(reftype='object', target_type='Qux') + ), + ], + ) + boo_spec = GroupSpec( + doc='A test dataset that has a nested dataset (Spam) that has a scalar object reference to a Qux', + data_type_def='Boo', + datasets=[ + DatasetSpec( + doc='a dataset', + data_type_inc='Bam', + ), + ], + ) + + create_load_namespace_yaml( + namespace_name='ndx-test', + specs=[qux_spec, bam_spec, boo_spec], + output_dir=self.test_dir, + incl_types={}, + type_map=self.type_map + ) + # no types should be resolved to start + assert self.type_map.get_container_classes('ndx-test') == [] + + self.type_map.get_dt_container_cls('Boo', 'ndx-test') + # now, Boo, Bam, and Qux should be resolved + assert len(self.type_map.get_container_classes('ndx-test')) == 3 + assert "Boo" in [c.__name__ for c in self.type_map.get_container_classes('ndx-test')] + assert "Bam" in [c.__name__ for c in self.type_map.get_container_classes('ndx-test')] + assert "Qux" in [c.__name__ for c in self.type_map.get_container_classes('ndx-test')] + class EmptyBar(Container): pass diff --git a/tests/unit/build_tests/test_io_manager.py b/tests/unit/build_tests/test_io_manager.py index 01421e218..a3be47cf7 100644 --- a/tests/unit/build_tests/test_io_manager.py +++ b/tests/unit/build_tests/test_io_manager.py @@ -341,7 +341,7 @@ def test_get_dt_container_cls(self): self.assertIs(ret, Foo) def test_get_dt_container_cls_no_namespace(self): - with self.assertRaisesWith(ValueError, "Namespace could not be resolved."): + with self.assertRaisesWith(ValueError, "Namespace could not be resolved for data type 'Unknown'."): self.type_map.get_dt_container_cls(data_type="Unknown") From 1abb8ec6a28bc2419099e4cca975e006ee79bd23 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 11:05:37 -0700 Subject: [PATCH 44/82] [pre-commit.ci] pre-commit autoupdate (#1179) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c76f12bef..221182985 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.1 + rev: v0.6.3 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From 044f5a579a960b9228f602f2c9380414b216df32 Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Wed, 4 Sep 2024 10:28:09 -0700 Subject: [PATCH 45/82] Update CHANGELOG.md (#1184) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97d89e320..cb51dc538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # HDMF Changelog -## HDMF 3.14.4 (August 22, 2024) +## HDMF 3.14.4 (September 4, 2024) ### Enhancements - Added support to append to a dataset of references for HDMF-Zarr. @mavaylon1 [#1157](https://github.com/hdmf-dev/hdmf/pull/1157) From 874db31fb171577bfc0c962708786f9774ea6b1b Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Fri, 13 Sep 2024 10:58:25 -0700 Subject: [PATCH 46/82] Fix handling of read h5py string datasets (#1189) --- CHANGELOG.md | 6 ++ src/hdmf/build/objectmapper.py | 5 +- src/hdmf/utils.py | 2 +- tests/unit/build_tests/test_io_map.py | 131 +++++++++++++++++++++++++- 4 files changed, 141 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb51dc538..c7919c81b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # HDMF Changelog +## HDMF 3.14.5 (September 6, 2024) + +### Bug fixes +- Fixed bug in writing of string arrays to an HDF5 file that were read from an HDF5 file that was introduced in 3.14.4. @rly @stephprince + [#1189](https://github.com/hdmf-dev/hdmf/pull/1189) + ## HDMF 3.14.4 (September 4, 2024) ### Enhancements diff --git a/src/hdmf/build/objectmapper.py b/src/hdmf/build/objectmapper.py index 3e8d835f1..83df1b427 100644 --- a/src/hdmf/build/objectmapper.py +++ b/src/hdmf/build/objectmapper.py @@ -602,7 +602,10 @@ def __get_data_type(cls, spec): def __convert_string(self, value, spec): """Convert string types to the specified dtype.""" def __apply_string_type(value, string_type): - if isinstance(value, (list, tuple, np.ndarray, DataIO)): + # NOTE: if a user passes a h5py.Dataset that is not wrapped with a hdmf.utils.StrDataset, + # then this conversion may not be correct. Users should unpack their string h5py.Datasets + # into a numpy array (or wrap them in StrDataset) before passing them to a container object. + if hasattr(value, '__iter__') and not isinstance(value, (str, bytes)): return [__apply_string_type(item, string_type) for item in value] else: return string_type(value) diff --git a/src/hdmf/utils.py b/src/hdmf/utils.py index 5e0b61539..50db79c40 100644 --- a/src/hdmf/utils.py +++ b/src/hdmf/utils.py @@ -1140,7 +1140,7 @@ def update(self, other): @docval_macro('array_data') class StrDataset(h5py.Dataset): - """Wrapper to decode strings on reading the dataset""" + """Wrapper to decode strings on reading the dataset. Use only for h5py 3+.""" def __init__(self, dset, encoding, errors='strict'): self.dset = dset if encoding is None: diff --git a/tests/unit/build_tests/test_io_map.py b/tests/unit/build_tests/test_io_map.py index e095ef318..730530a5a 100644 --- a/tests/unit/build_tests/test_io_map.py +++ b/tests/unit/build_tests/test_io_map.py @@ -1,4 +1,4 @@ -from hdmf.utils import docval, getargs +from hdmf.utils import StrDataset, docval, getargs from hdmf import Container, Data from hdmf.backends.hdf5 import H5DataIO from hdmf.build import (GroupBuilder, DatasetBuilder, ObjectMapper, BuildManager, TypeMap, LinkBuilder, @@ -7,12 +7,15 @@ from hdmf.spec import (GroupSpec, AttributeSpec, DatasetSpec, SpecCatalog, SpecNamespace, NamespaceCatalog, RefSpec, LinkSpec) from hdmf.testing import TestCase +import h5py from abc import ABCMeta, abstractmethod import unittest import numpy as np from tests.unit.helpers.utils import CORE_NAMESPACE, create_test_type_map +H5PY_3 = h5py.__version__.startswith('3') + class Bar(Container): @@ -460,6 +463,132 @@ def test_build_3d_ndarray(self): np.testing.assert_array_equal(builder.get('data').data, str_array_3d) np.testing.assert_array_equal(builder.get('attr_array'), str_array_3d) + @unittest.skipIf(not H5PY_3, "Use StrDataset only for h5py 3+") + def test_build_1d_h5py_3_dataset(self): + bar_spec = GroupSpec( + doc='A test group specification with a data type', + data_type_def='Bar', + datasets=[ + DatasetSpec( + doc='an example dataset', + dtype='text', + name='data', + shape=(None, ), + attributes=[AttributeSpec(name='attr2', doc='an example integer attribute', dtype='int')], + ) + ], + attributes=[AttributeSpec(name='attr_array', doc='an example array attribute', dtype='text', + shape=(None, ))], + ) + type_map = self.customSetUp(bar_spec) + type_map.register_map(Bar, BarMapper) + # create in-memory hdf5 file that is discarded after closing + with h5py.File("test.h5", "w", driver="core", backing_store=False) as f: + str_array_1d = np.array( + ['aa', 'bb', 'cc', 'dd'], + dtype=h5py.special_dtype(vlen=str) + ) + # wrap the dataset in a StrDataset to mimic how HDF5IO would read this dataset with h5py 3+ + dataset = StrDataset(f.create_dataset('data', data=str_array_1d), None) + bar_inst = Bar('my_bar', dataset, 'value1', 10, attr_array=dataset) + builder = type_map.build(bar_inst) + np.testing.assert_array_equal(builder.get('data').data, dataset[:]) + np.testing.assert_array_equal(builder.get('attr_array'), dataset[:]) + + @unittest.skipIf(not H5PY_3, "Use StrDataset only for h5py 3+") + def test_build_3d_h5py_3_dataset(self): + bar_spec = GroupSpec( + doc='A test group specification with a data type', + data_type_def='Bar', + datasets=[ + DatasetSpec( + doc='an example dataset', + dtype='text', + name='data', + shape=(None, None, None), + attributes=[AttributeSpec(name='attr2', doc='an example integer attribute', dtype='int')], + ) + ], + attributes=[AttributeSpec(name='attr_array', doc='an example array attribute', dtype='text', + shape=(None, None, None))], + ) + type_map = self.customSetUp(bar_spec) + type_map.register_map(Bar, BarMapper) + # create in-memory hdf5 file that is discarded after closing + with h5py.File("test.h5", "w", driver="core", backing_store=False) as f: + str_array_3d = np.array( + [[['aa', 'bb'], ['cc', 'dd']], [['ee', 'ff'], ['gg', 'hh']]], + dtype=h5py.special_dtype(vlen=str) + ) + # wrap the dataset in a StrDataset to mimic how HDF5IO would read this dataset with h5py 3+ + dataset = StrDataset(f.create_dataset('data', data=str_array_3d), None) + bar_inst = Bar('my_bar', dataset, 'value1', 10, attr_array=dataset) + builder = type_map.build(bar_inst) + np.testing.assert_array_equal(builder.get('data').data, dataset[:]) + np.testing.assert_array_equal(builder.get('attr_array'), dataset[:]) + + @unittest.skipIf(H5PY_3, "Create dataset differently for h5py < 3") + def test_build_1d_h5py_2_dataset(self): + bar_spec = GroupSpec( + doc='A test group specification with a data type', + data_type_def='Bar', + datasets=[ + DatasetSpec( + doc='an example dataset', + dtype='text', + name='data', + shape=(None, ), + attributes=[AttributeSpec(name='attr2', doc='an example integer attribute', dtype='int')], + ) + ], + attributes=[AttributeSpec(name='attr_array', doc='an example array attribute', dtype='text', + shape=(None, ))], + ) + type_map = self.customSetUp(bar_spec) + type_map.register_map(Bar, BarMapper) + # create in-memory hdf5 file that is discarded after closing + with h5py.File("test.h5", "w", driver="core", backing_store=False) as f: + str_array_1d = np.array( + ['aa', 'bb', 'cc', 'dd'], + dtype=h5py.special_dtype(vlen=str) + ) + dataset = f.create_dataset('data', data=str_array_1d) + bar_inst = Bar('my_bar', dataset, 'value1', 10, attr_array=dataset) + builder = type_map.build(bar_inst) + np.testing.assert_array_equal(builder.get('data').data, dataset[:]) + np.testing.assert_array_equal(builder.get('attr_array'), dataset[:]) + + @unittest.skipIf(H5PY_3, "Create dataset differently for h5py < 3") + def test_build_3d_h5py_2_dataset(self): + bar_spec = GroupSpec( + doc='A test group specification with a data type', + data_type_def='Bar', + datasets=[ + DatasetSpec( + doc='an example dataset', + dtype='text', + name='data', + shape=(None, None, None), + attributes=[AttributeSpec(name='attr2', doc='an example integer attribute', dtype='int')], + ) + ], + attributes=[AttributeSpec(name='attr_array', doc='an example array attribute', dtype='text', + shape=(None, None, None))], + ) + type_map = self.customSetUp(bar_spec) + type_map.register_map(Bar, BarMapper) + # create in-memory hdf5 file that is discarded after closing + with h5py.File("test.h5", "w", driver="core", backing_store=False) as f: + str_array_3d = np.array( + [[['aa', 'bb'], ['cc', 'dd']], [['ee', 'ff'], ['gg', 'hh']]], + dtype=h5py.special_dtype(vlen=str) + ) + dataset = f.create_dataset('data', data=str_array_3d) + bar_inst = Bar('my_bar', dataset, 'value1', 10, attr_array=dataset) + builder = type_map.build(bar_inst) + np.testing.assert_array_equal(builder.get('data').data, dataset[:]) + np.testing.assert_array_equal(builder.get('attr_array'), dataset[:]) + def test_build_dataio(self): bar_spec = GroupSpec('A test group specification with a data type', data_type_def='Bar', From aed1de3ec0c26f9fd492abe9d7c9ea1ddb142f7a Mon Sep 17 00:00:00 2001 From: Paul Adkisson Date: Tue, 17 Sep 2024 08:07:48 +1000 Subject: [PATCH 47/82] Wrap data in set_data_io with a DataChunkIterator to support overriding hdf5 dataset backend configurations (#1172) Co-authored-by: Oliver Ruebel Co-authored-by: Ryan Ly --- CHANGELOG.md | 5 ++- src/hdmf/container.py | 48 ++++++++++++++++++++++++--- tests/unit/test_io_hdf5_h5tools.py | 52 ++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7919c81b..7e56cde40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # HDMF Changelog -## HDMF 3.14.5 (September 6, 2024) +## HDMF 3.14.5 (September 17, 2024) + +### Enhancements +- Added support for overriding backend configurations of `h5py.Dataset` objects in `Container.set_data_io`. @pauladkisson [#1172](https://github.com/hdmf-dev/hdmf/pull/1172) ### Bug fixes - Fixed bug in writing of string arrays to an HDF5 file that were read from an HDF5 file that was introduced in 3.14.4. @rly @stephprince diff --git a/src/hdmf/container.py b/src/hdmf/container.py index 88a083599..7c450770a 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -2,7 +2,7 @@ from abc import abstractmethod from collections import OrderedDict from copy import deepcopy -from typing import Type +from typing import Type, Optional from uuid import uuid4 from warnings import warn import os @@ -11,7 +11,7 @@ import numpy as np import pandas as pd -from .data_utils import DataIO, append_data, extend_data +from .data_utils import DataIO, append_data, extend_data, AbstractDataChunkIterator from .utils import docval, get_docval, getargs, ExtenderMeta, get_data_shape, popargs, LabelledDict from .term_set import TermSet, TermSetWrapper @@ -826,7 +826,14 @@ def __smart_str_dict(d, num_indent): out += '\n' + indent + right_br return out - def set_data_io(self, dataset_name: str, data_io_class: Type[DataIO], data_io_kwargs: dict = None, **kwargs): + def set_data_io( + self, + dataset_name: str, + data_io_class: Type[DataIO], + data_io_kwargs: dict = None, + data_chunk_iterator_class: Optional[Type[AbstractDataChunkIterator]] = None, + data_chunk_iterator_kwargs: dict = None, **kwargs + ): """ Apply DataIO object to a dataset field of the Container. @@ -838,9 +845,18 @@ def set_data_io(self, dataset_name: str, data_io_class: Type[DataIO], data_io_kw Class to use for DataIO, e.g. H5DataIO or ZarrDataIO data_io_kwargs: dict keyword arguments passed to the constructor of the DataIO class. + data_chunk_iterator_class: Type[AbstractDataChunkIterator] + Class to use for DataChunkIterator. If None, no DataChunkIterator is used. + data_chunk_iterator_kwargs: dict + keyword arguments passed to the constructor of the DataChunkIterator class. **kwargs: DEPRECATED. Use data_io_kwargs instead. kwargs are passed to the constructor of the DataIO class. + + Notes + ----- + If data_chunk_iterator_class is not None, the data is wrapped in the DataChunkIterator before being wrapped in + the DataIO. This allows for rewriting the backend configuration of hdf5 datasets. """ if kwargs or (data_io_kwargs is None): warn( @@ -851,8 +867,11 @@ def set_data_io(self, dataset_name: str, data_io_class: Type[DataIO], data_io_kw ) data_io_kwargs = kwargs data = self.fields.get(dataset_name) + data_chunk_iterator_kwargs = data_chunk_iterator_kwargs or dict() if data is None: raise ValueError(f"{dataset_name} is None and cannot be wrapped in a DataIO class") + if data_chunk_iterator_class is not None: + data = data_chunk_iterator_class(data=data, **data_chunk_iterator_kwargs) self.fields[dataset_name] = data_io_class(data=data, **data_io_kwargs) @@ -896,7 +915,13 @@ def set_dataio(self, **kwargs): dataio.data = self.__data self.__data = dataio - def set_data_io(self, data_io_class: Type[DataIO], data_io_kwargs: dict) -> None: + def set_data_io( + self, + data_io_class: Type[DataIO], + data_io_kwargs: dict, + data_chunk_iterator_class: Optional[Type[AbstractDataChunkIterator]] = None, + data_chunk_iterator_kwargs: dict = None, + ) -> None: """ Apply DataIO object to the data held by this Data object. @@ -906,8 +931,21 @@ def set_data_io(self, data_io_class: Type[DataIO], data_io_kwargs: dict) -> None The DataIO to apply to the data held by this Data. data_io_kwargs: dict The keyword arguments to pass to the DataIO. + data_chunk_iterator_class: Type[AbstractDataChunkIterator] + The DataChunkIterator to use for the DataIO. If None, no DataChunkIterator is used. + data_chunk_iterator_kwargs: dict + The keyword arguments to pass to the DataChunkIterator. + + Notes + ----- + If data_chunk_iterator_class is not None, the data is wrapped in the DataChunkIterator before being wrapped in + the DataIO. This allows for rewriting the backend configuration of hdf5 datasets. """ - self.__data = data_io_class(data=self.__data, **data_io_kwargs) + data_chunk_iterator_kwargs = data_chunk_iterator_kwargs or dict() + data = self.__data + if data_chunk_iterator_class is not None: + data = data_chunk_iterator_class(data=data, **data_chunk_iterator_kwargs) + self.__data = data_io_class(data=data, **data_io_kwargs) @docval({'name': 'func', 'type': types.FunctionType, 'doc': 'a function to transform *data*'}) def transform(self, **kwargs): diff --git a/tests/unit/test_io_hdf5_h5tools.py b/tests/unit/test_io_hdf5_h5tools.py index 131e4a6de..58119ce9b 100644 --- a/tests/unit/test_io_hdf5_h5tools.py +++ b/tests/unit/test_io_hdf5_h5tools.py @@ -3801,6 +3801,11 @@ def __init__(self, **kwargs): self.data2 = kwargs["data2"] self.obj = ContainerWithData("name", [1, 2, 3, 4, 5], None) + self.file_path = get_temp_filepath() + + def tearDown(self): + if os.path.exists(self.file_path): + os.remove(self.file_path) def test_set_data_io(self): self.obj.set_data_io("data1", H5DataIO, data_io_kwargs=dict(chunks=True)) @@ -3823,6 +3828,31 @@ def test_set_data_io_old_api(self): self.assertIsInstance(self.obj.data1, H5DataIO) self.assertTrue(self.obj.data1.io_settings["chunks"]) + def test_set_data_io_h5py_dataset(self): + file = File(self.file_path, 'w') + data = file.create_dataset('data', data=[1, 2, 3, 4, 5], chunks=(3,)) + class ContainerWithData(Container): + __fields__ = ('data',) + + @docval( + {"name": "name", "doc": "name", "type": str}, + {'name': 'data', 'doc': 'field1 doc', 'type': h5py.Dataset}, + ) + def __init__(self, **kwargs): + super().__init__(name=kwargs["name"]) + self.data = kwargs["data"] + + container = ContainerWithData("name", data) + container.set_data_io( + "data", + H5DataIO, + data_io_kwargs=dict(chunks=(2,)), + data_chunk_iterator_class=DataChunkIterator, + ) + + self.assertIsInstance(container.data, H5DataIO) + self.assertEqual(container.data.io_settings["chunks"], (2,)) + file.close() class TestDataSetDataIO(TestCase): @@ -3831,8 +3861,30 @@ class MyData(Data): pass self.data = MyData("my_data", [1, 2, 3]) + self.file_path = get_temp_filepath() + + def tearDown(self): + if os.path.exists(self.file_path): + os.remove(self.file_path) def test_set_data_io(self): self.data.set_data_io(H5DataIO, dict(chunks=True)) assert isinstance(self.data.data, H5DataIO) assert self.data.data.io_settings["chunks"] + + def test_set_data_io_h5py_dataset(self): + file = File(self.file_path, 'w') + data = file.create_dataset('data', data=[1, 2, 3, 4, 5], chunks=(3,)) + class MyData(Data): + pass + + my_data = MyData("my_data", data) + my_data.set_data_io( + H5DataIO, + data_io_kwargs=dict(chunks=(2,)), + data_chunk_iterator_class=DataChunkIterator, + ) + + self.assertIsInstance(my_data.data, H5DataIO) + self.assertEqual(my_data.data.io_settings["chunks"], (2,)) + file.close() From fbfabeb87c3f10242f7510bc9eba19d14ad7bb81 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 13:19:36 -0700 Subject: [PATCH 48/82] [pre-commit.ci] pre-commit autoupdate (#1192) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 221182985..c84bfaffc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.3 + rev: v0.6.8 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From 335abdd55683b6cb065c72b4831211982a25b4ac Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Wed, 2 Oct 2024 13:42:30 -0700 Subject: [PATCH 49/82] Remove mamba key from GH Actions (#1194) --- .github/workflows/run_all_tests.yml | 4 +--- .github/workflows/run_coverage.yml | 1 - .github/workflows/run_tests.yml | 4 +--- CHANGELOG.md | 5 +++++ 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/run_all_tests.yml b/.github/workflows/run_all_tests.yml index 8df190d55..b713f4763 100644 --- a/.github/workflows/run_all_tests.yml +++ b/.github/workflows/run_all_tests.yml @@ -165,13 +165,12 @@ jobs: auto-update-conda: true python-version: ${{ matrix.python-ver }} channels: conda-forge - mamba-version: "*" - name: Install build dependencies run: | conda config --set always_yes yes --set changeps1 no conda info - mamba install -c conda-forge "tox>=4" + conda install -c conda-forge "tox>=4" - name: Conda reporting run: | @@ -229,7 +228,6 @@ jobs: python-version: ${{ matrix.python-ver }} channels: conda-forge auto-activate-base: false - mamba-version: "*" - name: Install run dependencies run: | diff --git a/.github/workflows/run_coverage.yml b/.github/workflows/run_coverage.yml index bd2eeb921..08b6c59ea 100644 --- a/.github/workflows/run_coverage.yml +++ b/.github/workflows/run_coverage.yml @@ -101,7 +101,6 @@ jobs: python-version: ${{ matrix.python-ver }} channels: conda-forge auto-activate-base: false - mamba-version: "*" - name: Install run dependencies run: | diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 5e0b3bff2..2e94bcb62 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -139,13 +139,12 @@ jobs: auto-update-conda: true python-version: ${{ matrix.python-ver }} channels: conda-forge - mamba-version: "*" - name: Install build dependencies run: | conda config --set always_yes yes --set changeps1 no conda info - mamba install -c conda-forge "tox>=4" + conda install -c conda-forge "tox>=4" - name: Conda reporting run: | @@ -239,7 +238,6 @@ jobs: python-version: ${{ matrix.python-ver }} channels: conda-forge auto-activate-base: false - mamba-version: "*" - name: Install run dependencies run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e56cde40..b72ead1c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # HDMF Changelog +## HDMF 3.14.6 (Upcoming) + +### Bug fixes +- Fixed mamba-related error in conda-based GitHub Actions. @rly [#1194](https://github.com/hdmf-dev/hdmf/pull/1194) + ## HDMF 3.14.5 (September 17, 2024) ### Enhancements From b5235a72e3afb38bca8ce9b98669e52eacb70181 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 20:54:31 +0000 Subject: [PATCH 50/82] Bump actions/add-to-project from 1.0.1 to 1.0.2 (#1144) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Ly --- .github/workflows/project_action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/project_action.yml b/.github/workflows/project_action.yml index 0f0d8f3ce..6037bd4ab 100644 --- a/.github/workflows/project_action.yml +++ b/.github/workflows/project_action.yml @@ -20,7 +20,7 @@ jobs: - name: Add to Developer Board env: TOKEN: ${{ steps.generate_token.outputs.token }} - uses: actions/add-to-project@v1.0.1 + uses: actions/add-to-project@v1.0.2 with: project-url: https://github.com/orgs/hdmf-dev/projects/7 github-token: ${{ env.TOKEN }} @@ -28,7 +28,7 @@ jobs: - name: Add to Community Board env: TOKEN: ${{ steps.generate_token.outputs.token }} - uses: actions/add-to-project@v1.0.1 + uses: actions/add-to-project@v1.0.2 with: project-url: https://github.com/orgs/hdmf-dev/projects/8 github-token: ${{ env.TOKEN }} From b78625b6c37026b6d398618a18de44b169605d54 Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Wed, 2 Oct 2024 14:45:30 -0700 Subject: [PATCH 51/82] Fix scalar dataset with compound dtype for export (#1185) * convert compound dtype to list on read * revert dtype list checks in validator * update CHANGELOG.md --------- Co-authored-by: Ryan Ly --- CHANGELOG.md | 9 +++------ src/hdmf/backends/hdf5/h5tools.py | 4 ++++ src/hdmf/validate/validator.py | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b72ead1c5..888a3cb70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,6 @@ # HDMF Changelog -## HDMF 3.14.6 (Upcoming) - -### Bug fixes -- Fixed mamba-related error in conda-based GitHub Actions. @rly [#1194](https://github.com/hdmf-dev/hdmf/pull/1194) - -## HDMF 3.14.5 (September 17, 2024) +## HDMF 3.14.5 (Upcoming) ### Enhancements - Added support for overriding backend configurations of `h5py.Dataset` objects in `Container.set_data_io`. @pauladkisson [#1172](https://github.com/hdmf-dev/hdmf/pull/1172) @@ -13,6 +8,8 @@ ### Bug fixes - Fixed bug in writing of string arrays to an HDF5 file that were read from an HDF5 file that was introduced in 3.14.4. @rly @stephprince [#1189](https://github.com/hdmf-dev/hdmf/pull/1189) +- Fixed export of scalar datasets with a compound data type. @stephprince [#1185](https://github.com/hdmf-dev/hdmf/pull/1185) +- Fixed mamba-related error in conda-based GitHub Actions. @rly [#1194](https://github.com/hdmf-dev/hdmf/pull/1194) ## HDMF 3.14.4 (September 4, 2024) diff --git a/src/hdmf/backends/hdf5/h5tools.py b/src/hdmf/backends/hdf5/h5tools.py index da7f78a91..36aeb7c8f 100644 --- a/src/hdmf/backends/hdf5/h5tools.py +++ b/src/hdmf/backends/hdf5/h5tools.py @@ -700,6 +700,10 @@ def __read_dataset(self, h5obj, name=None): kwargs['dtype'] = d.dtype elif h5obj.dtype.kind == 'V': # scalar compound data type kwargs['data'] = np.array(scalar, dtype=h5obj.dtype) + cpd_dt = h5obj.dtype + ref_cols = [check_dtype(ref=cpd_dt[i]) or check_dtype(vlen=cpd_dt[i]) for i in range(len(cpd_dt))] + d = BuilderH5TableDataset(h5obj, self, ref_cols) + kwargs['dtype'] = HDF5IO.__compound_dtype_to_list(h5obj.dtype, d.dtype) else: kwargs["data"] = scalar else: diff --git a/src/hdmf/validate/validator.py b/src/hdmf/validate/validator.py index 2668da1ec..6ce211f96 100644 --- a/src/hdmf/validate/validator.py +++ b/src/hdmf/validate/validator.py @@ -147,7 +147,7 @@ def get_type(data, builder_dtype=None): # Case for h5py.Dataset and other I/O specific array types else: # Compound dtype - if builder_dtype and len(builder_dtype) > 1: + if builder_dtype and isinstance(builder_dtype, list): dtypes = [] string_formats = [] for i in range(len(builder_dtype)): @@ -441,7 +441,7 @@ def validate(self, **kwargs): except EmptyArrayError: # do not validate dtype of empty array. HDMF does not yet set dtype when writing a list/tuple pass - if builder.dtype is not None and len(builder.dtype) > 1 and len(np.shape(builder.data)) == 0: + if isinstance(builder.dtype, list) and len(np.shape(builder.data)) == 0: shape = () # scalar compound dataset elif isinstance(builder.dtype, list): shape = (len(builder.data), ) # only 1D datasets with compound types are supported From dedc1dd23580b87fa0067c78b71d7f4bd22bf9ca Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Mon, 7 Oct 2024 05:52:28 -0700 Subject: [PATCH 52/82] Release 3.14.5 (#1195) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 888a3cb70..f3f02fc32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # HDMF Changelog -## HDMF 3.14.5 (Upcoming) +## HDMF 3.14.5 (October 6, 2024) ### Enhancements - Added support for overriding backend configurations of `h5py.Dataset` objects in `Container.set_data_io`. @pauladkisson [#1172](https://github.com/hdmf-dev/hdmf/pull/1172) From b7a5fe2ce403c2563ab91b4ebe835a12ebb052b7 Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:58:43 -0700 Subject: [PATCH 53/82] make untyped dataset of references expandable (#1188) * add condition for dataio in untyped dset of refs * add tests for untyped dataset of references * add tests for compound dataset of references * remove deprecated call to get_html_theme_path * update CHANGELOG.md --- CHANGELOG.md | 5 +++ docs/source/conf.py | 4 --- src/hdmf/build/objectmapper.py | 6 ++++ tests/unit/common/test_table.py | 51 ++++++++++++++++++++++++++++++ tests/unit/test_io_hdf5_h5tools.py | 37 +++++++++++++++++++++- 5 files changed, 98 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3f02fc32..245902d5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # HDMF Changelog +## HDMF 3.14.6 (Upcoming) + +### Enhancements +- Added support for expandable datasets of references for untyped and compound data types. @stephprince [#1188](https://github.com/hdmf-dev/hdmf/pull/1188) + ## HDMF 3.14.5 (October 6, 2024) ### Enhancements diff --git a/docs/source/conf.py b/docs/source/conf.py index 9781933f5..c20869e12 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -163,16 +163,12 @@ # html_theme = 'default' # html_theme = "sphinxdoc" html_theme = "sphinx_rtd_theme" -html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None diff --git a/src/hdmf/build/objectmapper.py b/src/hdmf/build/objectmapper.py index 83df1b427..493a55bab 100644 --- a/src/hdmf/build/objectmapper.py +++ b/src/hdmf/build/objectmapper.py @@ -934,6 +934,9 @@ def _filler(): for j, subt in refs: tmp[j] = self.__get_ref_builder(builder, subt.dtype, None, row[j], build_manager) bldr_data.append(tuple(tmp)) + if isinstance(container.data, H5DataIO): + # This is here to support appending a dataset of references. + bldr_data = H5DataIO(bldr_data, **container.data.get_io_params()) builder.data = bldr_data return _filler @@ -952,6 +955,9 @@ def _filler(): else: target_builder = self.__get_target_builder(d, build_manager, builder) bldr_data.append(ReferenceBuilder(target_builder)) + if isinstance(container.data, H5DataIO): + # This is here to support appending a dataset of references. + bldr_data = H5DataIO(bldr_data, **container.data.get_io_params()) builder.data = bldr_data return _filler diff --git a/tests/unit/common/test_table.py b/tests/unit/common/test_table.py index 00b3c14a3..38175b230 100644 --- a/tests/unit/common/test_table.py +++ b/tests/unit/common/test_table.py @@ -2852,6 +2852,57 @@ def test_dtr_references(self): pd.testing.assert_frame_equal(ret, expected) +class TestDataIOReferences(H5RoundTripMixin, TestCase): + + def setUpContainer(self): + """Test roundtrip of a table with an expandable column of references.""" + group1 = Container('group1') + group2 = Container('group2') + + table = DynamicTable( + name='table', + description='test table' + ) + table.add_column( + name='x', + description='test column of ints' + ) + table.add_column( + name='y', + description='test column of reference' + ) + table.add_row(id=101, x=1, y=group1) + table.add_row(id=102, x=2, y=group2) + table.id.set_data_io(H5DataIO, {'maxshape': (None,), 'chunks': True}) + table.x.set_data_io(H5DataIO, {'maxshape': (None,), 'chunks': True}) + table.y.set_data_io(H5DataIO, {'maxshape': (None,), 'chunks': True}) + + multi_container = SimpleMultiContainer(name='multi') + multi_container.add_container(group1) + multi_container.add_container(group2) + multi_container.add_container(table) + + return multi_container + + def test_append(self, cache_spec=False): + """Write the container to an HDF5 file, read the container from the file, and append to it.""" + + # write file + with HDF5IO(self.filename, manager=get_manager(), mode='w') as write_io: + write_io.write(self.container, cache_spec=cache_spec) + + # read container from file + self.reader = HDF5IO(self.filename, manager=get_manager(), mode='a') + read_container = self.reader.read() + self.assertContainerEqual(read_container, self.container, ignore_name=True) + self.assertContainerEqual(read_container['table']['y'][-1], read_container['group2']) + + # append row + group1 = read_container['group1'] + read_container['table'].add_row(id=103, x=3, y=group1) + + self.assertContainerEqual(read_container['table']['y'][-1], group1) + class TestVectorIndexDtype(TestCase): def set_up_array_index(self): diff --git a/tests/unit/test_io_hdf5_h5tools.py b/tests/unit/test_io_hdf5_h5tools.py index 58119ce9b..79d7e1e6b 100644 --- a/tests/unit/test_io_hdf5_h5tools.py +++ b/tests/unit/test_io_hdf5_h5tools.py @@ -21,7 +21,7 @@ from hdmf.build import GroupBuilder, DatasetBuilder, BuildManager, TypeMap, OrphanContainerBuildError, LinkBuilder from hdmf.container import Container from hdmf import Data, docval -from hdmf.data_utils import DataChunkIterator, GenericDataChunkIterator, InvalidDataIOError +from hdmf.data_utils import DataChunkIterator, GenericDataChunkIterator, InvalidDataIOError, append_data from hdmf.spec.catalog import SpecCatalog from hdmf.spec.namespace import NamespaceCatalog, SpecNamespace from hdmf.spec.spec import GroupSpec, DtypeSpec @@ -3040,6 +3040,41 @@ def test_append_dataset_of_references(self): self.assertEqual(len(read_bucket1.baz_data.data), 2) self.assertIs(read_bucket1.baz_data.data[1], read_bucket1.bazs["new"]) + def test_append_dataset_of_references_compound(self): + """Test that exporting a written container with a dataset of references of compound data type works.""" + bazs = [] + baz_pairs = [] + num_bazs = 10 + for i in range(num_bazs): + b = Baz(name='baz%d' % i) + bazs.append(b) + baz_pairs.append((i, b)) + baz_cpd_data = BazCpdData(name='baz_cpd_data1', data=H5DataIO(baz_pairs, maxshape=(None,))) + bucket = BazBucket(name='bucket1', bazs=bazs.copy(), baz_cpd_data=baz_cpd_data) + + with HDF5IO(self.paths[0], manager=get_baz_buildmanager(), mode='w') as write_io: + write_io.write(bucket) + + with HDF5IO(self.paths[0], manager=get_baz_buildmanager(), mode='a') as append_io: + read_bucket1 = append_io.read() + new_baz = Baz(name='new') + read_bucket1.add_baz(new_baz) + append_io.write(read_bucket1) + + with HDF5IO(self.paths[0], manager=get_baz_buildmanager(), mode='a') as ref_io: + read_bucket1 = ref_io.read() + cpd_DoR = read_bucket1.baz_cpd_data.data + builder = ref_io.manager.get_builder(read_bucket1.bazs['new']) + ref = ref_io._create_ref(builder) + append_data(cpd_DoR.dataset, (11, ref)) + + with HDF5IO(self.paths[0], manager=get_baz_buildmanager(), mode='r') as read_io: + read_bucket2 = read_io.read() + + self.assertEqual(read_bucket2.baz_cpd_data.data[-1][0], 11) + self.assertIs(read_bucket2.baz_cpd_data.data[-1][1], read_bucket2.bazs['new']) + + def test_append_dataset_of_references_orphaned_target(self): bazs = [] num_bazs = 1 From 6e5534cee02c421f7c992471785f1aec5be8f419 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:15:54 -0700 Subject: [PATCH 54/82] [pre-commit.ci] pre-commit autoupdate (#1197) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c84bfaffc..4122f041f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ # NOTE: run `pre-commit autoupdate` to update hooks to latest version repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-yaml - id: end-of-file-fixer @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.8 + rev: v0.7.1 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From 06a62b9b3dbdae30391b54a8d51d7d87e550ca02 Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:24:28 -0700 Subject: [PATCH 55/82] Fix DtypeError message for reference validation (#1199) * update dtypeerror inputs for dset of refs * add validation tests for references * remove old comment * update CHANGELOG --- CHANGELOG.md | 3 ++ src/hdmf/validate/validator.py | 6 +++- tests/unit/validator_tests/test_validate.py | 32 +++++++++++++++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 245902d5b..c1c490089 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ ### Enhancements - Added support for expandable datasets of references for untyped and compound data types. @stephprince [#1188](https://github.com/hdmf-dev/hdmf/pull/1188) +### Bug fixes +- Fixed inaccurate error message when validating reference data types. @stephprince [#1199](https://github.com/hdmf-dev/hdmf/pull/1199) + ## HDMF 3.14.5 (October 6, 2024) ### Enhancements diff --git a/src/hdmf/validate/validator.py b/src/hdmf/validate/validator.py index 6ce211f96..daa5adac4 100644 --- a/src/hdmf/validate/validator.py +++ b/src/hdmf/validate/validator.py @@ -436,7 +436,11 @@ def validate(self, **kwargs): try: dtype, string_format = get_type(data, builder.dtype) if not check_type(self.spec.dtype, dtype, string_format): - ret.append(DtypeError(self.get_spec_loc(self.spec), self.spec.dtype, dtype, + if isinstance(self.spec.dtype, RefSpec): + expected = f'{self.spec.dtype.reftype} reference' + else: + expected = self.spec.dtype + ret.append(DtypeError(self.get_spec_loc(self.spec), expected, dtype, location=self.get_builder_loc(builder))) except EmptyArrayError: # do not validate dtype of empty array. HDMF does not yet set dtype when writing a list/tuple diff --git a/tests/unit/validator_tests/test_validate.py b/tests/unit/validator_tests/test_validate.py index dd79cfce5..64667b3e0 100644 --- a/tests/unit/validator_tests/test_validate.py +++ b/tests/unit/validator_tests/test_validate.py @@ -524,6 +524,38 @@ def test_scalar_compound_dtype(self): results = self.vmap.validate(bar_builder) self.assertEqual(len(results), 0) +class TestReferenceValidation(ValidatorTestBase): + def getSpecs(self): + qux_spec = DatasetSpec( + doc='a simple scalar dataset', + data_type_def='Qux', + dtype='int', + shape=None + ) + bar_spec = GroupSpec('A test group specification with a reference dataset', + data_type_def='Bar', + datasets=[DatasetSpec('an example dataset', + dtype=RefSpec('Qux', reftype='object'), + name='data', + shape=(None, ))], + attributes=[AttributeSpec('attr1', + 'an example attribute', + dtype=RefSpec('Qux', reftype='object'), + shape=(None, ))]) + return (qux_spec, bar_spec) + + def test_invalid_reference(self): + """Test that validator does not allow another data type where a reference is specified.""" + value = np.array([1.0, 2.0, 3.0]) + bar_builder = GroupBuilder('my_bar', + attributes={'data_type': 'Bar', 'attr1': value}, + datasets=[DatasetBuilder('data', value)]) + results = self.vmap.validate(bar_builder) + result_strings = set([str(s) for s in results]) + expected_errors = {"Bar/attr1 (my_bar.attr1): incorrect type - expected 'object reference', got 'float64'", + "Bar/data (my_bar/data): incorrect type - expected 'object reference', got 'float64'"} + self.assertEqual(result_strings, expected_errors) + class Test1DArrayValidation(TestCase): def set_up_spec(self, dtype): From be602e51804922e01bcd0e6f89b23873f9e13b4b Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Tue, 5 Nov 2024 15:43:29 -0600 Subject: [PATCH 56/82] improve html representation of datasets (#1100) * improve dev repr * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * address ruff * add changelog * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add table representation for hdf5 info * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * add test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ruff * handle division by zer * add zarr, array, hdf5 repr tests * generalize array html table description * remove zarr tests * fix nbytes * fix use of nbytes ahead * added TODO * add html test array data type * add array html repr utils * add generate_dataset_html method to io objects * add tests for array html repr * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix import style * update CHANGLEOG * add test for base hdmfio --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steph Prince <40640337+stephprince@users.noreply.github.com> Co-authored-by: Ryan Ly --- CHANGELOG.md | 1 + src/hdmf/backends/hdf5/h5tools.py | 29 +++++++++- src/hdmf/backends/io.py | 10 +++- src/hdmf/container.py | 32 +++++++---- src/hdmf/utils.py | 48 ++++++++++++++++ tests/unit/test_container.py | 94 +++++++++++++++++++++++++++++++ 6 files changed, 202 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1c490089..b4f0fde80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Enhancements - Added support for expandable datasets of references for untyped and compound data types. @stephprince [#1188](https://github.com/hdmf-dev/hdmf/pull/1188) +- Improved html representation of data in `Containers` @h-mayorquin [#1100](https://github.com/hdmf-dev/hdmf/pull/1100) ### Bug fixes - Fixed inaccurate error message when validating reference data types. @stephprince [#1199](https://github.com/hdmf-dev/hdmf/pull/1199) diff --git a/src/hdmf/backends/hdf5/h5tools.py b/src/hdmf/backends/hdf5/h5tools.py index 36aeb7c8f..e9156dc50 100644 --- a/src/hdmf/backends/hdf5/h5tools.py +++ b/src/hdmf/backends/hdf5/h5tools.py @@ -19,7 +19,8 @@ from ...container import Container from ...data_utils import AbstractDataChunkIterator from ...spec import RefSpec, DtypeSpec, NamespaceCatalog -from ...utils import docval, getargs, popargs, get_data_shape, get_docval, StrDataset +from ...utils import (docval, getargs, popargs, get_data_shape, get_docval, StrDataset, + get_basic_array_info, generate_array_html_repr) from ..utils import NamespaceToBuilderHelper, WriteStatusTracker ROOT_NAME = 'root' @@ -1603,3 +1604,29 @@ def set_dataio(cls, **kwargs): data = H5DataIO(data) """ return H5DataIO.__init__(**kwargs) + + @staticmethod + def generate_dataset_html(dataset): + """Generates an html representation for a dataset for the HDF5IO class""" + + # get info from hdf5 dataset + compressed_size = dataset.id.get_storage_size() + if hasattr(dataset, "nbytes"): # TODO: Remove this after h5py minimal version is larger than 3.0 + uncompressed_size = dataset.nbytes + else: + uncompressed_size = dataset.size * dataset.dtype.itemsize + compression_ratio = uncompressed_size / compressed_size if compressed_size != 0 else "undefined" + + hdf5_info_dict = {"Chunk shape": dataset.chunks, + "Compression": dataset.compression, + "Compression opts": dataset.compression_opts, + "Compression ratio": compression_ratio} + + # get basic array info + array_info_dict = get_basic_array_info(dataset) + array_info_dict.update(hdf5_info_dict) + + # generate html repr + repr_html = generate_array_html_repr(array_info_dict, dataset, "HDF5 dataset") + + return repr_html diff --git a/src/hdmf/backends/io.py b/src/hdmf/backends/io.py index 35023066f..86fd25b26 100644 --- a/src/hdmf/backends/io.py +++ b/src/hdmf/backends/io.py @@ -5,7 +5,7 @@ from ..build import BuildManager, GroupBuilder from ..container import Container, HERDManager from .errors import UnsupportedOperation -from ..utils import docval, getargs, popargs +from ..utils import docval, getargs, popargs, get_basic_array_info, generate_array_html_repr from warnings import warn @@ -188,6 +188,14 @@ def close(self): ''' Close this HDMFIO object to further reading/writing''' pass + @staticmethod + def generate_dataset_html(dataset): + """Generates an html representation for a dataset""" + array_info_dict = get_basic_array_info(dataset) + repr_html = generate_array_html_repr(array_info_dict, dataset) + + return repr_html + def __enter__(self): return self diff --git a/src/hdmf/container.py b/src/hdmf/container.py index 7c450770a..8f961936f 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -12,7 +12,8 @@ import pandas as pd from .data_utils import DataIO, append_data, extend_data, AbstractDataChunkIterator -from .utils import docval, get_docval, getargs, ExtenderMeta, get_data_shape, popargs, LabelledDict +from .utils import (docval, get_docval, getargs, ExtenderMeta, get_data_shape, popargs, LabelledDict, + get_basic_array_info, generate_array_html_repr) from .term_set import TermSet, TermSetWrapper @@ -707,8 +708,6 @@ def _generate_html_repr(self, fields, level=0, access_code="", is_field=False): for index, item in enumerate(fields): access_code += f'[{index}]' html_repr += self._generate_field_html(index, item, level, access_code) - elif isinstance(fields, np.ndarray): - html_repr += self._generate_array_html(fields, level) else: pass @@ -724,18 +723,23 @@ def _generate_field_html(self, key, value, level, access_code): return f'
{key}: {value}
' - if hasattr(value, "generate_html_repr"): - html_content = value.generate_html_repr(level + 1, access_code) + is_array_data = isinstance(value, (np.ndarray, h5py.Dataset, DataIO)) or \ + (hasattr(value, "store") and hasattr(value, "shape")) # Duck typing for zarr array + if is_array_data: + html_content = self._generate_array_html(value, level + 1) + elif hasattr(value, "generate_html_repr"): + html_content = value.generate_html_repr(level + 1, access_code) elif hasattr(value, '__repr_html__'): html_content = value.__repr_html__() - - elif hasattr(value, "fields"): + elif hasattr(value, "fields"): # Note that h5py.Dataset has a fields attribute so there is an implicit order html_content = self._generate_html_repr(value.fields, level + 1, access_code, is_field=True) elif isinstance(value, (list, dict, np.ndarray)): html_content = self._generate_html_repr(value, level + 1, access_code, is_field=False) else: html_content = f'{value}' + + html_repr = ( f'
{key}' @@ -745,10 +749,18 @@ def _generate_field_html(self, key, value, level, access_code): return html_repr + def _generate_array_html(self, array, level): - """Generates HTML for a NumPy array.""" - str_ = str(array).replace("\n", "
") - return f'
{str_}
' + """Generates HTML for array data""" + + read_io = self.get_read_io() # if the Container was read from file, get IO object + if read_io is not None: + repr_html = read_io.generate_dataset_html(array) + else: + array_info_dict = get_basic_array_info(array) + repr_html = generate_array_html_repr(array_info_dict, array, "NumPy array") + + return f'
{repr_html}
' @staticmethod def __smart_str(v, num_indent): diff --git a/src/hdmf/utils.py b/src/hdmf/utils.py index 50db79c40..ccd3f0b0b 100644 --- a/src/hdmf/utils.py +++ b/src/hdmf/utils.py @@ -967,6 +967,54 @@ def is_ragged(data): return False +def get_basic_array_info(array): + def convert_bytes_to_str(bytes_size): + suffixes = ['bytes', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'] + i = 0 + while bytes_size >= 1024 and i < len(suffixes)-1: + bytes_size /= 1024. + i += 1 + return f"{bytes_size:.2f} {suffixes[i]}" + + if hasattr(array, "nbytes"): # TODO: Remove this after h5py minimal version is larger than 3.0 + array_size_in_bytes = array.nbytes + else: + array_size_in_bytes = array.size * array.dtype.itemsize + array_size_repr = convert_bytes_to_str(array_size_in_bytes) + basic_array_info_dict = {"Data type": array.dtype, "Shape": array.shape, "Array size": array_size_repr} + + return basic_array_info_dict + +def generate_array_html_repr(backend_info_dict, array, dataset_type=None): + def html_table(item_dicts) -> str: + """ + Generates an html table from a dictionary + """ + report = '' + report += "" + for k, v in item_dicts.items(): + report += ( + f"" + f'' + f'' + f"" + ) + report += "" + report += "
{k}{v}
" + return report + + array_info_html = html_table(backend_info_dict) + repr_html = dataset_type + "
" + array_info_html if dataset_type is not None else array_info_html + + if hasattr(array, "nbytes"): # TODO: Remove this after h5py minimal version is larger than 3.0 + array_size = array.nbytes + else: + array_size = array.size * array.dtype.itemsize + array_is_small = array_size < 1024 * 0.1 # 10 % a kilobyte to display the array + if array_is_small: + repr_html += "
" + str(np.asarray(array)) + + return repr_html class LabelledDict(dict): """A dict wrapper that allows querying by an attribute of the values and running a callable on removed items. diff --git a/tests/unit/test_container.py b/tests/unit/test_container.py index 9ac81ba13..35d8e480c 100644 --- a/tests/unit/test_container.py +++ b/tests/unit/test_container.py @@ -8,6 +8,7 @@ from hdmf.utils import docval from hdmf.common import DynamicTable, VectorData, DynamicTableRegion from hdmf.backends.hdf5.h5tools import HDF5IO +from hdmf.backends.io import HDMFIO class Subcontainer(Container): @@ -423,6 +424,23 @@ def __init__(self, **kwargs): self.data = kwargs['data'] self.str = kwargs['str'] + class ContainerWithData(Container): + + __fields__ = ( + "data", + "str" + ) + + @docval( + {'name': "data", "doc": 'data', 'type': 'array_data', "default": None}, + {'name': "str", "doc": 'str', 'type': str, "default": None}, + + ) + def __init__(self, **kwargs): + super().__init__('test name') + self.data = kwargs['data'] + self.str = kwargs['str'] + def test_repr_html_(self): child_obj1 = Container('test child 1') obj1 = self.ContainerWithChildAndData(child=child_obj1, data=[1, 2, 3], str="hello") @@ -455,6 +473,82 @@ def test_repr_html_(self): 'class="field-value">hello' ) + def test_repr_html_array(self): + obj = self.ContainerWithData(data=np.array([1, 2, 3, 4], dtype=np.int64), str="hello") + expected_html_table = ( + 'class="container-fields">NumPy array
Data typeint64
Shape' + '(4,)
Array size32.00 bytes

[1 2 3 4]' + ) + self.assertIn(expected_html_table, obj._repr_html_()) + + def test_repr_html_array_large_arrays_not_displayed(self): + obj = self.ContainerWithData(data=np.arange(200, dtype=np.int64), str="hello") + expected_html_table = ( + 'class="container-fields">NumPy array
Data typeint64
Shape' + '(200,)
Array size1.56 KiB
' + ) + self.assertIn(expected_html_table, obj._repr_html_()) + + def test_repr_html_hdf5_dataset(self): + with HDF5IO('array_data.h5', mode='w') as io: + dataset = io._file.create_dataset(name='my_dataset', data=np.array([1, 2, 3, 4], dtype=np.int64)) + obj = self.ContainerWithData(data=dataset, str="hello") + obj.read_io = io + + expected_html_table = ( + 'class="container-fields">HDF5 dataset
Data typeint64
' + 'Shape(4,)
Array size' + '32.00 bytes
Chunk shape' + 'None
CompressionNone
Compression optsNone
Compression ratio1.0

[1 2 3 4]' + ) + + self.assertIn(expected_html_table, obj._repr_html_()) + + os.remove('array_data.h5') + + def test_repr_html_hdmf_io(self): + with HDF5IO('array_data.h5', mode='w') as io: + dataset = io._file.create_dataset(name='my_dataset', data=np.array([1, 2, 3, 4], dtype=np.int64)) + obj = self.ContainerWithData(data=dataset, str="hello") + + class OtherIO(HDMFIO): + + @staticmethod + def can_read(path): + pass + + def read_builder(self): + pass + + def write_builder(self, **kwargs): + pass + + def open(self): + pass + + def close(self): + pass + + obj.read_io = OtherIO() + + expected_html_table = ( + 'class="container-fields">
Data typeint64
' + 'Shape(4,)
Array size' + '32.00 bytes

[1 2 3 4]' + ) + + self.assertIn(expected_html_table, obj._repr_html_()) + + os.remove('array_data.h5') class TestData(TestCase): From 0b65dc683f79ac570748131c7165ab0c8a29c063 Mon Sep 17 00:00:00 2001 From: Oliver Ruebel Date: Fri, 8 Nov 2024 14:01:32 -0800 Subject: [PATCH 57/82] Fix #1203 add missing thumbnail for TermSet tutorial (#1204) --- docs/gallery/plot_term_set.py | 2 ++ .../figures/gallery_thumbnail_termset.png | Bin 0 -> 716138 bytes docs/source/figures/gallery_thumbnails.pptx | Bin 194970 -> 306089 bytes 3 files changed, 2 insertions(+) create mode 100644 docs/source/figures/gallery_thumbnail_termset.png diff --git a/docs/gallery/plot_term_set.py b/docs/gallery/plot_term_set.py index 8bf2375aa..50945889a 100644 --- a/docs/gallery/plot_term_set.py +++ b/docs/gallery/plot_term_set.py @@ -65,6 +65,8 @@ For more information how to properly format the schema to support LinkML Dynamic Enumerations, please refer to https://linkml.io/linkml/schemas/enums.html#dynamic-enums. """ +# sphinx_gallery_thumbnail_path = 'figures/gallery_thumbnail_termset.png' + from hdmf.common import DynamicTable, VectorData import os import numpy as np diff --git a/docs/source/figures/gallery_thumbnail_termset.png b/docs/source/figures/gallery_thumbnail_termset.png new file mode 100644 index 0000000000000000000000000000000000000000..29a0db9031a270dbe66b287eed620d07603b12ba GIT binary patch literal 716138 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

    cJ6P{5 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⩔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

    28XI%Q?8JL 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

    &h}S3zzazc@uS>( zj;9z=1X3~iYrjcp6!7Ho4q*5hA3;wFZ;+gm$kOmU+g(6KC%X-6bSuh3v-yTrEwNNh zk~l|v;0k^Jlp zj?c{~PcqDQy)Rcn3H5v46DLSfmN`b>TSrLv?_j}VcZg^n7j9vmtM!z-Z_A2pv(W23 zUYe&k%n3tuMo3U@NcLg`5*bBoI$6nFH~(el7DnW64(lz*FMZ^EX(okElI0Wia|$Z`N6hN<)QpqSDqaZL+Cb12Lk z7wH9-Z3O?|Pf^bcan(79@S#**Dh|}z80etT3!zcX801WbP7W*l7daDcNu0Lpm z9tk@Qpbn6I7>S>N6Q=cGf8dq!B+jUO&d62{qdl?l7dJr5VHiH+^xT|P3>72uGF28L zEj^v^7iJRHcT791XPpzY^z0b(01k|@Dh!$yh@_mP4*hhgo`ocbBy$X19oAYyKuT+i!SO#Whl{J48EFjxk0^z3DLyd6y2q zh4~tm&&0M{v;a;Y^R$5Bc)P|d#I6YGkgF?^!t75YWb^*sk%IwPcs4gFBsO=5j*0z6 zWJLzdSZ406B?i-5auzq&N1+?Y%{-hT^K>G-K0CM~*h3))_ARLEU!5)N3LAHGqO{gs zW-PB(x)}!eN};gh@il2rC27C2#P>0{Wz%SET3Q0rZ_!WqL^eg!beRP@chWva5=)-i7&Jl%2yE08G|RSux6|uN z&StZ!;U;8yhi@9IoC-=x4LPG~qu#?kE*!yY$22cR`JY*oXAM91BlOm3tI;vf8x zrpf1-jIHZ^ep7usMQ&8fRx2X!Jomil2`^9dW^<8U{A9wE_WbFQ_6)C{XS#4wGcE}o zEe$^;Nkf!^y-lvv+lm#L@Zs3G$X{w5Btn|x;j~)>*Hn;eLo@8g;{uU{DV2* zFcxBo8cL$DH1QQ*XKupfSk^w-RYLzak_s*2@K18l&so7$6)a;Js(8RGVOM?N@yS6v z&s`zMec)Z?k2SW3+W7?7rws~t>_Y_qh@=|81cwIdsr13NaRnuby)Q>eAHB(hQQhF5 zrl5W76KY7>qbN*V#1j`>zy}cZ%Os3Y3}mJGiwYJc39JBR%I}>!tl8O!nM@rD-$-2g8uCbfyTZx!6W_m_3>+PQ~KU^ zblBApZsLBz{f@67uje1*Z-G5hi6>lwHux40Tso^Gd=vLDETpEJmxSuPdO?6u|B)xQ z|9q2c@lS^YcL0f41*9Obi1RwITy4otlU9-i1;CZzd-ce(i`!ULQm%(|cry=*fmCS> zrT6U1Q9P9)|BP6neNL4;%M~@G%}d{gE#S&4|D9M2=0$(Z%YJ1hn|jls4R|t+XRHaw z>Wvfuu%gU~C{E5d51E~j>;3V9Ns)9XxG0#mN}5PAAuP;Yp$yNs|B*pcCcvEF=Kcc$ zF2&|k2pB5hp9E7h!te`VIFci134`lsCpp<%1zcdZl)ZG@ghZnSn(pJ&6hzu*Sk%Az z@aSck#dmps3}RRMsy*`=MJk3-J*sCSY78$HB{=tE32SK{h+}s&i@Io9aawICbd!oK zIU9!HhUD+*rnjgcg#>(|D4D{g^m|$Tie`Pe}=hk+`Albn)Wcsk4nmV)L6JeucK2tF0Q6x`(az>&Y^ww&yVrg zAG`R)1?}`ImFuSMYX!AtN(9SeJ$|g~O8oZGZSrci71MWv0fy$PbkW$jh>7P#QFz03 zS%N30m%7*1PFHE%Ps`oEq(D0|P6-XtlR3LeuhYhW`ycPVsO{SOD8mS!2=m z7g~CPbnl#vy;g5haGjtneVyPcCBxDjdd!z`IZ&p!)-00VB0KUmiz82^%3~YQKCS5w79AjCmWD==D zI!w5>?ziCV_Kdgu;dehc#XQIIz+U)D>xMyu`QR=6;|JQ-^L4hkffRfg!hJ^rOe;rhnb!tOAIlAk5$i2BF4i*^fHh`_DIdyYc87}=mmg19PJHnS@VGKl zu!80=S~Or&Tn*Rj50zirj|y)W=TSaw)@{YL8_sSH4=lj;G;|ot=a)(kNqOcctkG>_ z7RiSj=v<@YM9Ae0Tg4WSE9OJYg%3LyZmGwqN>))rm z?S4aRKG)~7Rnr|1Y<{2J%HDN@1JC3{S;_&-3`|o@0aG3tdiqLI+bur9_-w&@3Aw7> zm?vp&McGHVbwil(R~yHBP}S_C3>u<%jZZzhZh@vrS3JaqNi(8`@s#t132O$u@da5^ zoxCqrvZlP&7el;0To3G3<*MY0pNaOl4mpiql(!kFxMaql_O3S???d~i7K{7eP9>i! z>J)u$9~A6sE#80riE~g0OLtW#jxBrs8XeVa2y9e&P1((kRY<#)%$dAkb})9;sRgIx zzm$BHRKGaipjGz3fR`t#`TgEY{7N;m#*Jpz#!quUCssGZFd;uIHFXj(LJt-$RlxHh z^;ALHI=WA}mRcKqt8H}a6p7qu6DYY`7!l1-y(<*mwXGdju`atFb=^a&adFkEhpt}s z&EU}%nAeyMyR&SnA)jmd2WXnaaUW7XsGx)98BP9=nt`N2c48%u1Nw#Mk%Sn;36inv zoo)Y3p1EEtz+U`>;%dnL?kli;bpC=51A;4Q1su)@WSVd~zf>@CTF&c?c7cdb`_nna z?YO`l$aGm}{pBS_y_VNkvf8}7YIMh)jd0U@iDg!%#yU$ndTWL>puS4LtE=opo}0M? z)ocCkooJsY6_3%LoFi}KE7;%VrV2)aCk%Npkxyh2y|?nlo*J=Ic76G@?)J0;626;c zk2i>crM99@=)W%=NNpM~5x_9y(?>o*UUe_f$sblbmdP!4) z!zE8;{QR0UuLsHN7RPW%m_n%P+l~1lUW#{rOh=Ba*O@yG^=X znC-@(#d`r41US1V1zATa_q_W=d>8?@xmReVv6SM@i58&CG4QQX<+73(AF#tGgB;FE`|{FqXK6UDNVfVtaPk8}8?+bBz;J#Ar6cz%qr1%T-K^ z=ay#Rqejxt$x@QPdJjDH-AWhP^t9{}YipZiT0OyCUK2u0UWaJ@HHIpGFmVPw!ay`&x2-KKJ`sM<^Gat#?v9zcB!+Ag*H;!YMe>FLRH)a|9juq`XhF!|XR=|$j;W^ujr*0W$k+O4Fgzpb^N zm%ED`P)Tw}^|EEavJi7fu5g-F`k!n033h0T0NehR`UOnL;*hs=zyH8bTEg(QQs$Yvle1=s}!Z};H34Aytfe^hNw_({&a1_Y9#or@Q8Y;>q&?$_5laV6lh8V_VxwOcU8yQe>>;+Bt1`OwoqehkT^M#xKYur zJ)S5kOBWh0+7L%(OD-svnkGv_>x&uW)Nzj|)MEn6!<@A1_6m7 zh8{#jE`*^&xv*?{MOhZJ(h&=Bx$jRT&2eDa7o;{2gazC`Jo-q7`3GeVL&K~cnHxXEu}K3GQlI%h zR+;vI4e7~rQSKZG-$`A+czZJM(bnnLNLC$@kAzmSl-d(@CLZ>K0xLH&YSWxTFW$XY zUZYH>1zwXNC9E!N*BCJDOmO~Vde~j+I{=p&pl=W*(2_AwUSD<+d3EjzW@8k}RLAvt zfUCNX#wayJCr}25rlgPb9jD%|-JW=?rWV=~$mtBSzW-F zgDad?C6C+HMW*{huX{DIU@JOv7k8a$pP7~RmyoXxwt2ohhsu31lSl)r&A0NhvIpw% zSh9mMO+sQ*W@bGT65N!8sPuh66a)-eJ9G!@{~jz>Vmy4)g!aGhpsNU-bI3fjCMu!!2C}srjyb^J*45U_V<$ ztjqg7`?GC&yeaa1pEFj{vaI@BPyDO)V1gOsuylIy`O)|z8XEvhvq8GwRACGODS^RTC%CT38K2kRlRK3S z-z}m_iaQ;nNvqbll+M9FrNxq)1-U?-Z3qoP?dP?pLq?wyo7vODyv?XFPsx&eKB&RKyn*+tF zQ{uEtixTVKeb{<0KrA-$0nH-JuIZE0DC$O(DcaSIuBnRnnJZGjBV~edy-V4opH;d4 z?(LWTGG)1C9|%DD%N>EDVQ;gXdoN0!qSETI_6;@8p7&hh!M+$fO6T9Z6#C>zt)LPZbzlW3pC z2k1W_N&6-Rmn?<)!~G0h_h@4BqS#m>AIAy72FU!VDLm!L-c2;QvjtZUTTO zHs;2)Aqv-?2hrPO-_0`dcM_Dvdxd6O&inXeMRvI%pQ6O83bglFXbZmA`18WL(zE|e zA42zJpV6O6>Dxh03tCQo?~?TT>6e8pY8lq%7PCRgPa8~bgX(B^6+JhDO(U@Bo!2jl zqdKQmi%P$=YLS&T)?~k$eDLHXI71GCGvJm@1M&uuH4-&i<+ar3*h-ekpLF~QcUzlg zJf`k}#C&-}XuG`^ljh*rJAi+vvC3-gtk5y7Bl575#2vZPO9SS&PdWSk*CpQj)eu*s z{P83nXC6nd<&}}Zl6n&)@ZG;;J=NQ^s!{3w18_H>@^F#7~~G>iXkxN zU;8b>G>8C$nNB?GA=|rFN)sf1)|akRjRrgyWa>{Gz4Lb))|Qj2;@K?e0;-tKKbsfX zBxNczf2BAlsV@ZNhuCAfc48)8Xu=2Q0-?5gyAwB;3mO{sVOQfqH%c68B!qOuAgT3R zW7)f=40Qhr_pXF+y*M(F4I1ixg?XSDXy8J%g`cBS?( zZ6AIS@~mn#mC^Hja=kWQA!F#G^GSAan(W(x>Nyp|f@$G_x(Dof#V|fN2ekJ9tDo=0 zKMrVLa=ACeuEVg%I=`deOJ7KZB$uW^d!kIFNEJZdKs|_p&zOAg@86MtIzrkH1!{n{ zDZr;Va9MKkGfF4#)^b8rWHGf>7!kldE(an+wbL$RqH3iI+h;14q&u5sf{Nc_HPN%V+}r(1Ur6%F%)g&~|Ww%^Gz+D;RY zcOO%Eb*+&E!UG@@bIrttpuu4?HDMTz5KrD~dLA?6+FUW4!N(z1X*Q?o2<^n^!7j z9%~MG1(e%*(D&oZV8f5f7o`(}?APB)7bI)PJI6ve=Bc?=Ph>|M9&NX8!||L%5L#k& zG`JS|_#1z*xVo!*(Y>NyWquN-&{7JM%GKPWVC%wL?3Sbk4}?DwcW5axC#~V8g4Z9j zYBoKQJ^1K^e1=_=dQ%v_Jhjn(G>%Mn+is)XPG|gkS3eHfRyzfaY#zh@{2RxnMX>;k6MMkeKECm0vQT^ zHMa53F_|AdS1Sw1^Un#q3jZ${Ll@t>=O=+TLaTfW=S(K{_F;;|!*aEoHR8mIZl#J5 z6TXdlD7B^f1&2ZR(gg(5WN!?2GpFNOE27JOLBosDjrd-)qN=6ss&|QM!;c7!MM`mN z0peQ?_V;q{Uov9Ugw_|?JL(V_Gu!39{#VZ0SH+5!1`lqw^lr2)@#BBEUJwdOzh3wL zCH}H01CSm-zMBg#$`LQ1+T*&)D6~QlM?aRSwG~^esL@>tTZ>5)43vfBtP%2#sHhNq zXea~-4q69H&R(+Z+BBcC5uc3G9Zsr1B^`h0u^lj{Gm;VZTF}EehuEF;A^{xhOzUIX zax3NtBX#h6n^?9iP>d(K?rO2d+xs*Q?4An||4s*22!)t9?{GjOZecPp;bXU6khHJc zYea6F^=#wc<`^g%^*~&fg_M6++`$ly*xc#XWE9f8VXm?wPon?Cc{ne%pr@4iKr$v# z#5@3y0IIUk%Yyj|%a3U?u94w{y)@V}DXz2M!qiVw5l^ADVW%J46g#~nueqp9AwZT; zcmB&kdr0xL(l2dBMg<+=Y7q@?vky)pGjyY5)c2T_L;(c{{KN!=C_*d&e}`K$Mi=&X zI9;RJQ^mo2kyQ@%_98rFJQ09N!aBlS!h@G2%1zOpyxW;<04AM0p>3l=0hlanZ7jF; zz4i|i@(I#hI+W^n7|0vf!HR~D4@HFt0i=hj-MRxXA5=CbOpyx}jPdDb@1$0zfhXpb zLR3?o+5pwCAWWE1?OO?HY6b@3B$Y9`Y3hi5Cq$k}G!+G=o_Mu5A5-+5UJ z{lrYQbQ(qS)|y{8{;2eJSPDW}>T@z3vxAnecscho*gtEv)Dr*{7L~!!YRWy2xDDJS z=99-=Tq1$kd(yn|#CV}tACuQJtg6M9FYG<#`9Wk$cWHRsisn}`--fbHfjO28+^#E5 zDi9r7Q~Hh1dlMf3i-1DW>m_NGKAmwpR`LKchu$}iyjvf7iHi97j(#VWGs0-jV^#|d zN_;FFm7nxJanyLrs=3&;dbjVSCNf~dVFR)iKl)l*Q}cDW@kTT292mC>Ho=Mwfj4rAn3uGia(Jz9`X9f>vrt0Cv%s5dzmuNUG?2XP4#~G}#7kl*h3DAi6p^%6 z5bH0`P?nQ)*BG-i`KUD|Yxn&wthkK!zI<%Lt%QX1%bcwq{zzV~z_-p$2SaouG5yS2 z3U9u8(=FzB4!(^YO$|aDLMI4jWzVX~v|U2Ec=W;|>rHh47~c@g-Czs+(km{h)(*l3M3y&?A^M1KUvy5l0e5T%Hz^ zyonVmmjDPF+lNDUEx_}*NfOG*axa%?QA(Z)+A0-ijj*2Aj#&zsQYe*uc8|7ybRthl zU@j{se@395z$4%^@3O^p=N-!|*bRQM)}@U2m}9cFlBBR3rFvPCQDDik${Wo6*fCS9 z>GlADg3m&e=`_0z3)2bp$F3H~ruAoTv;myxv%&5A+@FDVqMq1bSGG)%>zvkhJt8Li z6j8vZ+~@@-ttVRkoTW$7<3s@7Y0S^(EKa&~RFNAha!Aiy(?Lz&_EAZH)(C-_&>{gX z1+iuZy<$t+$=5-BY#|=;`%xlk>qt+I0OQM`uC6INnd*=bImG_ED`7y=n>TNCci&Ll zMs)8dnI^#uP-cHy%xRZ*h&$qvlC@FLLr3C|Ynn!Sq*NXNQ>2VAPQF2@Adkq6K1X$=9sR@o>W+`EBJ%OiF^qD z`0v%PlgC-r@8Y311zTb$_vXY;N?1gb7v;9^MN~k^J#69r9AJ6&CC~cxi_spb0?azZ zh-=6TE=No)rZE8lZT(6Z?>#zN9WlrcWc%)0|ECQ<>Qe`?Bx>{5x}p4$-UM_U5OhTR z-S`TRi&uZ;;#V(AVp_5k!IXoZ8S!^sYcpJ_%N#BF-?pRHfBuSx-=OA?tnU#!K=xAg za%K8J!rb0r^w~CupCFZhziPF4&;$t9Pm2Z855tUIi_;pvc!3Ww}tQq(tMLCNHJUHfTs`c zY}zu#+Wq0p^>)O6BM_670M2}rj^$VqofVJ0hu`ObuJzT&zZNa z5pUj4l@A{h_vMOCMF<&c$4dM3i^$DfYTRBgO-N`Q%y<`>PK?Mi00U@_=pXc|r zx*wA|%!)%eL1_@&&h^HYsXx2~B?g-a)cHkKB{3DckJ=E`+h8h?bkq+>|QW%v7pN8cqPP0)tjX*P5 zELAY(Ni5tVA!bEn6!9tLxSsf5=m!6$smlZejxn2&w>l0#xsD#x{LoXIce5_n#Mir5LFwluT@=?K|(Bq0%w^v%?_+=mNOc z#lQkdp-BrSTC-!uupi0@p+5(T5T9X5^a;;N>{ekcL$sQp%BO-%rpu`MS+Q)D7l=)k zD0&nDlhovCRcm|K5$k1`|`=6DF1?)*+fD zI{%=`&rR;AI!8mN1?&XD#}id;=RvEYUW{sM;sAkpdPdvMxF*!MPbYpL#2x9V)rn*l zq>ZD!efjD_vI?xJijaW}d5iS|-v~QhF+Qp57zyV)j&$3pS4=7hP!Mjdn`buUw~<>E z@U+TOJJJ=J5ZR?AUFp)~fOAc7eiqpWa!xI8Mqdob)F64rS+p}wOcz)ntn2=?=Oyn4 zZ(TG0Hhm9V!5OGu87uGktaCl~@oqR%YvLH7?e4Qd*-cAIX`w)eM&BgXCn!D=HvIbYWVmVp!Lh&TD!_BYL*Lf#~Ul+||tc!pg2H8v>E1fg7eG7Ef z^+9+_LDK&x(ZD7jREUwP$#?{MXj_sNU6E(4Y!oU>)pOxMsz7NpR{d zjNa*cQ90lUO7&^Wa{SMZ5YF=%ocaO&<;|XaQw81?b5HlzjcCu-PUPz!4`%G%;riWG zq-n%$Pb*9kb6Ou_*1l|MfzEGyHvJmPtxq$alobi5(6BK07$f;;7k`hnv6{>7F3Juz#+;; z`^1JL@aEW8t*_*sZJwDbTf{N!X%UMs77Vk_TC!M=XfMod5)?=BC{%Lnq0D7W9IX)U z2}>crFSoQ`bK3IHtew)u2a0`=oCOq8t%XT*{l%Q#nMB>GemututP7>E`(_~ImnjJU znrQ0N1Lt8COQ7v+grFGlnn=P8JlRPpsNuk-Yb{6?RdW<1pxw&o>*%3>M>-p$}GO^FUDeJ`{SvHkfL|4>SaFCL<8Zt^L08QX@nj0FapNB-ldK77x*q z-r_;xLFm2&0i%4(yI6%Mk!2403vtXoE6gcL=zf8<9Y=*Qe2tr_dydwQ)v`PjPg^y4 z;jN)Yrx{7G^e}m8JSuW$SddJ?X1gxhvfupf8{dVov**EQ;*n=B_k^jnebT4IB<=Tk zHBVFww#%L=rS!|)pC|FyTwRUFWr;rn?0{iA%sN8oH^KAV?f%{Nfff>tf}gxT4O;!BhBk{`--V9)o8xqk&{)AQxm7;3yJQn(_e0{=S(UPzqGu! z8)|zaU&@ghYRm*bnb0fsc6#x%ZWWB|9+|Zf;`-`Be<|3jNpBXjE?7uE*rKUxpv#-G zEy-ln<2IVh0#$O9tn`!6;r87RXM!8phFbTTjf%l~i)D^$$Xy<>ICUPmQKf^AHV)1H ztSi;7i)36R2c|vhbil)@f1O(za^`Jo_UOS*4w_0eYly z&qJ7KEV)={Vcan-B$8~EoARgr8{?@AX1~qVO)!FhVoJ;gOVgNS)NTttEjQ=;Y$@ft zl|_yKmZqG|r`Yw#b1M0(N&yUxL_gTUTpFh?U1LPUU-#P0^}2QIu4=}yiIBu7toNqr zHv|IHA4k+GkSTehN>RlJ78}C&K|~Y0%!^il)3VY57*KbI#*9I`0CVf#d|Nql^#D7B zeQeXD1`~n&G(^^2UB5x@VmJ@{nf{{}Nel;|3lB7@L-BM5c$)dDpO3DfRvoxT4yBv< z)xkHf7DT|PCr{I0t1C~3W435I=trvBd#njq137uAuulvyYJ*Blm9L8ptx>Esi@-R% zHk5eLUcEf8Ye3piUq2wosMu^nm%$8lw4CmV`F6@YbUsC+;L6a{Gs}- zZ((Y$abqJf$O-~?RRm1;==leqb&J;`6v+Kw@zIF=oY{iKYd!3Bd?Q&?M$F%R7$yh* zF;bz+DDaDlUI4B31D}^zQ63~bb|XQpFPa9-0m3n16GSIqQzf~BPwD`I_CEH53>7q` zGDs2hYD)m3*){rmq$F=dV%GYAy^M1c>-r=!184rdVg?h)uYJAoLG35m(wcQjUW0L~ z@e1KI^p&VP{HYSjMbnt9ePV#^<)aj&`D~zlYZG2t_ReVXotmr2@(dOiaJVgwVPCCJ zN90oobGxwnw~w#9dOjk3-RIqdf7-|_(nwW*BvgdLazrqNwbk$YHlxR{W!>6 zxb7Mx8ADUJ(`)Y4KwT1N7Q5W{?N4`Jr<@X})0tmsRP51`V?cNAk`{;(Wue8m(@)<08W2LZ9a^RYB4 zed;J6wUp66-()%`A{n=L(r>MuhNYPqp6(JcB6b=jCVsrha{XXj=?LcsL|UizXv<+k zJyEZx3gws>?zo>u9Oej4r)n?uIfiZNe?e60W=(yLFbp5hOZ`}O+y1AChWsXxlFizV z-?UMo=oK9B0{8|rW+7t+Q-g75iogn`=8^+>%>cTRKB%u=iZp|AMPP>SbI4l=8okep~GjU zHCGODt@BfEv|ZbqZ=FK! zM_`AAFMn~Gbeo>_em=)QmGDoApeCyZ>gR+|vmmh?Wp7VOtf-_o*GctBh3!wMz3DtZ z--B04&GbG(4E!25g;Z%9IRe3HlG3u0Z?3`2)idhTP;VdvKV+9M6+h_Z>ksDWb69r0 zol;0902C(va1tI7K$KzXW=Lr)%gW>+^;y-3spfN++P>M2%Kx)+;Qu{Lzl{XR2cDZL z{dp+kahj{3@>^zogw|91K;&dCiFvzujd%T4RZ{rqhWIE+VVr`8;|7< zEtwj1-)Cm(HScp{liN7%AAg>|;p$9xwKJ`_JR1M}ZC&H(X_9_phLeY0_k)pv?W~xH zaYcKMsdV>id{5}KQ_u!u^ZLD{X?!}n4zhc@o^$(xym{0hallltMGoaOK=}o8QrU1(z2yE_<=lpBq)MWInIY9An3 ziddw{g(*-J>Jiqy_Ds^`&L}L?zULJ+7E{q3O2s015Pme-Cuzj`b35$%iWdGBBv4^2 zu?zb=&Z4h8cMg?lq%d$ zi|$Ko9UjH4*9_}fYp`Lw$0%AzeSWa9nxmWZNEVMMvi3bmOyn}VjZ?*~*sVYJuD*Ub z73kWEhi8e7WsBshKsYh&kDEv1v%(_UXc(H53`9Ep^KNx3qHHu%H6iuwx3_T~)!SAR zpG|7qPb!YAU+v*dTN%HLvK@y7Zh<+05veQ|cN`;?uy^k9M{i|<2eTt(kM;kV2Jep0 z30pJ}g+{35#hgi4<9>hr{Ws86lu)qJK_>LX*R1hm84qTz&f#BbT`k5NR~tHaz#Gu*z&>JE8nA-eCxr=<_;1nAMN>fs#DP|=ZIT- zdf@w7xgIn15>d0?wG4CP=suVWpGAxnyDpQeZ2i9dQI)L7zh3OC{>+GiVfm=SOTZ^x zLSHfM?TcllYj)qrHF!^tAB5;x?gy3AGD|LO+*H;AW*Ce zs})lA;~v6@T*wP~FY-1~9$k9fxiO82dzvYMksuzd*o^zlJVd?L?Uc!HU!^98*QJ-4 z%LcXq${Z9Mf(yafi3Ng3dQwa^Ls07}Y3?*d3-);)`_W5H=Ah=Ywi#{RlE4#p)~Ak^ z%aanlLJw!`HkdX`C~Eq-kr}-Er~L)#%GOPa;-HP(Kp1+`jK}Iyjj_sIpfLRBV0mIa z;GlalCef#e#nuVwWA#lTFzdAJmUh%w&QtWS-;(sbX8e&t6_`(39GYVq@j;R7Z|Ku| zP#(QwbRL3ScY6yKFNRwH^j{rmI>1|uMwiEzx zNn`eQ=^eBamTB^*0&X~PQMPx0?rF+Er#fE=;P_LyIXOTV$Z+cuVU!>g0ui_R1~^Bt z1sV3JU=X~PWCwNDeF?mXA<_OLLY^q^)U;A&xx()B`VY1`i1kPkHrC>wDo(I&->2*Fo znnXY%lH(FG2)Mg3ZS& zTux#0jroAueD5riysWnz7Tsp4Lpgv z|0k9HJo@KD_3#PA-q)MoEH^>3m9B|?8*?{z^6ukj@y9`%8S3+NvDw4_UQPHz?w54A zM&~j>&vq$lm2sr;L~@{oz)sv3Rdv;ba)1H`QDpIzW&3pVP(YUK3o#6J6iuCg(ZKHF zCCTDF=;fqbp}O}L(I-o^SL+pE_M?>g(IQPub7Da=f4jPGRYq~FeAS@pz4dwYRn67x zu^x*HG;027CKQ*&E{pl8LmZ^AD^Rm|YA)0OX8&<-7_AHxsJLEETD;Cd50KHUTlSr2 z$pG!*Bq!}^^)W<8AVr&pPq#c8!{dLV&Axah*eMvM37DDWv*yWO-`1yZS$v?$-s|%5 z2$F!l@=_MwcuesuAoCzEAnc*mU)yFj@a9@}qRT04J2H~e-oZhPnv|7{%H#bMED!?^ z0jQn1C5E(o8%a4=zG(``RD%yV&D7kw24g{=z;9Pd=lAe1=Tx(=S4k`0VF|FZOg6)) z$&<%q$O3q5M{iOl)1*vZeu6B!;F)lvw`rCj7xv|e*;&UfL2>|x>nE}0XYjC7%dSYq zs;+Zf>Y1q6?%`F>RTtTP59`E-1%e}nhK7#Gc5Bp9v`O5fge8^y{5>+?*ZHo~i_=!X zrdEAKYg`AMyU6P)_g2A=V83=fPGMmk;dJewXNPX2`nbPnI5uCP_Nz|X6s|z7GdM>y z5Z9TrzugqS;VZhIW7ymQjNE?k3vr5_#3PGmr40+P=7B6N8SmZP=GPvdye1>4UTWYv zaVxNO;PEGQGeIg-No{mjDYSMqDd_4;-`6~|n<>$WH@aahS<8&KU2gAxAb6}NkS-o> zNpjKBmVa%eSE&j$IQ!USJ!eENJFC<9@J+9WsU1rZ8C5J^#(qV&cz~R64TcV4Q==RH zk*#;QFsblzFMTB`k=bak?c3n0QV>|T02};0Y%b%r`jNTWZH%?|nMaD1Z=2^-y;}jk zn}f7ZyVAG@7|GkOZZfGYZ{%sL>5=)7eQt>{qY6D%?cd=UEX!hind#^-$(54e9P{~~ zTy$KUWRnhMBvtR!eC5dK*FfIP(JFt9Mt5@e$=iz?&*uLd`$;MvgD6!LxY;6Wj6f}^F5PS*lJ#-brjb|d-fw*R?-oaZvANvGJ9$T7dw-Cns*ZOFJx@C%jeW;{70zd z&ctW*;G(%`RW9}scHdVR*U!Ln8lecEzNzWOc}0yLc0A>!<{&6gcwKfmv-V^7wy+Gv zEp-iUHxxT>m71A-cccVMmp!wm-`0ep&7isB!TF7P7~YY3$49NLPiK5&Cyc+B=)Qvd zWV+u@CI_aos5OKtYgImC{@*SD?+e`}CKMYr+rd3ozaG$AOYKzIc+rDsA9W$RMOnP| z1y}-5Pf34h%Px^WZ5ueGBN58+`{y0sE$Ogl1f^|GoC9cu2;8l=EQjgb8oXCepuN&q zl29>7p_;%3=$o1%@%1bvZY>l3n~q*kawgC*iX=nse~IEa5s#S`pUUGAT@b3PP!BauRSf-?fRbTEAqM;ayjx9Kf}f(9?S6_bTA8f z5@19Y1v0rYj}qvKu0VL+6jv4bt1i;{h@GVD!C4OHuCT|&nmr~2sftBZ=nBoR9+?+c zeNL%pqbyy>Qfs>2oh(=R8xj1Jvw-@vV#y27lxUhAew~23#-ovZ0;JVhgr_-#^U$F0 zM~Sj6ysB!PGK36wU}~r~JQ%XJ#-qcQt{%jK2hQl7L-z1C&=c>aLO!y~)kFbz$_*sG z<0OR)`N@58;@B+!yYW1Q3HgYCu9FC+jA~O3+Wt`ACT}rVKln@8^!eX1C~T;AAM@!V zCa0}4)i&5HV@DyAlT`>NG{JSuGD(Yy+Xf0JN%FLLL^UTgM^5c|XMD3$pVyqzu&CkR z&(J(kNcThBLPBTjZ3;F6zO2VJjs1ziVmCuuTdF-M-JhTv)}8Pu^vkUpdj04Ya^B|1 z(8uxu35jttBNLR|r|2^yrLeAJi%M_;I&r32D*;V(02(M2`GR)s7VSZbBDR7~S z1N)mK*03XkWSO1$IIiPNb;#?`k|z$%U$Io!zK|9&A~n;UR=XMl%gyjFZvoj;n5Z`< z$V{vH9X#)mlK4bkMwi%B=7MhU;_tg*0-fipQkJbH>*IWqmRiVBtIV zGs-x|X5FH(W{1_YrV z&HthXI9~qX0i2U*h$v@cK)z=R{f?0XfaGwdYXQ-< zIi%OhsoXt&l5gfnATI&RDK{3v+)v*nC2X3*qZ&rC(0x;%P0nC3BU-;)7StQ$KTYwT z^cCAb8HnM%M`OFYyERn}4wzY>DnTdsEkU~VTc8SPebv{BfRuwWY9i}psY8RpF+PL$ zccV2*dDq%QwaHo2E`#`ewAx1maVuVYe$*Y!E~$O+$iS?0Tb!Q6SgfCOjl;K9FiFqY z%&{^=tY}{O$B`caZ{8NJZ`Hec@*G z?%rT9l9k{+M;z}iv;9@D82t6gE-}|gDrP5P zurYq+L~N^@*_LYba_G_X&I~G}6#28thU4#Qt^UO|lMy={3L;PWs%m(W0O&i0hxi58 zcjIf9xP|L9&pkMJ>N?3vsm5QI%DW5SpcQg+oY5C}RU3GJ-+P8wL{6Ef3{KjQvgS{M z4D0-hbJUsuZ?b>;O7YyJo-4biw@yNl6fQr1OKicxrh4Pbz?gPTR+oF;2s8N6gIsb} zCf~uF{*MA*$<5v^3oXh~Rdwl(BfVePw8<+}G)XuON#YxM0?FglO<*1baE*lFP8CjT zlw?7r0PoB5yD9!{r!3X5j0)D}pIhvfsdGaS4Ypr7z31o_ZVK%KD{)|X!l95**@Qa6 zbI>X111R9f=zO10t7G~e-duGsy~TIxmVMML$GXhp)Pu5Y_x*#9Wl z5f@I4EWwA`6E1)H+_6gDzRUM2sSAUGI=ge&2vS*f5&3@k7)uo(f;#=V)^L3ss`mMHr0LwC#|s`77nmHah!l*rVr z{|ZBd>b4kSp9lh11yH#D>?iigrMf^wH`ujhsYM=__FT2ZhW^=^uI|IG`K&h(Z?!M8 z+mC(s2w!tNCf$s9XONiY5Kzp3*~{t-C0~?tJ$YZ{S=-w&A3id?z&58c2eMNKKsokt z(pD8d`HVoKt&IJ6OEywz&u-+5_{&%i#6#P|^`!iWjODqA6(8m%g2apI=jK#Fie z6y}%2*SzwYVMpQp2%LY%Oqc3e`-TUDdZDDAvscld)eqC8m~OXmR3t2B-Dkj{RdURU zT3&d*7N__-3H4fCULN$M0xw!;1JFbs`RV+{DAW6>G+4&G^RyMa(5bV2&G#l*!#^M) zKz#KU)Z6;klSVG0t})KqaXC>*3*iT*l8oSKm&_`d1kK(5TyLra3*yeWZt&ZZQhLLS z3fucF5_Ipx^IO;E{KL)^Qu(b)I^XGRq55?}X3OU|W}Dt+22FL@n1rYJmzx?V_Ppy@ ziof@5vy;k|ua@iz6SAC4vZ^)){SSv9yD&&i9Ni9C7g5L(P1LO01*fA+77u7Yb@%*Y<$Cri~JZ^dFGhUM(||5+WY zL8q(mgotzcf2OPk$5YHA#kIL9bhOe+H!D8h-yY}0+=sK3S!6{O4{pC}2Iv!{ySqeJ zY(@2=9lgIf^AgzR83T1Dbl>S*-^F|%{j@K1fxh>SYZwR<>IgHzk6nIN^+v3M zO>-u7F5dw{i#!QUgj=7T08yYli$0>JPT=#o-bJN~jg(sAIMqg&dB;1U*#3F&>M)Q& zkVX7hzP#0yaENh;W}~E_po{f~V=)rqw^dZIa7*rG%+j z4=e^B%lz{1l_b6f939Ljg@Ax;GQ_ZcZ=V+$2Zr4vgQx&8!rtHJ6&2q^8QHJj{-B^k z!PYWW!c3`77Y$+>c_l8`?Si^CxXxh)FS}(W+n2v zE-?bRL5S^AotpDtjRYFPeK{+VpTB7%(M4xls==tKeq<1{;Cy=CZ|X3Z24pu>_p0hS zp3l`)7`mAp@OQ1)`pU&s${Z69m%xSF5E%CV%Ica=B(NECvuB>x39C*C6WbUW^w_`X z!k6vZDqZLuh2H!LKRR#Q&)LLJa00JcGrNogUB&-?7`Z}q`#V(8 zl=44Zd4nw^qv#i(8zir2zR2oar1!GGrF=JYbV4E-x@o}%`X;Y39x(ffX zfU+5lwXv76_cc~Fi3Q~i@3-CrcprQxb{f1rj>N;Uh%KTItIUHR`w;+5Gt&77>SlFN z6g1fkhEBB<%jERW_CGUC7Dx>2uMSmv)k>t0)}q(C{rhlqw}))9JDqbLn7%1=Qt2)w zi8lu)gm>%LMt>QIKU>iJwVBC7WxHQgFGodtFi10h?2$5f$>YL0bM((D5sP5j&>2ez z-Qfn(5+UJTNNwQw-AnjlAu^NS6pD~?+d} zM!P#Tya=!zr;ZD;aYd!$LJ+-PBS^AUcyimNYrY-xe?$Qe@?R~InO!wqO_^Pn&$SJF zk3bMWPsW9$d`D%mo=p($!OJUsXEyH5h6(VW8Jn(8u+MKp?V*vk>Wx;~5n47>$tvj2 z6%)S0E47!sE6?_y`9i~cI}1*psdDBOb)?%SiM%SB&a1!uME8u@;lyVk3;gNg((&GE zLAtMu$6FD3uA8enH!uF2D_sR1MG`M8uXQTY)>)JC+`Gf}KgufRE?}eg!_~3lv}0tm zJj<0d5^P7nLjY%|Kt@Cey=(_VW6*VRITbcM`)o!9UD10f{tq?pESl?&7(yy}rDMm) zURV26cYl`Xfo{+Up{4zD`0-CdTJFpy*4b#!%tzKjE{zHRhTw8hDSSYM$`UgRmmf1& zi1UtdzU3VR?dd{_maZ`sYC_(G$%5{fJbV#%mu6LsYyJIn(O1+z73|qs#g7cAQ}~XZ zoU{65Fe|>(T`J5&%YRD~P*dBbt#60e7cDh4!}Ad2v>o(*nS~ZX;!W+(%~DL`FpdQ9 zE-e$^bRWPMW&q!-LFF#6q>aSkX&7;KxQhcV;E!eG;>$ZpLs^QMO%k{@im`Rr0|XU3 z3a<3RV{j5cA=-K{zCq2VDFNOo8IcOeG~ME(ZD?p%iGOVx&2qI!T=Pxhgc<-{z{?sl zBjd23$=?$s6F?M)br1)T1r3q2ap6CJkN5FTfDsA{qwFXy$r0PF6kdS3Zs!S`(#eR_ z>Bjdf0rAlAUlcETb;nU?M_8&O_A8#yVYS1097IbRpILaUbsr>S?PG|L~wgF}^X~#lq4g2OsU|n`8H4+6eV}1hPlk5PTrG0uwpzI>b*1wF(xL zro94J`(KBusVBg>gvwZ?3;@(~qm~1AvIFL^XsqU$OD#6yn7}kn)*s zg5;WPXoXX|>%VKk|0!qU%taV*rXBF|&r<7E(cEhjp2gvPgBbDK45>K zU*Fu zc^_<{KuE+~e=;7n$J6XXoYx29@{d7)WwzTCg$vFMR!)QsdFZMT?tXsO9*K6}$?R%85p~x0|t#Jc@29X1yhvyqW9ds4G-Z7XZ=$xGNMS6wrqpYrIMW9`( z;9_36SK;4g_!yDw(&<3fM~f;auVwP2otBz!@5k|v&dJU41>*+a*>DE@ucL;zw)x6! z1dQSsvNXzQK~ouzQ%Q)A*B8F$?QWv*M^uoJgZfmppfD!z^~*`A z+CKy^&FNjpuEYV2XUtn*YIcD@M)9I$mbYnc@5lex-oN>kzuTnQGKzgxvn*urr(lm2igZu zqq1>VDBtl8j9PR3+iLUh)#1vjBnXY>Ck5{BE=Svf>s(wCnq+{2%1JwT|4h5IRE+OwR2HOhYx?w-X_Gq&cRusn)mOa<{-^o5OZ?_ zl*!Z)MSbW}%#=VWpE~xwo2>$@sLr9B_he#lK$hPR4{*{DvykhlqqlNq@V#7AGd9J) zWjD3$+qq}sWqg|0BsovGm}Do}HKQJ@s)wx2a@RSn<=YJ=V@mIM&WiHfz>9nR`GZIg z=FOLr6)M2#%KOa%vZb{7kOKd-iMte}V2RBdjdl7m%7Kc#GH1pMIIUT)0Z~$(FR+Xf zFJPH&qcXM9WUYt(FVfyS8t%PoAD*IxXhHNL5fRaQ8zoJNC<%gSgJ?kzZ5U-FCm~UW z=%PlCUZM_0Fhp?a0 z(Y)v!+X{}Hv7&Ky6b!(xtLSFYERBc{*NEz!sX|1jk#t%q3Xa=cNdDr<3diyJJ{1m$ z9Mth_0)bnCWmWy`V(AxwO_q&!<@2T43T_D;<;OC-x@CGx1AN{MpqG~ZdS`YyRRFbNwR;#Mpl(|>A&I<| zOjq3pq6uPD!Ki?F#fU%J=i5cq#!d@NzMJfq>E)HDdzBYs_^iG;W_ha0Azl2J)M)=? z{8x`44W&;=RC?1%+K0`%(m3k}dhZ-Dp%cA37~?#?yvT!JYnk7(^t=x^Jf1Nom~1vU zKyCtgf{8J$ga(+=2cvSs_5uG?m9gYPhZ0#Xh5xndEcFG{u*0DsV0JNP*712lbjZ7s z1t;>J!^>2b`1St6x6w!;`4o=r94U|EBG$%alUq-#Rkg|xlk@S=V$so9<0*#>$XdZ% zZUA09tcLAXr*-kXIQ7m?bG$^?hF2*f%yD4Mb&5mc@tklIKI4?2Fq)g07Ou8E0m6~{lVjlUeEgo10kJty0y?WMi* z)QkS$j)a7-BpxWXOB^Kh#?+~T&FroZ7f0*-cOgITb(c|5Ho7gP+c5TG_5+;@&8vm? zhAte~T5U_?KzKk_>6x^8ikK%8;}K+?IA}i3OV@V&qm3r7%1a#AoNE2?e&jE0l_&vb z+ku*?UfDzL?YX%7(cDVqU#_970O8!PVr4r&SKn}{H1j$Q_-sQ$!=McTTt{ASg~wS| z8=)mYF)mci66{A0WnzF>9f&)-%$sJUC{Be_g!BF?Y4JQf3ouXIOu|q0hlJ5q_`Zu| z_;p{`J9KRW;0j>%Oj=Lfp4ZqD#8}tx$VPnb`WFHAu6RpiAkqWy%$7?0^k@$Ch4DT{ z4$DEQ#&aaHg!v>OARq&vg`-4>C5e^^0}M!8yETGnzNDgWn4H7MRvjn@2xJ2uCKJAW zFvA|fT@?R?-%*22dyXJDl98LstKYdKFiZsCwt@nT3(Xo7NLfIfi=-zcL8_MqpoyhB z!!#8D+tLh4bA?{6F_33Jbs@3R6H;Ue_31~RBc_p`{AS1&gl2HLzuB}(kL?Y11@}95 zDpt$uD~BC4ryl69_m->KSsR{Cl)2;G+atQU-mLG6tSqmo!XEhwHRLS{j-nmPytMg= z7WHEE9zSq@=cPu3TQmHYunlcA*UZsJMlxC&%yVl_@`l3duGeo4^kJopVg{ycqu*{ z7=`0Hi^RJBR~ja~jCVWF>?q@GlFlsQ_uOzCAk^DQib5)@^@QxvR_m(CG(CJDTG9Ub z_UGh`%+~9Td)FliEX#)n#?b8f_z6QlxyrXO;tSke3fzK-``#yQHlhi)KesBWoqH#C z#LB6b@hmcR#L=tWEkB()YgCLd{cx)+l6=6{DcZZurk1|)$)16WI%fo!(#}t~&Edq$ zeS7D^e_!($AL|F}mjB$j!$Lj*CC*Y%tMNKe*7(!7&?^kvLbvKwXcXr8+}u?GlIX(- z?Bdjs+b^TNwjh1QU8@=K9(8#(@C^_e_~e<8d#TGh5JRa=HS*aKpw`|83Fqd&va@|V z3jBGGGWqv71$USvqpP#;h^0_~LZ2c`@uU$FK5+VDDov-~9GO`6bEr}JW$Vp6m;U|& zuk*`M1f5cC*y@u?@s844MrBr`r7xj0T5vK=v7>vOan_HQHBY#eRAns#88I1N0e=uZ zAuG2e+x{$0q>6j5nb4aQaFw0yE$e*}i&U5UtisreTSmK_rHS!s3nq`(<=F3nIi6*c6={IuxM;t4?v^#iu2r!(_^$# znb*ww@wuf6c}NJi@cFR)uGaea3Pn3bT!B9fo6V1ST;n|Y-mdGb1eYlT``qlW;XF;F z=%M8Lp)|}KY1pZKL8W4eysv$0CEKdeciZR&tXCTC?ioK3lMieQyi(Y@`G(b0YNP0f zB;|9V7fF)*lf5H6$&eMB=~Ca7smVhRw9Ia8eFC-V-5tmkqK}8F;v@bq{k`Wus`Q)4 zZ9S&mMD*^l6^l#muD94AQXIE$*wC9b$Hjg(6vo2us9{1_jFQA(~ZL}W(g%_{(%ryzMa+jG17WThhkkq?7 zNuWfnocJHEyRD`8lyqDndGIV8G^Ym(S#E;Y>iPb@JWOsLU^`)lokZsrV!@X5u^6G; zf`jkmlNf&_{PE2;H}?aF!7Dh*WlCY%xKPPZF_$2bRLND$Eui)rbH>zL&fMB;;T)Q^ z%fa6BeT5==RGrL>a~L+Wd|RixfxTEbZy;+m4}j`32|&D}-CWCoERFQoJTOvzG5EUe z4;DZu38(j0cbIX=M+xhzA;j=V5l|tE&itORM-IfYlj1f?9xE%B!h~qnFpDb_6-XJf z8N9JslkZ#4KkolaMV)m(oK@O+4ja1C&R67C^=X)xG*zN43gC-uIeG^^qBx?p4Hh~f z5WwEUYr&;I#<`H(#mruVH#lIFMoOZl-zlp|0Ehjt^@DXnlA-TvFfbyF@Tw@|#q!L- zuRflsyDRTxfUUs9_pOQ+Al$ITu<=}x{#3Df6B$8j7gqk03AhTPN~hkm%f3>cUNru? zwyP1k*fs*d^-|=}u_Nc=I&x_Yz~V$<3%|N(dja7ncrWt9U}G)UNlOj5w4b!dfeLm# z?z+Jah1t>Dcc^_W4C|bpzR7|;^#~cw2tCtq3!_&`gX{UxzGBwA6^tDn!38b zsC=39;Jp<`1j$2VLJzOz$5gBDQQOS^Mr%V-7`sBu#i8Y;oB-M;4PlA49euPi(X!-y zsS?KF;uQSF(@E14>K%A^9?CwQb9l32IKTQfZEVqCmLgiT(9l8n+xL6x$()o`Ht1UF z(H?Bwc)vc!$*Tk@`{W+aKJ=rN)Tc*>TXoY*%KrQAXF=zdoo@|Sayya}3SYQKKZqfi zcj}$qMQ%PJHAL6sjyz&h4emKn?}<2RZ!+7_cU9bO2HSN^2~cSZCOIkQt^1?zBleQJ z3S0F#E?$AwiOLgQPZIZ|#8PI5cZoZU+$nt-)c@YdHT8&{Uu{!J)d`*-OrO7R`Z!Y} z0)=AbaW|K8*@eQA^bLo-Jes2hu8$jn?M|~N5({U2Quf+K`-v3C+kRE$?m|lu7Zgo* zg+6^--<-Jc6rXlqS7J?MFMt0P%G1$v5&h}g>*YiIj3sAa`6Em?ZQPm*ROPyt``Gtf z5~Jwp%7yxUdByF{{c6R!t{y--d&@Hznp`~PMOdZYuh>8RQowU!073wksS)pJ~zI zayjOiu8L|E4= zWkT3$dm!yiw@=}$26zK5eVM3ak zZ@}bMXaa{Oh~O18gC%pDeC#OXu=iNBskIOQ8+b4q$$oG}wtdKLc>O~~PHoU0>9XbJ z{_2!dGH`qr7;%DpU#@oQk|wqWhW%vmT~RmtX0DDWT?>mU(0<4^&-nlc(%C7<1})XFG6hk;G<=1ICFq>9xoe@$tgNl(_sXuvQ`4zocLgP%M6|Uq3mnWicwx1oUGa8uU*`Pq43u;Ja?R85F znN%;fyS1$H^&4iVSXtUZuA1KJF?wTKV|^eowMiG4qH5&HOkZV>s9C@WP)V8+Hfp(z ze(Jbpdc$jSFYH{dU%qxz&1`z-pxk|W$jzjGo7uZ%z7 zAr|F{mB=nqvq`o#i|Xyf`Ydm!xm_lo4meUzEJB#Q1f&RG>4;~S+f+64K2o(|RGdOTcMEt%A?e&8UnNDyHkTcT8SgOt@VzFP*+HE`kfEjvRoa(*b`G@o4yO>yjAf~bkIbYQ-MCRy`NeOzRH91w`-9r2rY}bzxEXbSOi`C@Dw%N!?UUpS7KS7GVYp6&!8piL1dkmK zt)v8_MR^vR9wl^C-Q*D4vzP!xVm@cYQv~?MsTj+6?rWuR%#~Mk_QiPZtr|}t>Z1>R z0@>?k=w1TPh{}l!i0516{l(w&RQdJZEE;aEN76PP zhbAnvj|dZT@e}M}sc^s%-8VxYY^hjso3>YcABC=nt80P)WEVaSf3l>8llC&6rWJ%v z`QRQ5F;fCL(lv$xqH%&DXm1kbp7Co;GiWT33%pPaIL zyy(~KKI7FQ#%Gx^MUZ9qYKQu1X`JsrsuKIOsmsMLX4iF45X!KNB077*J)@{~Y$%I$ zZe}4auRV3yE3EM}lZ79pnxS`@dqR*%H0Wf82cn6B9tR z58P|`u0B-L+ETGZjoDh~w#n|$H%q2smz$sB^3sz?ra`UCeXd{|nH}4HY!B6dK;H=O zys|YtMLQl!1_FVMu>z(sf+jAx2wfs!<63wDI85Uje`2L@fmw}$2=+Yi@N_NRlVfB9 z$s@c_|9}KGzE&0>;ni}Ggqn`ugs+FW9i?7za5et^GWzg6Pe;ddlf`6yuX9fw*i;B> z&*!BSFTWa}Ukg;he)db1t0}S5&|QtgqoCm@7!7f+<)4B5!;tfYExWOnviWNDZjIOi zG{1@O(61G_=RPVg{1Q@IZ+AuW{9PG<$t?ib@D)|fw5)7ucpl3zbU)wjyn`zlV+SW}%rv3C!&a&&*HMM@|me4chfjpWs{wy}6-5HP! zqNa^Er2V#7Dq(?plimRDXpa$R6uT9X#U^jMeBrimoPcbKk}VQWv>HxgD^~v#8FjTV zg1Y>Jz=p8ot_2%#wok}`)D%#1utimEr?vOOeszF!<@wmMkj>|1{os`X zwbm-j_3}pw?tLHq+R_u548o856);>7krJ+`wMM3N$K8^pR}>@7a0|ado(6&9>)0e_ zc+}M+wV#RL8u^W0<>t-+kE#*E@}-b3@^n*j%&dD9j$@|^B@Wp zAbE33a*F#M%6D<<*Wwj4P?@}nI_EBuCzUrfvk%El&`b~~4KcW{_v9Vkj>6VaJe3^f zMra{bHwZEwQP5nyvpzn3QPE_&^U4w+t|W>R!%`fzJWANX-JoWQz_()zEb2qvi#!6@ z&Vl$d9FfU!a_2Zb+TQRueRA-)<3v@TSv5iJrbw>U5+KB4Xlu@N1=Jtx#PHF*e}CezQR! zOkS;13~ZwCGDjm%x*=BRI{J}{ku+6=LsM)*#cH~I!19#=)i z1Hs9N#p;zB_uPNP}Z$%}csn@_{X*SDOL zeK?+nhi;ngN%%Kx%gSwcKm1 z?!sx{Ow0unMy0@V7H6h2x%=3@Y{{Y7ah+d$++%k=#mChv%CIvn_k5{w&D*r1BR=N# z(-HUG^sh_v>v!`a#Ts*0a~$(+$U@Us2I4&R4-AJl>WV0owR}#$9A=?>XCJI6o-MB_ zOU1mWXLuJ?UbdXiE; zejRzi#NnfF+!P#~ZC*WcV4z zBFewM4*6cITwx>d*2ywV1cJZnn)!O28v9=0j2icCN0zFZ*9kMZz)z3#tNkPz0}g@{K^fqhS!x~(A5;Kzd;mru@yL{7 z-~>pQAB-6iMZk*F=&|dvH%MX(-g8kw1__m?z5G^~n^Z{b?bfX~d39`1=;y1VR4{HNuL-Kq{-;eU$ z#?V){f4Q?KBZv-bl4A$z$@U{D>XMg^<&U2lEP0H(m1Zk{ zlV!wBZ-2SZv02iAF7ejNYPeIIcCXV9N7`V9v5fs%!;AuL@a)dn>A7eAzJLKGVBD=a zuB(ebrx>aO*Ep#;Ljm;G=yyxNXka*Vg9T(rl!h!vIknIQLgM|h$Qz&3IW7!oGOuv> zb*?sE&d{(`Ero4?#a}H0P5}pKEt?pH=ag1EM#ux#adNQaD%*5kSW}!`j{)@-*p@0z zXe?WBkFc4eXM%~^6m$g>-N_+0Mg~C1TERlnxU52g6?#b>?Zoy4)*dBXF!OoSi7Bs# z?!2k%P2&|-SYL_UVHR6BKVj6bcf}j`k&NL@okd2<6gpE%1{7RTXT5LF17b?F2)|-J zOmF)RAM(Z3F-ILT*2{QuKw*1tdiibly2Roq(-N&-fSll>(ih-BzvQ|3yP*uyD*zd! zboMwz(fXX3B9e-!o@FFh^K{WL4SIBsZV0&)Vgq3B_t6>tYQN9zjA1`33d zWCN85L0-i7(9^&2fj-8loHF&p(xVd$>&=aWA6giM6~qpw22dP>8JAPyh&f628K;(a zIbM`aP_>aS(;i*hj;L=3(2&d%!?8ZqOWzxmj54k$-4Eg6EJof6=#8T|Ajk_zI*j04 zkQP>ERxDLvI|H{XLIlt|eL}v@X(D?cFMu2E6rlm5wtP_an3`x`%}j9!36MCR%any# z>=am(T$g>RL_fY;M8iU=GEp^%ouDdLpzs|@JjLYOwZ0zu)eiUO5OWbwG%c0VbAhv+*^u%v6SN{ zDRjMDa~TRRO0cZ!GBeC!Hu+|?XPwZw`=@ny=;^4BT?Ntg)=0sbatZt?roDt!-9X&w zX^q=L=86>~n%~j1T4qCPI0C(Ry!{kWQ0es|(dx2k?Pl>!#v%^|*O_?JrBA=~GhAbm zBjNA&i_MRJEUdg;V7#T&mPrvyz$c(RW=ZGs=HS$g=<$6G4Bai78%1NLb++y`ZpWDy zW6P8_4os`jJ(wi4=!x90pN*{ZKC*!Y(UT^>f{6etEHy1^Mpk!!H&cb@8zWA}-1}$h~m=x*4mLdQ!>ZWOkV*B5McyzO9vqHIDNo-?r^#g8kBW zs)ui0+e^X2G9br(S~7MQ3xrX{>Lp!C_XMH<*<-G0{gmpY!u-uIx4~4NJU8jP=3gG$ zvC}UkZF)Q04U=L=5juJJto{42b%=Bs$zr>_Y)jQjO0a0ED%59e+D4D;1-jrRP< zH@5E=VPh9N!!NeV6!Pj;3KtyD_OAYLb3#=-SNe?o{v;lNBSVsAkPWsFy9HKCD{)&O zc`;%aBP)!y?}lmSZlFAX9e|`9>Q3TfVw?n-Ht{;++{|Somg(aP*eL)IF~duH^hwfA z5%bVO@FWz#Msn)|kNVgg^`jF`#Z=8j%^>MZbLsJ=9^>}oUY-qbaGI783>_ke*6sHX zk-r4~tZwg`{hY*$4sU`XzmfpO4UrQSActR~U_k{Fu(|?3cQesQCM(e1-j! zB`_LpGxeT|PpR4Gm|=<{))wL|3iVp8t{A8-qoaj??1^@YJ206@x0%F9Ng1iVI3%<^ zk$PpUmjrjqZr8GC7riI;M@dUhgr8#qxY@1OPtbM8br6hFK0uI-&{BsRm#y0z7XzzY zmI9Ji`p!{VCZrz6c`-oh03bEH%)I>-rfRo<^Tbl7BbuX~5aPv!Ur03fUe_MK;z;0C zj33+FgZ{O1>zv;e1)9{cVa6>TR%c+^He=h1BywB&<&{9wkNJ2`9`A|!> z`B?67>j?n0&uR(4Kh#-lLdOXejd!tm`AVO&Lw$N@ zx`|1k{brNd=WmTWzr#xGH!nFQmv349ZM~1Ak833N*|AU2+Vb8S@2^nV>_Fa3O+Tk| zCz$nR!m75V6mGdVJxO9gw5$cYC1A1`xi~%DQCd|w(s7#=-kBs`1amhX)*aE)P2z|u zPKzB6WE>4lsihrYI?g~X#63|A&f9dYmr*I%n^vXckb8w68L8eYnN`T{O8XRTsS|sU zg&H%OA!n^T#D`^b93K{GO4S`peaVvL+LiX7dltt=JVhWFXMb_3mtnbNP)8?y5n)Sz zs_`d=D11)X7=A5zw`vSDeGl|<%|^ut5N6y&G$ys#R8%Txb{}RPk|#{V4R$P6E@lpC zh0|Y_O4*w2KVTOGZGb2G6c`UN9w7nvQ{TgOfpY(^#EveZ+`?Lxh%=U~3-}3J7Kejj z;8W@Nq1$~P{D_u@xG`B;l3#7oT+@o{H|i~2v|#J{DKDDM7Zg8VROPgiF0K0qJy zbdC}UGp3_OW+jY)yFq6l5QQ^MD!Bm#Spn~ME4hCg&>dLdqS5U0<>Xa61yez;!_8el zPZhH3j9LuxNsau1w8Tzrf@mpv^24SQoK@;Ie{fB|`Ea!1L@}d*nIH`Pkt{?Jf4@kj z_oHn?0VV!UG9$taJZ;T!JUDl^wLJmMy31dDeO+wvVT~DC9q?K;mIa<%h2-*jzJr!2 zgpFShcS-X)TwswX?%FuS3Ihq0SWUAz#seg~*QA&*?jF~$qfPd}Qp#pH7jF6?v69-@ z!d>t==vGWG7XbH}cdz3lY|AR8)CEk9Fvv9&f5lO`%q zCc9Gqp1o{JcYro7EyyD$Mc_;Onxt@3VJUY?&Ae9yH&n8?MDlA$=Ur#)wa~$pwjU%$ zn}~itEy#^_fny!TJ-U?*sk0iJwFl%+GC=2A6g;Io-?{&F^&C?}KlG$urFe zyy%lB4}+=!oS`Z?Y+jlAy8QxmMss$eb;;{}X-SuN)!3n)m|Wk)q}VKh>(WX=HI=BZudws>pG@ zIqI9c>3DfvAu~XQ%8~SnZzsA<;mFhOM_bQbF~&dcc{;e)$x3>(6t6-{)BU0gw*(?i z3-2oHj=K6CCJbM=d3V^qE=?eR2AgG|*_Yl)`1a^e!oVNAG~t%w()WzD#Fu)FouPD- z9+Mxh(R~Hqj z#nP~C2hVQ0NV0-J`%?C+M0R~#jn!v!nnKGEgju@GhS`+vSSI9%YzG>`4}$eM37f9o zZ}JvXdb5>p-P=eI-|S+|D7fd~J0bx z!|uafa@kJ1yoFzH*cjw{6yC6PEn?YWA(G$fi}yCUrnhpod3c^Z{bc&` ztG;s{C-Ln^zGRK-Y@_$OUA0ha!$nRxY58=S$mI7s zd>bsuu-+0jiQa{eS9vV!dD466a&u2U{|B1Of1T4O1c4%-nVtcmiG5ioa^Y@s<-P5= zH0;%mU&!=#VFDOSKsWgl1N7BJ%ufF)DrP44BrO@hLV5{Y$gsct8LymLZg&3Lf zp*^YB7aRMfOa2WEcU1#^>L~Tj&D-V0{Po_S#DSE80Vw$Ei8{Flq3F@rVV${Z+Em)2 zr1{2xnP0T3yF0CA0#?W{tjnlt$i>=Htpq52LYqUxchP4*3oh z;{P>O1+CIuN&0U1MUiRUSaP+m9RZoSNZ48V8j0oo-(2zUa~P1DgE{rTWM8?wd^ep7 z6%QjcAUQJ-p5m#!Ggts*XMNc&?os%4=>-FZV+)J_B78OMw)L4Hp zNKQm%Ax~%nClkHO21uv&>Ae;8~Aky)e)p8RtF^f65Yx8 zal+HY7wq#AX9a@LuTMU!3%YED$*oUrV1kh0XN|J7$q=va62nl$(y1LjT%eM4;Vp%r zs{K&D3*e1+PR7&6f6q7h7r+dA#p^tv;>UYV&0dOrx#zIPx9e$r4LL9DF`;mK{zjGdwk37N zqC%1|B9HlK!WcdPHKEf}MW`2r3+Sbn5?%W#Ot;^piu7`bnER+t_qiY zBgE^IL6u7^`={Q9xAf9a$;rH-Pao%T{J+mh8&DJ)%(z5?LmyGq**^7aktnLe&RNb| zQrsd8G65hTtK0MmAl=gdE4-fvT7FTn`{)7xMf;bd(QOqcFAX2+S5NLpiH6kFDLg}=@1}@XP^m0dtepbbgW=~gOU&CxhMu>6#1YKrqb|V%f7CVd zRr{~=%#;qAsiWPF5I1F`W>dv`Pg89(^>QspXMvrGLPFFrb4)Gz-+9xOkU=`uN-RPD zkyQEbgYcW&NJc!ho6OpOS#_Y*N(m*QUx7ftFWrPo-*RwK)0Ap1l_pq*_$aH@(A|?u z_QCb35T%eg#yJ5fb)sXf$URinfUO?ba<6$*#I0iZ#7Ou z#_XZTVQ#&g>d=WBS5tm@ltZ9?BB6 zoXkdn3ZoJYLsQqX*d~6&r0)e?%urmjX<)lJ5fOf*m!;z8l%=QOWvaPu%CzUph+(p# zl6?-a97I8jjFiF_zXwRB-%g6H{?0Bu*WLrMTc+DriH2(jNj_t4Lq5 zU5BqD!`X3tW}cLsSAyF7uggUmWcrd+=iFwtx9;29c=wmj8I11e{7|kFM0p$|@s%vD zSM7&`SuB|I^8&|U8KQqAGG|t_r_3H%qSM;=h;Lt#&7xe~RCOJ`N*?}N%O?ohkr$>zimMN-<1YT_OA0bu5tfCk^j>g&klJK z@oq_{P<+Cw49Sh>rmU?L&=~FX$}5RX;psf5V3Y~^>o18l!4cZ7T8Fkf52kIMj#C+;)=gs}IDwCAeV>|Pi& z_u(5ZHe)jmCy&g{=Af^99|uI{AyZo;^BTPWMOyqPLGZtGt_L&;$}2wo$X3Q|0V64^ ztuPX~m}ItbWUHnE#vJhZ5sP4-yiCHaD5*)KWCm+k#4eI9*zIL?@qEf%OgcDRUdeJ^ zMmj#NU9hdBwaF3_)-2@J*fD_cTLH6&nrQo~{{Ozd#{qSS!z}%&?S8ju@D~*TDl{1q zKy`jFL>Zp$MTp6Y3Gxrq8`=}3H|+x5z}rlcrmK<}{Mt*pAPA*yCj^3!m$ohnZ$+cG zR}Fxuy5sVl{xu*(G0uH3n?CM7Apj-b4^B(_%ZJ3hB4;ZTDm6|gzChk7>RZx0Q(<@==)+F@*Pl ztYZ4UF3Am<;#+bmrkjUN0F=9x)3?h&FD(ZOlSk3N`6&2Vf|QU|<-tyC^I+0f<=g-K)@jJk znl>)Q>NX1$lf#(V7$NMgoy_J+VUx)8rSUWiO39PFJc^vYHJ43WuSB&mUqI+YG;Ls5B_{9R%1k)6&t0@LG z(^omo_Q}}eKpbdgrCjcqtt5PUoq^HSRZ{(-Ys?U;_B+xXwNxNHAIo`Kx2=5S z5v?L4QUkn_Jzb9)D(G<5LH@pFIe)s%2t!wT~`(eo6RZ5P3Dl>UEeJpJq9Y0TPuv_WncVNe5n^;fSO#ds;+b)VUP z(R{A?{rW!3*g09L$aqMbLIB3{1Dm65bgc3$fEA2n142obnM!`$IGabE2kyHT`57fg z{XWC`zt6B4C#4p7qAfA#jrY+^6vS7&38kb3cbI29V78fOg1OB6xGGZNOA6opdG=z$ z$v1C~n+o>HzVGYVU1JL+-ang8$sp(+vKQ_;4>Ax;@oaAz;8GIDSi(QD@3i=>YG`r& zFQ~+SdnPb?V!t`+sd$BxQ;yum=Iw2Rx1pJVt)kOrCXS>ba9_6g#q|5{XZ9AhKO^h7Mu&gL@m)%OamUzCB`QpGotzc!f3S3ALxj^~X(7Af9Ky zORHDxyK)2~3sMXHEyiaPnB=kE24M|IoprAap=CwhLAJ^xp2@o18Ezyo61;rodXp!k zp5Q;2B0oi2O|VO)Xb-qyKr%B)vj@Hm2ey z<2#E|oVp5SIcuwKco59cJF+&@w*{n90YdEK`g&N8aT6l5{11G41gw(ojBAnN`V7eL zZ#0Tb>xQQ@>e}U~oH~6DP*GCl9GdoC+R9ooJ-x3?s4B9e31Ke#*BiUyXSilQn$(*( zt^grHxUbLIPSkaNRlA>`*GoiVJpgywcSx@6w|P>n>PXG5B2;yC2T~)Xe;_uUf)_X{g<>=pJPFJG(zc0OAJsU#bMxZZxJ$S_*vTF3 z9~}#OioVvV5&sB3`O^e?Gce=+b4y~|K~k;oB=`8u_pAX-c~F=N5JgO9?)-3@_8kDl z9v&fC)2J-8hu%CZ0ic%h4O0~MH&GOD2X8=>lp}q3cB@^PE=vx*^bR>djJQ`f3&?yW zn9&mf=R%o{C`ABJ$O!oRTSu1z>92|$_+nRe2-x3B24<$|SYr6%lI+_BwfcbF8B5+j zuS>WXjuy=PADjgDT&N`w&o2vGlKK$xGYt$En-V5$Fn9B(D=O5Akmj1E-sRd25$&G) zwpZPfMYrI=YHDW^U%e58s(&v(bnk9hOy~g5)FX|cJKb79`ZcKf{3Y8r&gqW3UjS@B zJFO;RbG;bLjlRBV~my`)ZTD97e1CLK`91 zZQs>ARot|!uRE|)T)cm}PFGkVf*(Si#>rVy3x`ARHy;c7_w)Eh`BrqHBn{Oq#y)x5 zzLj(Y8WrZCZVy`d?KAjR{&H$ZNq%X^CbY7MD7tZVG4=5bC7ou`Sely-S|$GxXVh6# z;8)S@i%sS|)F|ihXzsf{7hHE=u_QTj!*<0|a>`QDX?D(7(o>Kyf_O{lgNDt^m+G`5PG6U+K9tBoz3`5Tk%ra@(s)*W|kUp^FY3uUAi15g!;0nXwsM z-C}uL)tUKpR;Mdu(6M8VK;*%3I`_$mT7+j z2mTC%wD)o!?)O}(nIEi1bwkT4v26$}NAK-D!1hZ)pl-_u*x6#!wu!0w$gkM;sXvY5 z%ObiIx);zh=b^V-Z}Ul&7-0s+wp7LLCo9MIg%%$w*cT%_hmeTfSpJ_c3JoHbG$1b? zruw?23(7uAsqg(DyIj`GDF~~}LOR09Gbzh$eDa=W5)!ZK%Pemzi2I0K2$$e4s6SL< zf2%u-!WA}^&$VYnknUzUVZYrgc)mUoA%w;FJPr!OFEe%-P?swq_S4MvbL%TT-9xXh zubI@oK@7IU_=XlPusnQ(ZK^5B&OL17IzIkX(;02^t}55B-1Vl#(1&CB6I;Kb-sA&= zYSEq&#|B}QWQ~1uKjftjxSJjJ>b7!5br1KZxwk1}Vb{V^)~RTh6=2>ha%Uy8EXAX6Y>0PxY8<&a8t zY{QnYJ}GX5>Zi{&+5c)lYz9h*VSX|(rh)t>qVL9nF zzz0AXvWYOUpM$IhpporIb#0!+yLY%!#N7ek2m>y!%j1nUFEXy;{vXc1JDlzIZ9j?G zQl(mA1=UuoRkLOYEyB~5wyLNt=3^HTE27k>5lYe8qt({lqefaJcC4yBY7@jJ)-QeD z@z3}7zHg83UpaEfaroSy`@YX>o!5C?pArUK8!;@q78DG45Sc0Zh`-f&Yo1xhjR*!@ z3x@|mxcJEzr!Gx#I{{TC;PbQqIyDWln1_sQIq-{RB)~2{PL|v1t1j|^snQ4y zu;V7ud15qcP-#8eR-qV(tW!JAf}}Ob04_h{qo%Wa!#C)T2LJ74z)MM3UrBEAJb1@@ zZP*2RNJ`$US+HeA1_L0yQ57`J*1WTDE`Jv8XoJQ>$4=j>p}JLXdAk*l=0VD*)&81F z&h9!n0#!E;q}u&n%C4g{?oTo6_>!C@gT?83(FttHy=vUj)}-v5UHR7aDGlFx&k$Zk z6(L)TtK`KAzE$J=?!5j4TLzn1TU-eZl#<+aJ+HfeEL!@b=f}x6(?M9$4F>2l+iqsb z?m(Iso!&OM0B6n^gv>Ru40UFOJ(bs~rKbB50vZfFP2CuLE9_bQ;Cq>6=*>p+nO7{( zZWjd)>tgE3;uh&);CmlZ)*C|&o|0VYs1t&`_*uact`{(@;+z5;G5P@Gu^GWA$p$|F z9q`uMiCqmItMc>t96drk7o6}YN`L+^0%bbMOp<&xmu{Pe2C9FY6(d z0w>1X8I|PQXFZC0Zg=Mr$o)L=o!=QcB%(92st!=yY8#F7aN|!~H-O>92H!FH|2kyd zntQFHxZRM{R5j)+RC)Zf_Tj48VkGv)D^*Z0ado3TSVKNv&n)^Pr&fPLFr%Z0p49PgnV6=vBfDY=6YRKXcX& zP7Kx?;LX8Zxe*i4Hjf8-lW7^%Xqjojw3>8kbhDR=d1F{(F6`k{0LB>OoX@(I!_toJ zpP6vY>imxh0d5QHG!OMw>Zm>k?h(!;hsN~Fxd_@R&Ka7gGnZ?w;~`wUyGm#;~}6&%X4!EUX6^%JM?yu ztRv=}wN!RwSj1taivM!SNx3SYhcK*=+KDN=(xMQY+XA?afaF_Xk|R@2xp#{C z$VD9t)I@9?5|dCSRU7z@Ow}xK4?0{EpJ78s8}Kl!+AaY{VJb0jQI|9&qfkd!oVJYE z?^yr8_x^v&{))4mB~=9#>erpL*!LgrjmU#dxmKb1n`=YoQJs~;%60NDKEA70deNZu z&Ln{VH+eP@M{luRXG~bI+M8bO?SS?_w5268<}B9b@VUOg%X9rECaf*>)+skk)pF+U zHLO;xMvmbC)Cy+P?-;a+<7s~^h`m4srvWekDB+xpRg5tjDtCb@^k9Z59j2g@r zz~FawzRJjV5u2Y`eJWHs$YjEM$w5E|SjwFxHR=4;+*@MKjTy^_n7ZOzW|acw54%HZ zzRd%oTkSrvEME-*)_jYKMG^DgsxhvC`Zd(XlUqMyq6`>iKuKMX8y9xIk3Yb7si@&Fa#^_-LamaziVpGS?Ppe9`C6eNR?h~_9_1X z>phUr#;b#!&_m*cPn=sg4z4OAP4SzX~s|8?sCnvMgneYfACz;j3CZ zE`)UoRLtpAIQa=ya$LD~{!OWKPO_+uwsYi-U#jpxCBbF&6n8lKN%wpA5|(_WmG5a5 zU8pth6B^X4t-v82&n{E(Eqh$60bslX)eflQf&X4{)&*LSKO~Iwk?;sze+A5>m?%4Mfxh%8!tiaK?yVCQ$ zFopTR6^@k)8KhU7^T$MD;n2cRGdfdyw*T*=I)~*V(pL#51lH}4A5&S_9G>LeqCWR` z+LixPJ>kEyl(-Bkm)cA@apItXr=JEpCToo$_;Dnh$1S^8E<-yvTGPr(Mn2m2`7=eF z<=w9cF3f80?8R%s^`y3~QenL0#p9lk4LYFAo5W1~*t1zI9qOpUY`-9ZOT_4GCoi=) z(VbE;@E`|w<}rZv#?&ngzd8Ii;PP#i5^@6l(JN7^2*17`fowKK6DdPt5MOn;tC(Fu z2ViLBbhoJMq$9JP_fv7rrkmH{8DP?Rnf*il@m!{AK0iRf9R!NJ(~PiH@xYj(iV$lU z-&vZ4k~APq>B*wZqL1Xy-a!kf4ccva|Netb$i;oq~QE^hO)aQF@(cucQ)Gu zR|<%;qmd5H)r8W$=oRyR2n4^86zCdqnJepi8d_&StlV6B&wrRL3vp6z8Y#0KZam8W zC9)^!RB~!~udo-iL-U^3YSm{n+1l5maJAQa@F$2S3d{AB1c8XA5l)Jar3h(XlMpWc z_Z?L8$2uahusmqGV9yON+`EgahE+-`cA+ z-8F0~fzlLju1Ytg#`|7MeeUMF_dI>hcixI1S6U;o(hfzwQrSV}!g4(hXZ|VPzD8n_ zZVt>PkZ-;I@&Nm*;dKYJZzutm?Tg9*EXH1+f@914sl3cY4E7R94&+x})~$d1m0!!n zyX&E-)YHm~N#19l!WX4nEZ@gcah^R-$=vBDW9(mJ`K%oh%^d5gfR<~Ea=I3q?UAms`wQXybtw@9tX;(TLfJxR$ zaHaw5f}`~4#HM_u8E0X)KT)=NquBcxO0Vf~_;+;F>bAQRafCQJOyB&s%l4OC-#>F{ z1JP%Xx?o4vlmoAm{A9p286cK#Jz7s5gWw?>EVv`}0;8xU^^{P__sdgx%BPzT+P`N= z<&Audd#hVOS093$TzK>~ueJN-8TPYKg`uQr4F>+z2YrEB#D$b%&}F5WwT_*w-^8zw^`acG*n_tUR?zvFRYc7<=Hv>XZ!%@-?thZD;ASdZ2?&8)} zzJoaNx-<4~Dpp=mE*E)`N)npcid&+gg+uK50bIq&Vd4)Ihe~Bo2DBQq&uEN==6Cu5 z1w#PU1hU9h<}Hl=pj$cmo$Miv4)z0s*0N;CkT5k5zo-_Fy(4d`B>eSwBYIu!Cmf4H z8~XNr6bm51@O^nNH13-E=zG+;?8^47?Br0~s}jZum9s7I@4{ z7m@=#rIT|!U;2kc6FF*ye0vfta{T?F?Ce%62-O*r&8MUe5kRDNAMh=f)K)c#Q_0l@ z=`QmMXbp}%JdNwUg0f`1E;=xcdJk|tGAmDuQ$Nlg2%yV zBsEfchB`u{Ld4f}?5pgDRK=|k35YoyFz7V8`5doM8iIL~PfdUnnS zh~rH0olEAan1q>Omc`imQhQzW$qgoz3r1<~yY2074-Rr2p%&4{j?+7XTaN2|1f#;^ zqv@I#jr)~F3jjT%^om@~e}0|@`r>v9 z?+acbvO%iDG+QY@*&s94S+`I#MJv%e%~=#|VaQh()w#D-Ejik~z^vFQ&!#64>XlZG z<+f{n<9GIpU!SMEJ+8U^T2NGtpVbHn4}w1b3EM%y;+*%V&1|I4KADQwHBW)>#q`?e^ub7V4b4n`B}|W1Nc1dWL1_EPY3R3$Pnx6y?vNk4`h@j?YrNkKx5C+ zerhixnw}x1lHL&@_w5PMb(}*|`J%od$AO}O{vbIfFjtS)ZLTBsq}YxV-9Jec|KkhH zJe!)I^mm{X1#J5w6W-QKbjwg@8FB#SXM&@AF<)%gyFE23*Fy1yX-Z}*_X}pmzUr6UVIJ5h z1et8-j$V)Ae;`)3<~wRrkujaYk5P<=M1qH($pNeqwU*ng@ZFoG}!6x(->2_4Dw`9^C0b>FD&dQcQ2Alsl zpUYLm0jzR!MU%_n+#)^=?5<#u&bqtd0RUPri9lf(lKGUJ#X->;Su8u^5! z8RVdGypaO6oVc*p&^NMX`1N$1k6)V@fRFfRi(LP@ZVuo24~Uw+$k@vCh*gexpWzWB z>#e#~U^?(LQaEz|yG7KwL#2pSpo-t8mbLUgf-8|Ev1p*GfN+W&LN%a$(~FGQHQh6CGqV$H{ntLaA(EuR&8N zf44&`n%11|MUK<%O^4R^r2!=oO8y%yT&7N=O#=(^Q?K15@e?=avr)?;IP~yDNJJdkkV*6*ea2HC01qhS%%m`S>QC>}4)x zNU8Qe30Nj5L=SQ}L^U)OTB8K4k1<38COoS{>+`n`jAXFsp@F_*4Z-&Y(y2%2xaXQ% zr^NiTwSLtKZhRrhMVil^^V~c3EreqnpXCF#^ZjP>vdg`AyVXc9^ik)pv+Mae!?MQE zJrb8Wp(o~~dI6MR?KfZn19UyH{lw}Xnbe*TjXN#7ePYMFe)9O^e|4+;D`EG)0?7aT zO9swpa*w}1{d9-l6NsI2$Uin_ckL}aQgFEMo@O*Ztb{|Mp`$yZ(+$Usm9_s~OdrsxNrac!8jSwo8PmZ`(v+P({SrTk(anI%i%LC)__^M#? z7E6O-UXP|y#ilosfLIgpoN?kiSw(;H$I1J|+GTI0xIt=T#2qSvq5dxXY9mUA{{U!g z4$K97C%b8wM4hkEMkA;ErZQru_uKK33>kg9n~o7w9@$6# z;XkBqbQ;rPHYYaMc>2uz*L0hxlVGBe96xAPC@Y0GW?@x%Q-oHbMs0m02ix+IpYEJ@ z{Ae-@PRQ1i;~EQ!)_j(i)vlCA1}Q*bmZRRb3BH>~0x%^TL`xJXo{k8L-m;tZBzlie=ShXlk=xJ1|B$>|cx(wY9hdU8MCX}7!$<%x`y$x8jG5H$N3Hkg>C zVpdLUdA`S^U)X!Zg~&b;a948}d=nwPgI9nLUQj`{y&2Q)40P)`*T0ZeQ)^y-N(I-f-2sczn`=w_&DIKzrR0ipRbb@h zuM3SSoR9TujQ0GVI&II&Oya+p>MVshD$YnXiyNswZQ%uk91Gxt1P}w?8fbaSvg4Sg zG#c(jy&7jckEJ7ni-pW*DP^)bI_Rl>WVAiQyWa+CfST3I|M2K86*yHdg_&(zOcL`D zMNR|u5nG8lq#JLLz%PJL)qo$Een2lsIsyrZx=!;{2#-!$SLxMmmWT$_UQR~?$H6q# zzPnbeuf0LObe}lQ&0LP{Y(Ek@d0gY~sNLUIWgnzxESjVQhYS@L&KSb+y zGmR-jiNt8lBX3)+Gv9ec!>B&f6Lo;yIYh2k_XHtF%X7@;o5w)V=!0{z*^UoSofI}Z z*?UqVyy>Uf<$6H{h;wB-i|KGDBB@HL)w9+7Z$NAIV7J65%-u0vRAF>*?%>FKl45U-+jihDXRGPe?`lTQ(AOJy%!yWF0L}Rsk8tf4(Fq1^{jz%K3ogY zR95ZKp~c%D6~Vdox!vW`FW`on*oQIEddKazZfW3q=Ud;z%8N312$ilrAO^xCgn}Qc zM9;z%60KSL`gc`Rlw@ixe)^+cFt`48HE|_h`1h`kwQ|k6G4M;>3hxSWC+eIY(^L)B zRCqg5_0XSW+y^Qu{|CY5K^myZk2Cx+`2!OAI!K(<%I*?!h`DC(9y(a;9MX~?-Nwlz^Aw8FnaG_dzda&5&octsTS zj_;N^MS!N0!g88BEB^A*Ok=u2fbZU&{Ee;==nt0u4r=)SWp}SFYcN&1$Ui~v9^}~k} zNaMwCbj+9b{N81Xjzn#Kl(DgdC=@%_g{0h3W8VfCBb)#O21PyG=Rn+usKB3q9Y(vv z#z*sq=yaS5m7;zhuX5ZAsx0{lDnvltc6Rw!+Pv=X3P|_^$9iEnA$KcS7EqrQF;S8M z^nZdy(p)^()xHsT4dz&#izfNEFGg%K#%JD`Q9WGDb(D1s4ew=IjI9BAm(BwMw!cy6 z{36FQ1m{B&It91k`XdnwX{BJ;R=6_FvQ>RnU4e^Z&+S2u!->2q=kf@AzIQ$wazH(( zm$K>9oJ(={+?3n?HFnc+*%y}6 zZY}3b+9YezG3mwY)APu7R&5Mhv%h286J-{Pn8_$=eLWj?S&>INUYx&NPs4&eMrb(p zV`qO_A`E(7K%zbD7@{fBr)NA&hcgI@vap@AHe~pxB9ELt;N&Lnb14!azi@B0u4@MB zY6gO$6a zm<;`{{PXWqT9#4gDYgu5Pe2VI85F{laU&qS4R_IB@*3_YNCxvL5>}{sNECf=Oza)J z?V+ML7z#cI=%lJB_as%^z3Fj{mNh&L)zcJm2QsfH>S++LOJmOK%Dn>b^yx9HfneTZ zl@j(?rg(S244z!FrUJmjPoo#@#7Ja7m`wS%mYZcaXa=+S=Cu@iVeSZQ7K2PA-6xlW zO~=V{uO;k3&+VC{ z@oE9ImgRG=J;Np2x!8B?un`NdH#Sqo0|G)6H`>Fs7hA{d16BqdgA(li;&R$a7V?eV zcoJH%kR?V@H^UR;?%kXxpgskO9wyej!HTX#Kn9}j*l5$xt?Yr_D=L9}*dbz;(4dUH zsjdIO;6`;<-bp~JVfyrOK*p~1l|M|!2wg;dXZd34)^xuwp&*&`pxyPm{CH6z%YF&E z;*PD2jsE1J{QDp7fHzy^A|}HJ2k#g3tP_ZtJO`%q-xs}W#?*HtC7bYZ$fkgk$JVpg ztNP#ms97=^lGwUe`>XQ73-r%Rqy{#W^gY=E(jf?oOoPii{Y0_@ph#1vE<V(%!D5N;)@j1vM}p1PcIt&qwli^im4-U1>4 zu>g(@D+XS5=6$t{0@X%Npt#dW{Itk2*~b)*qpM>WqV2pm1wmxNFj9C8E`1eUp!QsB z=aEjs)T=>^rHXkzjl|v}{Zl*zDC|6%kSXDj#cL!@*M|@-Zc<*3{wPq1LwZf7j+%=u zCRflX%wGDj-LiMraTrFrv<$c|d2N?%Cg=7_gOBH5H=mW?!$yY7lvor=22i<>LjHiI z$1E4yP#6khXX1-_X2<)JLnf}O6Zay^2I3X$fMi0-Cmk4+X&^UXm(>1tIHd9&&#L?5 z?lHy^?E`1WbO{a*OJctbG-46{-c!vhEfvGX1>arWVUGl`H~)y^juJewd;Xh6}E5~ zXC7288UQ)&e1Mxt!oa6AD&zU+srs*$n@3H*v}hq_`zeVzW&tZMqwi*lL!+fmxq6sC zs1$*ao&Q-2`mA#DVYQKvXEw#h(~iH8^>Dee0%%UFMt6}eo|a52euUMwqUbWB|VNdZ+Xn2KA#pd=dfSpZfNBW#J+OvgQs+#fDaD6_>H^N8GtGWs&Uk23HpGFfkhj9(M(-89G0ah(O^b#0lWb_ zh2D~;8^IjOgo%;>w70ZOWQyUN-j{_8OgETir0jmy$uQfcM@(C4mv{xvEb{wZxx4E`bSAz*m}1vCbU*>5b2D^B3LZkstol@fW3Q z*smbwde!ea*{|##)&|1&whg=;NE?g4X@67p!TRv-44KLBEhfy1`gv@5MZ`q9ZBpiS~OTt0NG^LiDtX2aZ90%-9_Z(i> zg}Sl1_cH7lc3jWZ!3S5;whv80_iWQs_s;o-m9SiLG=&d`9@Ujf@RlgEjPj!DJxl#n zCaB`l!zs&ob6nsrW?X+*eSY9gcXG08)W0%GVP|RiF~NHdN&ARYS6MrnJ;OM~<`RQc zd3G;qZlyrpOQhr1ge9yfhxsj)1LE0WyKqa4Xajz;OzV>CKB`s0;qhSkA_GP=Gw+k+ z%|Jr!*acHwnTzCfvsh<($i`ZdQ$dRR{k>g-mINe*jeT=wqCdrb9!f3vmE0m0RckL@ zo!r{ocBX;+wk=@ez_X3ux|g?pQ%MiblE+^%p>pd&4!R$I85o6_ac7P9MN3y=p5X4O zXi!Dj)q#(J_Q0z^nG404EV}@U=IQBZg?M$doKXF) zlUQ(k=9$CytUGrTQxwO9oictR)hB!5ku>Ktv(LCQ;fhJ;#!=Bp{JT`CC=0!L zb-t005T||?&gZ?b6}(?tMEbcQsfLdSu{yBDrV)4b`kw2{eYXRXBK#=l#Io#|uHPRk zcdkLE>+s~B2Db6eNQxf4{W+HCE^hRPq`^Sldx4F|o3?#C6DR#FZPO*a7I;+*f&0ZR zRk)&8X;9g1A{MRP0@uSEUX($beTc0U*|W0N&CT27wb1;+{=R#;uDl{cw@SMu&?L22 z%i~f@OD)%qUpxd3l}1Zl&M(lK%+HDtQEn8hMSws>VO2Q@gIh zb!%Bt^UTXI0jb2Qlaf6JF7M5hI4a+L!gq7xl3uJLlKG~kf63)tWm|vQXzG+7dcg;c ztzP0?8KSqMeth<)$P&U_=I{9<2E1Ttj_ZHX`$|CUU&Q=uZ9VA7+f2Fl!kwi}=X$MQ&+!|R+q8@+v%40B+UJFC)2Dnp2#pG@$9&VJ@!J{U~tT_T{1#EbbgW- z_vCbQ+d`=W#^-zI8lnoht)+uddl0W(AElFy&pRE^Mbnq6&CP#b3b*>0->j1ckVS+h zdv~04epVobqYIaJRbk6qUln%2_FKZYp6^-ps4Ds_jvDujdp2Mn_e}&8aUM^|%`bjT ze05~NAbZ9{`AA11qCA?3WX z)H@#&I6CzseYJ5{E#SRR3m)@Gr9RBIypQL1)&@F36 z%?Fx~7+p+nF9ut^g#~^sSVf+dXdhxtD!=D*_gWm%Q!M7gdH>~PZLRss_a-F$HO|!bpN+6-l z)SBQM-ph+d@d zAK&WNRdyUs+V$|HSWGrnV*L>6si6|JV?X zc8}KI`TB`jgYr`8R-5HL{9PTvqUG;;$K?ej`0mDK@WS|-%*gY!x+KZAw&S4a`dj-h zPEnhs(J%aW1$S(9@D2?_*4?oVYc$Irqi~NOeAP0U<&z#0pft3+4IE$1KHZ~5mi$b8 z?Z5EU9A#ntnv9AA&$N_;AX6x)9+o^4!AFe$Y|8)nfzuEoZ!esjkW+Oico}>eS?&$* zK_n%;tb03YvNd}8kpud}a5z@iaDUilmY^w{mQ|~Jf)WeplmhG+ouFo1@?h6SCa*(Q zBE57W2N59el@Jf43On1(8^@pQg>?Z}Eu7h6mpv%3*OmC>ebKlg zmT~pwB0XT{!!aFkv0;{2(Kaj1{CPHS<^+oVPgL_Uy;C1Y`Oc<2WGAo_kM!w74E{J( zr5H&o?}m-tuu&P1qCd_(=1xS%sU%*hbu9b&_T2lMY-w7IgPbN)j5;%qs?N9KLb*yC zN$z4i&vRWQ-Op#m9o)a0wd?Vc>v+B`lCg3n7(F&CU|q(AnumVqO42_LbNx}Z8+xgb zKlrhmoSVUg``UlLXKw0K!sylTCb3hBm~_BE3bpR^Yd&O8--+zxr9iV;u&mgk$HQ% zj_jMP%Jv0DtY5v=)!5!8tYTP{)SDS3`VC)&z306(eZM9BbEg}VN^XH6Z}q5$Y~`}+ zd+AKiub+LW81RTO1p?C4)5^d5;I8>rX-M#Z>Bz3&L`U0~z0Ya0!HYRCF|oS|Tpia! z>ph@62>3I0i`Uz>EbiL+T?AG9Uug}S(u8s@YL|>`A)N08vsRK63nD|7xw-$$h!ErD zUnP%y${z16+{N(s-BMx`prh2lNC!cw-tBj=`Db;H--&HJI%HNPTNK~@<9Ded2nggq zZdvC-uU4Vu{m`mVkN1ceKHFvj*|3YsSSWF8*Z;^j`HT%h6Xe?df^fB@~c!f#T`%)c&6Q_Z-x3^5K|7>6|OHSFU{6 z?W@#zSYaf)2ZWL3D^|iYNf4z5u>=>~9*v^EhQUs7vmC8et z1Mig1gv!&G>Q=>%E69hQ^b`@+J~`Q-$D8(h4#x<}9xM;7KR@}y3r_`VK%nsPk=Oag z4aLUVhmMZH_*HgD9H8c*^R4efV4jMS5}qo5O4N`Xhm0lSzB6cAf_^g_d%85eRYfq0;Qi z${DPdIcbZ0)~I1uRfV)dJ6!0-H<})sA}1t+^bUb_@Vgw1$B{Lg`-)3l3x7mbR7(Zu z%RKp|=@4&cK}A~8`+V<@^|rO@!$x{fIznp?Qm98^g%p zt&OHJpUT7k`|F}G3e5TMVg0wrp~RE%V>XEJU4-Je z+h0P^=siBSlDDb0!SEbBqXRpHXV@*jF>b8-y4acM$AhtaRsFFr9aQ8YQ<2v+xgWM2 z(G;}4MRs%DsiK+!mUDrZl_1>lS3mu!hx9K_ltDD^vu?tDCXglYW^)s>4U1r*ska5 zp0=5DI$S0v(F$elx|Y_{#&dG3FCGuQT^0Uh+i0y+nM6JKA8m!9G9?J~&E)0lFpuxQ zcS+&6L`ZpJy3z}BvY-R=tRC)iuGDO4ze)8;@2O;HVnYFz;eY6${>$sXPMaV|o<1Ld z+W$pBPmpUJOue>);kmHOBKY)K!E0PE%z_EgB+a7uibsUEuiQo7e6H5teRlPyYF8X zny?r&j3CRgUGgGdYO@0=(p|2}?(WV~I=djUo=6_zV|@2;>nZ$=lu|;0+joSJY_wHN z+U!s+A-yckN>JhNer?alJdX}0E|W9`YOllXCYO`#{!pIDeZ2c$Kb@#J$t&~wEIF#( zH~llv{Qs<-m9cK!f1T<3j8JHt3%W@Y#axQeaddfXHKlrmJgS4ny9V)$ng6Gw5E}J+e|UP|=>MDh<2oTkPTQ-q z-G4(spOEp5^+@^Yl2`%ZCqjJ|tO7r;LZhwlu>*1e-)A&!##q14oV~9pIg|g_1&GdG zFd@7g9I0_C`@&Mk**HG&3Vv8Cn(wn4lDZ?RYgeEXtA$=X=#|`b31h#F7O6WTkEzcH zk5J*o7-jz3SC|T1Oo6a^icW|aB?KN745xa!!q8f6x6WinY(Me&x)!Q4y^-Z*ju5X{ zQlm?*T%V=vG#i_wjxVoPY+U8$p8NNi>vn(F5k8bKDbgeKO?sm-Dd5us*uB9vUA{S6 zpNmSd+!f)G&=NPep^!@V+lRp#JF$FJTu>NyI}9f1ma#+lPu``=9(5C;d5l9+5ZMQM+EQFAv%msvw zRZ5?KgNF`^v>r#ke!g?H;e(CS^WCH$?S#KY{&ET(B`>w{N}U>q5|<5N0Q$`0{5ZT2i@x-h$7y0bV9 z=ii%6>Q``g_O-dG$C$!J`+6H?<63C?M?v(ztm`^$w;OHR;Tu~PtpE3)(icn*+w5Wb zN^sPea+lkcJKbxsm$K1{^(7yIBIMFT)4`r^dWPh}mRs){$A`Fmb@^NpG>B5{@VZF1^k|40&1pM5)$vzT4__Kts37f^%?uEtaYOAv+to>Axk{tIP}^IB zQR4E<*prliUPdh5^!GjLX8Da}iq-l>{~kVH1T8Rvh^P~O=E;+w$)571GuW9M(QZju z?EIrQqE+vtd>|b0<;s6yr)_$bt91GLWl53r!kPLb2_j%*DX5*m6JT^KA=vo#!KLSG z2GZ6p3lDCj7VvO0xw*~psWV2hMZY8`&1y7jsnFwLMNk5Sf0X)Z=&vSCGx*G<8Xa1Gr^JJj5|5_x$>dnyy%Q2bWlILtOe4 z$fp1r+=QKO!K!R89~OrUe~)tZ$es?7t()F|MakTI4r2Nr(^5l@v?m=!0jK2qjk}ZF zC9WEFk?fG4m!Z2?2qyL?HngqbJ>apX%g|_HrOivbxk{A6FKpaG0as+AcQ`IUQcu7; zd-qXCzfO-kLI}r){1c9d{N78Tg)^YJR3)8jwF9@PD{F$|?q8HN+CO}LlpDak?(!ne zqIym=bo$Nx*ZZjbPyAxxVczRLKgH_~$Awfmrr88{Kg#lH`Lx774^U=%T~M{x7+~x| zwSZxtcj$>zsjtfXn?3zM>y@z@@B?YF0sEl=VRw!T4f@wvjQj$GC}V5LDxNjhP(^7F zb(-xL8$nmJz!Z~^+Vmd&bo> zKHke;E<;a_Cr^*byH$<#YzAx%F5Ks*?*DrA6MFfpo?HN{3d(OQd2%c*Ld(jAp{l0K zi954qaO(iU^uhjrGV+m(M?+#Ti~S#y`yXPHz_ZOvqeR1XEP3d}3B?AHVA}xCD6nx{ zwVplpSl!Aw|B5`6I$yn#Lm=y+hdKS{$f;(BG#dK9FQE*8dSIP#R|Z(AI5~y7JT-UY zXt&9CLh8ck#6huymg# zbo|HEQOu?|ee`f6Ps8fXMi=&n#rUU;DOJBith!XnlRD}?XmbW$u@I~fG;f)fjiOyr zQ&77#TM6}c-j(#OO?`HVc!i0BoNn-k3TQ?NUo2&)oc6d2>-S3mebxBoF><+7>}Am% z)<$!1uA~XdKmpXU;ANgI3MRuO2JJvT@zpcF#v6 zFL4QHv(K?yh=EM*R#u%W@*Q7!u7>t#f5*OZyYXl`y$0mnDe9<>T+gRY$fZsVoep^B{oJG}P#1>UOl-RkMwM3!t=iz794CK7HREHd z1cTuX&3qQMF1GSE3M*>AlUxmn7e~|^MsJwazO8b|(PxEFrz4$ergBt;N3F%p$_YVt zg_X3OB4_d%e>sG@9F(rd=KoGq@B~vmS6MsBzpqnIjcXk%`R(U{cX{BjOKEW(n^6QKGX_Fc=RAmWY5d3ZaH~k`(yFD^Ofl)^>d;wTu7gY z;=~9`K1k$t5^R-11?S9IXiPwgjc3)@@q!(9=IJkjoFI5K=FUg;>b+?t zbX7=md`a7j+Znx;j5T}2cVPD=DClxb4%~e|i?jYyS8n>vJ$L*((bQ=A=>l0)a2SXP-~I)x^AQ@sJDJAI@uff1LW&V6oQn>dU!yf~{JiwJHsK&!z&f#(fqq&{ zP*k9!0!cG?<^a83QA(i+%$<)c^4ikRmA-MKtYqvy9iT_W@i7xxU zaX5lg-00qKP~AeJaOXu$@;SN{+&iBrAtQvh&v&suXwDmdTOBQ~^qyAFfl=*7EZ&#a zAh>4wQdD7qs2-`qW!SSfr|yC4`qDsCU6JxsC9f)FdjUf4-;h%SJlx@~TI$YvK*)XV0JXKVNdPUfOf8>FP&@KSKNrv%9%6>Tg*uJtC8W9X7JWrB%4My8 z9ZF?fdV7*9UuMwW-BxE}fMuEZC}_j`Xi4c|<=q0#cgbJkdbfiWPXU|xdxcXHF~(Hi zOe2qsObQS9C&~L)>@mq$umCvUwiz;fCIY@qpzb;C!GZgX#6Ok4frRLpCA*v1`Egmv1Is|V8` zGyjHq_pUl#v=OpY5_J4DI(*H%k0r#5i*9W(u=Rb-=?VP~$h{@$N9cSUraDV6w$JS&z3%9tii3`S#sqeto6w^jipd-GPFJt`{q_M3Emh8R`idW~KDuTxE-LA1eN#o$_`Y^LYsf;;n z7FXL4R*aKn7YJ&bj~P>XyUGT@GN&k!Nx%p1D}Zr4GuV8qJieA4{2XL z{{HvMv|Cu5I>cT)LZre9jIW%yv)eK%)(;-;_UQXx#%lCT;^7hPynQSB9-lrZU8goy zVb*m}AC|ip2kbfw_tTDKO{5G{IQh6Z6YMLL&hYG1BT{R|L8r* zPZCk3tUMC$5xghtDNGi27dc~6WNO!(xSfc*|G3C^N##6KEFs$1q^MQ0xb`zu>cncp zGsU;*v6@)Xx96JaF3d!ZIu6JEZpjj7@hMjqmn!8Z+Ez)GNlrzJXujWeqVZ0X6rYLw zrGKle#fa@%b@tc)AB0Uyo=D5gmLRP)HJ64Det0bOVP+iShv6EryU8r^aZ zIKH9weI{Hdb$!E6%#>^6YaNMaO0q$)H?^>%%#(6WnOhEN!h|My{JjoIzUEHyNJ@Ws&abZ8P<|Ruc6nO+q>-ps zuO;?K%}G6rum_0l03Q~M>Wat{9gU-&K9~kB>H)OP+~&>2rdb^3XVE{=$}GIuCCgyLqCW?R;{Qc|7zN$Nl%1V#G$rD1{h=0hDQe z=4%sv0H${V_4Di3aK5i|Hg*0qgiEMPvH(V<+>(Ww-^K^qw!skGK*N;BT{m4nD8Dou zhmC-pRGTzge93K3Wv?$?@^gq}lX(>jD{3?x&AF*oZ7cxOIFcJ}F!En#LI>0Xcjg7% zYUEFdNf^Ru*=a`Lf2dIY_kRJ4_lwuG_PmHYnSJXC0e6%j91w6otNuEYh#6|)>q|&? z=Q3)|CN%Kb090q6b;xbThFtZ1W*vCkckJANahf^!4mN&CUco})N1vT$B0Uc#DA@@j zYqd5CaqIYJoooJzr+w8DAlk0fxRDb&z37EKS?(`g4o(sfhlOY90_~s4tNR}2nIDpT z`=-10ISJ0wU2)x=q4||Imre4Ft_m;aUzV-Fu33xhcNS2_jWAnUw@ACVhPP*ZSj+In zI6u`hqM4=KMq$}u@g{K5iRM?90!nlrK>2lD62!3uK7KHRGI}EJ^aVe2V`1H1jJZ`G z_E{J@(OkawvmJ6e4^epXakz^eq~y2QERIT{T5SfJsumfhQ6_G9ga{lJkK^l~6U~0% zIZ02d8$URz4F&vmgLK@^TL%{bM2=UFyG}d!(vKDQEqwY04+8twKKT$!nx=k~x=qff zE!s=1u4Si6^J!vIsC6#^#c9dG@$t0fSOJ*P4|RH@KW?q{+I(cOPrxAUn?9^mw(#RP zI?W*^=9}50>ov{fK!-D`{I*&r&lB=@NvkKmfoVPVor=XV8^@<#4lXr;cO5{cBlIZNoG$e6%lxd`mphmWJqnDX6EJGF*5I?NCfGi$^C=UsWxA&L=dgG_Z_H8r{OR=MY1vmp%mZ>4NEa?9N3CBq zoA9B^&5RZALKm;+8RA-L*=Um)^ERLC-Y?Khf8k&U~m9-o7Yk#S!D!GFzSrd}Jux%R7)dOD>Lfa2o`;JjG;8MBiY$vK*lwWL{hy5})D zIbEGu8aYt662eLxh9&~P4PB4(?!!nb!o5RQZj`YvGvi}6xaYdLdLMUFkH>1K`J>}5 zLk8ErqidR}q@w0^&X-0NZ&&(UeJjXGSm#BPn)+uwlc2nge{HCxa~Y68aplg#efa5E z%=QWh3i|DaS*?JF*(yBrtLI}h)LNq})_XTz1C9-u-qOE1Tz=YRf3~nhF&^d1PAUK| z&+X}!?}zKFvHRjfR@vfe z$Ed#iQ^oD2?4mm=Ur58@R^9kIIq04&s{TvUf?7YmPvu}Cz5NINdS1ueC;#$hZ6^+F z!0TldYUVqYy8gK|a_WkuA0Z=fFBG6&C}(<{x-Tx2VtdEDohWzkAxRv{>PD+MC&5N; zc(=6#6roM<&zP&}E@LEhoo>Ai^%CG9?oM83Cw-xUVSOt&>x(w9G09Toaaz+SwQBq{ zL?q75?=?x5cdte_b(6-m6}h;TqxS9${pKN0K)c?UPsA4JnVM4Cr#@||t&<{(ox6Cv ztjR;B%4u13?p3FIu**BKxRd2*p}S4LNv@-M1C)smv+5EDaZ~L>8@5J(&3E2%7y?gY zqh!F%vfoa{buazr`DeyfUmL*jb2KnOo+`B-9dEZI#~o@osAX|D<~z$Tp-D?qt@m7> z0fHD5USoI0apdu)viExy*KO^PA&c+cW<~e4{6TF04t?)Iz!J6#o9UU zV!Z0nbYQ?+;<&YNTZWINNz!6YDT$$bBWV0Z12$M5KY|E2HllKt(aJWrG<|?5L&dwT^ zp!Q1Yn0arY2HYOW)BiqUK^q@nwJ&&@;9n=8Z#QSb0{``O&6>&w$&4ren7x1RK|WdB zC`PO7Fy?ApBFEZRUs#2`7|Ai+zx`t|N=KPh{&j7;X0I*ER#zNZ#*0j%Qw(Gpk27)> z_kY%h`em*d&#zYOt`&Eum)V9Z;I5q}MUhKEa@hvpe$UUZaXvM_NI@A}Mm`)JYY$dv z-0%VVZp2Osl(8k8RC12hMT)0=tMOeZ5XVCi4G|bWyJjj+=Vcq%@W*w5`~p3NI)ZRh zugjrCdghS)+JWrB!^oM^eQs>CdzdRHU?sCYRad1g!-d`^Zy&^_Q*howMvB0*l)rq04GW{+Bz&#X$~woiO|;mayv)}3+O zk=BZr#}!%CAg9tE0IJLF6^^dC)q)Zs8sVfUA1Ny?xpaH!9&W8_)Ou2&8+^~}{pr5V znNNe~3$IX7;jfaOYF_J<@!yP)!&aH*6#D5=H=kr>-D3o+2@=TMc(MorSd+_B-CqTBOd`Mw#8cbj%IH`OeC#~to` z>haNIrYd<}URYiFSb|t|K4s@#hC-_UZKmD(m!Jb0pULO=h9o^yv^0+5SBxIUU^bUu@>1xU8_aa6Gn2=SGzSFRZ99~bDQYAfhDI?(#;R-=`hg0=M zey1q;+F<=J(Bzkf7VvTe&xdMFIJIpzn^LSgO(n>gI=2Sq@FS;&$H6z-S}faQt;zCE z>~;uVX6F)*QuW$0s1tAruM$?^18V7euGKlXB#s;sulni+Q+ah36uT+BSgO@q7F@>W zKZJH&AF`h2^e*%xcNuA`4C0@cDJZBf@Z~Eu73!}o;LlF*lUWxSy?Trty&5poSeNGqWn>wu1)@F`BA~i5~t1f<=yr2dw48k*Pav#)>Hcd3P263K0hOtFO6ZnJl z8Y+_4<%5q>g2(UXvZ&hg@7V&TdC!*7r?316qpg2Uq#NuvimNT(XM2ser<7DgrUN$} zkFYO6cO%K?QUi&iR>V(mpu2qVTBFUt$6hhVa%Gax)*Hr3nN97$y}}nWg!rQo!lyc% z`o^F$A}yAv=F|=*9=Oii<&!nm=uD1;7*sbWr^AaFd|4uWoS!xm-&%drRQT=|$j^9O z<-6yQ+0L0vDOcEYAU*p(D@R&zwS_PpI`6t+7iHShrvuA@imb62srEP>LFB#T<7al!jR;a~73*&BU(=9T)Ze*v*G`xuEsO+m9Hh*15nkmI zgg@n8@>W;n2`Zz*4MEX7d0VnAz7!$F^M0qzjqv$|O0ObZP|g`5-O+)Hxsv>ivoSnF zb8%-61VZG@vGd6YI&GY!`(6d-=CL&7B*{cmEfZKSc-l#k?<1*gaZ^5_b)CxHb;Huf zkB;-3QJm3KN?GTy>5CL6n;FUo6auBZ2qMJn{76G>g$iZ^|mFvr#l)9ik zeo^OpTWw#U8R6^AHBNjkVju5ydjYBByoi~Ry$XP^ug!)Ito(`hd0WbouOWJWI!L1C z_5bz)SmvzRwl+BBEnH}K&vNy$l49Zr(Gr4-ix+}`xeN#0Rr83&1#S9x6J6Cfs~@+* z^m8l7Awl6r6JDpP-=Z%F$~-uXZa$1AMyb;p242ZY;=*d~y`G+HP>mCft7bYodW@xI z?mMfD%uIH%7pPSwNEa_(udHFk3+n8EMXz(iNrg1tyfdlSptWJ}IpC4)vqRJ@>DmOYd@ybi>{N)e>xGTL(*^UXG`G1#DitcIMBEZBn>^w@))oROGAxcSzjyz91+xIFJ9fT zrji)Z?O&aG>uiQ1T|4fuMRZ7B37UtihWbQauEu18eC=3|_ix3vd=91=h2=arVwc=E*C)4Zi9| z8Nz+i^nc*_0>Jc)to#F156{j=>JG3f?JcHY#8FPjFMm%{nwCJzdQ z-=YYj4e=()YeTiRl2F0&xmW6A4Mhph?jtFVo8vfZQhpYik`pQOMST?taW)aMsybNT z-?%?o!Q0zZc7vDwen0qaEfc3onZA#j)5mexY)i!QntF?BvibnxQuo}-^vA}^GwYe7 zqsSWp1dWXgl&&LKPe-E6C_`H30AWa0HN2Tjd;}mHs(fPEIk@CKN(c&3ewkjX@lc^8 z`aHGJZ-+GO{s5dY)3EXh|3G1|S=Su0qlHH|5t=-B08^d&Xq}j}*m8?8bGbc^PI0_+E(_R zEK}$ix;{>!-)yI*;>D-6{X-X3KGrNeg(qiqNqP~%RZsXVxRp97oV{v?K8Juv>8+R6Cbk2LE%*g8d{ zQ4~}BjXRY4&$LR2C29ZB=$1$IX{_c1i0fRRe?|dI!a^Mf3^!s**6Ol77 zAFGskT<0_}O(+&k^X43nX{6GZw?nz&20b3N+#0p*UX=;0=|p`nSTpsee5RT2a>P1; zmwQfl3*^NgT|BNQPYj-pf0elfm9knXWFz0=F`f|PMfmj)J!sVEs1leDcF2B=-akAY zW8qtoA)}4|#fOWmMbyNpB=iS+)cg*VdxAZu(K1&^>|H__>3ScR@;$t50I#eU$X@jn z^78GTZ{Z4;N@+Ig#3qQc^C>-7j#92tMj>D6<_>Yg3uXIX#hJ4DP;ZLR>9>q=?LT6b zy?|52$%?xLs|W{BX0y4bytIH~%eWDLrHD<`K?B=C^BHy0C+z}K@HhH1D`J_s=P%Le zs)ZI~Xg$|;-dyS}CF)A?UXJ*Zf>fp&!=yId2J!N@md2Kk7cwgZLPCr!!Q=MA3$4aT zFJ~0TWhF{1+q99?A1xbV<)}FCo+ipY@M+?k7+%K^t8(uCV3l|F!2N+M+u9bpek0FJ ztCGHvMax@ai`R`XAk=m=T->khiUOM{y->a(v%aOLb{P8{%kke9s$r$Y^}X~#K0|6O z!^_gVlDbgILa*P&iK;diQ^cN%c->rSJ4us73YR}G*n1?)(%V<3{B%^}AL76};~=|k zaC~9b@zj!n=v9jWZuR}hyP`w7_t1J=-h_OamI-~2Zt|*|^^%+i#%t8Z3$^?JPM<$D zGX~uVKIR29+yya7cB<4#cbeougZM=S5V5+}SIyYdW>YwnZ;|{V^Ge_Ay-Qs)d2mMK z5Q|ED1xM{-7Tf4l(AcHlMjaC{Ir6RiZWWPV%J`a?)7ycCoaS)+itGle;yxY6P|YWGi-MfYZ3x+9G8Z+$4PD0gd`-_i zE1Yr@_YFn%pMQR>vIhSOmXXD=rD#Gex|0>R zD`>;*tVc#0_A_ufo_zIpq}CrN!-H82iigAMBt=X+aboP_H>let-MZ;rz+R+0KMYbA z5riDCRX*r{DOAMp2ji@rKb#hqhT%l{ z`?k}KcMtyx3&vkv4OvOsWM4=Fd7bAp<4b~HrW$pCH9@ew!cv8pivKBi_ptkK7+{nU zyPVjYtO-&0_+g1S;C8~9IF_@k(LAvG0I8oc3J#9wv1`1YKD3>%VkxfHp}Q`l_4!7A z5T=PAG<0M5^>=QYv`(jSbKln#thu=XbW{S_T@{V>?K+mS(DRR}qh>HdULTg>&U(A? zUy`_d{%Q`c=wxFT$7j2~{YztKTGBLyPq?=oDC}`r2{9?^kq~(-yI*~;LfI|E4ccQp z>pxH?8|@&vdE7sK6cj{zy}sjsJh#X5&UjAlk}g%RaYvMEW*FBq;`_@D5jnH4raLpS z+pHG}k;XOxN+v{qIYI!H%r*vyZAcO=pP&MPu>spU09gP7wR~tiM5#L zDnI)%e3I_xFWmPH0B%ws2pR0MW3%0bVtLA)<|9M4lh&-W^F66fDNe%sULWC>tA(7u zpjMSK3e*(~v0>y3k<5dgKr54dMop*I`p7Fy%SlcTW}NSh+DaGhl(xzem3p(TN(J9w-1t$I|VVQe-Tk}3eEAYf@ zFX-{u+Y|H;e=wJiLfT%`LT5nVH~i!7fG(P~Y}dE$y7x-LxxlAZVz*fBTz)O;E$%J) zYwMZmu)Ye-gJ82~Z;eD$s6a=z?WUSMS*K{Vze(gS|2qw3#=>on+dv2pi+OQ$#*)B- zz>f@~=pQ~@14@<_qo(|o0unfsR+PuAegpA!o=jf94(QuHcvTk*5bn;E=FeAPsOt(Q zN}dtJ_*CWjKDLZy{9Ih&Ie?>)k5^W1s5a#*Y`RbR&-o|x4Ix+3A7l?PTHL=oq-dcM z;3+2DYHC9f(X{Ds1CP&t&K45AB(DCX%NRrX<@@k4VI~|q}p)vU)u>o zz@8|Q{1bi;Bw16m8{e$RWo^g2WTS-NIo6C3VR@%BGg10j<+ZL&xu-yBg)i5>zO+R( zS8S|$dB8k8+C7vZG4#}`516(}QWVxu+YG=v*(>kDraC}8Im0{gKiRE5Smbxmar*3U zf^o3^15uIhYxhjOqBK4tTL&A3pR^?28H`3$|9N^lRkUIm!0P@*QA*(kPjmn*iOjV7 zDudWH^66j3liZcHhk&3JRA|)&e&373OOUgpzK$;2q|eBw0D@Ry%!LRhsUh+eS=&`q z()q@9=z1KLdCv}BtY2M`ynJ6Ae`)MJq0vXzW^I7t{ow1-c<#U^!(OQ_Z-xA;=|aY! z&;qsH9}%`NWYnrfKI(~|Q=qH#@MGk{8RYOSarW-vYe29NP^ z!jzh(0bwsTqX~A!c3yU*$@V=MblCTqZOJM>fWJyy*m4j<0!{9A%Fkql9}SOD6s>pN zO12xbVH|__MMC+iFfs; zetAvo2fv%ecf(`~IfG3D+$)o>>hcWISrf8lCJ&70?eIa24IiQKo!Q(tO#MqhGy!-L zPe*cO!jsPheLxij2k|F1-LnY&qFgO)IsScLB+=n=j~?1v9=AH!sV1BXU1JMUd# z@w&`bpIC9L6Aoj-5ky5lOPY721u0IhkUCIA^{Bo8SDu1fO}#o5!7h!sK=gevMV(g5 zh12s;JQDAb+Lr0;I4EGYd6ZF;Rax{uOz5?U(r4|$p@7qN&a)Do3{694F`VyE4qg=a zO{N6c>Y+LVFX-hT-nJ@KxD$M0t&n&ORQv9h&F5;9XP8nr@ZOIw-p^;86mzs5ejtgh z`2F}EvWov#dm7ZyMChGKhR?OXsv4kt2UA|7l%{3I;!oVK0NQ}Yz3*4|WhqLMzq|8J z#ME4u!qHMpXw1O??lOe(8|vy+@y}WJU(^hHt1)ot@AxH+rZ}a^THm47u$!2lTY^8h z{{a7GT46zpoY>73^2|uIvkk09Kt!;HfJxlJ$@W$G#0K(%q?4`8@V9m`*cx#;+V_M7 zN+@0>(9=idv2qCD+y|cH(FlRfuiKfyz_M@~G3>yM`gu~UB7)`4&}b@0Wn0bg#7oS0 z)|tw&AvGpESQw$>#6W7tvByn5Hl;?emF`D->)Oa{-x$sKnsdiWn{q?hEi`|=Wy1!VCO|=9c(AK4Su~pEur!zo+rk! z`F{GZ@R$!UOCm!SQFck!syey)itao|JBYB}GFI`ZB>jj9^SS~u|BKPwS*SgnSg_4@1x057qq0nU<40&(r#5_#@{t>%>G_hyFoL zbv0i?tY-4Og;l==X$m+heAPW_752NO%rIHz3((3Y<4mBOqN8w62sRAJuMSeV#mTLV zR}SP*Q2L;Z|Dv3ebnag^+CbUAyo&j*iHq!NQ$VLqIq-r({|x8cfNtttY=xj_alEn8 zVC=KI+($|IOi}LlQivBXW^Cxu?G5Vi%65th80*IQ?1mTG3MGo=Tk?(5Wl=c0jL}{K zC}%3t$kr#qK*; z(+-||9YxG!-GZe!4~=QBy|*S`{vi-_WHKhvPz|+^6GezxyYQ2;1rH!{G*YZW^CO1>Eexa#NkY_J||78#H!o@7MtVlu$)%32o z`@W$a_q0o;vIU(qA#S^kk$AVlbSWqc&aV2v{;72Bguz&nA07F7X5i7fg7|u8{eEvc zTb*vA6RY|K`dy+jo$*?xr2-v1_t|0E0phg%v7k!~d{79d@Wiw{26C|Gpg=&IB1yBk z*y@cgORg~pzk?##)rlp!^sbS@Lx{pcK$~l)gCgM*95h*Fq)p-?-JmE&ZdLj=;Zxrt zQbMi-INSWfRJq=<;a+&`u$~xO?sFXHV-JzP88md0l;5*ed_q%W{&xmhX_rZDmjTLq zI?skcuK5en#^6vfo6PVZ9W0`P2itY7Iw)rc<7o!i@78CTD=O2vH9LFLOEt&IldBCb zi?F_A*MMPwEO*2g2A0>jr5_Ie%^#$H2RL`eB}d!hzU+(bY`Rzt5 zbTdmhzrDF@lhmYggiVy?oP}U@wT`SY0wQGeCLxCVG)X7<{pJb(zo3%~B<u^j()?j>@8BXoM-Z*I3A$tJGEv>Rk^N7$5%Fhkmk zv)O#wxd~C$9sDy#H41Z>OS-Lk(>XBgL1Ff@LcHa_b}l^y?5zqVX#ec1D9%Gs@G)%4!Y z+WrF>a`k}?EMNEV&ScwdVF7#UPrB z;*4y*cQU8lY*|B){_Q2mpqpJ}XBLijquLt%h)3QgRJ>Z{PbNyeIKIo9^5{y&VFp1U zMWA41Yf)fvY(Y&&dXF;|t28^N=J7|%OW=3~=8`!3b58Pmr0|=QpJlxwx82MKZf3R# z9NUYEP0=O?T#bDCP5Q0-Vf}VSwz=B*w?zDeVZw-v%evCKN}^DJ5@w~{yx+0Ete3aH z%sD1wmMv(keFCby#xfp~W6IBJXHU=i!ey>yJNk0#ilohexO2bnUFm+*{5`+(x_fXG z5zfknT-HqI%mhxFC0rivVzbD-!0I7JzZ>pOckt}K`k+8G@yWmva>ENVmw-mtA4`}k zB-1|(JGa0NX)3Z&5?yvbO%En1?I&f79JsOeVT>!6U&Vzng1182fkN9+I)&K)W#><* zy%+847s_VS@Q9b~7ah;(8>*|cgx#U}3CS)Qee$dq3w@t`swuP+Pv*?kOL9KvA?&a` z$@idMLwE>snY}nsAiKX@1N=mPqCzH-K{d5?GEh$xFRR?MV|SUG{#tckf6(Nvt0r(rf@Mslhj)! z-vb_tF5?mZ!{DrNXCPgD;ELnBJ1x>Bci%81vWRjBau!fmHKk?W8@H{;E-<4yj##j> zED^M~gLNr+YkqZTtg0$R497KF5FGR;&+c_EN`X3vMtM&rjYkasj``SB&Z*SiR}tn) zcGda@S6?%a4I`S^_QK;o;Hi~r0?4UH2A!+u`$3=PvufTgHl3Jc2@vdf* z6_(=|IDgE3(tTu5(~;)DZj7A;*%*)TTDIa)PzK$VA0^k@F+uaE;q0RwL=hlDsLC%W|pB7{u z75@*IT?5@Re7bNDv56Rh&C3}erZ0Vdk+n6($$!i#bbR76G(T32w2nr{z)ts$Ttp$P z3%cS(7%~nSqm@PXXpuCrVXpf`XpTQ;}8NufAfZTJj8E&)ZKg5>|_8EQA4#Zow~KNA&-Nt@?QKW zEy!ruo^Y-5ur@|m(nf87Rq6SdjeE~0!D{YQ15_GWr}PT80K zH0%kd#n9_5eCs+$er95wklW_RVU(~Ttsrd+QY&Hxh_hNx2L&I}lV~YtrA!3$ zmF}Cg>OZ1cvDh}#k&aK)C4XP0=kqTU`sYAyhr*L-xUFyDumB6Q!xG<^$&*1Ee*bwm zi-GkduRmUYTgPsD6WZYUsS;c~;rRKlH&%i(?Hi_7BByn6*iX>Z zc~MsJYVpd68fo)?*pu-AsGWr_;@+av%@nDHN4dI2Hl9oE?>NOLYL~Ned07K-e%s8-Y_&MV+UW&4Er_yi$7@}Y%BIP~?d@hVAyfn}KaGU*I%b=_D z+}j$68I`!C1Ek% zBq$%!Ue=dSP*$E9ChbX24zbt)jN>8Jfy{l1H`T}c7tEfA~U6(wuvnC z!pZp6b(3%bQyZG1OxJ>JI4UN+V7Y!nadA+xOCgG#5HFAZM@LaC^RFz%1u6BMoA{4L z)ZLWv>H2I*!#O)4FGFQ+J@L;0k0boGgk-O_UH^0KHy1;-BWt0TZ;br}{@}9$9Ei3i znrEsHf(d{Ruj=RM zIt3FkLEn0A6T&6S!t3~R908G}alakqHBl zz-U1tVq(GT&-a4j*ItUvDTyTWj;!%7)x~%BGvxfTW};rfC29MXf1c3z6oUn|)~TSl zrRfAbXfX><_VI1E?n4KwodeNbBt8rPA1p3T1z0D!fE&@zIoJ2@vjq-3upIN)c(+jQ zx2qY^DHMZN=YawORg>_<9}c_IHGLGv`L)U~NmZ*&KPLKSe_wlf+S+X$5_fcY+j4O8 z^SPPI$7|f!)d>qK?7UM}k;qaWYm>mH&(`krLZ)6bO&KIPi72(JE|WHDntDisvH#t= zsHN^*%NbR2M)9Qk>@b|HJXgpelwV>!3IC{9GkJ)*pDJyfXJ9gxg*MM570Ogq+G1Z! z;ox8XgRSo@hY!AVmXSnj^*p)fP;D>`9R)+5|JE^!sq{cdA$s#eTa<)lZlep14e=X` zRpn(y1~eZFy&7MU&9lQckWkjvBS^jB&u-9P>f^meZPWj<;d4ic&i-TyVGg(ya0_ed zCW5#$=g$8Qif~m--Ia`JmJ|>J&b_xWQ~q;RmVihNPWhD|NQvG2-R>w`eIuzvfUfUj zBRH(&26h1e9cXu(OXsY@q)1>OVZGq5+^F|lax;fNZeSw{39GZ73$;WC{B0v<qOLg5m>zEqI64pIHi`3a~LgTW;&m&iu_80+F$y?D8{sqtlerUX_^AVsCo7K z_8!0EoOgU1J@L|df_t*B3btWSw5B&(*56CBw!3N&2LOpMIPw&hT|lWYrJiVqx8 z9V;B^9B+-jh&vYc8e7vgn$RRRz~zD%T$o>Zh=$4hfF16s!t`h4L3EE$TSQVJ z3{%!mlFwqv;wE1VbbFCqussRqBIWN@SgT_SAqPxP3Drvif<;{clTTjuO$@IWkb`o6 z7L7G<;EGmG>3<0E@6S303tzB%UK%`>V&{xfV;QPGCc{OGY^d$%jl1l)lfWOWk(fgHm-Vc-8JnB#M#1lRi zi#pGaCOLi?x7oPAR}SsL`^md31o#RIes^lN3nnsjgo4MU1$F{91uvkoYEDDWTiVv& zT4vlZ(s4lB3$FzqLfq6dSI5Abk5)R2oiX1Y~C~6xYv)0G*leP}OoUQgmobCBQpZ{-!rz326^1sv! zN)V2%t6?YMTTv(SM=5-1%qtQI-#bOtL3WvMB~CbF{dvHNhl#;r$=`;o3B*<6of$nH zEn3r=3*?P0(Di+g_kQz~-~29i$qiJo5CD3nJ9&t09(io@{8|hk!~nb9AAANvJ3np? zt)3VFY`-qizzg8Z>-QekdAnnfyD)x$r|BV2n`47vw=b4b8bPOg9H1+H16Ke#T;;Co z<5{k_z1)*(+<`h)uz9!t9YwZysrI=pm&HQcH3Q@2nLwQ7!q!IQsA(xzgB~VTnJ$y6 zOF%Ah6$p?r;~nS`wO;wFlZ|=^;4^kzJ(s(xtGdEz%=2W%$h{F&xR0K^)C8LXyr<$Lx1t}M zMFRoMJ32M*ybC{4CyonCDpEDB>FIi-z7n&Esl;3((bg5#1ZPiW7v;0eS7B16y}W&P z(l;Gj{o~Dkh~3+R{ZZ51&{+SIy6?a^o>8-+v{b7tJA2otNhvcn7Qu>$qY0M;xXlGt zGL|#C(%ECKC1&wDPmB3gf+9?wWppR*eVtJoXI`;Q+Z8A-eabCC z7jW^2mEf)|B*L;m40$W)K}Q&Jq|FqHCSFNj@P=O1hB$kUMI3#B#Qc(XqY(0Uz{@~E z%9fl_Ijs*YwBHmX@T(^@2i5fM<-Kd$sN*# z&yEX`|JXvZnv4P~vX+itf6RlB)q?zrpB+b`*ne=elNc5jtp{t=IUQk<;p+6@_4{v9 zkUc!4qqU1`(T3G>Zoye1QF*0esun#WbM0H0ksWi?hPX@jn49;ALZTp`#i$`Z|V`k;Xi~AKG@=K-Ytk7tBu~`SIV;&&(Jo&Rk#eew|1IGv9dlG^+fEsc%6U~{HX7Ke$ zT!r{8{*g&#&xr|W0d>KRzgkB`*OILE-=845~WGhP(%4)KQ`fJ9l{;))b591LA z{deVo&}W{bj+X+vFiRmzT&n0+)9=9&QLn^XyzX1qcb06gvZ?%Q!uS0ydq zfo|h2`~p#w6x&x;2fagy7H7B8WM zN5xbWWV{Ev?|9m)e>gvZg2fR@eTcGWJHMV~r6!3K^LN(Y-ShrF|7e4!NR;}xu_ivi zt`Hsl8M78{q{wtNuYhe6Cy~op@2ADz%+}d0D->ZQk(Bfy=aU@2K7+a4<5^)MJu&JL zPD=LY)DQYgX(8j&^y!bv~p`#a-z&VO(}9{2rz->-39&uhH)J?B2Pfm{M%oXaP(Ovj7sH+lba2nC_8 zq$5>ksQO+`UwE!=);IKLIZst?T_KB^jWIFe?A|wAeF2@zCI?O6TAMi~8Ox#uc}&ec zW>aluN4M0YID* ztNM!S_Hn|CI7q1kMA@TuYM#J$6B1Hw>+sP5F6(pC@f*nGMN_*7mMXj23ViGwDvy^u`$S)`v{x-92#r`TNw_*)~(GhK806$zSF`OjhB8CY9;Mk zzTQ;0@Q}7KT9P6u5xkV%^WyTl1Zu)`08u9DJPv;ICO4?$(+5CBwUnm!qSEs0E$m@_ z2%tx4jlv=QZ*A5x<3+>MFYbVl<-5(|=0CA<*jPxT2L%Na+}Yo_dJEv_xAcSD#=;|% zY*#dUcGRd8;s6$j=vsx0)~M)Nt#2hzpw=9mS`{!qI!`{wQ!chKO&4>$fY^jL5RmUn zJY(-elwn~l!9S5Z&)mxDV9`o4c)c0cR>PC_*SPM@5O(uMw70?7)BlMdwV>G%qAZWO zq=A7^q7hcv121E@aZ@oXC6P&Um!Ah7!CvZZg|W2;b8uPNc8!w$T#DYFSg?0-DFr{V zkHfexL8cRmP;aGH+t}y_UgxYJo~!N6VMf;g=x;UFcoB~YYKE;iuLKA?ZbxM}VAH!F#> zf(6i`$HsfE*3wY&dJc1%UB~ zOO5yU)cuQ^}@ zZU8h{G8K}xvGAhv%xk(`U<{}*asPA9ddG7pdve#(@Ro764sKZFo6Ne%vOUIi>n1F) zC$qF+U#SzOQrtwrQO(>Lox8SxE+$-B+qhII&&yjre+@`|x#=P^Q208&u%ftA4>ljQsubzeoxwQ(oAzy2~yO?rUTC@VoA}u&^4jM$Ic_< z35L!n^G%_oP2aaE;7ytN7^~BH&7lgO5jCY0%>IyWj%!PYQuAx>mT~X4Gr?S^00+-m^l~jFLMWag46X;8haR+*2H3Wf}#?;akrjKei#3{h0Oqt z_56j~gom3$-&D>W?QN{Yhr)h*D!UK;%8Dv>Jj*xD)vmJDFmhiz%66Ev@S@xMf-|r6?{~539iAnYe0{qtjVWTu*!xEgz z6%Bhm_u+q+HJd(?HCQEPrSUsYB{mi-2LuFk6t2a^0pG}WyOxq|2ag5{6p8fkUXh04 zP47{eld75|3$JrEF7-E>9rYpxde7o{>nL+zn!+W9y3WR|bA4K(o1MAPzTD-JFD2OW zJwn!8_6dWTo&UzzOlyCNwty()=|p+aqSD|}`IyODXxPGE5OzBj)9E%2+ma!&;|&9m zfi%{mRM#_?+qky_x`?qm!gf{!NcVDtRA;|eb5-Pq=Z+o}C*D609qeU8YaHg96KW?r zAXop&pNT8GrE2SYN zxcHvgXlKAUY_dgOA~r5Jz@dV#Mq5TYbK8~Z067Bw#lE3o$F9%esFh0XqOmi*1C@z1`+7b_>0ysXZczyN zMY;XJncgplLxt=2>HJ52`-yF)^}%I_ZVA53gee{zo5agYbe&IARHvK!&t@o11zFPv zk;KWixVRekq|mZM031JtMSpJ{tnW5-LqFA6A{L-Wn%c43mMrm z`Q>1!2{v%)C6>hN7DDI4#dRTXS$^)N!J9wi-dpw28Em~fS9DdVk9gO&lPM(K!u_Nqr4wb|jz) z=iB}u(eKyLAy5j9(`t$}Ws7&2nKWfHk{DmGq=**Wr#+X(`s_5NN$f5)gZTS@h&*f3%LllX=`g zB8*6d95DoW#e2U=8K=&_>>YA`GTS_-;!n9p_?FoU-$K_fAho%6Zd3+aUh!JI1UPEZ zOx~|732YOnpyoUvjBng-fi) z1nkGMa3@2A%0CgcC@Cr9QN`q{m3=K}Hrd+fqJqcEnM&kAv1Wk!X?e&*hS70s29L{o z7;j9uLWi4Q#81t!vBo8-Z`=(t`_>Plm;6F=jCAdfOE?Q9*)nI~}OA zJ5s!IR5c~NorvR!e&5{0gRw6;zE!*QRjBe^3g<2O`%<>^Bq5OujOR<{OEqN~jh2D{ zsuK7vOfiCVb6K4%%D;u+%JiC&dRDgO@BM3JLfkFl zyUT2?9K)(ceSd<&F0N>=qq1+f^tRivq8_KO#%V2JlE8eP#L(0mfQ2Apt3y!HRQd^FwuzVG6ltq273HrIqL?zg$dV(C^(bL8)l_6g`51H$4@&BKv z``2#Zv;XsO$A2I1neTy*ZlSCE6!wz;$o`6{UuWVGZJfE>Zb)tsWk4M`p(Gi;{|f*N z3~Q;$*l2uQoBCH(u4*lFk+A*tShcg3_W^E=mEc?A6L}vcHN-#b-GL%Ru9L5tsiGD*8VCAAv`xZJQ7`1L}lIaTHq8(sfvgP;WK$ta%pb zrFeH+Y*&8*PWhT@vwrVZ9&8ZJDSqz#&-*&)HOsxYJb-RUUD6n)NzuS0N$sU_tcq;_ zR*h!}(TP>nkv3VP-?n#n$tkqXJLU4|gOH=_(3Oga;Wvq}jue$QJtzrvVZv~y!8;bY zq1Ctft%Lo{j6jpko7WE!Z0tlr<>w}^0|^K23W@&Zx&b--hh=>8Vbk_B*C9<#r=jI& zH!-F}?uFf@_#p2))9|WXOYk179G%eQvtjT0GCTL@Sen7bq016;|LW^P@`UEcE%`jP zv$VljrlKhM9;Wcbjl`iuPUhrsbX01@n+_3*S(?~E-@|0Zti&`^{DUCX0@Cps1?@#w zUogb8qo)=pwJ!R+Wp9&|_*lJ#E=$5NnNM8gps{p1bfhzO#zp&U$Ctu3o#7dn{enK} zSn8|?W9JkY$>5gvtnU0ZiwRo#{G=$aQ0*L$S^a~xT0ly5)_Uat${j5XNk2GQ{v>B7 zI;z|3fW^Xi7lj)fEC_jVCmWKbF_T35yTB>YucC%^FdEu2z$||gYqxbT|HZuFotm1O z$(Pn_pQLPEdBGK-;)hv9rI`BM)c5~v1irE&P+~plzoZoHMiZadJruA#`4Tcw=;!nC z?U4jH%w0<;np=4%kfa zWDxeV@PUxGsi(pb?SHWV9(R_g`>m0Cp`1CV=MxPq%Doj|m5v%3g7UM3%wBzXZ~B&9 z*&$0^<%-%Tae7oYoBrERyiBQg??kb;F_Gut^sj%i#|c z+9mw~?@@PD<(9(sXw!Dn*Pw&7=VO!8%bS5F=S@VVcG4Jz_~20EBpu4%ROP>X7FXqL zm1^L=n!YSWl23EL=RbcNWdQsY6sbyISlJ`*=>?K;g}V(IDajSWO2-WTO2Ph{wBlp* zn{7&l#AM#&^1zWxDD{r63Hf1{EVGB~FV7wLPGUxWmqDc6dTa{Ui4+|p1+J8gUGeRK zJ~}UW5YkbHysfyz7!A7T{lZPqqLn9y++E{9JKU0;HT;&@axcDF%PaV5R4w_VSI}`_ zi~m1zyXa=!J?QVl3r>3yigpZCT^~=Sim-7pLybT!a=Cdjx3NJ`?6#=qCx|->hZp^} zED1~ensNFB+AsJCM_9BWs4L1Y?CS=6Th@?{ zKC+w@ojVhgc{toYKXJv@CfJy9Vfd%<((VCDT~)oq#oOhzg}Y^Zb!TyCzLu}~ZWB0m z`vLua`%UR6F?D(&@D;Q`l;C}|@~tAz9>cmNYV@{7MY0aSAR*${O9;Z$GQ;=Djp_@7;HAZDR`W2Qt7B6DQpO2lk zv1ZfsfS5ZrE&(<8AG?mU#?MXvI0FC5J(f@Zb=2M zYg)`_VU!}qyn1+x+lFpc^CVIBj;cIl_4hZl%on`(jWKi=(s?pV+ouPg=Vu6fzz6e- zwc|)y9(bTyTZTdCnh|O(s03-$koaUQr{PkZlve_b=9pTpFH?@>H2|e}5%c~3vbPg% z>ZF|I8{Bt1T?69ub-by~L?C7Z91CIWXz5LKS#}9*U6j^r6#r7>&@FRG3AOn$oY0}{ z+Kt+(BpTxHLDwYY;&kNlgX@(I3m)8W9?e{>hGv~ao=~+_o$$>|?PbD#ko$Qc`bSz9 zxX3Dsm4G(-C$Yh+RT+o0Ht~r4pBTFb{42MauQ}L_N9EY2A2!8}D2((bDtPL{_=g*3 zz6qi$%K9DY?N_BhKMP@AJVxZ(OQ$|{A`%#!f9c4q=CVJATLQSlnd2F>R6fSn-@iQ_ zmDsRQ*EgOxnugBUn(jf1tU=xj(py7f@x}?rp(j+@{q?`2=%WX3NV+-%xfh@P_lfmaS)=cx^v*6LX4&$xSQfP&B_Q;9%`c;6Ze3rDCZg8TO5G zaag9W|KqG>Ej%_H)R@5|>G(yBoVK!-7%#Jy-Z;CRgo{j#%-cyXK!FNNeg|-F2bG>nY??gIc2OP zR)c4@07Rh-Ax)QuD{+m@E{ZPUHW@UK_8p_K2{wT7KN-9my*5>VPNDdfSHIaT9!A8$ zlH#6Zm1*r0jd&K`B-Llp+(GS=gUnu+7NNKbNQ{;&=-kW zq6yWpM>Gin3R_XI*IKdXchD{KOzJZq@r|zrIF;B6T>Z1y6O+!_{Wy~99+HybVd+my z*+ekq|j^Kj@DWvWeL?N zHKNHt<~6!2M*^2$J6eWbzqD#}(I(wKQAUd->E>D=(Z`o;aNPixa(2|2E^x(WEXXZn zO9FYYp9d!2N+~}sqm~iE^UWpBJ@&-|Ig>S#dAxb9Ln>N*K{5#zox@h8m}AweZx8!( z6&e`YvXLY?L!k=T1_ZF)(Ku`fZemHQ(hns+tf_Xz?dKy-ellA)GHdsV+Gf;MOZWNC zJ4~a3_qt(C!%pnOv|v|vS64f3Ztcdno8kl()R8*Md;OYDm!ZEx!Po%x_GEQY%7j;Q znSlkO=|U^ZmpGgl78a6MAIQ6CR=R0_vK(|U+6zyU3cnNk7y-TemXEvp1YhBYyY#lP z>2ct}v>G#cWBL>k?s91+5S3PllsF8@VdhVWNN*(M0&pz8WzPr;r4fMs9iL<<&IuU|*@ z6ySVBSmR{ubwi(IJ^GvY_J6nH&VY+EP~=XPR_(Nv9B>9TpW~Xyy&&~kKtwZu5|&z< zq@ML$9XkiImm2g;?)1C9Ek0e-Ht@YBqW!x>{xx6uHc+W!ynWEuPmlaV^*G)$YM^!Q z&wWfuJ9_ysAmb&TsO_LO$=L>RPuF*-dah|r1K*Yk5W&3PS$U{+{6u7Uj1mMF9dG$9 zt#6n+t-0@kUky0)R}Sffz|V32Z~RdSnTG0Hq4~ejDR7~SNa`dy=G5^nEhr>d4-f_0 zRpbJUz?}yx^QdL)7jik60xsD-jYLJ5?3IJ^B9lc zeIW<&s)vm8)dyn}=54^MUi=S|6&5X?%kf*@vi_}>;-6G0|w>9 z&%T$$9?&EJAsY&|8(Lj=%ep#Bc>tl6QE)B;m}08=t&kM!mKH?5s6bdXeg6q?IjD2A z$u(z}tM1Z7*jz?Z`p1k593S7eW|qsw)#96^g7-<16j4FKy*}eh}CO$2BBT=!ey3*abb{z0SUD;CUc7w$faC4 zO@ViU74(X70Yk1z79W7r!xO03zHTUy7V@cbY0Aj~i|_#+MjV&la~Jix_1X8nL6=u^ zwrF$ie4SQ)a~33=jf>fK6;7T34JzpgZPYri@{+@fpjRr2t6!nCRZX`thQ-^>o9jA% zl@EMpT!CiwV)h-I-*vr&n2pB!Kul@|*cqq&FXqFp3=Ym*&`7YQ0RFdr`Ywv(M!cn)hEMk8#RuiUZwNOBKbwhVD3(nc*bb)uYp< zILisiw1t==N2O{ND-es`I)^A(YIgK(#mTzYgF$rQG6&t@Z2b{l2t1*a(8JCtk$+Hx zJH?bfa0(tb7*m)i46=}yPYC+)pQt8=8y8E@r_KirPR5~=y~x9!OTRR#|K?Twol!^A z1S}tiBF`2!ah3jt*f^VQ<=5r`LqG-Er*^@;8)HnfK(3lpBx}gC@zAzBzlYT_l)<-u zDVN&J#ab<|8+2DcfhA?`vxLzfx`W>zc&P<3CfMUkDNU~zBz5~NQ3k++jfa>$Dd|$^ zesVnb-gxHdW~I2ha3$eIt?T*vbN8+7gi00TztcVj1*#GY)}eMc(xX3=J7*j~rN~wh z$1htqWbl53&F_uajcXBw)!zkdVm1Vnd?UuZbt<05Uwg#uCn=yT4c9MID3*!vC3JJv zq=`}b^J#i25tG1I2alb8%PncdeF4-&i#;nI5fqAT(;O^~o0oD0pKLSMq`3t>ItoZt#OKHQadq z%eIePNrz8%hW!%E{+s`LlBhvxyC080IICvM!I<~Ff!}wpNwpxKR;PwKOr-4tOIG!z zZN$u^oV)90akJ}YQRvv@1iFfN;Q#IvKno+;;v6?Z$v=D=Xv;eT*g+g29Qz!h!{Q{N z3kA)Ij-Ap(4jczIiIu$@=Ap8MMST+(NKGx1`P8C3t#&ykaDu(rP<}Q4%)3on-Jp3s zF+353lYuB*Y?a`EGn%w_n?)B5S1_^QA9xLMY$oX>>L#jH4U8u|OQgEKXdZj8#1tqM zZL-w&C6xYr&G5kI395H!zd>7?tk*4UIy-Kq$6>~)F$m{@5f>T zoH>yww+o2`LyF(MJ^ND66S0+xxVSg$1Ercm(!vi!(S{gC=g1_j_0{5vfRyO z&@H(1*!)b>H_)mqg8B3`;;6);fpluvEj223J@z25ELTcZC@BCZ6KG@C7^ORk2DxMz zEhA(X#X~#I zDAHSUf+Mrg8V1~fME`|PIbSZ-9X|LfKey(3*lh5D_mn94#L6Y~6dpQ+JV&9*(=3dw z{?j0zylH_d5o?GYXr)5fB4S$dEksCOe$|t9j(OrQT8!iPN&P_At|KKJZ!aioZaR); zex_SOk%}~XNh=`Me>6axOFrwQL961EE=@xF$@Wj>&;f+hdQ-p#|3%Pm+V@ZVZn34$ zkg?fxlw{AOEvl}N_X`ofKc5C?%hyf?o)9C)5atnGb#>of@p;Jdm#KVhpQF+5{%hVE z^P^I<;=jwc{H_lT@5VV$@;83N#n|cXK5LlpLd%UC5^p9Ie&4P0jlz+yHK=dHQADx; zRj16kf#vXwrp35a(7Dblvm#_gCw4YU*W3e+z2VoW@ zi=?=CYOwy`YL2n}FW+cq)D~yBGv-`lbHjP^hUDCr-p1p7r!1%ONj0CEtz-OK!M#`G zUleu+mCA5QEG0y0XG4pCmS;Ir$@{Wv^UrCp+6gG9&ELFoNZ9MsSh898&QW|K8TL^Z zMZ@LSWqxJ%)fjtE$rZMEiBp6ZOAe0BGq2$^PdT~PLZ!c@=O>f{_>;NxyOC%Wz}8P-d8)o zuh<~E1g0vBPedX9d4H#V6cfp{ig7|ISIn=ls6O9Hle3Ec28{qc!<^Oq4rN|v+q~I> zl@!SUS;`S=)3=71Pv}7tX3IP)UgtU)b`m7%X6enGQAWk%d9Eh4popZl~kVf98rS= zT=)t9-sUIA?2#w-g~!gTD;1vZ0=Z{qwL6{#DQ=BEj!BhUe<)EI(u>x1p;b$0h}E-D zr3a|63=p_4qcq^ZQvDKX8duT=UYC+`$M)|?;MPO76DH}-dqo}nj{kGIr|84{VMimx zn1PeA&7$RyIuv%iJ2rO(8yWst*8KjQIA~_U0WI%mn|GxA3{h~uU3zSaoxm?rj&@|f z-J^4ZYuIs)XGcr?GTh^gyU5<>hgEj@Q#;o8hrjII5`=5=DCyC=O~$Na&Ej{aM7KNC zOg_L%`>QB&YnS@HA!DUBW36mwEK9%F{Z+~9coPf@Z(cvQn|~LtB64UJ4j=sWH4dB3?TXU<7xd*D2OE%HHng(*2@7pPshK z#ydcI#!Ysap=m#l&daDTao2h9eBtFnA<-%FK38h+Vkpx{JuxZKy^8+EVmUTaftt-> z)+$wFrA57p)+#Hm?~7;akP7Gi53UF6j+3QIHl>Qk9S835;|T{W)E@hRBbbmOr`gk6 zY!j?0$zrH)6ZF=deM{o+)OTG zau|KKFm}mNz{siRK05U1^L4F2a!+ozmN2CKm;77vi%gTk4`x1NdgG_b3 znYkYA%A2Xm>hw<78}xIX+4)!Zv<;K54ZYJvxRmKE5EQ!?lzXa*T|4~=;dg9;of>8O z1sIazBrP@9y={lpc;CQDL-^Kf(U)(piR853we%Qd_;kVcBDanC7%QYvOm9xqkOR-8 z>Aj%Ar6OyCl@aYhvt-nIFZU}Ybm|$v_%b#qJvQoR0b;iyRzI`^p%w`v7nE{Ia8L6t zH->*y1Q*1d{sXLFKlz*eAu{#N9k$LfL5(3Tti)rmF=DN&y;7nm#e*u>;@|^|&d(NP zAiw;Ro};um9EWO@BB6lW8+kCdGQ@z8^O|0JQTuxK5H>rujO$K|;-}OEZj_xLwLITN zj6rHeCz^m-7-RcFDC(*$aIymy+LH zP7%%_6Mnt>QvE0+Hd>N#)THDiGJxrvy`lW4AnEn*(G*YMG)L)o@)YQ5`p&16p5XmF zz^1@PYQh~4#M++9jqMLU`hEeEl^eQI3w1;=A#1=#cur&ax1^0QgW38td67Z@dlq^{ zTLL9P)UL0L($jqG?F3M%%AeZGTk6gxCNQoPoK%67P1nPerlzLRAa4QM9G*MPxJ8Ve z1)esz)7_;`bXAR0q+-nwL?w5~dN5>a*jae?DP)x=3z52ULusJ^zU5j*`a_AOZD=+f zuIpMIvv(oM%rtIX9;&oBtDY8QT^g%L-`^4(m1%l&4Ys@S$8nRz{V1map?mnU-h~H& zLr_u$OF_QVlDPJfXy{T>7n?t0Rc*-W0$_X7jBfPZZYIRJB>+DR@$~ZGoN#<uMq}TAtL0 z%%1#0H~PG6Z2QlROU)5hiz22I_t})bq^vNpD3p7~?|fJFqYU16zbeI?oJj~uY9bFa zo;%ryUf)*K2xQ)egX!8obai=wO8%G>QU^VJf6YE}JX88c?(J_W<&J~ZPdklU()f2D zvAdrXxu`VhovymakOy|+s@RNtTu800 z0N0}>v7@mn`1E<#qu%dGWI3yeV7lSEwy(xyLJ)7u z3?rV{@g|ltzuWx4ayoAos`&jHWBVkdf=X~Ppr;ZAl!ycsWW>22+OTutvjE!TD{cP_ zwI#~O%D7lJ8H_u&X^UZX9QKm5a$Fp3%r@*KS9h$PX%+OF{|o}~%L<$eJsLok~gg1CB+V1@AR}|DirX=?^oMH&x2G`O3&jte|!>&zR)Nx0-DfM73RZ z=mRn@lx%tLfl~GBC z?GhGRKGk;)-08Q*IFFUMl{sKFNj9Gys_e1$KAIPGhZw^}fuea5?PYth?essC=*4&> z_g_nTz8pD!QOIXXi8kKeQU^r3cY3n{n~xQ(3uV23YMtX%RPl@ZG9UGte#=1>VJWXjZH?SRKd#$YveAadT zoy9?EFDb#N4VX2%8}IL<)`$X23r(Qp^i-og`P5`@CYM|O&g?q`QP_a-DN0MZbxq_= z_gS9PKh*vkf9PcLi<(JA&1-gC5LE%?Qx)N#B_XS<$Kd^sj8Y4~&cb>2KM)h)iut8Q zflbfh$XlFNFhWuXb%*92|0RxaMFv_G@LpAP8RYj4E1NWY^x|xGK3&;c=4lzg(v>i^ zW`mJO6}QRdovREJ$sZt!r03Lf-I+GWWpGe&?Kjk_Vlv}}%YL0-t?)t-Ja&AfbZB%) zF}MQ%rFj3H^CP=UtS43=~)zltoNLAG2$&T&fXj$~Ka8{lal zXY7ip{AS~(tGhEA8T18T{Ad5-Eb^| z?M>1ZL)E#~TP*b$v38&nIa_Es`5lmxtg5UUr2bwBF@4H&i7u1hvwdhLKNf_q8a7y$ z*Jb<$TAuXGWpr2Q!D>aK5 z51CKi2j-U!$vL-Xn=BRItq6QkzC9_~!TkMTZYKRC%U35}AUR1l5ax|Hh?u8Uo)(eX)8ys(7MstFz*)s1h`ieNG-QL#$9g7<#XAoa zvXvE8wDiboatN*{55cU&_~MaHn;v`K16Ea6D%~ZTD06RD_n_gNddzt3rc>Rrso@vz zeRY)B4x>nHFX$!4cTwrpH4&&~I5W9+s~B)-3vE955y<-Yo1Zxw1e3JLGanT>yh6*U z6Ae+L-K%KSOxNifYQ+x&19Knp@XOV+Dl-!%V>9G<#Dm)sTQ=oMW!;c|20Gqh}VpyWK0$5#~`OfebJ;HG!w zS!c-4Ce4u@rgd&cF?(5Ev+9(bTHey=kVIa#|G(9{M*xr#9N7*% z(B7JO>u+ds^vSFcxw2P%{ zK*ajXcDtz0KQgMzY!WPN!f6+Jl;^J^12G&H%cvk9~A2*2>vSE}F#-Ilf? z&|dnf+%hb!pxg6RAF|^S8>QJ_=Iqmk4B~<&98ZXd(0Z4>6Gz}~q9noi#r$PN3I}tN zq2*pd?9$tCNkvvU*E;J9TCf$AWayG+gBZPtDtl9;1W8P2qg6Xpgv>q`WDwd}sMw^J zD}E*Di|cwPqQ`LhS4lmo3X^_-#+0|m>UEZYAl|K$*}A!gjo4h-d%)txV1hLlNxDC^ zRW?yAZMg8Dc)#KqLc3Reh?Q;N* zU7&3)5Jb|7gnxxlX-eB@Ydo{&wKrjPz}x?0ukP?c|0yT%E>H?Jh%BGzJ$)(gj{L*1 zsV2#u{U>u#^2Qwgvq0b~AdoH)vfQ6*;jo~ksI5kjQ+eoZ%X0lnR$>FTD-nsWB&R__ z*ClNwBY@u7LYl9N_mg#_Lry+JoyUvcI)A1$CuA)Lu|&`OR&Rg&RwVeeBP~Ug z0cgj~zA@9(aA?0}?L7V3%rZHwyU^;m^Rrb4PscS`=X2_iKh*LLWT2(H=Mm6=$9ILB z`win;+Q2F_fKUp+(~l3a$*=k72Y7d4BuBprBJh}*{$ zKx4lDGq~Q^RCKi7*PUULZB#{G0@s@sc>0^rZP$ox=}f@cTfAf=GRnKTno8CU@Y?YA zy#$lQc=G)+g77Bz4e*xW-w>K0>5bnpt9eaH#rQWl%Xei5)z)W`pAndDS4(SW?riEK z9+Cdv1>wx}E3v&VBLQ4}_qnCJ_EySZ&ykJ_L9v;Rm_Fe2vwkiSe#Nz6%vWyvO?XuL zq)Z0!Mv#-`(PX|P@^0dJtYiJy9W;GLNCJ*rzJxpO7DD~rrrz3Ingz*pCDyC5?$v?U zjWW;JOHV$8?$2e#^$UAVejMo3ynXajKNINo+7Qr5C3F%|K~b23KsC2FFR$HMKim7P z1$zA?<9`>W8lnD(YPc;`uU%1XNk>h;^f$A$duVJ0r zE})COiO$&GDJY|1~&v++OahyF0&}KX2soFsoPdo`iN(=^@gOo^W=0W&% zSoX{U$oI3t5+m}h>iv@1=Ia);5Bf~*R@}R>y%qDDr~8RZC{03CT&^g`HRp!VhIzn0 zZ3`ar5a)!DambV|>aa(NI>xTJ>TeYs%xt2HmJPmexBIUscSntP!`7yZ+W@0Ba~0|q ztPIe$o;M2-k_p7v^)fa*Wxslb-+G*Leu4DZz|?sB)k*ja|HBF7ycu*LVS8(L zH}sVdruR^~G9%eu@_lA*aNgv2*_Ro{<3DeJuZ0 zNN>o#u45=zkYNvJJPv5sxnxW@2hTvzu1>h)7 z8__l4gg@N8XXxO|EPQ~T;m>e=cdd@at@;Ak0Cwy%Qviku-X_a^n^svP5bBw3Go!-XcyZTvT240)F1F_SnI3y93^1xoXe zH0or3imDbAb_E`wHxEN^vXM`8*6%|wd-+{Os>C8S$C60bs?g8!R}4GH{8O~g*cE=>Lb@+L(OvhUuZePm~gci7R-`js{ig?k(?Th5K}%#_3z3Q%VO z2$6~{QLc?UKo(oA>l^GP%rfi^eSnM4kbBigm&x8hwWq4eZxb*aLrL0+de%<|AEh!TT4ru3zfQ*kUDr%olE&sQO?$lCc@*F1=3r&EIxI+V#bGl&!mQTfr1ZD{XlokWvSr}-H88Bl z?7N|`^WsX$&bUat`4aBCY`uGPDhAnm@2gdlBcjAn@6RcHeLipyFxFZxlFNk0N>__k5W2*vN=WJi%)C+I3T!!t9}tMVyDHb&_kx{x3-py6p)Vi9Ct|SR+b7U60-V7!qH$qEVZ682eTY# z>N-toCI3`36Rl%3tz7CQW4U^Ci*e0j^h=99;(ET%IY=njL<6XD)d-0x=FSjzLb=c+ z5Zxq(a_mX`+&q&NmaEw{Nv_kO9X5{)S3iTE=Wa}KwT#;5CidO(e6TLLDBMh$!BXTJ ze|9wgWQhiOLEFy*VW-z01uJq{D2n_5IYi-}0!3DTl?J({P7j7GI);K`GOYfy27X6# zN(3~2CeNYHLPBeEnQuWDXDKp_#@p|hua=9K&@ifNO9MSulK{pE31>_hl%TT@xU*2h zg#3?gj7LCGI{IBCs}7yTc7RPkGCDACQqs|naou!*?D8l8@jOm>pkG4bbheb9QJ{;G ztaIY``X@8&<;Li3?Ec(+>7l_XCqD^{Wi)k(duO#mCW|`IHqv% zP*I1z7gu;(BLu=s!;@ZV$7~-qZI-D-e00#(HA^E_6^W8#oMWzCbs9EF2VK%N7IC@O zWC0N{3qE1Y!@YNzfD;c2V4IPyA6VMQE!sjxPjY&ygp1wA!9kpO0Xh`ji59#6mFrpM z0p%Wx?6FD3oo>IyDA#VK-sr~zn^U`=29}dE!f01-_4qQjgJTpRC&Z?EdEd~1kR!32 ziT%n|Y$J@pC2^;VWO{VwO_$yE#ib7NG^N7zIEWv_s;*@OCXHIHMBW}H7wMDd9#D0C zUZ>7|cR?7dX$gyLs!0gvB0ZL25H?+lO%sW^@t<42 z>nb^$IRcn}VU<}0K*x5x-o%tEAx`wI^vl?=*iHIj{44{*ac^||Qlw2bBV=#!3&?|) z?UH<5Y;G857|uLLQqxC0Md123!JHc0$Kw%(dH!|V-tDy#qsnDq^aeoOEyW^8m_%sCGU;q zcJ=OwY8T6;#DJ-W9vJ!^FR?paDc1@0nc~u4r-*ey#PWnq?`^H<7&Jy|M-&^PBB9=QA ztVz)^|AVpbjB2uN)_qkJ5S69^QbJQykO(NDM+F3=i6}+7p@{TeLK0B~loE=7NK=X`tJi&ebHkJ6J4{yMe}`V?Ajz3A!ow2N6iUEpO>r_!KCw#x_2;)9a8 z^^Mw1YrB;QPiuJ@-=D8|LX{Y_JwsC&FvZi3Se&J*xSXzZ)jdF|ayFH&Lb4$EZg;bH z)L_NB`Ev!??UssEecPwtZHT?YV78%7${PUh&K~M;Psjfro9DYE#mn35Ro7FBFXnow zjQ*zeQ?%~8ti*OC-(E?z7srbzsST7abW+~SV*1Ge zCdTxUp``^~Pz2jPD;+*s_I0)Qcx;J_IKCW;VW##nZ*>%(GEI>6T1;NxIBfk%ZQgiC zZJ|zZvW^l4DnmYS_W(EPSV^>`g##k%7uu6IZ!mj-88UFCL11^0zH;%Z;`Ao7KCa}H z;&Pu}lj&ah?aGT*X7X>tpl@zc-NztG9TkU0B17K-loC9Wpnvfi410XBLyy0PPE+*% zp|HalDtr{FCv{&SsO1Q*?Xrrrv*jgRN;zNZa#XunX~WHP5Zi+Uo$@M&JrOWPzj*z^ z--6&WYYa8}sDr0_Ls+hsf_j*M}NU23+C~hqkekuqH>qE$1E;dK6GKO_pF zTmY*5$ATga*Iy&INlzzm3=!*%Sdj)u6aZTLoG60WcO$6w?DTY~{MbBVUEXaP$sOs_ z{zccI#MSaPu=PASL}zMU>)Og^A75W?K5JZ(A7{v^hV$S$;8-llHGj8LK^oS1T;_$-0?}7yAqRp=^>G@@`OZi1BrSor- z9jmPnAMs~?bOHeId1hH;UVbJpjM9F~DN}szgViS&|Jy+B#P|9x@3hN6TgA0a0+DX9km_Sn)yp>nZFCgl6ezj-|%d`-LM-43#d6rtCV~Re7 z(DbhhamgSM9tTgyvBt5c9ZO^#y4=m_uakVH@vm-~k1d2-^Vy%JsozY`WT{^Z@1P;i zcYYOLxJ#qyIQ@n_LT@ffR!O=uE%bo0qrL0S;w(RTOK@@**X@MQh1XiMre2!fXrxQ` zPd(mrCk5R6BR3-453WEj=q%RC70RnKXH}qfT2MZ5DtZ0pv&MH?p~qS;i=T3I;^(UO zQ_NO$B5~DmO_pYh3x+AN+QtZf()D;*p9N?-ehS*e2Fsgkz3UT`an;ehGl?Ai-2EU! zJEf|{I-GL%s?`q2z|FrPUZ3Ob16h?bbxIkH@?IhNz~y6ChM0`;*NyStwje2;@>^Mk z^L8c$1AxTAp3-=asJUOU^D`GqN2OZ8v)S~ngJtXU493jHt(SxnDraXsD29_&rID=s zQ}05bJG%$ey6)=%NiQ#(-91%=>}4e+p*?3cYFM+pSI0+(Q%bYJ6o`0Ptfu2U`u&Cg z_9O_>Lw%s+=W%WT+sf0=NU`Cy=g_|MeH%G2@bj+0ydHGUezXrsx!J(juxIMelSm2*bg$DoUBdkxWfT2TT4!XeBPuspoPJ?TU3W9N zjytLH7Leibd|;j+bPm>({6R|c>VmL_+=?gIrWA5m;KJBwxzrRf_+fLQ&h}t}X$z9? z*2j(K5!~e|=w)hS*v3HsjTW&O%JjK;r=SEoTQes^&|ZaZS7WIZ4J7%isr+i# zSfBao#sEC1)G3m+JMQkgOxzfM!tH3e??1dw+sS>Ddv@c}37+ZF)bO#ulbsO}VxEu2 zshshCsGhPNacYi>v!dwJ!w^h=o8<$F>>>~yyJ=eK$+=+-4i7qpqRj>!| z*36-Aq7#AZaQYfGI}bqZO*1n6{OIp0OX&-N2{mMJ2^iW4U~X!OrkPn;ax9-oPg16z zZs;&c$NNkS1kBhEN9hKkzb+n<;C)!t;0)tu^IPr*uIV5ZTUDv5nV#$U!Z*%tare!P zKQV3;8LqHVsF`p!vM;ao=ZpiP#&lAGtFS>GyL zIKQlrg82a7Av<^izI6>~CT5v=G@JLPU_n{t56j<~!+TRV@AB8Bpw+emU!KR3pfW2L zNOxgx!WRX)i?iHG)Bx{hpG}?j?_8*)LIi*8n+d@)hRUO*sk+5IjvRi`1RhfRg|ghq ztPn8d4CVo96VEmrOe9;G;B78yCrUE7SS2wu*7s(S8^!y9R^2isiR<4Mo>iabRwk;U z_GU&VTR#&DV4ly6XP7{cHaFT6SMrK(-*GkS_?83f{?L`}-KEwYRyO=G;WK#*5h1h^ zWF^c!CJo=w!CWwK{+Km*aFfW(pXb?cx)NcPfUmHdtVy%6;v#>6Uwi$-4vvqIq+E6w zS&;K~4Seljv7iU`yk|0YQ)F|%LmrErT||y5?*Stp4!SGRaO=g1zQ6$qfkU@Gq$He7dFZJm zdGm|pJIT&s-iy<%G*RVs>6qm$>6Tkxr7j6Q=D$q4Kl|slp|-QyCnO+XoFqgV zQd2kQLXUpwg@WSD-p(xdu8V z_loXkC=nMS-Bk_WI;nQwV~gDK)65JOlQ7Nz){s#be6NJhsLbM)Mb1uCde|i->4+B7 zXr2mLcLpK-dQQE+?y93gPv109V&t?|ef){oe^IvRsv4l{Zr<6AEby|Uzs3Ob{#Ig! zfTU)6hx%!nu>XvoUF$wY;s5V zhSeRwRF;{OPD%hYt&F8_>g}2LvvH2Y$;Ky!!fG3oP8%lFYJK#>ICt}K3q50*DDn~V ze(E#Cvpc2X(sp0msIv8>4`(0moKJJ(m6|4yJ8I;#!jB|1Fr^P>>iN={Ti#e-Z61Bs z*5jS4N^07ETiOa8(5vnFMB}qQ|KWJbZ29Wo!ls%TM@bcMi zY9F?H#0Nmjnr_!N@Im+p67zkg%OP4V&Zg>d>!uB!zvOz$7ue>q#W4jsQIoUsH+pNI zn~QHvB{_t{e+mKRO3H)8B^wy($Z8v*QADN1150EvsBzG!4HDj-br2Jw)2oN4?}(nD zCTe-M2XOV&(mM8^-~@Brkw=JUk7 z<$+}I)G_8okJa7S)Op<$-R-{!i|pQ%Y^nJ{PU)piK+0$O)f6%}^m}EoNbKc>QI8zS zb(!slxWE+e#e$-+3zi z;eHa?l^1hi>%w{MNunIan|6fCMf~K-G1=htzpZLs?Qi5e=Lg2L^#%)^>*sPxJbE>MOC`u`cGsn`M5#Mkufhg%i>GQNgX(t0Uq93;n%}r}oJX4IrADZc8dr25wnxE6&+X7eKct zG%ZTVBwrAkvC{1UoTt5%JbSydYl? zVz(u;C1c-C+H2hUlHz?;PJty7W{a}&Sfr~jxN(y8*&D5_t{rDUaBmDhOzjZ=5FXmzVbPo?lii8+t?ppJZQ^EHDL=JQuq zKHj}$Z&kwWdEB^1)eeuOv{yc|$l?^c)Of$24FaD@mdpfW&sDnfEo?Lu=h{O%#y-x( zcyW|o(?nkknbBYT{~|B;xg`70ZKMb zraCq#>D-+4*MS7t@TB^TJ?kBe7d@M^8&Dpe5lvW>k0_%2qEG*E5Wkh^wY!udMgd*4 z<_uy;sE}~e_#h$OVqSNv#R9g3DFvO{np``!6YKGQMDnRn%+KqLHtoWC!F}ALLN*!* z#YD?;8lQczk)7RaO93G^K-BC2u7k9lCXq7Qzz)Ew@G0M)A6c=NldC>kWW2nV3Vr># ztTjeX#a1$_W?qW%^z4I)eqxEP$A^uI=mY%z`31jlD<`d+Lo^cZxap`18I*r~!~Ywv zk!DX}W|~*?oUU0*G2d=(7=FLIJY8vYlU|c!)Rcj9NOi`3f57in&sdMw=QJCKXYVZX zYLkn9>BG1Ioly!?@vueiGC#bXyzB>&$hQEk%4_pnI^YE{o5Yql0hgI`G-X`M>&%0! zjZ;0sJ5jL-s zYlasm5OV`sKEi1G*oExGGk<;VvdDxN7jvSiY~<_A^vcyo=ODqiYr?(c@|vXGQkE>c zt!5-sG>aDef06f18)64QyruMC)2Njo8g;hNyHhmb2$dW9+&qWScP6|5A5I5XY~Aw~ zCYdr@XL?l9GcEUg*d#vvg>qke1YBPCKnmp_EFSY)nHZXWtcHIJzgUVgZJ3sonbR`e zQfz)w<-Jv|rKWGVU3OU7V&M08%~^=}@zr4lqzi1g%}tkCb{z9o3eL1_?=jO%aGm#; zInaUyB^lPOY-1V`i)g#K>wgb+Dez<1A9Dd0Q|FQno?V!hXp3z87Fie}B;GQp>%r4V z-ZWNd82_;G;~y;mp(YtRWT3aJcO9>Lv{a4FBXcq0%*NGA7xw2apT>mCa&cb z*jT3dn4jY+;HqqEaLrpekb~)xq(KbFbd2vYOeLQRx>f&kpw!XX|MS^>J+PO%Ig-<) zQ^+r(%XGkmx|;twMKfuGy2RYAL0hiAtr(9AE#S7#IQKW*yyUdqb^UdoT0Lj;gI&3> z8HZ{5@?_9PSbdRt0!L}JS${aUQpeD=ZKTvNuU4#)l#_+h`WaK%IcW!8t-aBAuFqcV zhk}!9#pZ;-Fyr}I%tl#KhZo9mV$QeNv}HgSR(x^zDY9e9!LDl6qbELfxexxr;^TZ9 z)-VB@yz83giW&@&_-N;$mFHJJX?`ton>27$WG$8cSHXtL<6}G(Koh=`l zidL{Y@1>YA&6ne^p%sEo$U{*KZR9$or-#*MMZ1%qM z>^OESJzSy}=T{!TzvTAKZqzW3x=X#5HX5_ZPz?L&1P|v*%ZuKvZJeYPhy;0hd)qR25TR$9w&%%)(4RT%o6!h1_`7IBr%=x~w-S+LUngNO18xxcN8VgZ~oegbD5+56jx!#($zHw-|5#4$YFln+KZ|ijh8M)lrcL+ zDkR>B1Kw2+Z;SJ0v`VOP?_m9u@GZ&sU0uD?lHH^?iTRCRsraH0j|YqaaK`5$>HBTY zW&6XsBhR_HZ-DYc!iUb(>;}I2=_4P_I*%5uV>NDTw(NSDr?CFhR>nztFC3q}7z+sJ24bEZ}BQJZ)D z%tNjdDUvRm(d^iFUIkBfqTU2(oF4lQXKsd*Z4hl+4A>KuE+UETlh2a(Bn>=@`uxc@ z+v#0(9L_4h@V*1vGjBx|Cl^OR{b<|wql>=MPo1S&N`9OTQmm@t+TJalZQGMqoJfN1 zd5#Z|v#0G7&ny&wttc|7^jYq>BoVv<6@u3LOJ+L$QzB-%$FjzJ0zRVw9_!xe=P+PE z3fQ7R;b|{tysy|WPiHm}N~?uhh((FPO??Beev#hW9b(8!38*~9O(k%d8h6}3me_~E z7Xa(lOdXrt??#z>y@I5!$;kgKOj~N5=OQlQ1{(|Y?8Xn2f*Og@5`5EqUAN|Jrv-p6 zOvRqht`^Uzjvp#N_0L&{;w!-rZjWza>3-}_qpGhv)EE+g(|z)DcV3=hwwIVu{Kn2X z!7fy%W;E8W5xE}wOGeW|#rI62`9V&;BQl^BZ1zvOtbix*wRN3^(x>*{Bg^kuRtvCT zdzn8DYzMAji#LA?x@+I7QC~!R&I02tcdnOTqv?e8)YEKxVR0dsK;rrhF47M+}2iEv}_RJeuooT!Ew-E7;Dg8 z@x=W`5;Av0GhwSX5pN=E*U{p1#zZv=o3@Z$+x%en^*50a%4C(6#r4`h2~NumR@bF< z-GlK)OhR2i%N_r>P$QAAo%^4A=C&Jzm5pWFUB^GuI!9PuKqANZ$|2*vP?YlQbcs6yJLqvxh7zSbRy(cG^$EBL1MMr-Id zG4-?Ny~GnmLfY=}%LUAX*5~?ua<>Z)NDMqq34*@Cr`?vyJ}I!2iS&`e!rTux^*j{Zal z`3n++wDJ2c1i;8V~5dJQ`L_$&5t8=r!u)96nPc&ODFL~{1Zgx-F8mF7b zt~~I7DyYz{?Wal$^DJ=;=kabhqbF@g$${St)hEGTtW*m}^cldfV^0>2fD+F=hBE-W zVC1dcjY?Htg`g`1!T(bo{OFS_Cypey$B8oLBenYE)oic1T2CUrCw%$#^o0|@A zjACtUgV179PLNCYU#>WvB$3(!cXn2h`&6ays_#d&Os+eEuK3J^G6dte3w-ZP*~3rtka+tbFkQ|(!&=WGlA z)ozB!_AAqcA@q+RY73mYxc_&JvNc*tz zmpS6=fG&U=x8$?lxtQazb-}PaZ7&Wn7@qz|li^K0g~2p{)J$;G^|RiaSp{yw?1F_-O?E0DfFV&`lhsT0q%8@E3+ zrP?^V)ZK;fin~IEeNtzPq-%e?dw-RO;~6fTT{er^Pm~Uw3fA>qAw@5KP2HKj)EeIB znQ^($?J^I_9HcnS>L;1Ui{=?M-{=RwV6Iv~iboa1938b0nd5g8t{VQ6d^>*}!+UJ@ zzBL*b$=!++N$wr<%9Dx$l<$o=1RU)+M|wIIJ|y^H$2*;S;_C!usN6Zh0`7;Qkz`1r zdlyM=@|}+&{-=WNpYP(Ko7|e#$LObS7Zcj~`DnDD_Fq=U19N#cK3e^}l=&SC0$7dkV*H&7Qa zlyFK?PLQJRm>km=yYqn#y}MJez?{+i#EQED27VjO#f%u)+nBuh>vmP?Dl=5o*VN0y z_{MgFk)6);5ab!+Ny zTcjQ{7u%>?r#v3&QwBbqKskXbex9ZHF6$m`(!JMr9PxAiwqusnLR0V6gwe|us8a69 zf@b=np*)}3iz0Dz9V6ZepIVOAzW%53OFi5NNaS1ChCiW+UDwg4Y9M87f~G*}{ktH4 zVt*b)v{uh(l_QX*y7ab=hDQE(<*?uH94{Ux?$oytIMVi{2;W8XSF>Mj#a`MtkJkCZ zrTZc|x&K49tYE8kMH)}Mjilnff}fa%OtJgV?nI&{)X)s>wa1|gx8Bgl{B!$cm8n_) z0t(&N=c^T&b}ORarjGKB15rU`?(4Xf*%k>G{z_4K7S7n33N-~rq@Lx~Vp@Wg{c?Ke zWb)bw_oV)O=C!uR$)W{n>yvDJ(Sp(E<-fUI;tLHE^%GTTc`}REdCH>^E)?h{Fzu$$ zMS4LiT17{%g);apo2J?~`v+8Nv#dpT*hlL4)y&;+8O^I%Z}$!}85dNa1+5$~ zx&=5#^Od(*58nLu!}@PhtkgSGxkPF;IhWgWPN|^O^e_) z77Iq9P-_EbWKguwPP9Po$;xz&j~lNM1G?pM%MKQJa4D67g=!wEC5q-6{u!ju$Q_8- zSu?@tBerN9g!BC?%th%iXc@AHl?UpN?Lbp`N8PH*b=#1I%t8;mQ@=>Eu52IE;F3P6 zC~kEbc5(F>;=H~q!vc0v=YG~ff4Kc;jSB3;D%;FYI5_ ztciNX6Bb(VcQNn)p+d?^O-wE@b>hy%1Mk*)(P3}7uA1UdFV@KbET~D6o?3wvIN06#rR;Q$;v+&v4(vTP$tki)W)H zf|o1q`G(V4=A#4mIpb`t(Gufrk*wbJoNU6cS`+OEt4(N74S80M@Is}ch6Xld6j)_$ z>&#QGA~+Z9z`o;ts3kvAqZow7Hy86s_wtM;u?qV|3- za4W*-vuu1s-#z&8w#fSj=+XM3Ptpa*B%#i|aU145 zqP0iF8H9h5T7{5jaA;RAWwbY!WyGIpME@Y5|8nHWmjbZ|IF3^APWrI7zu+q^@LVCg zU9f9bSF2QXwpM(Hz5rBK_>$K`o+E^RzKJ&BhI_zUp3&|HX76U|&Z+1iJ{#2jAh0xz zva432DPC!vZ7)EO-OGX+#b0$Ez!TN9@~?8 z(Jzc)b^ZL5&A}8iUa`{d+^*sz6GLvrWcLHl%O$rUnXTWWAQw3*jv8iIsEL(gvBGLx zT=ByiVG~3yR%A)^noLKM5U9J|^AXPV8O}dQEnA+327SGV%KK`F#c0A-zFZ~)vY0F9 z$XZJuSM?uvRLQCR4{Pzi*reZ&a*9^x;st!3zehv?X5**w__-sSK5%_w6|mJxW4(Zu zG%icyi}z~Ob?^E`%=q{N6rY%>z1;aGMAB_6eY3*-Of|~?{@%jffEt=6KC{gI!MjK2 z6T=vINYsU=D=vf5`a^DELeRCv#JS4hF_hi(2oAjyCEXIuVYi3K*xGprWQ>prZ3G7| zsFk;SBGl7JT+kV#mc?J(hwQ0Ug^RcB8Gv0Q#f$ZKl5cW~sK#{4Xx=lR1smb7G~6Fc zH>OmK9I7vKHQG0^)6}n=Z!|!DCB^V2q^!wA@xI>VbTZ>ryClD5G$sU~;0WdBYhz^) zbYvpupE@7Q;@>CGR_H0=UnlUto}<6Z^Z&=C{+Q|%m^!(E0UVAl-+vTt;rW4u3B7j5 z^i2P>dh)c0TUIO!dh^1wt_Se=Wj~Ep4Md${P>Tjeqa-<+7hPk2SdCh5ChMctLcK?A z5_lgbef?1MGtkN{oU)~jUEtvq#~$6 zBc|zgGAo~y7~+i2aiN)+52u*tpcEETc>>^g(PIXXzq0kCEA0KlG))jYL2>b#>pAQf z4cYnR6C~b-e`J~83J;vEU?Xq*$`e3*O|m~>RkU!Kt0+PHI4;9fS9RNh_uWO^fUizajEH7|V=sUfv>!1=3)GDvg#-*Lje_+aH-2B4S z%=U>v43~(ALN|?mD#kIt!nxkySGaylR4QKkRl0v`6xRYW<34qBQ$i_u$9L;{EFfD% z;mWiglmU5&mpwduNqRxLe$+`_pY^+^O*^FI&yD^U3-!OB%HNB4BCtOHusgp5eAodt z)Beb%AsH%>!Xw3IdohNkSc{_P{pO)m5L6yQ5z9x1=nI7?1rJ=dYf2KH3MX{OUbUM= zYaqsUXA(}$;Am>SZv0c3~aPD~+VLd6qfNTH5Xr>lXg!LR%K zcoDhN4_c3kEaWBJ@441czN^w)9}6Hm2p#8I65Z5FQ_^AOV~EP7{(=CojL2ZaKXyL% z4lM{X33T7m`_G>2e;mC(Fm>-ueg44^$|WCxJLJ)^J=35Wb7`BOQ?Rj9JxoZ}R=CY` zJ$ygZZUpGs9V(^B{PTWd+uWxDd(#xgz(VBZb$Uc5;dA%WPWa*g#(}_O7f?t21yuTC z<~J9&_eS@L_}}c$TEpRb2LW-A2FCB%VE0yH3w^p9VyU3**>MLC3qnnU8)D-1+VlJ} z@HTkg-pE@0bq8%v;U8WD)1Bx29qt$J7mi$~h5Jt~tJ%(&j&Ty`nmg}Vp7G&lMDBVF zI|<*FYjcz7V#T}hgYuf@BJ8DJ(fsmxz}qO@odxiO`A&)9nb-NKBa7t7dfbia+J6bH z{=e)LS*4!wNe^^%V9cU!7oi>c?yfFAqcQN|2bFu)iNfta)_t0KOp(HkPns@eT)w@`?Xe{` zKcApw^2AH7FW*UClSys(nig0Pir|Wl?v%L<5>$9+cj*~K1e67IxisW`YfI{I9b2-X zfN#z@nPYUcGA2Q^g(_!QT*~?bucoF1rz`}`+*BPBO#8=tP?)iD)Wq2C0<#;6v;Q!& zf9IZ?tJ>3Aj^j|lUWmdhG+9N>Cz~{gw3?$BJ=4tAsK}OO=@rF)A)}F2Qnq$7ds@exXMls156aur3_lx$=1XPaj%Bz8^RE(mCcI6A&o7Jt z4?FX>U8zUh@RTLL>#v`SC6puQ^V_DET{_luKS*u(4ad2IKDBc1*W4xEwaca6of5gc z7krvYFuR-(Ln`kbx|)iugozU)=%Or8I#KjhZcYm1D*lqQ6y<69ZlYf$3m{>D1mMySu~mx9o)=HImY#bjN=B-W%NLqRkUB?_W=rARS2fwNFUgL$3M@ zh8BIaM&a+?ozdtLUiq1;QQEHDiLWCT=kJe=22C5V{|B4F!$KFp0c{xI8&%qV)00QB z|0UeDnHJm~i@N{lESt4RZ&S#m&ikKUc)CQ+FSqim)AJowT5e7Mys>{}78+eO{5*>I z4m=};_=?4y-qO_?ZUWi?STkby-*2w)@2dohxhanfpt4fd^FXB*2N5FWhx-osZ%O!q zKdcr8aBNTd3k8cJY8P_IH%pXtj=WybE$1bZ1oDY}?cq?b7AKgpOu(M^EaoXx2A1?y zq1XCjD`)(8;{-ADJRn)JxKrXrjq$;Q{s+&yI^&)x-WubYe-vWUbgK|6Ixab`9~^Ex z$gnAF40*Pl(ZMojyz+==geV6Tf)oH+0+Cy87W|sWks`*ZDb5Oj-+P(WhN`RB% z)hk6$5=cB8r=bmuA|(){Vi_-QxEXaelZdYjW1cD_r6+7h|B7hkFZ13hl z4=(+(QUmS*1t%>`jGbx6Y=^|W%%g-7w?0*llX}#KI&l_6?rYN%174q~-#Vl{#KwNX ztKI{LqbSBA9T{nO*}QK+AciFspjPH~le(iaXyd~+z4|uL8rbBwrkT zD1#YZcrt7{m{+m%&b#eGLPIE(t%>g<>O%ZM-R3vIhTGij#!G`D z>(e%d&3u;!lW3n54iu7bPA_co!BFD>pusI^Wxm0D!TH?t9>c6f&M ztYn>aNqG&<1rrUV8L;&Q)QkMdYg++!bXkC0*+ zs?&mz8CtSAo^R(@))5-&0Yee(m3`G$1`Xm&Gyr2OB$95~ZEfy$WXei(p|I8Z4gaUl zkk;FgHMFkzcgBgd17IV3?l5c<^AHGPLhdTisp6&FaSy?ak)pdAwmi#P49F9e68#G_ zkOtyFQNIkA0V=WSSzn%19J!0vuYSAP(<_7af81=nfzZoM-=8NNd!8b2+>N>>0K~p) z6~wjmRrtUwb%Q2!ZfZjA5^=o)=wfk58OF#iBoEYW<+_v-0~X{7Ghx8hrhC8!P#q}( zLuir-<;c* ziPi_`-PR_9%qY#^#ly)9zIhnp+ePk*FUJTqwkLAzL~YQdM4;6JDXYH<+odn=7D(r>1xvwb zJ!TmlV^uWy6pWkcW5h?>fFX3{LMmo%e})6aDKcZ;KYWb0Zi3)EJba2e5-N2L_llOs z+sw=hvzE@Q9-kAZez6EkzHNV_SHK~R&)BYCAAzS|&zBA-8kAQ(!IU}}4o9mxy;v?z zqdLkj|0u3fx(>NvAEGsM{l_BpX{2e(vm0DOT+R6&Da$3XI&$A&#BI0>+9av<1E#In z4yDsBzx~AnW6Y5}Sm)LT8uI}juHHEv4zxk@`8ie}NDRA_*G%5P5L{^1dSiJ!AT5|V zoe794fGx+klopFCONT^Q$gwV)cHxgS<;YF+2Zu z6>cYVJB|p?gpuSe94%0b%Vy%%^_uxLC9<$+Wd)VfHFy%psk~}3cT8hg@!Q1gPEyG- zz=!HJJm#q|`+8cIBXxk9Z?V5c&ecQ>7S_(TYsxXMFA7DjTBC)1mLS7hKBe-Li;;wR zk?|UKrEDi>;Q-PFj%~lau{RbD3+C?5^2C^ps^r!TLv!9eH5N4_cxlJG7LqsY955b% z>54=d%af`^%3|{nkE(wbif#u-Y(BC1@A&8F9gVEoktPLM_&=7%by$qBOuNllqj!dG zv%LsFeu(BHJ)LKe(n!TC{Y1Oyrk9JHC!HHh(S6+Dc5NBv<~^3)(&8I%?A<3+BjeMQ zGo##hyu)4Yw+AZ+C9?r;uK>C)lStYwdr3;<1Vy6H_L8E(-!%Kkb-EP{60wv%mQc6e z9Qd*ha$5q%pNUCG$i(P}?2k@5fswgI#N{$AnnXQJo_F0AD*rR?$z9hq|B7s8MDWgz z&yz&^?jtIA1ZEkC5gH|=V=sSMGqN$ba-55ARSFUx=LNI_f>+}%%( zSPN~U(9)By$x%@7vwx*;hknPtx#$}Izj2Zb0|UR}Da5r<%|;F6{uB*rl(nFKdN8d% zsH6eR*4OIKXa%()m<90RZkc@OyHHImO?GEblSptu?2$*%N@eT`(ll8hN#!s8L?{+B zc|b1raliO#lBQd?U0O|3a-wE`1<^jBqp;B_C4X%h2RQT^GkV=)bHJV4iZGwmfWS|FVWuCRuXI&QMAOW-LD_M}6O6zSsg)yQ)uPkxgd}8=- zDGa&5b0q0y-45MGhIcJnpP6A_m!;fk0#H`?b=ay$TBOkW;!_=7j)_k`*~#t?xsndn8 z$EIi{$sN}WQiT+r``wS@)^SUv#V=$By7Vo(Y#Vh*H3M|%vhlHAK&;Ut3d+k_`-O_G z9iRKHj7pA124oPgwYXvTCd(%_Qny!THJWC(hT8WznLYPsI)Kg$9%+>M>@oVDUCX`A za;1sxy2TapglHWG_1=uQxa=i31#@Qg_gJ~3GM*qIYfR$ifThC@OlV_+V1yEKs>kF& zx6EO9aLK}nBz?|GoHx%fBT~eCp~-t8X}nT#MVi?GB)fWr5RA^(RxrDDC8)h8U_*-V zYa+W$H)Z>qcX#tb3DK0C5**Yji|^gm%&1Qp#*VJ;)owd&E0HD%I-ojza6}(t6t@?L zg`XRSnleEUvN%+ir*dbP>!xnE>8uD_$jR5n*X*9rR^oIi(OE9*nc^ z_*r>Ik964ekl7uQ2Y6D6yoKCSLirwWVEhtNhjrtBr7@?6B0&n|;1o;01@4JTYJC^t z)8Q}gXuj=J`0IbGi2o~jK}(gMJcgwez-S_!t6$4V?FV?(Q2E>F`o6GR#IxGYP=On@ z*||}+r~1QuYwC9qzDOLqzt-~lHXR=FNhA_M(ThyHX|RfgzTcV<34k#OCiz~D*tW7p zr}EsLZSh=Y1w`Cc6j3^uFgdJ3c=+QzED{GhfJZzBhQGd>)Re85ML1aEDl}|fy)B@S zH(4#DTX&;`I0qSr+XLgvuR10Xb|IDF4r(KL69d8fsgm)DZ%)d11%s6X_T@{8uiJ-; zbs0T0c!UD3B+~TF$Jqmhm&I%QhVRD{y@SlXpPTsiaCdaB%O~oY|C-QjfE-wtx>V3w z+6QUVfJC$(a5u_FDizhPgX{7X*Z9Ci*)QWB{bq#f@7|*kMD&b1OJ#ooS4#(0hCD5n z@5$eXW*!$`6i%D=RI^scQ!iUNggmZw5}?_T(3cjw?S^vpjBbovrxV|8Jc?9V+>{Qw z-jpd^*vZUJYH#xmeLJSSu59W#!?S*g?R_l1##H=;auqy z>gknww?+dzHZ}N)ge0-|c@&kqzj(~|><^s4eO7MG->yI_Qugyz8M1sf%@voKetuYW zOWD6bQC54OM{_1Zv{2^X2YRp(Io zj!MfOITO38qLM9;deAi>k0(iCDO&OiUp#DmpB`fCW-+uBvyP*B(B4e)_fVr<{D#tm z5@icnww*$)BOm0S?Av^`!*OanvO)}ElE)lIn%-E!b+3vj3(m{3lFotZl)>Lzg;Z2T zWK$9NT>=yK;NITUu+j+7UAB3BN%TJNKsGX|0@qire+e1NE**I$hyD1xo_yqs^17e7 z!-_CHuz!{*polaoK?yFSqfARg)i`SpQN4=K%rRbMu>MZU7Vq zXUovPk1(3@`Er_prv~-ZftOD#mWByrQ* zu%~>&{xu=Cctt<;rCnu-lWz=6SGPs4L7v;U@LdiGs7OFCFqXBL3pJ7OlRj6JSk8iNZip%R+BAmsUW~}D8huJv zvm&4+U{)E`lr<|%ch4F)do@T+ILjuxk4ska9iPe2Wx=={Ab=JF|1|A?yl6{O>t|jEt#Fh&da*o8k?O1#vE-4Ubz==1;`BGS#sEr zDbX|R;Dbw$no4C4Y}q4a)_2~g$8o_tzc#{t7*38dBGV`a`@#wh5SH~w@bnHmCZh+rVBrm~UVF!cnKNuQ zHHY8zcwr@9T$*pOzUAG+P8{X)?LUDrbM{a0s#co`=rJbRhrR^JgLLAl3O zA^I$MD_}ojPXlRZ=4r6E`UdO7VIE+$w^j*QweDrgtN66ATUn5l!UAFH%ahNh<=PKQJ?3}q$bKqC3WVf=OOLYI<*IGO)Z^iilBN)JiR5vY8re!DMJCx3=_Yb=BpLct+i68^O`(t~E z%gKr`@78(<-9YK=}n>TDF%Wud@Yd5_f#N@?8UJ?6k?@sFEKo^qXf{PeMWbDUo|xzx^wiZFU~wRb-&Q@xbw^B z*1{SFF$^AWH6+*EDjYu8T&Q#?={oChMpM8mY6cXvHvuJe{?R#SPSk!R}Y5aSnOe#R>jzI5} zWn|&2ZoG(}Z#)k$!bW9ESxox4Xul7=UXD$_xR}&4Sd4_WIS*tv6}rj@^zRF6SfA^- zf%wS5acX*&$pOW&s+&7}WB*Ly}a*>+o_386_9P--Xw z0!lAJXd#Li1Zjdu?;uF;9fAmmKmvk*NbgOWNRtxjz4zV;z4vzVJihyU`|R`WagA|} zn_pxk%>EVM;%LQ0X=uT1fLxL#|r|RucsA4R@sL#kRyNIOlga z??+Bl&dtM-Hpwzp^^DRpgs@lNe{HvmN)BGYCH?NXG0!lTh=|(_R|&X(;2GK&w@GcR zj$Y0Gz1A39M7p4MEjj4T?sDuBM4&8Xl)5(TsNQroFtE5-`{*#?8A_{US>AfSDdZGt zvvPd0er5xGadfhnyQOO}OF7y4iw^PWzS0GL>ZcJo1cm@5ZRwjVW#^eZaDM{cpSq9pD|wM@N?IRqH#tFrjN+saQ{~qzoe2N~=Ytf<8)mLaZp+xZ467;eYVx-b}EZ+x58`mtpC2m{-kD@(gy4c_h0Yyn`|I891*ct zV3UTM!NKN)WA-66pYSQA#S+i&g0~=^Jwz{fS<#ya6C@{z;6%WAE`c+o%izp(sl>P` z!Xkw(pk`AUTO`e07V6`6*(7ewLV)MOkgktz9l<9I!5xSRdC#f79{+dpoFcT z6$XZ$t7D=nhTbAgI7k5U-^%ZAenMNryzB35yXy0$Ko7tr8lP?)tx~LL5G>ctPC3lP z+op|&bAFfG`1e@}q!J_wabI(kaC_OVUTLSx@cE0jXXp+xBJc{cc=Z52dRD&~Mgnd; zEoy1F816=S0dPuZ3FAA2pE5_J!pwl=zz|PNL+W&^%#ZI@B?CP~8&7Tz;RGfSL= z2}y6&cS-9)0gD6CWst`ONL_OKCtV+>epj>^sx~{~Ogj!ccEwXmp3rZG=kz7U4zvwf zEU5it^R{eq%IQrcl0@y0ptk+Hi*kOakiEOev>vIBrKx?FX*F5e^-Xs%%s zn6J6se6~9~+dnnL(3>PhbCNVmQ^7cTD4EZefTz}*4Eb#(S>ugYK;*JqWmWjVL@jD5 z>s@#Xg=Vh~on3;Xx|V?F(|A5b6b5%5wUjC5YEPv5yE6DEXKyfXNaaWPsm%LPI^DL4 z;BQ#F*r*`IT@`K)nY#;1m&{^e5#N7{5rCL}eQvE`11x=B=PvYDZ&kX}rot@Nl_2Tm zhHF+@_);>Ewj)XUgY)l7To)0mtxN&;G9IE$t=$*MwNKzSOBqkq*tf1CVs^8`p2jQP zUl(dBHXDy^$8=Lri4g3h@hEt)ZcW8U**3Bu(syE7$rI5QA-ZeR?-#DV{2RpniK_qo z2v9x&Kn^QTuQ`9rE$#Qg_{chjeSYkde#y&{)5>Nl8W4q%ei4l4*de`Jtb}tjWdQU^ zYta@Fd(z|K!^;uu(*c%S5*!&A^;63}A+^!};nl+lnhGX^eo>Zt36`{We5Yp8DWb8r z-*DSk{JzgY<7|G)-|(SX+~0`m`DE|jPot6mD-nUD)>6C#X{85l>7T(3px{ zr*E@;=mUzM5*ae|T%lg#p4p!Y&Nh0Lvim?GS4<8Ro`@^v~JhH5R zOC~BVMru#`>7Pw@&H(YXh%#U$n0y@Ies1yAn*%E zy48xC3}8l#j87s-j3`y3>ecyHoJj=s#%0N+?=ktE8B31Fi!NG9&a=(4+u}b{d@2Ex z>%^5m%w3Yy9To&{4sp+3k0)P_Z9@zYVhl`C@=IfGp8a$aGHFXS_OJJiQE+y%c%-R3A z3`3zs0dQu4>oQv6N-dv*KqjP7YpoEItG!wIA8<<4L&?@Ba1D?@4rMy_iCVGL?an88 zy%E9Wu|jQAxGsoy(Q;SO;NZ(%{@c3F11FT9CEG}U z%5gujpL;hXKf}M4d7a9Wc5Zm2`~1+c;hWx&M}m>0lhVTts^ACyi`{py-`f!;_FiBi zJ;BrKFb-l^A{Gin4mm*AfSa8uS3`);h6BW#R3@+4zv=z=&#mb!EX-|C5XP}qNRvIT zM~iw#+eJmocYCA6W%4zaqID5h_#NL4VBL|Z@8V(A2^mpAnzZIG3WuZ!7DPtta0EGK zST9inN>4TH$44@J;HSY&oOrBL4o!LgzspugQfU(j!w8OBxRMC(`vREh+X#H7Tt z%VD>l&(E9GWJpvPJvz~yzb?lne*!#etwxoOz4&)ZSVH-jWzMSfecN0UYwIOlUTqVe z@>Pq~dfbFHr9TR5EFUG*{1UPBTiow*-?x)Zi*nxS`p@xXffS9~=@!}MX)!s~3l+j2 zOFG}5p%CG^C>v50uovr=blDhkfJw|s&*LU>Eo98#bkbodoZ*LZp{`JoxU#qUSoU5| z>0Eh6D+&yytWj|fiO3KQ-bCuXqvm386M$R%8=?P+(*ONPb(l^m~M`j0nKZlpkWnoY!D_$b3*QyY^I0(27)#H{pKreHz1;CDH_OpYr7C z%GC??)yD^jxD>=?_5?o``yXCXxj&0r@ehuNL;D(ChKgIpptkyJi&m6q;`DQz!4DEKL7m{uO1HSvsg)#3o(4I z(Jkgzg$~wHqZoU~<7y|hf!o8sEahJ;+q1D^kwx{zt~%HKiyO~CCwRqYLKC@9Pgd0K z+(?i4#vQwyhQc}O^xRlduKcR?bh6t#uMMF4Olv|tKKudH2L;hT?vP$;PC=CE_KP(2JFob&!Bl&Oep_D}u0J$TeO($&@2}1P< zR2$yg(MHQi=(T*UKsVeIPcYe1lc^h8IJ$Xy13f*hy-bFt8or`%1Sx!3m)f9v*u-Ic z71hc{aN3vn^`RQ0eUIR1Mlqc&oXcHhOLQKaHI!C)oYFaO|JN*lsb7^rr5SC(ra7n} z_=z+nSxVS^>hXdUhaIw~HGknkc9Ybv`TY_mcEH$bYH%<8oS(?O_xBJFy)sy=bPE7i zF~xIy-=zVf>eHk8;qaQepH1^|8;X&c8flm|aH8_Lj~0TQ4QUNVZt?|8O>V_$6{DluUXlIC(3S1WWBN%wF*9ZhcnDY? z9?i43Bm9c@yAHl`vIHI3@p>I!K^!0sxKwhnBjnv^^Yg&t^uzC_vh^+nPaGR%lRT%M z*(b)0@pl&5tJy|JW!uQLXO04H)a~8=hTZ>pv&n%F!n;>9V!F<(xEXa%0P&4FT6zpL zj-UA-(2w@ibb23*>(FZtRt@-vp{(4BJ-&GS33FsZSLQ0P%w`uqqcgqvRH6lGy~9!} z#3dcv{Z?|(BHstZ^3_e#_}9n^VJo9ldWg|}6l)B@UetBYY2C_gTFF)!{y&E=5C3K} zP6iCU&AKg4bhc$0O=MhKg6w8HJJTEXc2w+k_!Jh92tRRFsjW%v+98&ir83OajeLG- zzwa`Apo8#uNWDPIWfz7K>1{M$^?P*9wnb2OGthjbIOt=WLgvGBuh;CzL`_cWGX8n| zI~+Ofb_()H%k`kd8E$1}d~pY96+GEl^_ zh4Tn4Dr}|m)Z2XFpq52{^rIR>Eea`^mj(L1&Gu7PzOTz3`7l;VvT9llpNDii)rLeZ zbRZXWrdP4yt?T+e4~A`<`|?MKZ*eJZ85522!Z5oIFkiru7A*?xN6HFLV_q`nhFnx( z2)zDuvaUo2@8oZ@^jyBI6RI(<0i!XRzq|qjj{CmecvjzpcRZ&(qhNGWW4@MiV}X0l z%8*oqo|gKl;!C81FXS5P{KLupV`uR<(hW^A3sV9|zE|A27S!y2V4K#5Ns{1dOJTm2 z4Jjw3V>T=wXd}03C)I2qu~}~)yxZJ?OBX}Lc|1#z!s>k5*`=i>zBKHgv_scw>GS!J zFk`qa3iJ9db~%Kxh!GU5c?75|Ng!Y$BxzY)2!fy=BcAdAojmmsMlfToJ}hDp>4p4~ zzt%P(>n2oFlzn1m5umh#sb+LWBHj#d1z zV@M5%+U5O$O#jF>^!-F6lkI98G^U-52##GzSck-b$ar8_&MieV;8>221^)B7Zh1gP8jwqjY1$IK+I>g3X_?yh(|s~e zX>s+C>3J{eE56k5sNq&+f_LuQwiaRm zTtbqIt9*$w}XnBjb04s*KQ_SB>*Xj+A3#j|DhZKY<=GrLnc|lP^ZDzX1EI}V{o_@DT}_4ud@puVUS!C&O~|K3Gzq*pWK4HUE23za%zrF)h};iGyw ztqizJ{jb6aoc6<92yE`X1qiK1$JKG(=t_BhfdqxJkiCPRx|yo2gndJo&F@p(@QPc`2)i>%ho}>^^XxApS1am&uO{ES3QRDH-4K+%tQMBfz-Gc zApgC!28Nrf247|8oYg0*#-L$e=;-jWQ{*cKhNI_pYDP+4ZS^=gG}?7h(8+kc*2{Z=C;( zEuXtu@Gtr|4FOmZNVGa0Yv2yg+s!9@a*rfYC5a@#kVjJFlDgrE4>rFi6riYLIg8*= zZ0|s8SlXkYsm!r>1Q+$M&JoUw>bN?10X(IClMAY>=h`Hy)V63$JjstcnB0J`pyT8+ zBLxgU)D_}_DFJV{si8dX7KGJH#B*loQ&-5nyk~o+;0f?Ik;hGKmKgY2#Sp+=>ND8S zc>RLfSF!{?BSnl>R^DD=60m1s%qC6ayO@}u>*DM=tl4!}!unS_;HOEN z^~Menur{1Q7{f2pFuU#TZTiy;68a_ic~dV-vW-hhC@ELRu_sYakCPwP$Tf;D@9#^4Bf#viKZFo&6K`Sm8B;lORH+Rt{E8^NOa z?B%;|p;m-C(4DT0F~KbOvKA0P7ah)f;CFjDR|y5+L-)`i{3j+#6pEjpfxEU-g>4t5`z{OAX3H7Z7%XPDCbIkf{7O(i)}}zT9Zz&| z+=t7e)()?!dzKP@e=`i(XjPz1CF2gHYE>qz?5=4kl)w^0lHD3h>o3T$#uSOD~^SK6M4JzmmanWFn9(^UnePfXE$>hN;pXech&t9a|tk@Jz!gqMo=t zpS6D}IF@t)Yd;IdevWzVo?Yn~h9PoW0SpBTMZs5CQs4JuDG;;xGf7v($M3&ILzuVE zqO_mlCYW7~2j?7-c8@!kwavpPU;LgqPTKe>QkjS)_&S>|k$$B3WKJJXuO1yX6^8!o zCSq%No&@o0L?#+QT#Y}9)(M?!ZA{Poma$o!A7cdX}n>31CHY$LH!`tRYk874!Ys%V3n&_kXKEY#rD# zjBb-Tz1dqS_8(Z)SRI#`A3(eIIJEfm_-H5n+)jQuwDxXWyQO;4x93#BUg}*mOlM2& zQPaOr))tl}*Y`nAgO}sZ*Kg5P7Ed|OIEwi4?h2O+c9;lxm2$;B_;Ty!sU<|zdPZ9z zt@MS|Mk5gl=M&vK+weAE@Y}6~(xu2o`>kUBGUid)0TiFv!bP5}ocvP`>qXEb>VTS{ zNDwYaV(G&Qg`|lp8}u%-09z`!xgArNH26T1k+WO9?y)`3wd#3$!DotfEilep31k&xhHO$R($xOd|P}WHs?-eqiVm0X5jpU+PwVe?4SV%$CaW4Xuz8^LyG{N*2`q zPhES9Wa1@CkKLNhqMzQ4=&!KYcDwr{eFKv>Bq2I9XlC<81do`_U9)Vq)Bry5?8H;j zl810D5FI64AA*)UF!^(@A?ho9mK^rImYmjl7HSqiyB;DNqG`DjREkp)4u=1b)CfhE z*xf{g2KJ#<{nrwNP6t+*e{|C=eSpo|_*BX|=1 zap;{gLkxD3eVY$L&iP{N+pL82E2`3OI#%qYFsH+`o`q6hR8T~SD3+Q!GS2JMuntae zIw(XNsHGxMm#9rb_d;%tUSy@F)cPfIS2!MJT2ksWJ}^o_t)y)Rx8T%noXx;CVr@m} zGc?6E>GGhmX!LWK+fLKC z$08B`EavNq^|}^oKFJh8y3I@qcPBgYJ>$nr_R*N~&DVh_|MR#0d|&mbW)BOP`MxLo zOBZW{Xz~M4eiwBM#r6BxwFlx`?TM?#eyD?U@y3Ym?GebY#-~*==k^vL4$7H*d}`zi+LF9e;?4$NbAs z2`oNIP01?j`WL)C?A{~zKOjs1RsaOzBg_cqjW*Z&SPgb!Ba0`2(^?{(5~W$mxJau= z+Twg!AbnJ(rc4JoMWK}f9;6u>0`$#8d>dJM{NGPJeKQB_CR-lbb*75LYF^9@SkbUB z9Rs`7sLdhZ5PM=O--aB!&ga2lwe6HDmu9qzstVkOe zziscwcVCueN)dgqWEym1To=7R*bWqx;Mv%FjAnCHBfkbtn-!`NIF{cDDY{OsptRsQ z*OKKAlsEzFv3^CamD?Oe(B5ra^8O}ZqtA+_2nc)B+Iw-lNjkjb30x5r z1>VxCOEK?w21|)0(;?Ue<{iKe+@q9_s2m(SzC8R;fMUQUzDsh{IgY35gGmb^q0jUk z?kOGAO5Va?N-_o+Mu>4gOD#n>U0gW$cX0)%;q22BMb#IAuuAw&?+WLt3Q| zPGp}ycz%o39DC>=MsmU3K*l(*gIR*A=lItKM4T18iGx>jy4@N0((iXf+oSqSeQ%jmX zkxPNzOe8YnCxIn9Xo;WiE!Q`McgpV>Y0}`~o+NJM$z*?X`3B~gMP}f~$N0TKG1!jM zm0E<#$yE-28fgw}b7lXH?3|sf8$pI!bbpKXn@Iy*;Ou!EH4j)Oz3yjQ3i7xJw!h9u z>XfWA_llRW%l{K)pe&qn01qYmlRkp~-8%p8re6(@tMi z_z&ORRouz?poAn<(ebPCk2%xCSdmOCeJcLOj+Tq=K?>FJk5Z_$Fso!{@y6Qu^Mp{R zo+u{SpwzjT4o7G0N?N<-`wy+|;4fm3s(SW@qI+s1X_VLEc4vcc>n*(>?0Q{?)IOc) z`}f~N>`8qvDzl6?7rKT~4<|Zn^_gscRF}-@F)h7!{+-XixnsoS)YT_d`971>JN5)NqxB4a+=Ru zJV)b452w{;krQewxx%q1cOd}3&)FF9(uYXuG5AmW-J6!WTko^>-!~;iUFUyg zv3Z37*fvcxcUDVv(qH7IQ3LH0n@aED+i;Hl0V8N>oX*hiKRWs_<9}&>#@wLW6a%rr z*ZKsuGEv|ss}VlUz&JkU=<#->A7QhEFe*3wDQcEj-Rxm4I+v`(D;o2J6V zGM--`=qbK7--QBWvl_lFX!`l=6p<9%yOc+sPK#sNa@hhYI>@(6watkQ-%_M7)Wv#1 zpvQ8{LNvkb=X%yaC{@KnB29xR*ibsna*5TCtM@4PN~rn1a=}cF2#0c~NscTkC%e^S5a_DW^2wElWafa$A)48oShYgU zUfzciUNGEYA(Zd{>e9jfP+@tX-^p08SpmC(5qyaPGm-B`qtoY@y_=>zKZq zh$xIb(w41l+!Q>>=QG*sfdezM0W!R1GPO@VWjH`2jd)=sS1!hXR0NcH&VekwWcqoR zmcT* zap;g}3enK*@WhKafLz?mpCqhH;4YQdHs@|by4V@`>O2lYSo&itOE})Ux>;N*ZEaG&419&fl&@HM<8M&C?EvoSt9lwF^8QZL!)h(=jLeRbJ!3 zi)@V(+Hbv(t)OM`c3g5);2I(>m9R%QOT6SbR(QT$fvu{3)`^jOYeI$FvuWnumDl_s7v2~J0yxX4pnj87Fd<_T{vBUd=gPB)?I@R=!R5J1HiD5mT&-{;ux0@Pdf9*he z#`Km~ip%#$L#LB{*nG6*P37O`;j74hzBi@}j$l;fNPHftVw?gW_B9YyKGL;1{vNrH7YiZ+*)=CH z0vPt_XoRLJ?a4Pse+chUK}d7SW(swFJ$7+%iP1b0I)WrpI2f0OGb0UKb#iqDaAWYM zm(KTId1^KT8HDUSTf@bYAJL57D{z=M`eEW@G;tWKYd7FC^3E1Of;-zn&jqh=R+;eA zkSx=Juhf5Y#zfUaj@mDy`~X4+$+YivOC_|wn!f^_rHf6`nhYv}4*-4ON95fdYBRNB z7=m5j)UL1GZxf2tF$1DwUz~cyES*bM9a7-m_*(|?ObL#^$<+cQk9(M2V;uz3%hr(@ z;|A7|LbbW-?^!fKjJ+9pJd(=vk{yLCiy*?9sfnLM$nx3YUyocE@9#lQ5=u-ih$J5; zEG3p|e!6)?13_h^6kw?=*8U9bfEXSx5MVjp3YplNo-T=O^Ru}%#n*-o9fr$QmRF3c z4Mevok%g!*kr5yn1qwnC2(Tlmr3DN>BUm&kCY&n?eg%uMfzG&j~;BE#_jeJqd@UxWzWDT!G zQTdSMqIHQWeZ9wghM1ae-HBMVYdtKE|EguJFlF~FYcB5zG~_S&9h?e0$^FNv9Mo|6 zXO4{Aqr5tCjyp`ex(apZC3yP&;IRw=;glI8wiJs0NDFuFI`hCL4OUXUOOzGm810fz z`7P{eLe7+9JX*>Z*LWLW%I89Us^F5-KxNvsRrxWa%X?S#d%TbCOx0JcIxk+MT1&fg zd!)m{H0kdvn+-zznnbwDN_OIdGxP|HFU1<}`E5op{}TN#wY-0b< z!KN)fKwMTTVb;AzAI#9^V43dHM!l<6G7WsA;4E23N{bE(52e;35+d9evPi{RIT>u& z{RQRY;baA|6XX)GHR8<^wBz%HWZ1?KbHTlH!)jr+J_}hhLTBc#IQKOfSNdm9PPleU zIQEzEJ}xORMka;N1Sx$tY1j(;wUN$v^i(Q>nt{RuYo`MbUM(g=S>T2({e+`=+IG*8 zE*sDLl9EbOrZbow1+sN72UUzg*zR;b#N!UdBq?IVQx^43&Sa>E=dg@k+(|^qX2=DQ zujLcn8KIT}gFFGf`K!=J!ZSE*=_<(qr@z!BBaOrNYpWw$2H-9)^~0Hy>rR7e0FFbB z>sHH3Q+?Nwi5Er^LqBm`3Jn~ivcIsqXF*cAU<_yu!mG4)yf&mKo>?PCG-MI6Kl^u6ZP|m{Wvu> zwSB%%($RH@tK3Y_rYQ#`w#~5o)cI%eL0`(dUfRYowk6!IPg*INWH31Z*2!%mU7C^6 zgqXB=)6YL^jfgo5mzjLdNG3R#k_PFE>!e(($*`Gwf0LtuKE!0aLO&Jymiy`0gWSOqqgo17n0&7Z%G}Ox4w39lM zjmQki#z}Q+Qk+ckSsIvZ#e+w2rdgZFoYiy8p~q?I%mQo<2ohO06ef_b-o?~ARP8z_ z#qKhkBt5{!qlhd8_alZgN-avjy^t*Vix4PhcxV~eKVqc2VeaD8&Frjx>Ec61-p#7E zSV&yAe`r9g%;bJXiQTQlCc|CxJGqkt)noD3f1~jKycG)qPJOn<8s~0`8=_X8{wOaW z9y_SntLEp^b>`e}uS)EKNSRu_)J}f8Us$;~DSfe@?9mZz_mn)oDyzjqk?E+V>rtF$ z%#ZI91*k&q3HZZ^mICpOhf>e?DE8IVk{K!IIB2tkdh^3Cs<4f0tHdA98{?qX)Xe_Z za}EEmvrwQ4e_Y-76zH|&mXO zAD+Y9zI>KH#G=m-q>;i*!+?BK7JVRw!}QhSZpKI66tnai3DfjjF5v#T`SCPuuZsH6Q|Q3h;z|5JF;?7*c!zsRsUWz)(Qc z@PCo`;HH8EsK`x(d?@a}GxofbLuN#JM&?0*h&W*M!mV0{*mUwj!i2b20Md&9rhv2D zxGRKf3?3%f{7*h8KYHP@HNzE^ZE>t&Pg9*1^(K{TsG^|s06#!#1OG+l*5mJs7e_+X zDz0$mIKBv51@k;mBzc|bo#=~d49z^VFJt!r(}m_XvqbjxFt8+)Z|Gfgg=vXILe>yG z8L!_Q4nLyf-?5bGf9N_SvomxvP(M5XHKP??{sTX42SU=h_a!uGmMS(rnNFK9e(_T` znCuhL>zTqBOb0RkkC&d0)!+N+HC%V9Kt`r<7w(&vK?a9wJT zM|DX_K>1zhCDmW<3r;p>Mxl4RKEuMamXJ#;1QP|OO}?4!29Cqbi^na3jI0e zPyCY8d0Kg-5Zxu&s+)mDx0vT zxbn#vL%~HQO}WOP`0)|5e!p8P{D^qDJ!+>G4q0TX1Aeag`u0Gd1TMIT5Jd^OTiY@H zsCK>=&M+q|l7f7FA(y{nB2sNg2SZ`ARBHC{rj11{*XAE=>HMknIlq%dG5ev{Dj(T= z;btRFE?Q7u&noXfGx*XbU-#Q2{lUnj0UF|UoIUdSV&)?)EqJp|8siL)Hq;uyls7h0 zZ+h8?C2Sj)2$(8sa+Lzv^(`hZo zEqrDoSN8Go$jB^J9ln*@vFilHT13-Z8RM%9mR9n{MEmkl%eM1Mzb=;CctV_j&fH*w z8!kauzuy=;zMiV54sm_OBX#34rXsr*r935&EbORU<$CgCq1V$rn2pn{r_wrO9U%dD zRBah@+DFvM|2G2v&pS>YRQPA^JWVY4_PowsZazw`h;Q_BMLn|8aa$3BXH4?t<;sZs z%=-l^^XB9G3#_x99HC{QVOC5WMDLChcKMrM?9)Crke1aqb8OPl=Rm_9+a_Ni4Xx)%F6WIoxaTIDtXtZI$4g=V)K-XRnCOnl}LYx%}V2*y{LOmI~0 zwmnIS#Nh(PIPLY(6a*eW`DM9QhR}e;jAVF9Uz?m;F<42$=tplb!VGtgn7OE`kQu@1 zAuWnV0!IKaJ^=px=Wi+c`H4L}unv3oA+88+a|P^_o2)+gNMDc;WH zu(h@)de|Aj{xF4B)eTz3S?XCaEtOY{+vbT`NWbbVcq{m{eXrg`kBk66d$)avud`W= z>ATDHl5>j0ptOo`3(Fyk zU;WOSjX{r7rf^qq)5w;;BekWE*ms+!1i^A@kt75#&OXhYb zJ1eRrHKr9D4^89ORK5QTW?-}U!s@w``rCW;m!JS)Ee|u^EaEv9dc5~WuP7>-Y5B~h z$q(G_o5w7$8+_yXK;uJ91#j4zeU?QsP{l>lq(-~XrQ1B}xyC~07zFp@eVZ|))XZo~ zS9r)3Iwk_O;6uy9Qi*YxGgjdA^Gc@c9c2?fU#}ZkM$thbfgaRRwX)w_*=#6soTu7dY6JoK5%qB?~n>LY}W=w zmt<+1Juv;iL~$+gO(f6i+2fdxT`#3Wxj(qRTxkoyVLIsp-O-86WR3Q0j#h1^s&F~H zKbqK6*FrUFW_NnsDEI?k{6(r@W8?)13<_k@N80&Z`TE~8ICQ?v$EcnBCkOE%4ew7G zEb?htW%^gR7N?)@Vw>{}=tm3_hD8Ywa6YXCy?>6CeToIN4;J;3ukv`T_+)8INVOZd zQd|xIxj>xP_^AvCdfWZ0_DFIMQo1U(N`(BxN8l1*1FXr;1UFc&mRC%Y4Ug#xl1&z1 zG1sAS?Pr<<>VDYW4in!rl#cL2cc2>`D2ghYoL^(@=pP57Hve=vPb{33f30`Ymlq$A zsN)%ii<$*(=--p<)<>^hJt|60mo(UHvC_t7VYU%AcbMY6ka$gCxKtn|9fsJy4$ zh*!dj%2MepR}njgeNe#4kq6WtH6#|`3h?;E$teEwB%WFTr-_Gp;v-%+&;kb^l-$_K zfS?heiDklWZ%~V9Q!_q4SXro;vJ*b*c<}SZ%;zw7>UA%G({NL1Ga~5Y3@{QHO{x*w zm0Y~*%P!;FM8u4srzrtnyUj=Wxx2a1AFUq`+;XP)48mSsfO79Ryv3W-gI?0S1%<1i z)sgwLyp~Y{eZxPv55RPAXmIu?yR|BddD&L#G8k%kZSg$E)B*CtyzuPnDu^hvzPwjt zW{$uLd~Grflv;E~WndR{T#a)X>I8>$G>xx1VWOZolCMSI{_0Ea^-ehjwtYNg(r@sq zTc@gWxjflkz?uYq3ZjyMFRi9jr&hMDmz(+`QWzS`*W^-^_*P#@j_RcN{=i}4Ph zQ7V3gs-Y#18QZOXL}Ap2kPjwN=b7VwJ_+KWM7g%@+F$y4c$_w2X<9`p!;%_X_v@-06Wn~hnGK&CKJXAQAE4vEYeE$2*|jU3+WvBp8R3tXe&90l}a zj##!7slCjRfaBH{b?T7ct8@45Qk=c3{4S31zr2Rtc6Vejg-8tD$OZhT=lH*ONB#5c zurGqvxm4OcU)j56_E*`ws*-6<;IGr_Dz&xHefJWA6V*JnOp)L5sPGBhsam!szWE|i z-!rRvp#j!yzF5VQZr!eJN3}AKks2l5lJEEYj#z)i{K7wX9BVJDcz8V+fVvWA;f6hZ znV)Q0p-oD8k|lh%`}R6bEJTgDmZSEd$iFD&KUngf9&Oar;5}8)6l?q4*8ju=gmUCV zI#M&>?ZeSYm0V>GrS}PuL=TO>w?|*Hj`Qmr`c}8K zPV>L*wf!1nZE-MIjO)c9N~u6{JKSN(AYLuh%pnI>;3QK_=omf$Rz;mfLqr zSfQ?7->;I(xZ*@{rYKYl8ss^9_&dSv4^(FFdEriGAYULoE;@twI#5k2u9T)BCkh6W z>*l1qWBr-6ul7YBR(-y)@@9kI${#}0;qK*8U9Ph6YoKnTEBV`(vi@2QxL+QI`!R?w zS`lkP=@=G0wmb(du(OYRA{OTb5p=q^x;U%hls29^+l{m|Mr1ibNSa((38&eR7v>R} zRaKwN4)^H;n-LwW-QP^xj!fi=)$h*xC2{sqj7hrhh!8uVBLQz9AwBpT(PIHA5*zIP z)sZSx{IBCBCTv=PJcBt%B(fi?xm>$!dLSG#f!;gRdLcnD7J+$T9z&eKDCQEZ=nf#! zuWsoI8=lsCB?m$qB60v8S`U`R7XYN5_*$;GE=OyKk!P*^eH0jqwx4ETkgQYqPf&0y zq?x8wa@**YMw{Rg@pxp9;Gp;ihDZm2F^umY?>}?oJLHp^;sSU^Ey);jT zu<9Q_?{i|&V!Xzgk*XCaO<$Yi;BM$XK3Mk<+bZ3b+QtZXk8gQ~j5m1V{YDFF>KPC9 z^TLRan4T8`3^sB)geE}-Idg3G{pmc)C&^n+QYpswz9qjS$;K^g##vMnwkP`8+DF8x zvZ)vWhQlTAVa#=89v_>UJ7z#A**|ny^j}-))*!X@1`0 zU+9tB#rI5nnlhv-b2jS$PW+ih}4YPwB~O(wQ;Sl46x}B za>AFP?&9wZxAy*;4PcGw!s^9a$SnKkEK95&XVlEm7KC>9w$hzua+K`$DGaAb@nvvY zYL^1-iTuyf)E4gZdy$&_r&oGB8N*+@{Upt~jrv+MjZSgY^W2tG=e-84b=_ywU2jTM zq5kI~+%f*{E4TzzY!;1^pNfR>s$5vu`iVSG4<#{On4Cy&x?tWXc0;;CauKCm8t&aM zb>%|J0e%Asy0**|X(l)P=J!gt%sz$RR)?%a>fP?S--=8-uN4dxtvLLhmGCAlfZ|Gh@=e;@bm%|D6!$Ao1dR9cLtotba+vVK zQTq4xH|9$;W4X8h(R~*DQVNcSHK5JVdMSPdusN#ttAv@O76R7*^ns0e>%I}IIjCYG zhlSD8l3*Y04E{_( zMm=0~TwBKoF`cfXv~@VMfYy0-|2%~&{gC~%H_0!f>`;hk8f0tp8g zuESO^gPlNoRMijNO8PBA62h}~R;DWAF%lhiW}32gfaaSY%|LB6q-^NOiC(yODqqNS z2_eM)#s@T4qJtGV5Meg*AWvhtyXbv%0LXZEw$82kxQ$o0f>sMIV|m#5m0K1Lhu@jf z53g7iy0AtF;=25HTxu<`8p<9d;!I`hBPR|D=XRH3Ui9ddvd0=X+C+ql-u}>8NtZnV z^3t7ZT`fxkrsE|RY1hEtdj*7rxADCmIcqf-h|DCpkn5B2Y zuG;TS$rw4c{>d#M^e)Cs0Z)$;F?W7@yM2Es%hJ>nBKEzdc{4}fzJyKlVE|CKwOoE= z|C7xId$EGBDwTDV-?Hl@HB0YF-uA){o_h#&lAPaM(Pd+W?9{WG#*Z4-Fie5`iggMqM-fn*m#^Lb z7nPSr7qmrlb!h1I?Z!cCK75|Y*`r3Qb>j6$fm<7Wnrj`}ZxhlTeY+H>+f={a6RXXwE+HR9wheE?%OmR*)Y|c&5-eDPh2ZY)?(Q`1?lPVC{qEGvojZT})af5xr*`$( zd#z_ZvTkw&*F;{iN@(2jhB7xeJ1;XBg!CKmR{D_2hZGD$4b@=)9)fr-iK)x~|eqshW# z2fr{|Dg9>&H8K!o&s3~5GD=xKBR&an`0^axx^_&h+}huR-zgAdkr-%`QNruf zUftdMnoDo>spE?~LvZb+0OT88WA`HCH|?N8Rj`8_$(JyrM~l6;^^W8^{J zTcCSNa%)AR+Zq?ca_@iQ4_hIP62=!R#oph3G0s4`&Hq;JLC&cf73jTB0?sWfElSb$ zmdV_eS*vem@6J(wkZTsY8cmVF%vERe^p|W9ZW}7_eW#E*?cIuzk1&r-xSRe!NMJc( z@zd(LrvRf(Y;7G zqxG{jDHH&pxW}h#^~?b1!(A!{09D{F;LH)WT#%EridW=1TQQ zW}&KVdt=;|A8ZmCpN9i4MNtGPdW+@3FUQNH zA6&%y`KO78>jo9}k=bbpdpgydw-y5;p2X%o z#>GdgS}eLYAfa6o>cFn4&@5guN*jo#9V~yYf$+4GNw3y(?sXK-)o<&#?f!#vlN^~Z z2USi*trOAbT&ZYxpo8k(+G2HB;SBx8vJ5`L*LPnuvckU`bIs^b5r8EGY6$MiP1j9^D!609S7k`!S10qG%D zU}jyJ_o7|ZPMZl$Hw(Lv8K)MB$(utt4C!>*V$fXfEAN|gp3O@rbKEB8z`m}Qc&5hu zLQykiXve8?UOW8Rgg7u}+FD>d@{bXavbQ{h{}8!4>wVN zm;@3g_sEwMd6P_T=$LZVtd#zQtMjQA}qZHJBY@2xh7_KezB=UiX z=D8>b)E~y@;zIss%_GYi?1Pp&x=ErFzys^)R_m8PNw`D0De|IR!u zk=n(gQ4sOrNWGuV`_Rj^%MAlEHyA}iId?R!jZl|pCs`Ns#%az7%V+a~$gRHC1=mgz zJRnsgZw1q24v}$@?njWeGdPk_65~_y+0GlD^R4+@(mB-C?KT|2^|*U@EZvzD*V9B> z1YFCuOKcJ92s8UywpY@eSs(Pn)g$$zz7=>I`dOtIEP69<;tqJ({2fa3lZZw$z|E&C-xQFPNqy`&-cFE!o zqVS8{q7j`?2*n6d;5YL(HC=c;vH z{RLvOM9DJ+E+Vy(;IofJW=Ntl5)19X=2cXRkI?C>i9vsV^A`lfQwm*i0bZ7g3-r4 zubLM+$~-%qCdZqqf279$w_p$|J^l!HB-Z@!>SF~m@8i~Ho})~v$JS!`dM3PgNPVyE zjAz{QA+MRe$K-0t*s=8Qq}rSbXuhmpTDu=R%A06*oQog|0j`snn~LgD8Y=#z-;t{Rwr44THjG4A_O5bvrp<_#b@6@w;{Q4eIUZ#g65Ye}^(Zt=rpB zn@;baR>3h8rDFt|Nb|_*xhWH?8a_IPNWzvcFVB5>i{F$SZemL)Vu_~(+hNo?AuaM|%d;ZiS} z!w%x<^@?aybT|QC*N;A3(2@CE3)_-eW-%zH#eSfNe zq`c>suY-l(;D31maO{qoyi)*28J2Vm3a+{0Py3tdpNen8e4FqnUY@YD8!?M@bS)`5hs=|&k}p)gM+lH7+h3kTX-P{O6d+)In@ zgF4MjkDZ@rdUh|;jMF+&&SL#zRDTCr4?yayYrSTLV+W7}sI1@&kHyDn?6$!{W1y}H z*$WILFIodP7OvR<9J0Ns#| z1g70CY^z!Jp`H-=N#y$)jp2j&?;JO`k_L3&L%VR1eIDVPWGXg*uYfI$)q3h^p_uuW@cx)>p)fYTxmOIaW2xQr(am>5LW=^T+oZHhH_&{eSm~D-6Pxl{7 zpnqp=|Mj^J%hOKYRphr}_8`@tanq-zPdX&D#L5hdPhWvN-%8I{TaX0973_bk@z%2? zJB6XNfF=AWxcR#}b7^tk67C_>AB9Du{kLD7W{W@QdMS7{YS>sMnkeWTOZ4NI=sw~( z4|3pi^wquG9Cl^-9HDNUEWOlKdkduh#VYY^)vWi|}|1fmkSI1~5j7JKJg>^MI&82S>)w#hW!yT@dQrx_@p=7~p^CKSrxc^VVV z?B(A+(_b(yurKDjwyZBR`-r0HuHIxR;WrRESeX$Lawd1sKMRl|*p=)fpA z4dXCif;B^sxt~!ov|QSLhJ9d>eQ*0CVmWp49d;Aej;1Lm8|>%JiG{^boMG$@VM?GT zy^{{2jv&YWI?Z*Ho7^IelyNP`Pvx>YgFwXa?9rt)K_5Y~pX+o1s;}FpL*O)B? zzV<>GjtO0SkgDtxMtcN}3wvHwl<{YAA*-@LvuX6mh2aT(78K(dcDKh{)HCcb4TKT3 zRMJ)sbeLshCID*A-TuT#;&R@={YEGS29#k`_00zj_JK1xuJ6I*0HmMTa|xHSxxke~ zB2Y$Q6Z(VKjte!~zpY*-!d1G01-$AqCStTdnTH4iD{Z{(RM+nXBi}n5#kXqN*Uk48 z&Wgd!BoAk8X(R>yY|A(uqlZf4yW4zRBh%6vT;TUm{nDlrK{&E~h9o^tKUm*M23J{6 z{#zvGOKT*h`Tn8t=}`OcBIiIBk{})NmL=i-Mhz zDBQwLfOzxos(lV{NU);TLo?a3(W&{$Fu=_}(V$``Wvf(U;0HKid;WSV+v8Q@8`7!s zT|r;%r5K~ZdZvxV@VSt@siY8{(w3fwizz)Zbf~*oH>5fb_Z!kWyw}!;jKEm`AJs@6 z)wh=Oj>du8sFR$)U^qADPj?3mMP2FPE{f4Fj@A4G_|yTyR=9QretYP96{@er{{KHe zG&J<5y^S`^Q_$Q}S7wX%uRM;62j@TY>sLCPC`(@5d_p8MW|C&5ADm>UzNk>Eqt1Tc z^9Crzo9G-kcmMdckFIu*qg!h4mpoXlp&$lCyZe1(X^}fRNh3tUq5?4Wg{DZB<*2-} zEZ00_K49$pLn|m!hI@5fdwp~IH0JQ_@)NAE>~B?<*jFKL3eLw=$G+s;A!q znPGb4+ZG#8|nV%xvr8#>S)2u^?pBF3P>{vOc2+G%xupTL9#5?X#$y0z-$APcBwd$E3@KH zV?BTPRr+AAkIreu&DJD8xr`7G`G|P(X6f7A@s!7El75uEIEZ~f4}UAcd?ds-paZ71 z9%;H09bN)pUWOlHS$ z@`e*K*pp3UMaAeWNyo#2?AwF3;AVROTwMABr9{km7^K?9+X%n$+6nxjTCxBlCsYD9 zw7NB4?|MkI(C{Knp9H&BCGE27l+q3P2EXef8f|}%x7o>Z1LB<55@V>l5WJ$0ryqE|VWxBs+B#<`l;? zXibF>))-e6DXIoIy>zcCaeUGrIBVT(Q^c%% zVMLql7v0RCvF=9oP6t*Ux4N-~pv83daWgA8M3I<#U|qJoecB20ir?ypHjuo+v_EbJ z5mv42#ou(H+H50Qz$0j8u9WwVV!&)PGb4bu8qR5m0oWyeaaIX}CIzoU}r0H%-Tb-B8YOW;b4ik@b@Nc%jH%hik1 zn9gthOUsO9>DSnDdmbmJ@7L^6C_U-VLQ&^rp+Z;WInVC|&-J@P$j>Gf;KD6g?aR3^ zBNB6rZla&Q39pp}#{XCH7*w zCAcf)8dT?N-Q3)rj+)tuwT9g~Dym~t#^w{C+C-oDmGLVLV!OJw;qUsJz!OEdepe@O zNFuLUk-L{!oV+>goD;n#TY9|ZwGjJP<6%AL?<+X)ui%5yrk%&oK=@hK_gYk*SZ(|g zj?Oq>qJ$B)e8Z5QR2uFJ)CALqO}wTf-7mT9Arq51ru?084ZMA~=K7#kl_XXXHHbzs zhBQhkgWqQ{LZ!h!rai$};R6^1<^=k>q2FD)uR+GOxJi_ zTUS}%jZ0w$kB*MQ%p;7;2yzgv;~9ywJ;_GDj}G6dx6J0wmM87oGxm&ejs;ZPni7?% zrY6&{1vfQ|IvC)+zq~p!s7LgF^+ys=CjB_!7DXp1zC4h?K$0Drr`)UiMg(W40Z`za z&U2dxxI&gg3FB9<7J^{|e&loZkJpTUf`+6J@>ixk0GJ#aw(*5#AD{$*s$0bM%^EKC zX6u=E%&&1k@`?&H%E;jUn;Z8LAqyq^>~S&^GRC+Z$T<@URHMcyFBCw8K)JQlm;*$%+KNyx>y&DR`5HXH&JgA47TLEbEG4?o>`m{~!4mZz z11;{=JZDA+!d2Sx$Do)`Vw?A4|TZA?&@6>ol z4dp1MI^q&~mAGIkfS3>BrvZ-DDh7jBM`c#nhDDge)~~Yr1q_RlqV0V+UMyFF_DB3x zY4Q|VV!QW79a{+IoRdF5UxFz_Jl`LQ)ryhw`)0#)qUU~E1JRpHO!;nJ=>A6v`QIHV z2wM>w8ynF<#XBq#->oH`*Fd{0*n}+n>99CjtZVG4mz4`*JV3oSIWZAN=c*)_iMf)W z&wh6nP)o2e-B}(pipzx(V@GVzL(Ovx!B6>XPupjyYmaL6mpjREbpIaGa&WKP3KVbr zZnnpI!HOf;h=LfnplSEg5x!c2Kjl!eMB>}i1&Ys&Pz7Z(L#tdJW%$k7CLZ49e+POE%vd$2tnA&JA5h%q}w~mfNq*n%S0V$@63Oz2BxpweqUq5dYP4{_EAftR5AH z+y)Au`BmN8wQ*;m4khVRw7v|SrU77WbbSZ@RdGjKW{c})(zJ}M4FCHD{(D0M$ItBT z#At|TIjmT33r=(L5Ja~oPj?aEoL5=%h1>ZJfo$jOH`{3sJwYkoft>6+e19Ttyokyf zIOwX`q1`RtiB>=V$0no?P0LEx=aMavxaWNCPYrMtxo{k?znr~TH{Po(>ihY&m-XlS zZ#h1)z-dW@{6U)(pz3mBz_3>Qjjrj_-I)XWHva2;noG|_H%WSB(_}F-j=c1%s=rp1 zG_s2#I}A}al-DahUu(ByD}hpsoF2@g!3`vi)dB)TCG^_0HDkL9SOyU&T~K{T2G2o@ z2ghV+jm`oWjm3}p`ZN@B7|7^iZtXT0g?Z?w2f0V0|VAJmtW1VhhIEB?jkc)~5A6-m)CmH@`ASbI`s6^?|@z8cD6qDRlN z2olOVkEqez8P5@VA7kyytB%?%^1kg-Aqb5qL>`q|Zq(wu$5oTr^s;x(K*&k>by zqa~$7Z6LH`72hGsrO$GWM7tm+bvysUkcz?i z$6-MlFQi=fkLinCGtv3v2j^o^(fK!C%VM4nrt>bJfDVUuHpLQ+HL_6M_IUS&@d1d} zKO8!;uWTL1cZo#ZPcrEinDx3on%RI#2!7BhT-3vn_6$NW(2Mne9;tXrljWu1GIGUg zYQA{2S5Sq>|0u5}{Db&%-O`qwU^p7G1{%{jthB8Av+p&95J3mc>ho>WZ2H1Ho79OQ zG{|^s#iWv9mI3~`PLmC3sQMEB=5q%ga}0)PrDXS}#tL|XxpF+)9G_w=U65q;F7p-Z z<(xSts(;Gzo4qMJ^jI`ZA-?kydkr`vFnFx04@Eg+oUf?9`YqpeY8NvVAlE}Oy>~lo z@dlVm9aSiop#Mf}@#<4|9|vE5k1UPD?J|B`CO$-uW01kPChYwub7dMNG?uYjRhG0quaie`P)PT|6{m?bm0r#t&y zud(dra2JT_^U&;DcP?uFpL6@ejK44W;D4Rl>uS*cd7VDE%gwMM=5*8dh=YTw8IizN zl?H~B$yih3;MVI6eeb%$B0w7HBi~2x)=#Bsbi?RcNJvvsSVLxu#v&>|Y(i3P1_!}; zu7Pd+mZZ006GwEf)0W`#d?wx){A|dsEb$mp1C(rIsG(ekr(jyy&y(QrDWuP+`yjD6 zo@|Xwa%>9Lif?c-SCdg0zvquhQE`ZJgTDB&n`%M#!2Ar}DL>+T2Lp>pWo&on5;WM* zj^k~g8eB5KyaAzGHUlvpws(`z(2%X2BO3@)=5{zsl#P&!pvn8T@9P4qB5W7>r@CYy zi4~H+mbKp4RFP;qDZ#UXp0ycA6a|Z-P!SBtZPX?b^P#m0(r-9r({v*0+(n%YB#bbYIEp!;Gk(b06jmND~x6g3Tus|F`ZFu zbGa9K|824Lxe%8Dm#~Oaz0mM_VZ-CNijl`)_iU)&r;&>3TnKMLYr~ zc7)V!vt`#m4=zm8GB=8hjBFcG#O_R~z&q;mI`S5GxzgXBZjP1#t~+P1n}V_ZS}U*`RBr+0ZVA_nCRW77~8x zC#mSz!&qqJ3FeURCO!-8GCRElG_B;Iw>>1Tp7!mi#YL@))z zNakra7q%v)7NIDe423s{wis0Je7Mm#?)r_zL)5}p8(H^k-hEV=$bU;yFpzY`g-$dw zgbz#$Mr}hjKt8+sll)wD_IUdEsclXFz&q{qKb&2C^tWxwdM#wbBL6zM5Xuw#AFkuT zslmFp2Z;x3LITfWn&D(gzd;nZJS0};X={D+7O&rTH^*s!v2aC`vY7(PA**QqTlcYF+=PYB z-2@kTwI5v_uYEG9Wb4c25VKwG3b8Pj%Q%+`&uPf*tLqcxk1Om zlxePxe2<2OD2(D0Tq9ikg5ERn#gMc#fD~x|mty-#R^()HaZwTT&K|0=K)i%Vwt=Sb zI#)A0E=1=hCj|VdO@bifPW~bOp&l!4$r3@LJA`$6*vTOQE}kJUF}EuoSy394I+-i+ zbP5j=F`A*d%DprMMuw8v-6Zz)9W27`uG5B+MIYIlfnmGPFw0?d(yQ#6-nsg#d>+eA zp&eluyOYYjn3S~H#&1+%@h8oFuEzI}?ZtJkzWTyYSTUHX5yW0VEfg${^P_g*M1JY4 z-umY>&t-ZYFxg<9k}S%KJ;~X1!nNG$sT5*ffn0TvAN`0ofbPSF$D@{P7Jd%Sp*tP{ zG29vj4I%hDyRjXl_yD^fr7y>pPVU23h6uF#{xO

    62V^q{PfAAHmt^ns4E&b>7RE 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

  1. 6;5aRelQKM83(wvwB9Y*bsq71;AtnEja`G9K9|zeO4V zY?rOD5w>KyP&^vvd+ZrWeP6Af;V5hED>MvJn#3TwQz@_ z-*!2S*7CrlLFWYfBDx2@!V6+AoN2x~WvhA#ZMF&RBlUdIO|Wu~1PEI@a%ZdSV2k@} z%Us~EIWynO50v*i;g@5ya%?E@GAJ+voYk=e9*;piMrUl`@CveT39E7 z+!1`2b9aG*`QY&(d4qRv1m!j|b!Y9(+eCBv(L_ zpC$=RE~_xb$v#)q){iJq(71Yw%1%?KV#7`yzGo*HJf!}GE2|bsesNE31Gxm4D&uN& zWazhBQ$W?^z>%h^LsJeilB?k<=eARCFZ1+ln4|}xhtz@ckAB%GF6D|WuQ{>D*3!~+ zlh?2Ayby7DYYJSEQcw|}%lIwp7R4Km4zlXwoO5;GU=l_46@aLC01~@xX|piXf&7;4 zD7_ZB3iCJ&&7{5J7^7o?#eou_+r^Fq}^)ZV`SNt$T9U*GsV=dZkj$|d-zJt3(k zB5c=Z%Od*nki6yhITy*M;Kf(iur9U)c^*Hc)O9wq%4%8TH09EZ=f_KzF_AU$OLt?E zK@)oer}yEKlQs4cdtSQ%b9esDCjZ->7cvvtI_vxH$fRGXW$NYV-37# z@BWi4vBNfDRJWm}fw)e`Qf2bNaQW^i$baM`e+r+T;4R(T2o#RIU%*|*_=)i4cP#`j zV(D5^>OyjgL63F&vE!Nz%m()IF>4TQY-{=+cG+|Cby@Yrg?))}thV_%VER{M;;t_< zNN)RI923DkKc=HlW#W=dXQV_?2U@x-2OsM;jo!E`C-TiCanPo8=tf9{Y<$%tVcW9d zqZb0GtRLevq|XvHBWbZY>b}>llndk%$P@ZKs*Ka~0P#z?kX?F>+hJas*GBHM-+pPl z^@w9xjeBw4V74^ukm&xuY{1tcJQ>-~70S5`(E&WEPU-hlnUn_V zw!R8a)D8J^^t5DDrOI|(u(&M0RjOcDrSmwH?s43zT^_zVN&)QKlMXBtU4GxPCXw)7 zlBpsNo;@Z~AaSc3!N#1HTqDG>=Bb>X_P3T1K=egCO~Lotpm@Riv%>!-()O%4j`@)3vQ^3KSMA>e>z zaOwFiYY|?}vM{6AG}b9g+)+=TzV3N0MI&7NLM?L#%S=7zJ~r;1A;%Gw^ai7W_sIVrB5;vM&Elj$VAg_+*za#lzv4WZgKUb-}Kbfci>m z4<5;uc80|tbsPCi?HL`2hicvkF!bw2$4#xb%KFYCp!D0fVhq1s>uvWH)!Aa`ycV=H zYG1*V0n#Zj@I5yeyL&?Z(nsrL>r?jV#qoN1oV}96>Z7mDE|b!azMI z*U8a}1PJNiidFs+_NZOhGkH$ljXTSXk&EX+r-mFQkh@rUgRo_sW|PQqGrNO4mPZal8y}~Cp}r=Or78H z`Anx@tZA3irt5o098`Hl5xDyeAhK&8BmqzJPVPQ$yOqG<$Y3kZq~2sqg&<@RwZ(`I z$RuvOz`3u|c1rqvd6t?Z6yUSd{`?p$gZT~t@fQ=21- zmmso7_AzJE+$7TNUs8raT`Ye-y1Q72m#157F?PYeK|PiR{gpY|Hxx=cd$kTz-K+Fm z9-1!}lz!7qxtSxCI%jzSl<2QiE6|(Zy*S#n{mLs`Ww^enKW4)+(gv(~?Q~k=^mHoy zdx^_Y{hesjhAE3^M^0sGg{S$mBP zb@j}pyg?`$gV`u+k|*8|{er2ZMtGDtENX}jU24&xr|BAdBs{7KB>EpT1te|O3V9b7 z#33@97x5TPZpSHlXr;hbo1IW5H~f}xM&c*{TmOkovx32n$)?k|1Hr{;92tXYW&3m7 zsYF=a$22Krb7iF$jAlD9`fd9#k^ROmjCuZ$t8d)O|7AuTEq{vl9229@$Uo8~<2}%? zf}7&x!eGPcU&9Z==OVSAZX^ctC|=N~)pez$zxLZ0M&!}I&JcY+-f>`M#!%iS;NDwo z+Sr~Z*nhSmZ@4T-&w-@n%|bPEZ1Rc^iJM-qj>gBu{@Ru0hQar(ltwRnu;cf=o?s?q z{(=fyrW88<)AfN)-~ZxO^Qj_7X zq{<2Yd6F9TJpsW;#(Qj;2Gk@6Q*iFm(3K6S>B3R2*cfwEJuFGDq9Rk^ywGiA+yXYx<+9ddWnyLp2^Xz(fT|nxwArCn$nnK@8v7cD zzs|q&R{i3Zi%GXf=lGa8iK!se>lqPo3UPbVn6Es&Q8=+GuCu(lL5yslSP*qO`METW z+=_swT449IC2;5k_uKxBKe_w7`RuF)iQQ>&&ps}F+#}w{R;9Z4wZB%a?{&e6C~*I% z3Y0n$&~uuzd3nbP>KU(g_NWsEebcQrw-*IWR{6vA)m~S4nfs4Sex}H|?YVo`Tqi3B zei3Vw<`>*8TW%`5vWP3E9&RAMi~C2ZI=(+Bxd-7xeZ7 z5<{7!5^=&abyU-#-YiqU|FO?1{Fl^TFAt%^)sV~01U4_f{Eg2CdVX-iC#T;i`z#9a zjZ1jHsf2Ln0+ii%J>9^Ng`dU;aMipaxLg{MfKbnne|K`$Hd1BR+%d>b@8Vj7z$1^y0m3xdf zS2_t{k7M1!%^+yQNe~dtM=5t&TKeJ1#g(f)daMUv)Cy6aE$S-i+V)^Ggi@vV1)a$I zDEVdn9C6vDa2Z<<(c_4g6<%bHU#^^1*2sOiah1tcBtk42)5|q9SjZ0c8&3gVj1CIZ z`3FbF#B)9}Na-nA|NoFDQVQ8@)*zb0xp7Y^T5*g7OT0nMS zsebaeU)KN&KGR!QVJ2*CR4{LBkzd~2ojC1f9*|MukLiamv1Ov^wRJ-ltS#5_zr&EO z66Hq7bi)#s0x`@$VbBV<6DgsCII_Oi2}p47($@`mtCrEr*Ha90w(7$P%WYVmcj<2M z5Grq{4q#Bs5(dQ z=|P6mwWxE z@w@np4)9(AJ*SGD|GR>xeFp7`cde6z7kT;u{zT0t+0=UG{C=IcbWo-|KkD0cTu`he z@ALek(>I&(lk8QUL^e=N0^dXAP05vmPplk;V#Tk7@ld~1eOsC-owWX7-aCu3g<9p5 zYUcC-%?X*I;-fi_o`^iKv8ycZ1tr(u5Co z(DV1PS;dq*Uujb;9^(4f=DZBnL4+j2y2KX#?fq&fVx~H(HgH9vFt9Wb%&Bf;OiPzH zQUzaS)W$K!f3Zpv-ZfB25ZiGO!84F)#R?!9Oh@`-7oR?x&Xm|zBCHMRRYU28F77q1 zEBJ5}T#VnbW^so|Q#0*biv;RqbV3YJM=!g@0)^@56*c!}tjRWoS zg0{I`h7xkzHCX{J9l|}u(}Lwf$Kv(_`KCkfW4%UMG>OY`%Vx$pu1#9=X}ii%Vw!w< zK#9Q;a@yeY$`xc3s=E1`9zGJ*^J+aSpD75t)VEwdLOP#7KNZ(i>6Abv#%nOqr5b1s`sj=tN+vd{rj1{S?8 zXZ?PPetv<||AAG*G(2K-Cy;ff+s}RoEK%rH8Ra6pu2*Xa(x&S77*}V9EG@S>q8;Kl zJ|{}9a7ZKb4>b*0#hsVly2krCL5@Ri!11X~AEMl~GLlt(S3ucfzCSr^iy}9Z+v3r4 z;#s~IRpp)4@fujv&Ti_c;J$Lrj~^rb5W0OcOVowy{sC~hOV8|33P6A)tFg43CLh zYA79gsLY@W_MX3fJ!!w&G+IOlwWKg!x3MQ=wQC+^r`iqoDIz%pOdHXAKZG(0si1fb zY7?I2QSJ*$s-x7e3jc1S`5E2$XF8BUg-V-|YQaHwbhsG0CVbi2h?nb`-N`htxSKZv zM9f>@xfb={c(I5k$OX_?zfplGPZJ{~o--qh_>>rpwOwtRbcvme%s{h3F=?Je_ja-4 zSHQ*VoEu;1ZDlz7JJ3hPVp-;_AsJMqL+ZY`Ob&)IN2p}G_|5!ixFVVN(})uOqNv*; z8=tGLcTy!<=VbQ-Vugn9x~bc;v?HiiBd567dL$FG=kGb%F%Q;IDOkc)_h zV@ZOrGL)Y?C^xQHROIUp?G2Jj04J*{gR1Ek2mcT(3Wc|iO~+K ze~8ka2($5M&@I5!pq*PgT=QWEm#-yxyeK|?k6$51ttwRcT2XucX?iarW5;e zo*<+xQPz9zsO($uo$8p%aw$yxy};M81tEaV3B?`#c9TFzjcmya=IOSOJzU#3$yk42 z*&(QIFs(%Ckg9IzIk_5v7>7ax%&RoP-M+JL=Su^+PO-wCv&o)CN@ zQRNTu<^GzGCW>TN6*!1M4&{E$o%8EqY{Sa0tF2JV_wsb5oR(Z!6YtMpD-j#{)*$uW zk(NtD+FHbRKzPF*ZAlGp=&B$$a~|{0o#P#PhauFMQ1fpf#NbeqmpYV9+yrM z4yC-`S6rqgTVdXT=qY>r;7&i&)nCo_x3Es_`XmH$S0RCscWmvWc^!yqE-e*q9uf6o zUQ~CqFI=n|kDt|C^~9f7F2fd)jQS%5*%!yN-{)@hKRotF)V132HI{dh*XQ`-Fy3R+ z-~!t&bmb=`Jc*&w-J`gZ`ANh%bHn?u5LQaV=1hwW^CQ(C1!~!j=(XgO!w&gj@VzKy zjqj)B3*3%GgHDUv51hX!Avu!Vh2HC)6iDAT4ZJSFT>KNv@gC7ZoLJQR+-BP~e#p@l z2HK9nZUZrj3wz2Z1%HkuX4d(#6rcd7zeTG5P~3!xzbURAPph|t$U%)KtH`CFy9hnj zd`0caLZPh4qMsZF*x+A}$@Jj7u2LOL!4=xRHHi=~9nt0R+hb$u*jymi`)b!)Z$*=E z3q*4U(-!0c=t!Yt;i9Ubhok`BXk%a%>2VmtQAn6uV%+$xP=M?Z+y>0{4kPQbpq$Ng zA!w=6plN=1!wT3AsV4o+Pc3(>uXLP{=B3F2PT=&l5|oMhYn>)IaXPpS1XbM7QkPa) zQD*9G%_4_zy|aYYCNiu$6rATCsy>xW+9CVw0%+CWyojm-38r>$etNSM2& zh|C6Tw58-We{(Dt|RvATit^)0@=!)9aDkM<9y-JPA?K zAC>mrM;#k*)IO9^7sLq?ovmCL-oM_v7vLDMIU;f!BhLIQxlh(o7mL|iZ%7Fh6m~@eW}OdeeH!nRh^fCiddPw|DyuBc9(4pIoOZN9|g_TqDLGQJB z6@CsAdgYvvKkx>?+6N*)lU5RFz&mIs>KhkNmjC-4HBWc%p^gEa2qwZAe6{xmXv-~!CLy43C*ydw+XgKD>N=G8T-rC58uO}A4RRMC#`g}w` z|D!h*%9yGMMW3NtxGPTQPc<>iws(7D-{DCR<8K<_8owoe_^TrS1zrD_tk&6`!WAmh z^PGASpQIb4^I|*hI~~*y@&xLx>5~7meu}Z*~*j6YJ3m<($;-<=_bJxQ{y8zr|L? zRk%$1!nY3Itd3aCpyEk^7esv|DzkWxtTzgbCvcR>w^9!u=qMPZay?}QvTej;LcS5# z^F7Y;bhKqtOV2&odEku&rETciY~cFcO8ngHj?ga}1Y%3}B*nMlc(~<~csBK$4*%Lp z@5ftb(*8Q^N)Elwf|SBU<0X8M5AG*GSz29QFD^u-cP!e&u1L*>$Fka2%f;0{{E_}n z-R>668utd@x);HSe)#ru13gF5xKUSJM4hH*dL~g;o6V##$-Gb*RO|orR&&|!489B0 z$+Sg~`~S^fm^sd5UUS~!w>#NE-ffgJK6Q-ZQr2Nsl};xvu?p-M&)C+`^ouChDv{M!ql4O9C-#^uV( zKEahPp-$%FKf8`>-U(^qa+G0WyqrVfpSxi5v?}pp_ome0MLSwi=wnA6tS);sn`s87 zI_wzViXl`I-_8JfDh7R*R6FdtEFcHE%%~lGuf1_TZ=aUFb`hV z!rO;Tv}cmPo;U2_#K$V`j(6MdxW9Xj{b*L=^qQPlQVc0or|o?oj5nE3mbYCr;^W#+ zz{n}Td>FM7y#n*gFzj0YjKtgl1io@kx4BeJ7itY1*N?9#giMxr z{ES-SLvwh$z5OWGnMuOug*6a404Pqv>ube2TSFVP$QgdB1hmoRRPn#L#08B$q{FnK zSO61ywQFI|hkx+Dd^z8$i}{V0mE4_#)~bY7|7SrFJGf2_=6s4bY*vcFXyv`qNb5u% zyg)4-%edCZQq%t=O3+f2y$6V+(Xyk+$w5uH4ww?9C8yh+W;~BK1w0lErG9%z;+t4* z3A>9z^Zv7hz4sK_OB81^`ccM2On;r?a*^TXWY$F$lb6ga%0)g#3R4(oAUnU6!hMPMMl&3-g&+x{XNc&%_vm%> z5u&`Rmy_no*xN-F*{s)^Yo5^9fD>J>?nLv`L*GYlSQ&?JAuQ z^;bQ=eXRCvBJPeg^Cm*O8=dR}e{z_wRI*fZcoUuaV6igl{ zC206ce+EM^)lG6ieLXtIb2qCjxeVo`uveFDEx4OaMY#g~GO>?Nsz-bXf$ZSgmnY_+ym(y*OOoM~#cn4fUsbH$0jsU_ zt0H7h$^-qc+R)QJF|GtR1RfB`hQ}{#8=|{Pw2WOzH}}|$OC&ohN1gYcrk|-jb06Sn z-vs3^FonOgT+GtlR&UKh7mTSX@KInCyOhVmI*}3os6u1V%axC+y`4VoYe1rEUJn^- z_f(+jpGK4%Y!Yv#E+U6|{pIDRswdtIvs~I$Tg+LkY?^bG%4K<^llnz=u-CR!du8q5 z!dXrq+;p!SvYl|dYHCFrSsmGu<-OgW)Y2!Xa!Ywah2;>lfHldBHwmApYxbMXR-0Uk zxvwEpy=vmkg?B9Thp6fvYa;)LNdyj>OGN5=Z3}XFpE2`pYCmXLe>QGw8LyTIqvv7@s*5&lF1OppY@ zI=v9bFR)mMF|UFynfXJ0{P7MPSN$dpEC{!LyT&uK&5{WT0^f3Z?HQ`DD`QRlCOP_C zD5=#0;t=p^o@f;=H+iKD<>k+{x)nvpc3=}onOTZVT%SM?e-!Oh>P5DfA@&lENSjBG z!hHa-qVN`vHS3!sdBSlyw@oOPRqlOx`23>^VvmtoMZ88{l}+FbpvLfpj#=5R6mYgJ z!jAdt3wwEHL*FN(B&#(tl&eGlmsNCCTUs&XsdH5v(oD=)XL*JV#wF3ZTx6iI(Z-G( zVL;ZTRO|@ltfgE5+m`i@B^Kp@LwypARulcu$vX==1GVC@L%#T0S%H<>R*{@#XNZ zmXlg;@&tmN{J3@=K*$FAR9h?F`GVayEnI|+Dec=6Ma(JoRzL^bQEtPY-74L<(C7wj z9I~RFt(Qrk7>#YWKT`H$?wUrFO!(oOR9MiMK#92rIj(%swP%637I#IsJ!G?0>2~0V z1pK+SFsCP(m$LPh*rM%K!Ivu9s8vi;ia_r5s%IK^G<9Rc#d7g7K3ogT6pW1pI&uok zSF0l^o18#ulTno!agTB2_?mHEBEt<e`!)V(PpvzgyFA_b@Y!~_VY9D z>gHsk;OKL|Ni$Qg6m282Mq>+-hKcZ=O44DXI2g>Jry!`zox(D7?zH28+01S|!yCK| z^~Or=3tO4?%>c?L-l6PEO6p@qOTRy}nj=09=6y-2W!^|j8I}@T`PKh%n3*OYx5Edk z9x1LP$^4)rFeG-srI?36-*?C%kq#0h&}&u@pKF%+fo6~Dz}sSVb~fF6w4U(CY$?EV zwXAG2!>*fy-Y%*|^}~vK0G!N@OY7ZLZZ$THOX$O(;k4oNu)`38QN1bAV?nvrYvK^M4au z)D&3te8RWCk)a|J*8&0Q@Ch5+-UI-)eI2uem@{j{MI^DBCitfwypxKu2f;(D9&%wU z`(M4H16XFm-Y~B9Li!f9au0%L%15|4Klh+(qk9H0L%f*NW-9zePv}uCmmTi2P41|l zpHJNIytcoIaCZ4o*p<&Q4U}sef3VEmqkOJp)oLCQ8|-Y98f7vor@bhn9UVp%9>LsA ztKv=ZjUCCVPMR!%{3>QlT30FcWgYXDXF918_fqlnlk$%Yzeg50YZ|0jPPV?8j%H#F zl;eZROCS|5on9khE0j;6>Qty+j~NS=meFi9Er;8$k%s;Tf6A0TEo|tvZ06C=8T5=V zaf^y><7M1|Y$iB634K6weygCVS6<_OIZX+Z-CwJ3QtZKrX1Y>OJB{$orle-xQ$Zsj z#{0j5-yhG~>!dxMq>ShAvXu)yt8Rtnj#+3H!a(G zy>PzmEbECP=NFl~G%9t~7EDj9tHP!B)U?Ad#w8$wcDK%>?1B^F?CyJ7iFgo)AgA}z z>Lm%QNqWdxPb)2_Uc_8>vDpzR9Pup?`(5AuaRbti!MK3*b36)M?zz#F6va;RRs6AD4Z7_~1-aw|T zpbFEa=5hf!OF0`tOf>Q-#b4zEmE7s__X-81JtM|PAAVX^6bZ-AI+uNP9TSLt-s^3) zV&j1TcI8K(I}iKJ2o@fUok=eG``DofBs`hVhBH3Q4TD6YoLyK~yCKx*K_dM`)L=R8 zCi^Be%xIIQKAw!xJ3&LLs3ns0XKSqaBupZx4SmEAoS#ql6W>hOm#ArJOL6(zDM~`) zliXtBUatj^f<DUlC6D_u25F@?RhR#%iqqokeVa zVYQwZb>G$(*91_O$dnk5d6MXGayjEmm{Z9 zS#N;QIVq20OUj$4&71ca%5!a{Cc=OSG1+*ngO9*Xod_N;s*b!Y+F@AN@hD*!%_)~l zx-%dpZEY6$!CPpj_sbFdS33Joe2;P&!V5nvu`1#FE}LBy&pVC*I!HTzXP4C_*@zv( zQ4EFo-g`0(ki1r)v$RCQB3X9EGqPnmS8(XzJ1|6+SdM-6?H<#|lTNuFB{KsNrPpK$ z-R%Gzzb|gl7i8wdnT_jQSamCjU`WepJ3pK!wt3#cXRV9=$M7tgUq9sP_x^ZNTD+AN zYO(7$8Lc8+4;|km6$l#r^egliK9n^I0JEx#7#xQJx?>t9IP6vxjCRK~R4iP31d!aK zE6tJ&8-?GaFdh#oN4EzID@8rzr!O`C;%~f#Qpfk&ZDU8i1v`CDv(3zC6i#&Q3x(4u zf2Lg{RLLv{MDaGm_>l_j(9ZMj()2Qsozr=k=Y0+)YgNx*%mHVXKZNATjM{lw!*qg0 z69~r?ShT8BaJdL9R;=-3QuoO@A&u>#6j__ZoMNJC-nZJ1HCh5WRlrGHK)aK?C>rhS z6h5wxZ7RntUDnflWz90eMHnTf5lWE{2Yb^^wuWkh+&)|MI%EJJ5vTKZlZYilT5+Op zwP!blOU7G%{Xb#@r z(?5>ocf3FHxgVa^&-{l|4?rnhP5z~H)e`M8$Ojgh;=m|^}v~BmlITDeW5tG>W2_i`&N5R%liKy_oFDViy+Y9LF>6( zqh|VP(EjR}cSW?@SKPz=$Gs?Kg55&Diho_=1?o+GciJhJtgbL8^&{xWe-}N5<&&5U zwfE~{^CxCtT?rLMH{AV0Nxj?e=4Lrr4d~+KRt3a&KsBEz!5hp?GFG z(e-X`_paLMze#YmwgeaW3+IRX38pPA9^E+JX1*7IX|A4k33^r2op5Tx5EYNlWGM?j z_wy8Nm)(BG`(M6H=FHPR>5Mmz?S(L`>-Ed-UQ8WCkSCPQM!~Qe#9S?+w?4C;@1n+m z&;);-M}A3nzs=@#INZm`rYjh^*HvI#zd^#1LB+XnZ&J8^cQSK84tw0&l_V70WmSyLzEnuC3s>XVP45EmlFM4nNCUKL_tzD*7ftr zShU+!6&?E9=8TtbuqD~FLpV`h(Kg)JG7}s}pUk?I!h@YncdUA8kmKQi*LPW|DV$ks zX9(%hc!By4AHqmXwn7Ag-WZL_8uggbJ=6a7ifA$fo@#@9+O88IW841{U@J4(S50de zyK-W-TCfr6a}u<_X5+4ARxqrC>Gy-fRRt_Es_Enc-&Jj&_8Ie#td=(`|bcoXUA@lbRvs84fXWnKL>Ch zLE!jZWbDdTJ73n!Y5v12seB^Rd;qL|T_$D;rF>F}s!xUfkTrLabBt<5shq;~T~e@N zKRgzg|2jGZfaH@+qyO|m{(pR-MURGmEe4!!zKKB4kC;VF#Tf%bAc2Qt-y+)&!$D8p zj{Y(xYgf<3B?i)S;2tO_NwcUG@-x2;_vT8z8m|g2U0genw(KMKRa_c~BT<^jzXHig{4kE%RB{F5uTt*6A^ZQI zBT&?~1@99q>iOo~6T~+fx=e3GCFsOhH#bZ1jGPuNCeb)sk^0LMDe*8*;iwBOqrl@p z;uz(nc)2gDG9SCX+@4MNM#GRX*W`7BGsB=6SgXC=_!?mgooRZZ#Y19;`_# zP`KrZa8W6Zy1I9N(HLBiU&n%ak=bi3>vDfD6)};iPfD=uMe&YKnao2_@p3AJ{RqmA z^HxZGQX3#Azd$~$M>WC$|CA4Zj(>T3aP7PQ6`?;ezl7&P#y!IQPlTYTvnPNVO#9y? zvH$4|`mGVVUf_$?2p?1(^W=nxUWh)dBm&N+1LfRpBcuVSS#9hHO(%TRmnrAV1BpT> zwSFeMAeAz)lzLS%zyQehRVX-~e%A6$kYg+(pTaXK)X6g~hsC`cy&fhDHe{NMTX0`q z`CXx^A1=kyZ%T2#U)RAY?2dBHh?KoGNp|U5Ys^M{R5dpPOn&)3ok9#8+-#*|T+R)8UtiM^QhONPL}2=Zh(iR z`cs3B&2WGqL(Zc@V#d#1O4{8#Tya+#Wn)U_h9BE=KXjTB)PL)w33(euMxUpWrY`M1 z_-2(?f33$I?L84f(KBQ>IhSt_Lr^v>T!fI+taM>btr7B0@3~{2Hlwqq=dKqzrcRX_ zV9t5gYM$Bj{=LgqW{WAqQUw7yxNH>0<-8qZ)E#2%rL~f~&Ha0(;^i}TvlE}F>^Bjd zn8eg>%un{>dnuGSm~$Kk#fVr5zHs5^ch=Lb7IM51de0&Mx-NGRB%-d&locl}*FIDI zu2L}~piZE^Bc~@O7B(qONZrpKeSaAY9Mf~WBbhe}rkbps{`b8+Cg9w%qn1?p7ZLh@ z70osPYe-wp-HOjv2J%O1nshc{ubi=Lh(w?`otQ$*-jJCB_xIJWbmXrBSP0D`Rt4yC znq!G0Y#X-a3RCp+=oN%YqHQzXxQ##%|6jcHLCTm?#r9oH%udVkG_mI2Xv&UW$knhO zRnT{z2Gr3F$Y@{Ci1d}^3eKDrlR`?a7vT%4zpVWU>rN+i8W}TgdO%c0xibTcOkbgxt;@RZHX zBFa$kv{aB);QMY&7s#S*_6!vb;WHzoq-FbCF*>h0I_Wn;L_L`)pup}-iQO+mwE-1h zL{afe;~K@@AbiZr4xFzh0D^l+XjV5h5G;hiOti|lThtci5p=unF-6PEBT*=y<0Em9eF`k=rmSEYeZ{yIHFr~z7 zJvFFI_D*#x!i}GPs8Hm~aBjXznzVhc2nvE28McijQ)(Ek(jTM|d4@)7F}BHsA@BTy zYVT&M%YQy>}=pxzNIql#bu9ijWbG&uh}GUe_V8JDQV zi5WK}yGZSdfy+#LT$U??Yhq{)Y5@gV*D{t7Qg0Kg%3_keW#)mW>*kfo=RPN0IxXjH2Hg zx4*F9kXM6+6TQQ@)J$k36!m+GOjtom?X=Jk+XEI+ln)P&LO+*uoRSulC2|w4`HIE7 z;A^_~_5`sBZbJ&T|4cc4KUcH}A8raqHq%SuSD6Pt4q^S07uTeFh3QRMC-$LFgDN+7JazV93P?4QmrIoMYLc1&R3@c$ zP5o^Y-Qsq&)jIBlf@nXnIk`pxJWq=Z_A(ri(1xP2*6+L9fO<7AFcRAL*_=roI;IMx zlS6i8eLC%{lXy4wVPN%QRO@qla%F6k;BDP^Bjbp!Wz;G+r=gp$Diogl&aC2a>ZC}c zBE8ki9fMn2s1qB`CF^QwoO`~b9ME4H2`<`+zoCI4uTG0_Nxk;gLsV2P3~Vh(*N!CZIcDr_G##k-<}YAx1h>5qW3sQ6|qV0?MFj8-^eS` z@I}U?ajFC`evVGJolp&nH>Ni^s~;B8$x}^{xcEZE7;Gs`w=+f?J;(iThrhfKST@H8 zsFFSF5>pX)%1ra;IR2&IW@}vLefYfG{W^Mc3SKd_U+c|1geU+GcSihnbvh>BnZfdN z0dqu!6cCVZg{$2sv>YfzMc@+BUfX>x-d?L?UdfNhCYsc0C+fYn#T1Zv|01z`sIcsB zV;)Wii|(7}#P|cVD86M%nv6`cz~|>l3w;1s33L6s&w8cm3o3^5@%*h~(rDo=?j@RKkQATs7)xp@iSP&6VB>M^|Fb4+Kc;kp$lA%2*h-oNX0jzYRVG z`@7m1H(jNS2f!y|6T^B)C=?d>W}G-Cc2moQhVCu5dKr(b`FD)` zPa5Ck-mAJRrl{n~k!jurhQhqMW4-p>FdtefQesplYE9!td29i!)dup2Q=M@Gm!X~XTp z?}c+X*?KiBpw`)S_qz0;V{?N90Na})i^T38*yWP3AQ#^Ho;efCY(}Hs^=e(e(%$#- zB~&Fc6l>iqliE*t=xf-AZ_N6)?@;#{cjQV^QV;sxo;E;kxr8+|Dw>k#AmpMT9b`7H z;k{otU)bt+)<-5V@%Q7=Mg*X22q=B~w_6mNUlTJxyB^FGy0-3i>5&rpm1-UgI83q< z=nk=t;+0hl8xE)WHN=kuj6Flh^jBsOYCZEq8YZ`XVhctd%s29{^Wm$QicZIV^P(*m zCNvyceCcw?Yr1-%VqB9nVRc`RU#q!LUrpiv;P?Sz2)D!x$^UJAPv?c!9xRnM%B{4y z1x>Zl4r9hHzaj*)&hBsJ`kzNl2k5tPyRC$5*rYmeQ-V(rwl2m&-cj}_Wlz20oRYlc zSujwPY5!fvuvY0Prbq}_*1XbwjV(3Q$ASW!Ad>ujHETBcbx z-?rd;o_-$-ws1dsP507Xdx%L01?rL&Mg(H62aLjF?OC~>(`MP8rf8DW6@FAyJ=(72 zZa2xF>M62)vOSQAvUO5Pr+eA`Mj_LUmqsuQkixxpqIFunQrLRPGGBH-u=Q_x3z?sj zL|ZQ0$!{bORfUyP5H2p?qsqm7orM>0wpz#e+SYleVBl{jmnJA;e&6{|DL3c4C+ld* z1K^}?xjQYrM)<5Tlt%~=g^}`trh&OD1qe=5j-ftAE1^q=mEZ53d=%RIQ=^Aad5q( zE9-BUhOzYJ_gl^t-oR%F#Xu2}#OW9|zXx6g-p05$Gl-VSc~rkJ9elFj(kT;pWvBFV zDv}c|6(u%q?yZh=`{`K+oda+);zfpgmlE&F0?NHTkHQUhTaXE5t2o|=OL??-1}MUb zH*H;aLkHDI0kz&GJO?tN{Pt37xrgs4O6zTTr6x8DN?GC0;JbRwSQl)qYT! z>GIpqQ^^c$etNv>2?8Kb{H54t^uh^-!wUx)u%XPuqlQmNJu{vn^FbKhH5`Ks`w!*by`$%X0aV z&ThsLL~_(!@R*|~q}nK3#hE`8DOIyxow4v>n(UZjOn_a#JF^5z8C-U_JCAJ*GE1&mc?w4APrx^c9 z!aJd;dTS^2TR%4n$zT!Z4m>wL=ZAE6JK8Ci z)Kj9>c0Rbt_qodV)V?3)Ds&rpQfRc$;-z4Z>ed8BN}7cUe917TkBaOnPB@PIF9JC{ z!J#k$?I^+Z$h)3ww$ZhcE#KEFuZA38$!MAT(va~w5eTF>7( z^rK9E2C;iQbcw}R{pSY1bk8tUW?;{+O!-{)8|;@hA^Y}&D6HjCIQqe-BQzpiFwEY| z>A+(_xU8b>@XQYVt^RA3COt|HZ_O?;8{H&iW8VP_A}`t+^&{5|`ucG$6Sn0Qe|6^N zgM_X>ds`EXb6i^Pt)Q#*iJutNsVs|rQUQ&)Ul9Sp&%sJTlOb1FJJ??|U&nAqvaq)6 z+8nj`$A`q*Dh%j7i+ldA^7ST@D0DDX(pIfo5>Ir^j9WQW$_nL0Yv%Ga!xZ^~d zG@#f>Jk;xIwJ=8oY^`qfSW?PFG~a$!LjKO;>i>!9r`fW$yN_#g*G&0Bw7%lr(e=dT zM71>s3S>u0exwt9to#9bViq7{KX4r!FDC>IhLm}S(sFQrk#_G>3UJs?dZ1vU+oR-G zMC<3-r_+r)@UT)fbmyecuGsceZOAYUMI~mV>o2r#_i~T0>l$OE#!(>xk*H9u(p!Zb zY8l7a#2%8_U9Gwd-OTSViCrX{5LEmzXZ{l;zd%CN$7@tj{OQlVB$M+^eITzDO%1N@ zYAl@_Ibe6$`XCo3Y3;w-9f_zs7g*sHtkEO%Js~?dYgh!VY8lZA$h+GLlQGWrCZrnN zh1ocQpy(9cb={Y;kxntxybH@z2D`TEA>A`;F*UZ9ZzyUseD)@55?kfjg!?O2aP9OH ze;mKsDZ>QHL!zITHlNLSY7d8hbaq(ILpGy?A#*XGc-Mqrgj2nM5HbufdMu7{5=WOo zD5q(1Tr^dR6y>F_8>4^4D=R2fV8BV_MUg*+O;g2nCnF?gDF+5=5WYicywgqCjQRP5 zY0Gs846UFl;;yR=i{Fb%6-TG-;8aOFXb9S7RQ0eF%ovWisIwAx$|W}*3u*jLaxkm;xJ>)% zefJS(tv+$h^taB#(RW9M7JW}Acq+`roTvhd>pSHxEV7dhi6Ur?VCtq`+gIJgdI-^@ zPPj8^%h~8n&d;6?2+(0Ae{+Gyn9f4<@KM}ca^{a`yk;w=UoDW2#E+dgPe-&dQ&Ad<)CK;c+aEj70)A2 zMBeX1qpgxSfqG11$-}*gs7O@BkP_az9ajNP34yw+@B{$N1Al(3`D&Nn5&u?$W775n?c}0)t zG0c!$%Bl`)7n(M7DtSN?Mo_?m-Z0XQUA)e413WJk7hXVx&in@CynbIJ^Xkp{xzGLi zyc0r?bakz970(pk`_NxgBlnohj|%1+BC#ud3cJ66&BGyQ6?4pojjh*>$Jt^IA@$7d z{ZUxP6wl=s#NCZ+hkP!AeKsLCOZbd-r;RN;jrNgi#4Rq3v-36CCf1dNTB2yt$5Yy{ zc~$VC*UbuW*aCKN`}<(f0zJf?z;D`ZIAEV}DEjv$+eK09Rgt%;$K>%qfk{#%bq9#8u|sGuux(;Na|fjFl9}x z@IAfFuJ^_DorYQY--=j63_bv=ZQ%!Kky@avnTAm5>k-7}wz`aj=Gm$o!05=DrB^#w z!m>X#EFC|XjZ*x&H_)scTb32r zHdXOs%&ZAge!NBam6mSBwRUc})+Ce7Qrbgr{ilR)()#X`6BNtq#|{xkz~%U=njF={ z{0jAX&%Djl+fTeInS=qmfUTIMGndxud0_w^0oxz3N3yJMaLE9E2>T5E;TMb2-r({9 zDrd#9(&Djtw&@pukS0ao-QL2T)xLXp#PEfr{0A=-W4sajVYe89?8dL_6lITiJHK$N zM+5>3c`uxM8KCgGk%h2NqSSNrRR8a7ycy980!vG(V(BR)|3yu zb*AUlP{&6N50MukW{?yGT}%Gwa_$ARO%Hp^#D@IwaQR$|;r1d#Izg(7B*vMnGdPe} z)-%F|WEa`P9*Ok~1VN5|9e>!%(#`as?b*@z#^sV zft?Yg!5Yh`l`fNx$nTlQbxAT=H^WJ>@o^?yg#i}s5msHn*svICbe%?#uqgo=!B~B4 z*rnjk_wZsOYFP8e(L8y6OWpO)V3FdgS<`JaPz9=^#-vIzH1vgk{Ge;>tI^5q!*ZDsox_tx_lhb6cF6gh2!2D zVelwxsFH=iP`@T>IN5&HV6+w+&jtr$lmC3FX7dHVJu$&21pgpznbi+JEQ->ODf&b` zZg3=$`6Ixc01Q4nl4H?@+F1n-J86a=?~b|{JVs;~VkH`O0SUy3PMx;)DR640{w9}D zqj6jgYOXtcQ@UH7m&lqGrr`@JjKk>3a*NFCsG9|L!kb^`#yVjMv+no%gWdr=7T=l> z&|MBVbW1hFg~CGhzyY5h@NF<>{TL zSlGkis2Qi*ou4Yxp5pme&GgackI#z*s+zEaZL&>?j~EBr{>R3i{U{^(u~g#W z{x_ZR0zprIyi9M}KK`n3<+c#tkgdIzG8wG&0nGiC$PFqUK9#%_AK6bzFA}yZ?{;2~ z_%o+AAWD7H$xia`7nbkgpF981SkvgwotN=VWp}Pk6+wQCp!m?J7x`f20XL}8M{)@2 zUqAJLq1P|hiV>@~oL&?eYGxT$_I{l`d#X4A}t z7nQX%+>fvNQ=ep!ci_to^!(7d&B|Yi^hTuA|5jE5q z^twJ1-ag7*z&Nu~T+$k$l(lMs|_gsL7Kwx8BvK5qhaQ;pu1 z?Ua*uY4E2G+(5Y>zm)9ukACxb@uy=zFbe()7LsNu735~bH^L_6_(2VzFYuSiUi_V( zw7H4u>T|iDg*8-Gav8bpE2)*_;8q1XpG?z~Gd=}}eiW+7`Yb{b(a_G6SFL6#?{As3 zE4z0gZxTvzWsij>i!!fY?Oq;hi(x;dX+7&MYGh2iDA;euVIQU%wwTmgNk6MJbv|fb z{<;Q$5dhIo6wr*pvSmZ%I^`M#k=CC?yt8IqP>6&u zd)>=}gdgdUfAGR742&D+55(fmB&cc_Wy;ex7-#*p?jc2>OX)<7r5eWKgU-fZH#gjn zT=>)?T_|iea^>eMR)eyLNKGX1nCJTQ>Gb_KJ<+?4ik>-?t`fEwa(D8ae6#&$7^VzI z7_+VirluH3nae@`g*1-gY2FZ3Mrxm%qxlxEYkQaOYH{U=TY-=z_z~DetxTyq&Dz;ufFM@mEO@_6is4b+6ubv}toL2z8xN7O)x!_O$on*4{1T9x@kOG0f=i5ry zD08usSNFR*NrCOYc7`|Iop~{8yjHmq)19$wPe~ad3?mg}F?_=yh-3?e=$(Gb1(Q0R zSDtsnRYQ^cGVQRUv%kOm`J6-M?+ORAM(D}~9&ALDfD*kpV^Ln@%7vS~xk)wTLtTh(=-ie|X#J1q?@c{IzA z5GTtVG35y95|zE8H)j^Mf5A4v2`1j*Z7+j^^r6fmKMT2O;ueU%d#3+;ep)I>P&@Mki-H z@Mw3I2`dxrGp*Hw4q6y%X6?FCR{&UQRJPhCXJ*O1mK}`-boeV8OUsjp5WaC2B31l3 zpq!8$m~18elYI3{+1kgztsldz?`sWu!0lH>3TKawOHO`Bi;wm-EVe&{-`Cg+6j z)+1$4Dh$a~To}pRy^>#;A@>^;_f6u~S>jLH33aymQ&Og6T72YxGs(AS+(awQ4pY(zzqniDFq)clA4T=^~GBL8h|#`zcP`|0aO{JTqf1;;sHK(Tfw zT(a1|@NJ5kodReXn#m{a`J__ao4rn*Qi2`Tyf~3i8__Gtj-V=r5xbI3ILgsQ^sW=- z`mbl(6?Km;Dc_i%;h^qvDYuY>D2RR@cR`j}t0EQ}^*E_Y(!4mc_Tzv;K7Mcn>4Pvi zft*&pRf3P})DE0zvN+GR z3lrUCGR)rQ6Hw%|hq}f{=||AXN8}=GRb8J}#GE`K73+|2mNsa(z!zA0DB;@V35RX@ z?f>|ju37T{jY3{^3T?6;`(N!cuCyWVIWNT&C28{`Lf-&VgZgm266Ej7-@c^Fzny1q z0*{8Sd8o|A?Al{(jQI~QE%D@|DyHAWvd>ky`16D2PG+qJ<;7Jyi z)AiZjj&OXaX^V#EGgy#AKS7SB8eH(((%~SDDVe)e zw>kTMwt8Cry8g>r3Hhl7WsLQ~+d<3l9Tc?mFM3zoEz%9;ka-=+lR4>i!NEbn2tgLt zNanH>WXv(uykVj=WR#Ov%I@Fy_ZNo9UOh!=kb8zDr+Vvjd z1<*#|6u(pQG=?i*Ep1yssDvR9ZithY45VYg-eoFOOj2xZm$(uCwkvO~9{+&k#k5^X zfE;p+YL27xewl~doRTSlp*f(|m|~2gHz7He25%%QXVgT`A#?0wIuYXX8G5GY3k2bs#K*ISHR&9wc01j+-W#JzVDgZ{qU4wVe`ji;9;FuKh1*A)lWGm z4xgnRG+P)kGygXKyV`vhYvo6@<>_{e0DYf+=T zmJ>98TP`Th-~+AlThc}V`v;CS!*I(^Ia%*3yj0`b6M4kQq^g%yb5)+xccrx+hOO60 zGsR8Yawzv#3*F@vCRpWa5$;8mlFDiCv)veFp*RRM<`t4D+%ukZu0rIZsd`2n((Y;g z#qn@$Y-}~f5{ui}BKOV>kFwCfE4XEb71+qEaYkzpp3^+lE|HARA4<$0zrNv&!2Cn(1zB@{FXXDlmg+;3>JwTT6d ztZ_v3!lx2#=|MGu+i)ZR+XqalS;5Cm?=D%GdZ#BrzP}g z^bF(VGy5m2BW`!{aP+6z{URwVW6Ev6wHoK!cDN zqSg3bQ^i85n%4|_*7$v2sg3)>f16XCsx!76(hd0OjZ1lr&&~u|^-rq0Q7(SFdT-$R z0mo$cdlH`&Z?ImajKBwcd&`tgwOdA?>azmb|j zcI{CMB7KIsX6bJm3zSN`v|8ihvymejT24zv3-YGBJFjmqiqeTAd^mryZ>v+0Yb{E9 zR8YtX*L_>c5{<(X-TqYZlW#3)FYfYbX0eh5OP&3biNdD1mUXOCjxcRY4QE3vqM`%Boe#*gnuU#N$MLq5U3i zhcTTZ^a)m4G|qSOi64)u`eCEsvkH$GzW4CITNhHan--L}n4uL4$P?0a5?O0j)R`)2)7Azi=(yE?fE zQ1g%`&?09p#mGZ8VacyMCY^ZoaCY*@w03N7$zYQJfWy=UhTOu_aKd$F7xzI*ROzZY#Z6}dU_H%w3U+{)j$%@xJ zY3r2J{fX+XGnY!lSAEL0WA@JtmjfJ`r)#L}6Yhd>gv^N^2NkISPvOmtt{_ahZ4>>&P<@9&=6;b7;tQV(74xS2J zD=ZUAf|aV$qPrhl+Ag@EHNwB|=@js<1w(Q)tTC0g3l=HIP9AGtwrEv2a!YC(%fRxd8l#mstRu_l`y7Mw+9UE?@idYHSisx{qG`Nw9ImLb5*v#d2*Us+P>y@kh1#n z7U*h-KlK^7f=Lc#;S*Py+tk@+Outez8kUQrp zTB~p_d-;g)j;{k9(9-$4f&y5}mqB5zZ`)22YbG=p-8*IX_tTBXObeCYRSsK~3HD58 z=Plme`_B+O{H)v?LFb>v^!Z8Dgh^7vxO#t08i^c+Gyz*hx_A|9MZB|k9y6XLq{}=N#5^b*pSjR zuCK$}cJ7}v-DynS-lwW4sQhZZ+iBHry1zY8Wz0NyGLLx+yQIcBdIdE7FgAmTk`Zy8)4NP9PX2kvvE|{r-Qj2a3!V;r^6YE( zPGtRJfR+2si<6=b&4Zgata6**#m}}b9J7w^2}uiE!(?6rbuFNvkX&lY3Lt6FlBwx! zCxhPk`ME}Z_@Xf{={m95saWR+E|+@rMae*f8t$RhAa{Xz`_k06#vEg0%UHdEpNNJ8TC5L->mZ70@rYOey&+@gk*cj+Io9 z6tGCLV9yW79y4u;hOV}@DSBZEfW}p6Yb1b>L ze1S~UQWwL*`JMZFSUEH^>ixZ?IRZ^5-=9~G|E!&Tujs5oXzHQzeQMold)R%6@9k)ewg$X3h8{h92HW*k1t@djuT@|1^4 zBXgx9x0kifdYoF**;9XKq#CZ<$p1B|xeIded+0WrcisKR;BTAK>55BJ#2M|enuUge zrPEk7mqf#7p8@dnAe+b4B9>U)973pRKi=f-Z?iT7j~hG0PIZ7l`E`hEg)-Bjw`Is9wWflAzZx=(2-(` z5Al1r=5RbWrGSd5MHYt^To6R!u@_3z>C=Z90lVyIr6#Rs zpEc=@j{Nz(7L`L`k(_JP{QBOQ*x!AQDvYAFFc>6ol|IaCdmY6jbg``O?xO=e?_)1E zGwx~#=32duYDL_ROpkbepPInyc*9%S^qJ9h5x!>+B(S^81|F1o5K5G(q|$l~s8kI)h0kMB{+_s9ar z@lfM_8?*CLw~Ws2o)uFv`6zPytvCW|{GFg%%%vlR%p<|@4Y6C9V1cAX(S3j;h7cN+ zUsM~3?Bc3^1`Depu=HR5sjdPB!F_uzAzr0y!()T_*e9EUKYuXVzrAp7kC_0xPI_U` z)URr?Iyo(tRWvzl7^qj2K! z|%V-|3lSVxHTEJ z{o@7-2oj=<95E1+5fUSYgn&{aB3&{>q!~R1(kKm5GC&0B?i^AZjndt0bTeSoZ@$m_ zKF9aBD%D=d(#y)u(7Cdv83VDz+F@F5b2bBI|o&EbC{9j9k!O{Vr!k9w}V+I*xLEoDPplC%jv-#z~hq9n+KR0bnA*H4>C zDA)q&k0N4R3YrHS=7Jxd^(1?|;Q5v8|8yJ>#Xfe+DR_76mSO0RXCr1tEDPy}ta@mN!_s*IuPnmoY_c{e=|H~9TCA8RFl zRLF|+_s<_Ko~%!^I43Gu)1kY@IVYm6wCfz+UJI_T9Ke zA;djq(uMqCR>#{AiOTHadi9=gk#I|UJ9M>GTx*lTwUTt&?i@k z;{8ewmP`7R#5@gJX%POcBC=_;o`3uY_zPKyHsvFSN*@S&Byo@Ek#Lz!9fxN2(r4!L zPHbwJJ#xjIrNaQ$Hz8@nFt=D&b?cHvWn)6YZuI`rt2)VQ0BY2erwPG3%Dvt{Axw^I z6QRCOh_)@7rf?Z4gpdr(9Dl74Tp^xgu8txH%;OKq6Zs*G=f2+l`NE*doEwo~LK zA`*3kNz1w5u$O(>>(+5kq?`Vy4*si;fuGl1=s99vz8z2VKLbVc zyK(|@sUXy}P*l#(LNN5v^GDXT5}{IuLQ1C(5+c^eH`hWVDW7cIw7GUGhv!2Q0Pj2e z(q~OSQbc3-@kHp6&zuHemT+!Te_XnK3h z=fzdYgUoaW_um!AvA+~na%EDS+42pB!!vrQEmGd4LgbSz^yU6!om=5sXGi*@3tY>} zOtb7P+V}q&CU*B7ishxcENFbVeUe%YgH@<1u`egh4yapy zMw|oC&eSp=*I3Yv8`YRh>rT@r%QWsx41ZOr_qAnB=2a-kl|e~=OL0`{UVD2jqu+U+ z5eJJ8#L$o|K4!Wpw)xQxbE&F2oUa%0M+SNs^ec`ZHk=@}7dMmdoY>80VwN*IplO1e zIi8R0J>l`W;Sv|8+7*Q}rKECF^DjNHmfBl0IW6mSM0y1Jp0G~lqbsw;m@!V_r-@m^ z`MaxnuCh1NG1O_A;t?$LCCWbvF|Iy`SoJv!u%Nk&is>8r;DJ#`OKQi;pI{-Fg6JDp z;kf70b(~QB{SAf760<>E%XjCT4EwP->a}4B-s{Pk<*g9rksv0Si>G$sf|pPnL593} zPsW>*7fZ2<2MK9Vk(XV(;3UfZ8`fS3pe;foVjW zUb7kTk>0@43VQL8@al-?%_CvfG`%U;eOr!|#k*F_sOw6>@OS1fTd$c>JfvubB|~7{ zHb0bEH-h(5w|2}~F`0bKt9o^v!IB2=GbpH%LzH2B<*qyV(9ZcEAnHSMFd44`+2*bO z&Wb{*KuSNF=>kemuB_j>-m5x`1EGcvcUz}wMsB?4{)xS>YSsd!1Dl3iF}t|MXWsJW z2Q6~`^}f>00CLe!?BDT5w_rsgasW>~hJ(-HPWyN^kATMC;rs)9loqT4r8B)I(F0=z z9!@2iE;q^^3Zt)k6YR)6Is|!_Q3_e*1>IX?+;^mY8b+JW>8~55lre~QqgQM+-k3;x zRN{<;MaNh4)=F~0d$GmJvd&Li%scJ3dT1Mag(V$zt4D5@(WQ*XxvU7_9mek#JU$;& zxq*zr))noV+542dGov1yU#d{O^6 zNUVX2%Q?-L=i@evkLJ{~w_e0-%c$Ngh`P29=bT4-r~xI%QgajUY#MZaVZqS7GNvjh~5IX`iI3^sTYt`F_yxE7E3bKk5lv>~kL? zeJh3Pa_u8aXl|^(neg;~3Nb9Edsh{07H9L{I&L>hQIlZRhV3`@TIpX@@w^($mv#Kk zt!Z^$^@!btdwkDeq7naOo<3T$JS6tFcxD2=tGr;i)A%QF<84@*I!KyV{?pyVpitxd0cj@Xsdvxj0Ph(zx|4H& zPm|WxOG)>;h8bUi-gXPYd#rwNVn6LPeHtsXYlfe4>^Ta01 zu&CFymk+2+G{w1SzQkh$QlTn3=GOB3=RAy_mZm%c2E+!tS>_ zEXdEExvB;hzl+mi+ZE^nhCM6}T6%O0wPxo@Y6nWC&R&tgeX< zhtt%Zy3J2J3t!C>v_=w!#AzG<%y-~8mz9@xo{;C&bbbF`~6HRc2Ba)=l} znGH3ucW_&yjTv@zzlXkMk#Ix!~T z5(VhIA_}WNYTQl{t?$#+@E*(cr}h)zWGWUALsu@YE8$)<>uIob1b;MDYbJjO3(WNp zO~VD+;RV{o#?gSz;GW9(G0K#vn6bO3uzqE~_a@KJzG<9%tI(?1 zcvS-I2o8YR!=sXV%Wd~9IV|`>WSr%x%Lpq)sUB|uqFvqbRpSg}8Rs0vYpsf@B83{K zp%oG~9Z<#834FLkglVMn;TRU`%3DD+PpGVBCM|5i^rkqj`1YuY5%dvrT@idYFlM6y z@JLfOQDH!V_Tg|TFx+0FJc!I?y0NP{b z^rq1oRUTr!?SK?EfmPVo=s0tjH5CFxA9(j!dtYf8L2Lvu4F_#J{u2pF=XvX-A(SZT6_O!gqnNOsmsx? ziHW04Rz8_@c^DAEj_kb zN0W=fiQ)J!MH|fO1WKmnQR&K)?Ci^&R_KtUaj=UlkXYx&Q-12Oj)hums4YfXtgc2p z|KCnlmEPg$X(ixnW+4FH=4WQ!15(n@h1&XuQRZjX!y0eDBMUz`z^5^N|h? z1`dXVbdbn`C^PdDM0i>L&x^unfE&&cJk*t}%*O}4xLEgb+hv#8ISl3Sz=TFjMQXgA z>Kqii*yZ3y&of`bcB7j@?IVM3_tFDtn{Z4_tzm^p5>stplsw&*)>iemw zKV8w^6)LhRMe7l*zOWEwoJ#A`fXOFrhDK<$EEa}>iDzIvsdd)0G@(No5=8W$a~r!y z){-(S|G#J0cDn1(BC@C1kQNbKq$sg)8F576p&Z=AXY4J;jS`>H!_Qy!ZxWla#J7$} zC=3sZ&R~Nx%t|SI%)}`t5+_)TF$uWYA(@zD`BV4V)Wuf_rY}cldSI`d-D~H^D{BGv zkJ#8YAII;`KvCBU3opVmp3>Z456{-n&9P(?3cTdUYFDX!j&uwMcn3S^SHO{fUB23{ z-r}fZF|m;{kIir%!5VTR%(#nwfG^lKkMSM2q&R9=dbN;kSS}mCTzKKO;~(J65-wJu zqd;P>QdsAJtD4ArS(9cvNOTR`btszsF7G)0d3Sa5N2CWQtw$?3Rw(%>cluE_)z91u zGx@Eu6G7mwCQ-~t!fU5yEyQ_lx!UWrr$PNZ;V*o# z+4<3{PPv@6)Z5HbI_f%O;B^}|XIhT=JD0iCyu~%wm=9hkT}`dzKM-p-GP3SUy_z9- zQ5f9ogY4Y}32x2a*!|mYU}D=Z&~HYC-Z_r`v&~2vMO+2lYuh_JOFLFOn|qUbn?u}N zcRL<-rayLPF5u_mBY((!6<<<^%AAP1?h+U4_Vv1w1^pFqbBIW1`?$5#(pO@VdLK+( zzfU7V-D>Z6A zm(Kr;jMs9ri0l~tDVBaU7lmDVyzro)#|61c9giDLXTNisuiC>zX-7Z?*NFOS^UDU~ z9>TG4-Itz{212PvE_gEutBZN|E?T1D^|Q2m<;V$Z^O+uwJKww?F_9Vpo<#XQ_}Bc_ zs+Bc|2zS2@a;mX{GS;$YKWR=zPAOxzX&05GO8LC;$;ocVPE#8K=0iumwA`J^Ebt^R zYrW+#i0>!?n*a`7jDwL|b7K-1C`qG0Jm>|hL4vXb5D@s$|~CwP4>T!W~vh_?83FlsC5TY!Zx}S2VTUR(%NigO0cdw zk?(hjZulR3zIWbX=-Mf|!rRaGRYQJjAPC77$$5jjoYRf9AKDwVrt$-)C7^?2{}PCN zXB=%QY_4=s;#g&8f5$+~hHM=0a@t!KY^`6L2FL;+9>%HNlO zro3 zn80Kg3dy>+e_W@J9jcG+tr{DNQVLr{+`rth(Wj^Eb zJ@=A&#p>agFHEfeXzCWlU@N=pV0^gBYp5|1BRAf=ge6&02KB6WS8Bn_QyNKGPS`df zIWIp2i6Q<Ob0JrLl1fVcAiE)OfHKr&~RzTR7b2XKS)|N zH*qv_Ov!O6f=U-^ARMNxOQRv@7@|G{u@Y0^7UjFqLENb<9!HoeGG3OZZ}|)bR!VHp z5s0xxheE*OY{`F6_rE&k@P^E=*k>~^{P-B}bCPZ|vjr7n;Ao6DoY~}xxiSB6Mpf}c zn;sLa%>7|a^f6PRp>VnDGbQe@m4K8J4pQd*TVD4gx7)hPWYa5gQE4m2K9uk zd4*s-2G$}v!zE-LR1Me%UVQ8!YQ~c(SD{u{fCX4qa%aMABQX_^OoP1p|q%(T7SYBmJ?bS&uqI4U#OJs1e?0nD#_%Dj)n zN?v}8OB0wW1TPad+WF|AsBdlZy_4aHjywt4 zO~=*ib^FBzN)yck$7>~4`#ta81;$h^3}~<}>Y0?lR7xx*18Yk=u25Nx#YQ$v2C<1b z@A4|OVI#?Cs!Pt9W^27n{2E+-BPYF!VbT%qZF%SC=f5|aLThbkd?FNR+MVp2gl?f? zP=L8(gvTJ@LU!41K5%Jc~)$5QeSBvkUZxZ77|?l zTv1(5{iB2fy6;AW=O!Hxq6_(cBSO3GPiOOPcWnXm&7C`A&CRkg3>EVft`Tp^07NcP zEBK7B&C=d4&N{}@D^^1Z9xK(PAJ0H^g0O*PpC=i$gy(#5@BM}E5I4@y#8XEBj(-Zo zI|gLT;^mKZTZ==o9TYPcmqd?3q?b^j=Q*L&0Y8G7-UcY`Z#`Dp7}MP#zZz)6w(KmI z7PH?$5T09k2Ltiy8rFtx@bpw(Iv_WPBi!HW-7|xEL@UC;k~{lqSl3_mTk$U z)0UR8GB#fu%O9_IPdsli+x~UbEBj1NP9PXJ$0Li}?131_@(dSW(7i4It<$F31WgIN z@NOWnLe4WTI7RM@laogwU94T=2FvzD84kDmUw5k!#5xTI=s_l#Yz}bDYghDH$3R+%EN>I(XAB5@u9lL9hx34Remw zs3%fcY((^K$_d~Jl&4iEa9d$M0;TnQIxX_!S~r2dZ7jO-&8RSf`;`8`0?cnPQPCfE zs8j8?epRwEs8F^F=H=QGk7^wdFR|#~csXHgqyFwj&7^SQbe)|p72yt?S3|$2YV}#X zt#;F**Lwe9XipImQ5wAJ&%Zc_+AL=sMV!V+)9;q}ty89K1j9#p566r*w?`r!4=Fh; zmU7Qu7I46AybxEW9woZ9y|Je6tNf6E_6oC9ZXeYuk`8lJvnVErj|QkUH&P_O#}_SL zYgn5x9c;v8UG&$X3d4@xd)D974=wT;xjH>=Ih|cs{am7&u~3an@z{z=W?$lVNr_v& zBpMu8iJ)4aYG$0G`dzxEvbpclpT%*SENwrGd??A|k3M`UWAfp{Y+vU`^&w3RbTgKq zLM+jOZ%vpX>nw9lnGwyRaMIfBrX3ffY$`|xVKwby)#`GjpiyzwPhY z>uA}Wdi-xI>`bQa|9PFDVzN^odb!G)_D$yDMy`yptzse<6q8{&13hIiD@mtW$2^o9 zf4Wr&f>Q1Zu3-z`BdWrkomg+*Ns@=<@UYIj`)8ym>z2b`@tFieuVr}yCsD&*iGez2 z^Y(b@6Pe2`z}8t~D@WsDU3<0nun-%{yakhMl$i*`+TnAD0!$($va{w6xCEQ!U zAb9%TEkaf4?J_gIwk2Kt{@$~%A~vhUYvS-UMV^bzCoKi^6BSCan}Wo!)R z8}T_ltGcywTgFhFjMY&kZ6YX=8wC!}WJde(v~q=q3uc1f15joWJxU)AUtzT69u&8t zUaiy8;6%O5=BKR5gfAA|mG1U6$QP_apcQ=8rP$`$Dhu#Ip5Th#j#b3U>$*6pMenMY zv9dY3-;uuqp%|Lk58@`@r7XZ5S@ZbLx`~}HWjD%JLpO*yoV52loQXLevV_3|{|E~N zE})m2lAF>%Y$8WXVx1LjWBJ4<91x#1LFurL8P1xlrGQdXQitHuOM>e0u~ zKe``!R#vZOUi6I*(+`kd)V07%vWd$^|6L1JFkawfxjQ~LH^<4IiYhb?aSYko+~lRd ze$|51`=`{Q8kXDMY9J$C^$ilZZAIuNxNcIgorf@l03*GYK!Ztr^m#HoWD)NI7~AM< z#ot5ZAakTAt@9ZTadZS=Ep7$*RT&$pyqK+i-XmyPwHr!yl?u5I$PJL61Pn#Zh$lim zpDTc<=^f_WXMFv?do3+f16G!@QE58?d{351+pFqwApX2~=|(z+6=Skw7m2@(G)sxQ zQZ0v6=sBu^Tw|U`F5`^j;)T&}b6wH2DoeNaBcAxZ7Pt+QkN+3slX4iBT&zJ#ZZY!O z!h73!|B6LhAPyd2rG;+S(}%6D>xYEdgr;z7^g=f2eZ-fL^MCsVL2Hyd9t#Xqpd(f$+G)M zO(ms@=&uyV@dxz-!KQr$lE@8xPOWHadij;+c(xKTH-fMNU|l`O1Deb4@Q^g5(1M$x zpNvS%GB7Ccp2x-{?62_?4e7)YRLa}euYR`jvoU=i!huS8h z$HdVl=%yNQ(z#@`j-b|An;;+Tf@}dsccR;X8=-!ckyj2f`J}B%0i)CU{s(Ziay!U} zRf~&487A=|$DxzD-MCzG>@DKx%tUbI`P@I3(fM!S)$+C38iK>=7wKCbT|}dM1yl+U zcph!KvVpT-@Ok>kNtMKq`>!UX1=yinL)Xo(%<*%YbY2np7Q@)<;q1`$P?3OucW{9% zc^069Bly=-)lb`bf>hx3KTH^fV?RWAc7))x7AMc7a@O9P6q| zg;A1Hs$p+cQHNZTzCcCc%_!i!ud)!@2NG3+0ebRJcGASpyeblJC(9$U5oSw!{}Quu z^AUHg$Ifq@O(hHr&*;luZZvGE48PF*X1`ejgf2hS&_kGf{k-B}@}7U}&2&PU(kb>B z{T4Vi{>{rgs=x6ja;(TBN=MYX$tA^gc79?3_$3wQc3YUFg{p~IG4vdr;0bl%E8(>^ zl7W@Y%x$7J6V6+|KRMt58Sqr2U(K*&WB&`U)Z8UAWC%Sz-nN74#^4v!g$6=gc2(K1 zLVW><9(6D0eD-Q(ySnRziZt+)h_|T6`vn83+E^2p0Cq3doM=UsI6%MvH}n(L5hD#{ zM4vT(#TUsWQ5o15kPnyS!Yty@ep$MW7CA~41a$>_4zrTLr%jn1mt0SJV5Ik|;Oj@+ zusP3wod+CSR~1JfF`~R(2Fy7L_@q^-o!8_~_Lh`2g|ib+l;3)WFq_}McRO5&v{k4hEo+n>xacZ<%EADQjABoKb(A!68Nrln zmza<8(Hm*}=*{jX`&=!J|05`rOh@U&y;K^kV_ZD`BTlkV{H-Rlz&OC*qu7wp7o5Hm z%d`7%<|W_5DB348Y8;end-#vRS1Q*aANgfwYVnElN|f0q)}i9TPl8Gqw_Ep_PMG7( zLH^Nt8;$ko7Hq;fr#VDPF7#+k$uBtFT_(+C*il_{F+Np^r3basXq7)LihgoPWrB9J zHM3UrY`ItaA`Iu?FH(ihsMKb0V)~O1?kG` z=h0YMwABy3lRz&{+fJ>^rzwZa0{kbf#RpGEZOiFSlZ-G zaO~EV*|`r$)i5{fv3Bj!CdlPVVv}Sbn;Uh(a1xqg!Zobhs6UjSW)q?W#XrVMD}8|6 zBXJANklzsXXi9!w_uVA{M$3FOM3SDH#QmpXn%$RVJD$_bdnr9ybjC~JKJqKMR4$^H zUpGSd1wt%Q)dxSziw`-49NxnXHRQvUM4diGid(Vq2rkBw1I_V!6={!MjO(}ooGlb= zn+FPt`yy!&p?O`XyOSLJS^s!@jomM^)_5{?Ka!XcRZnbrH=LhXKH+uuB&qe9-;@UWgK02@De+AxTrl#!Z%qrHq|4!K;az?Jmi|pUNKv-DoiLeQ?#S zj*I2b?U$`}x`+3`t}vq{1_iQpSq|c}YJqJ3O0BA9^`7KI|1-IpSAov{8Ku3J^z2!C zt)IfHYdaW)+Q)^jYwk#JG)7PBG4rZma@V)%|!TCc9p2;Lqf zK|vKRCb#f)U~PLZlUwGeA?I3kTQA;?S2*_O5^*8nAZQ(Q%)Tf4^6t`140MtVIiz`L zrdm`+x%A1}mVcwYk~+R9od6jP+Lgu_CZ~@6mkJ%^oRc`ompPFP^*K}1(DOQ;<#_Tk z!g!-HyU=%zDRRd6Dwg-*ozzyzT|xdM#}t|ehDX;)x}r+X7DNZR(bH%$t+}|Ge_OxZ9!9P zz_TAGH0*zc9hb&B-1QE3H=99!7Q^dM&+0U|+b{8~ zx*tsgpNl@X>e>0lW7g?DFu@}4b7uLTR-jAj%xbY=;Jx|6qdCa{9}vlZb$Z4#zALus z#&Qr#t0eD>US#)*h^GBMcryde^Q_;C)X=}tj#~#Xcgxb^Ct|)#%|}vOwt2Q>T(pog zt9OiG5@B>{OXKAS!;JhyVW`}sM2mSiIH=T7%q(+DJZhh(EMl@qWiS`#%TJNHgCa7A zhpT4u&#E6E`|bS21r#Qx-9sgf&*Pnwx|3}iL*Iv94j~@N0vBt;s@x`9xkUMi9d>h6 z}eo>I+%NjT|Pmw{bJu_8WQnm_T&X>lE597rPP{tmQh(H!z!5od3QvZ&{*X* zQ1wsfXDdNvP*7g8d^~U^Tk?)<t+T7^?By+s5ct3^-Qpg(Nxi1IwwDsS9n_4bW{f>?&inkz~o5(rA`IB>6 z*MFxwVMb8E;87Auh$FUelRFF=)oHr?7kj{@!Od^UBvz`w8!|b=Q7qK5+MAeQ&SbWaX+S2a^IjrkpHC0 z7!*;?H*nJ6cmBC|(q}_yz|Wd>Ah!5%(WJ>nw6j(ul)|FO-&rd3H_buhM#zubL{%+n4;o}17T z?!6&%ZBAWR@k2kdYN*53ek<)OE390aFl8^*9#wS3Pl^1l3z%dA9T^m*k&|!cx4rOm zX3r`jJ^wvYRT@xm@8}o8aaJBoP;eWS7uQQzydsl0 zjI@frIi1jwxT#j0vPdYo514cTFHad3)Pr3%KWMER9NRM+h3+~^2KhX}N{5iO%c1zSaCWSloY^ahL{yk9f{;$~@G2nnB zv)!m9BpThduMVTpjBv5`g6wi%42jHZHT~JlYYW^;#dE9w0Ns}KXa#WS&~wcHow@b# zlW_%WUFX{#@+t1qYM;yH-3G+r0%Vq8u2)9otzVza)EiGIz zw=yupGwxZE@VqYJ_0v&}U-?#V`(S5|O%Gr^v#U|ylx1cmiILp>o&=hVu6^^>ckco) zL_hPYNYNx2dG~-R9UO@9&0gH@Xh!zS>vjYwavCWpc3TO+;a z{X0t?y%(faBXrKyu*_+oy?aT;m_m0$d+qO1Yb27DiSm!HsbZ0utYol5jN`DFNnquE z`K=QHR@e7v(@tZ{ns*S?3E_s~evdJks4`$*1LGn(sbAswt6!JTDp=^;xafzyc}ceI zk`Z4pbnL_NgwP1MehHw!8K~2HVkpx!WGPR~HGR5wDcRFocSt4-H&yRpZz->o99CmL z!IaUA6-WfuyH8{-LGCEY%g}+XX)%>5ORJ5#5H3S~h>beVUU4KgR4U zYe*s%+G*ZXDA^!2155T&;JEl$!kf6cw=NDXPI+(ww{dUu{jhfWo~!xj-@9KL7B~US zSYLU@ReITI|B=x9>RL4BJTz_e5mqLSOEpDMao7DDItP~2wxs5tA2}`MwM_A@o=&`! zo??!IPa-NDg!1l+x+4my@6K%uR~LAM7sg%*ZYsXcy@EV)2bznyGIMH6BTd7D3AgZjm7quS-vCAkF68#kxW~1== zY3*vK`Y1g^ne3S_{V9FNVvh6usxvL?n1T^5HVxK;c5iySHX;bbDm(L8S~Z$ET6a!1 zlDmS_QCunD0it}w^uq<)$e8$$VIl&iwcdFa(>}$`s+W&bOiT9L)fpels={j+k0r{$t!-_&W0-VgB8;d?O6{!sIl3*?1`}>W#!97G`Uuy3)g(6% z8!wp83PG#;w=h)^JxR!*4ZlW)=u(R3YrW&Okg7{AWqsk1GUO|yMFPv$qa9 ztzHn=2~#m~RGiMS`Jsi6vW%?q!q6q`_*v-J)WqnTi7A~Gku z95wzVQ;488e>=OEq9ag!AkZh?)HT|^B$jGQYrY@pIh3)7{$wKqM@E@2ndFabK7}I> z8va`aY(jrmj!L=iYW1H6wvWo3KVC?g4OmKCD&~}_R66B-D0E8GqGw#@_e2{X+=lJw zZ28EvOkwp`1@<$xvgSh3YKpJwh*FPbb@JA3G`eU#mqQxtd;IZE4bR$U$G78;5o^O= zJTGx^;LxL5H(jaIWFMZsKC!bEFch(?(zHwUMV_98^{*hEoT2L8g4Dd@%Mj}G50sJe z22A+#H<-l=YW!_~3-)aJ4{;XKK4k?jlGw^@LYw%)MtQH)m#Q(NqIu>`+ZUT_@s zcK!cRdJ!Nx{uAJT2JB6StNoNSMu;Zh_U~580cGTT2DRLCP)A^KX=+l0Z@{0-B8dfh zllh%yUj6X2gs_a%(zSVQ>|V&YjNB9ytYLVla?JJy5Wp{F4N{XS-Nn|iF`GKefy=_Mngz6Hl;nCSdxj3j zi7Uysa1V~@UU~)eS1sCSz{3T@!)4;l^u2TOC4w@8C#eMMtxv#JEz#DC@n%t>cPU@7 z<~_^d-EL2N6$@MSBbS1CCzr4lL8euYbWd$+Z1hZspDo9w4GaLcgkAMuhWp}{+ajrT z7Lm>ZZi`6Rw0l>>XlGhkm6F%{%#5bUHvM6|cKRUu!Wj)$Yl|u zm1OW2MLF$@2kSAh!l7VGmT5h1Wg6b%qKD&U7e3VwtkVp8}{A8?~rST`=>6lQQoRrQV@U6{e(}#Yxo89 zGyeE9_9c7A&a-&$FmoDuo&y=cfxVYg6oBA>BNGEJ1;EFHAWJ<;bV8$kf)Ly)-a2q% z9eZ|9_IV>WgEEMYlVymlOPEpaTJVMV3i+4*#6_p2hh$6=E$+$BP4iT z9j`d#`h7Rmn;!st;mYX~ep>^NyUI^|&gK^<@!tAH8j-0xFU2MNPlx)~#QapK7khWz zeO93`C!r3wvcc(w>Yv#c)Pc)-op!z4^e1~C3(3tn89+|W}>2W5`CYZPFYB; zn){Tvu7-4UUZBc35M;>}cA-o#Tabvs{@SB*zTugx08(iSkz1;+9zW_Zr->rTkZ7!k zJvZPVXv-q?wJQ15%}pj$uN(X=ZnPUdbe{^^$gWH;?SYft97uje;~ELHZmL_c{Br(Q z+~B8hj6t;YJc2QO3o`T1-NaU8;4ESvl1~w@97sTy*BGM$dlsQ%6BifW6ZvV7`=FF5 z^5(w7(ZlHJx2Cg2_5bXz!c~gn73Uo;-@@~K^y_mS`y@ocgqspEZgm(OMmW+YSlyw- zuF#69M5IdF`YsJY;OLjj<}>5*5XVib$*BY)tu%)2pmqygG4h~xYEpb7md1P(nm6Tb z6`8m0e`DJ2lEj@<`ThoR?5u_Y zxS-zKGD*BpOmfunr6U2R>9&Zgq6+SCW;iUTGPK?QtRoyX-otRe!})7sR7 z^U=rRL%v8voxI<(E(f5I|E$LyPSAO=&-SSQvHS3X-t5jVQjIcN4x9t~d{;70de z`b6|c+xh74n>@Fd-7iXQVs*`w!Rrd#1 zV^4`vEeR~(9#%oSY2iqsw%FOVR(WiC+hWzOw={}8oMg?inAoQZniXb4ZH9*SNjKzZ z$xg3XPiY);#2hPlBX>ALqSG<5X=jl6QDvjnqwj|;{ZWenmolhMwb`#H_}0s4lOI%R zudBe!7^KEv;6unO=pu>Q==N=M`6UPC!I-)~5pb~CQ8Rq_SN)kf;)ktT7jRA=o7GpL zna~@KcbwYJg4#V^cCJz^VD%QL`5j=S-=Dmpg%k}qco38eJ+wqhWLZ0e8JXFsx_*IK zh3mnIsZ?vjQ`)DFQlmKCR3wk{!3%8U`SBkSw2jAliB1LJZQdI63U#~-U0SH zFw8yk-LfNvtG)l11@P42m^;i-AO2xmZjN(y);^O=yycoEMf!Eh+w{@G#(GFi<*=a^ z&HmD2FSc9#UX&92OuzDJl%%D4Ga&k-H)qxuDOHRTTbMDp4#O1#V|vu5C#|z=>h3O( z^VwsJ7(b=jusZuWV|1*)32SEVtsOxhwhOj1XS&QoTf=3{Fs;`dnF<~tqD!TGZ=+h7 zy2O3r zQ~vHenI$@(W3Rdp+oi-Thq+J5-ZS)+m-c(wYcG8}#J$uMVsGI1W8x!K;TZEc_Y2Be>jjL)_O}I(-NcT1nBT0#Edr?s7 zqK8;Y8!9VMix$#)B1_8Stk+bk2n@hk;H4cBKbg8`P2$>NY#1d^+jm0 zLfr@*JIMgvja;a!Ed>sj+nH)xkIul5lfj1jWCWc6`DK?#kBQSar)bm2NoSK+VpV;F zUe=aRNZaHmUDo!`OknURjxSTGk8-l1Mz@Nej4ebGhi{^H-ct#PzN7swqcJ8z20ZzV zeQJC> zVEsa5RAWiFO}Y&$73v(6Q$f|HBvYv-^vU9zlaj<%ewl)1X7T|~r1M(bOEqrGk55QL zE5P*?X<`)#`%#BZka4s<`)%OZ0`w_UUursY^M1O$jXp{{kIK?{b~e}V)xTiTw4d*| z%`g%4{Kdz+_3VCs&5R|}K+hE`(~U!l)lzgMgv%bBlI5ZerrsK-N@}|@ReC}=gvuXW zQV-JzV@RXw;#YYIoDV-+cf=dFvl^dyZ`JJ2qW;-@TGD27m-?8%HUMg}SbuWiY4=8KrZ_mGq`hz1dQqUt z?Xvas(qdALaxrEhIs3@U2=WthLY-McDe+ zP2AfGlx?3R!2hoQb!*t7QmX1zucE9}Z<{E$M3$ZGP~uPOyl=tR6O8l?Mlg9NJO;fU zhxw6f*?S`x|HdhhpA{RMd~Cj9b}X#X2eF_nUpgOZ0gLv^R%dY&pI)qCu2#UrQ20O7yS>*L&6Iye7Ze zTH4DN&%clt8Ss1SpxYPxbY(J8;34-Fh*|K^=Z|j*I(vqhiQXeugdA=#amGV1Oxh=g zQbm?q^!A}%B046()90TQ>f}j}fT)2>iMDI=ePmV_2NV>DD94 zr~-oRX#gUO`_*8#j;C2|b#(&bOWi#~le6rAiAC$};$p6MjYbjpkug-m2nV9nbtN~Y zvAeQrq(OO5ljP7}ad|m^kSMG^_i8Ar*LBzWj&E|(QPvhv>R~E&*PjwckLUq7s(%YtU z4~#fNr4jqu?a_N;5u}j0KTLZfKh0MdJ)0@G_H^}zsf^+eLA0=L*MT@U9_gV_W!(`g zK6zus!X^~?yfrpTlFOEgLO7 zAJ8H?^Y&WlB1lXrPAtYeUrfop!GaTVpVCR$XCY;~&Yj?PKDRJo4MZ3=|33yVb|p^3 z&HpucO@goOZb<@#SoI!qJh*nNjhhSWc|2ww+Hq$ zwH9MK^P&2M1RdkU+=vV!rOXTQAi%CjkYefpUOXrCfa!wBrh>%J%TthJxR|wk)?&!5 zq8rXwph0)Nm(N&ITq*R4iBwBZjM{%uq3GH=K7>7{4>PJ^PBZrMh2_-N-f==5GVvlv z6~crdY^TFyJBc-Hbo7BQ*C#636O4(2zD8xSYG#Hy$pa=nBRykV)vKY>BXv3Gtj(;G zr7bf_vc14DSMBN|?FbDd+}nvIbo54_#i0riIV5O{?d;B#x%K19t;FC_KFE>qx*v%yR+O7|IX=S;e zOz^Nrey_uQ+kBq{*}U1T6mD88A=&G{u>#VY6BmghBQL(J%7YVio;%1XR`7qNxOH0l zdY$&>!29*9Nr9UrGb~?LzRmB3lM`JYMMCh$PW9+$I*SfmsP@LNPR@afDA{d7Bk7~d zlwM>yBCr*3nHbBD37Oa=7`4)7g@}yM$mLejnp=eMd%p+NzBNc}8P|WXx6MEIHO&86 zpn&W7;V5{9l;Z%VjNIsuH4Z+!P3x+28bk=uqtM@Ahi)`Q><|1suEa+VdldefU?ssj zg_15}o7KtAsP|@S_f{QasgsE_l}?CW$(h}VK=2RZ8uy#j-xtW~zdC@;$An{*+qk&t zTAI@du9#oeMb;tf8~=RzXa65lXC2lA`?i0TmJVqcjS7+qjKM|^1VKPLMu&6?0;4-d zhm?dO-3XG>ozfuPIdb%G&-1?T_xt;6$FV=}W4o{WI$IqgD4wbp z-{wA2+{;pIMZ3Xgb8XrrNf=3eY2uF~VLdwick3XJJP!3V~B zwsy}fCb2bbA0sP6|D*Tpi#|bk_O4MuuI?Z|(H`r&3F%c++=K8ry6AZ^B>y4aYC8n< z4jPd=_i{DwCc=X(5}rF88?#HQEa6Q+<83U+PSfcR8OzwDBg&LxIZf)iI++Zu5^+=| z{R_Wax^@10>a(RK&iRxF=J;^BpM87L)p{40LJToyFkx$O3d6}YUHHNfPc{7wqxJT! zS$9s9@3{2Bu%2O(2IH&Ap;Pzxh=GrIvKUrc_fOL!%d#YRvL&M9g z!rpzJ9Jv`Np<5)r(y3E|8g%mmVNegHfV}H59=zz@_zqPWsQViZ&_|bPl^NLWH9Elc zVd5-mQ@r>;^a2o~?eo$8aO4u!{=atalo$!HQbtv}_tZ(#8?=FAGRf&Ar@cdoev;!B zt|*nu%XPxg1-k0MnO6v%RMxNB3w&5uxx4wZ_3UUfp`uqSu!g!N8>jTY57Qsi52I~3Fb*R zy1^$)_YTJEBlV2xpxwbxeY}IW5?DP;MeHL^oU@+*xk{kQzOc?t_Pm~_==OM~ylU4` zU)Dei8nXefr-59ak0Q~O5Xb%q0w|pA**S2nc%;zwjqe+x#W>+J;i=l|F|Bha+QquA z<+sy$>c$^TSrx_3a)=GY4xJE+ps$VPHp%xID*AR@$v6-XmUpSdsqJ1oD3kBmZsvJm z6!LcF2-`m?qx8Z(BSxT^a-nXft$(V07dl|z&+W^?(|H#OT1gp`2YIe33;`jfQlI*E zipO}5Nsy$-r~VY*rAeNA#r?O<}(QO^2CY9F&ucwgXJwRNJ9DQ77c2%8)}7G9E4kEv!`dDjC% zxVRjBx%@KSWP!YCeYhFmWvc$fw$BMhhAYUpz7mUEhR{|l{sUaH4xD1k6;7|+yp5{k zE&GL_hWQSg>}&Yw7cJ_+rWj9H3`)vbI&_LJsOvB-_eL7fM!l(GUh9ttc@NbmgF-8s zdj+fGL!OHhQ3LA@-O5LbfU92Uruauv&gkOBdb@K2=SiPvQS?nfxDV zk$UWRfNhX#BlojiKgDirXvbb)9(U4}^M<01BN0el7n&;i0Ulh{S2EWuaO9X*R{d5g z{y#T_IV+fDEgDYieHGl3h=2dC6ID(V$09Go|2Pe>!&VW_z|V%Y zVF}%+CO%BhI$u-KYg&EN3M64%yBEOhziBY%9`UhUkL=m$%_xQZdG}_ru#Obu#8Y!4 z7oKdN=J_%&lgN$E>?In!qHo83@uOJc$8lx1z}jxNVs8zftKQI6Cx$gv z)@~e!Rs8p_8194E2sN{dP1f+QyO0v8>j)<>(HnQvV`uJ4X}YygfKV$cECsENdtWQE z%CLY#T7~wu@vp{aOvOKnd0w+g8yA=GEn-ja?s2cR8*xwQT=;&wDQbZ1)XxEznFfc9 zxn}Q=tj%ZPLDl67M@ZHBiV?f^nXKZL`{iG!LP9eVEn+WLSPkde{f5rW$A0Vvzcq|7 zb)l0T@NX=%Z%LeUu5`?w()#=s4ogCIG3Nt=go&n|`MhWv>_&x82+7*YFsWIJ(G|GB z-pk8NAwWt(I^?DE69Fk74Ps!gKWz8_Ze3=&A$LcK& zxElZ4lp&n8N&kDSWnIy;*Hm8P$s&mx$>cu}`TVA^>@ZP2CXm5heudjW^31myQ>rQZ zAw7fajD$$!yMdXMAd>2|H9f#WskD*Fn5NsWEX5cd0xV&Inoq~Z)!J_I+KCBs(U$EhkgKb zZPfF=f#M$jBhA2HO`o@k(d@x58)1JH8|>>1Q6m!PU9ii9_1ct=pe$k0cGp-|(2nZ369 zz}P+Y=T3GLF<$gT#46s7WI(ETN}FKNYN9PMtm%i~qow0SPMaSe#oi>WQzyi$XbZ6D ze^WXafp2WIre4}EU7L@547NO*Ze9`e-JhqNZodCXrL|~Wud);{Is=~)HKX4hC$(R8 z$nOmcbxBiHGfUiR5J@N{)c)g;7P`}~yJ^{_S&Bx(si&jpFEFO9TFj-$wrPS#{SK)Y$7?F=Y`rg?rauLX>Uqi z)6K_#e~v*J>Zesj!iHvre%DX%9a`$v6>ohLyaDVHrB|K0RdpCQT?}NQwNo62P5IWn z>mmUGgSa;AeTOO6k}mIXfs%0VSu^5rapXtQdc`I+=ggdwt0z`wB^e2(JqI*IZnM9n zT2^ahLsm+tw%dN|!}1eGUL0U8bHpp)%^C94rNms%ceMed&ty1%vCuOSGDk6UF>yKu z+^nY0j8OFlQst69!EnZ~t;3U@nhl}QUiagdY!lgh@|g5RnPKnS1O<5C6BjqqjMz+) z+I+JTzfoBmkJh#U0tZ=birE<(vJ= zuY+F_WfHGhh{svSU!}#;$0g2~_^IZ`GK1l@AzX&+ z9kP9)#N)}?HSm7`4s|N-(-d{PfQF zvKjOi-DPyP-?ui{8@sM7uM)IrDhqeJYu3N~)?HZnR`UkDdhieNvP+UgZ6|QSirD8* z9%Ir?Av!d2LVq@;R{`#&bpDC*MzQbD+JArSW|-hpQWf1>-jUm#uAh+0y%=(UbR`RT z+{t_ESvhH76csN{ff*DliI<4%nTg(Lp)R}=dGTUIuLQo{brns^s>@;(qHZH;E8nJ(%s?ZcKh@!oK9TDz?N%F$|>32Q!Z2pqhHnPmF1O|cl1 zKfNMlI3n>HWiVZYe>iqrFYQ?AT!ZCct8aa$5_QvOCgt&@&L_)%Vul@~|2Qr`V`ltp zedbxy>j5fD&$??BV|C>2mn}Uh-p(!-$T(pdwl#vB9=W4Y{#nDs@2lVQAd>K=<$-td z@nN(XOU159M}FTfTArH`)Co}aKJt|bFX9@Zra$Plp+A}|uK~)C@MX6rQh&gF72|nl z`_$B+d*>sLu=8l9(zP&$0M0742_pl~M?cm@68gw9d4On4hmdv=By>gpZ~c)I?QHvP z6^6SE(Fq0e;K0Gx>z=;AQzA%kWS{DH_Z&|n&84T8vYz0SxEj8g%@A{B`;hL>!&yOs zINg1${olW)zfb+~T(LUr+lllJD>tj+_$O15hw*M3%Bir$^Ql3xe=zGnSay(E@Fpj+ z&P0?2Q0PK7-=u$?5Gc0)x3(|KG;2R+v&$G@@NC-#5SrjqbHV!SAOK#x4n}xuUjvu+ z${KmlBVJncCI!Sg8dZ;$My4|rlSVSGMldd3UzXJ~pF^Wf6f zM@zaCATTFf!h6Y8QR3BAxX)M1I9VHF(D6q$k(ak3pu*Mv{8hZFlhE3TTFv2Uw}(5KZ$RI@VF#9n5PO^<4jEu9^S z%|#~NjSu*OaU;;WCK3SgvGVxU>+?s;`*Q0Q*wN8+0oGNYL!id`?WGSJk&Zp$pxfd+Z!vR$>m*O&z~FE z*m18VDJA$Du>4@Ys8kV{9oShmYCWcS$wO1BRp-CW>P}evM$K(lkfZ!=SbyepFsb=> zi{hr6*Yk$rGS4&F4_=o%JDa|* zmXfv(NBH#``iNgK9C<*=7>9^~Get;}!Q0MLooL}M4CRZ#9bbxy&|em_Ykh(Syc2NE z%)O=Bj_cD6gFV%(I}}>?^exk{zNEMyKkaEDWjxsW_Vd<&(A?{SBy|>2qIfAaAztfR z0V}j@nN4*$XK=}finI;iQQz|P_*gI6q&iHi-26Ca8V!s_0c~8QL;6Q#ZsRPMTva4| zJ4be_chBnoBj)}5gqej(MMW%yqS-*JDYv#w%LG!Bj*;q4KFCTFk1m^0e*{(1G%29k zq$!(LB4#?4H2%qqys>I7x7?6dS=eq~@Rc&q;pgj!w>p+m-hbw8f21r{2PY^64mP!c zlNly8eUSV?2xYvbtjB6o^cGUyADF{q0@d8I)}R2a5_sTCq&ea!o}^|qZ=eBQ?*il2n?RLs6ZmtG20 z@K@mIxT&_>#V7cK24vz@`3CgMsI+H4*@DMo^Fctp{2An(-oE18R4T9CA?jFHb4?a)SL6 z;=k%o+&}h^NHH&i3-nuMIiBRWDG(L35cGr108;=k5gS@hbu^_Fa!lBXe19H27o*|!ll)9iee)F1?B23B;v$lwmW16ffH7`^!DYyHgqm%XyWD`h;lh z&Ew0^L4#_20jUznyMUa>Izi(9un?$)XGQIlBq5VWLeX&)z26Ls^D@EfFI=JqG|%y& zsVw*`N@%-~VzNyg}=~WqLvC>)%+`j|@$? z68#**O6L6Sw7@A+QJ=N4Q}CGWA{J@WG=+#2Q)8o_nZiDQ{p}ZCxRB=;bY> z#3PwSF{2q6t9U9NQe*(FyHG!C-vVIhGlBHGc}9m)ZN;e?x5J7T^Q`n$3a;LtSG9|8p4hC$rTSeXlU)Lfc7`n-NC8$m&|0ZB1KrU2oqH2R~ zJyZ|xI6t2Kf3kokX3I`wt8jVT!)^+kN8!C8q1QLP@-VLEWEAxgHF2w&{6f726*Jiw zueOnuZ4txw%s0+-x!Q(Xti|I_JBX%|tVQ$tFw_ryYJ1{2g*yYGvZyzeL`BuAwWhPG zb_(9~yBZ8t2_vm<$;Gi!XpF-jiJ3I%ky2K34;z20W~%&+)I4g`tLC6<*5jnvm}bmt z>Ww8+fF~9h&_C=AzkYZHZqu@VqE?PPjfeI67XI5` zE>Mz)Tt|!S4BSR_Q&5-R3>t<$JFuk+x6uh;R0I=L3t&DzC9>KevB{EOsD85Tpght59u`F0R?vpF zE6-YEEHZ48C;!ftk2)8>u%W?tK^#scNqj$-MlQ7J_aLM_rEjh0VsU?NP2kLQU|1$W zbuHSR8iOpUdUsvH`b+O-y&&{{hh(Afj|%W66aOr~T3o_wrb1z_e2&JnKxzhCy@~z! zw0i0RDQzt5Kzy$(ly{}ch^x=jW3h!SR&MF8*X8)fd=Hgd6e=Xbf@Y>>znEswSW z;p4HaD3%~+o@te4=M(2{)($3VCJ+;xdDxD;$*8z|6BSR)f?a^*5jpAWLMqF-Z1N%Y z0)FvHDM?ht$uV(>pEvaNMPnM%*E?UcVx!`pIR}FaB(pQNaS^!gvQv`zd;p@;52@|i zjLyL=TuhX)^C`d1LW3&pmU}sQ1N^Q88O6K(m1Wys3#o-lSeQ}rw-cN^Y0@+%R0*w& zk_6z~Y1YyVs9L-18GX7I_-+I@dI@W&xu3?ep?hJ0#whd=Ox0e8jBeUEq7yXmGX$GdZT7-d_KtObebU%sEZBIaCSMVaLa^XWhF-0mD< zri8P+ds!CV?~)yceV)~okzT0RtuWfqo+*|G2BMovS-zeBqm=(+r(MJK9!LQT{=HfJ zg$jmAqH~GURl5xA78^7_vm?j!x@{?r97s@NyPT@LWV16~nd}D+iJJV@HmaYUS505X zc%^JhUZ#+_e$7)5LBEsYQQA?R#`_!{V#qNnx^miG01F8NyD}-OD9hpFR|@{U4r3|a zn9^PT%xXDKv}2WIgw=zH5_TH3bP>aoD&Mpbz{4q(x0jhMk%QOK9u?$%b?cS=bulpk z%!5tqj4rMm#L>#9e4;NlqZy8%)3yTUK}4k04m!Bac@tWn57qkaYKaX;UO7M<#5>X0FF7|> z1L&sttTCKbgy=k}Ynq{X?V)MpJ~VR6r$;5f=}zUpovRIQ$tCyVRL#Nl|7_@!KJj?+ zci<+M875>xUB#MRSmn`^wXu>OmGbav1a#`b3xEY$$p~T=dK|R~cvz){9RCdJ*r*`J ze0dkV#GbJlonaw%&nS?Xtpw}l1km)-x3se<6>Y7_Atgf-(LUE-9>lKeDXbV$S|}1{ zg6k<4Bd^hq`8;+c0$9#JpmJ;BndMJ+NBtpBev|IR?3-;4OfnL8(#Ljr_PzhyE~~e- z8(hwH!>y!=&PzDi!2qD9^d}yd4M$qZXI0v7Hq~qGrm0BMezzU2e5>LEteHRv?T`L` zAn-c1rFUU$`XUy4{qf$okI09j(9A|{@ZnX%AI@~4GvN?myk*NqWqG`+!?@YY^mID^ z!?`Kr;XYlVZhLfo-*%zW`_}%M*wb-X_6hw7{S%$F>Dem4d9ljrH=c!JUIBdb1axxD zQ`E#e1D_(r%l3lWh0<0&B>A*2qJ*Jys1-k5;`3m+cz4U`C4V-+-(lHV6+;TIM+m3X zPsY*3R`&t?0Y~djC8{E4jyb4n;S9Ob_cKsIjxoVYDE1&is0q(d97v}ZMMU))J zB@OCJ9z-t3XsZCAh~TR*51(c z#eV3-`?@znt>atlI12}Mt~u#8CFhn1>8ZR{0qTm!pLHRc2&zcDxPskflA{)sn%>@^d!sW^-OY3KJiT}nRR zwA6gsVDI}`ji$cMfZ*q&m$sqql}6UlI02&`41RGl2D#NSW%`C&>h%_3yV%bwyz zBYOEy4AcLTLfXWh{4+c2_sUIaI@@B6xy(@=nlNmXj=pKhnbqvlCs^9M^#W(aB%E&>gs*rTu>x9W4Z-9qEbMX>G7B3459Ga)sW?jLRGe2JcNa`gZc0LCva; zm&}2!!zYrqftcP4E3lF4?v)X#Ur$?=yX9?=e<3{tOOj3d4sKWD?X5{{f&#}XosAD- z({eC*TeCFmgLYohnCK}eDydy^AuMQx3%5t!W=m4h;BIf83iI1WM*#N%e)CVd5?QyT}CvY zil%S< zb}WY(0&3TO@Xu5_xeEo^8p<0mxU6xO9Fd$DGI)e%!Wd3n+_sB{WeMq0%@uJ4>Hw_4b!sgDx2je7=1Ut-KUF=TLw zY|QD0Z*Fd02t&a=LIqUD!0>X1qj( z9=9a}YHq<9;eeTNVJ7)lOf3=$s<_g9hrYa_1(E2CN=Nw>kj~6tMFzVMGrLP9OA~>j-^a#;LP#(? z*k8DWyo=Qd`+elS1rmks=ew;%3y0{Ie(!v>->$`)xB|dr?ghkRjrMsottN_a{EftG zVzO;IFLju74|^1=H+sIEhbU^%DAvRx!X?nh=@ea~rCCMx0EmmV%BSv+(B1<#!?2Xq zH~#kf0#Z!otHh9sBoVV1;4z(;!XP^m@9+xH8MaM;coY*lD5z7{b z`W5Wv`{xB~qcQYsHn_6-fl(@hB@Jzr)EF= z8Gm6^;N$Z_!WMytPS9z$*tl{!doS10{l(9Y?7P`)>xEMN2DML#taS}I5@wHiZCOL= z51CKIke%djozW}Zfg+*#9ko_wG<0if>!^V{e6M2+g0^sxHs3>r*tmvr@>90eB=<m4_v5 z1ralY;>3_+I}l0KpW`quDw-YoZFoFM zD}TE)W-isr2rJe=G)^PuV||xuVBdx1{vnmUg{r4s9cK#H4?W{;-l+?f~58$Nn7-UV4~2+6aQS1fLKt zI)8SR^OlJZUG{EG(0|q`5cH;S>{@#>iYz`*-un-;%%=;6go$K4;1!Rm(z{8PsW(W~ zJDW4wY}5G9tPmCxBdP)CvV6HgneJis9$UMFkF4M#cWcyk`P~Q9La49cLgUGSQ+@jq z=M)@tyHI{>_9u>wivPXLBXJiH$hd$w)c9TTKC)A8U?p* z(fhY1^h4X!0~cC7H3_fFZRLaGo%?R}Y@`#G-?128!3K1QVs-Oh?B%CrT6*e2X>`~H z)WFcR$GdJ@HDz!^S&y1UhSQ6TV-N7{^3t7e2k-5l)^O|_*#8}hQ6kHGm?!l&qF;J3 zeyq#5ADG*Y08_9HnpQC}gp8o41rNSk+?mWUE&bk6Y!MS=3}-y4gx!h(qWmMF_POVe zvQ`C@!MH$W$Rl7Jn+--4iUjY&dd9$Y5chK#koIZZ*6qj8lB7NpE!!UK{Xh|UKzFj+ zBOULE&yl`6iU6Gyb>MA2FAzt_AFD219Hm6Fi0bj@wyBv3_HJae;i}rE!!zCP26uP4 z@3Z%CI-Aj808iDYRFf)tM54mW4~}2W1$qDUi4ZLsu1s(fSbdz)7L>Zel$IT(lHtfl zWyF*bDHhV!wQt6w&53Dw{9F;8c63d`H6xiOI!p-Vv!Ivv=^Z7`2>+z>b6ljRo`;UJ zR9d*QSFHCs!H{^{L|G(^%Gp~QySX>rAWA_TZVcsj$|QB@EbI!K9h^xl-1=&pI-7b{ zz<3+GMNc2DqXW?ZiELHuG0una+g9xn5B}g(4FyyU7o1|pR2``=x~Nx46Gc$rw2$VZpD}p!Si+=HN+yQD0yI4~I0s;@P_1xiExDVFjbnOp?!E zA|#so;~K0~Q$6<)oT(>QD2QN=DRo&mvYR_>jWcpej)&{?$YiSWIRX-^#_Z|`fukFR z;!+YOZSPS1-%mh6Zz!vP&l8wlFcA}x00wXw54@j%!Wd#7g^{{SYh!XzB6X~K82XtrV2Suc-lQs+06& zbe$skMAsyO%SuEv0eesG*lr)Y;J%SNndNz)#h4eN9&wN2ZsZt6yjnIlqu=L1A3e*t zQSVGf!ZyZloCT}U8oxy&Ozh4_V7~0_oaoP+w7D;2@G793r(KU#aecPG@`@f4HZq&? zaZvk-pAw{DXUCZnJ*O&%4*!m@Aq3PXr$TV4gdAe$Dd42AJk0n-k!tPq=+C)zx=DJIY|Y3wk!*EhUWZ}129UcYx24=Xy=0tg(5xuuRz)x@ zq=bDd8sS_Y?b@R46yA*KbTgY~Jm}=!nn|AvDw%CN@Asi-O@(fLVwz^1A>-8tbyB6e zIQTt!8iM|Fzy~a`!2tz}ru%{v^F{HCptgj-hPMicnn0a*Eh@3ZDL1Z35Dk7}%})00 z$mNgg5+_sC`-%6#Dr z9ZZkte8%7JVZNkzb=z$Z7PN-qT!-Uq0({QcEy6rkgOA>NL(G(EQe>3o#DAZ>6pK86 z(Gn%ea-^xETAmGh^6!Nw@}z^%A90(fvS+#Adw2BkV(TxmuV+nipt3<6Nm|J7d>kt} zpdrbPh^s^OYP3AqqzS$~qrU2do=fSE9|2IT8>c4*olQcvYUQOoWK6YzXQAsHKYBkM zqwM!^3vb``Mk7H)EYU&GlCTrNT{{FX`zEFJ*P4|^=L=ZG7bo;pvEiS^DVJvwH zd4A?CWpB#@etSdgyBxTZ2SLt0C2pSkzdA`Rj_WF+LnA)?i}Qbr0@KJJ^GXh~Z)QGF z%1oA(@N*8&EMDRb9n1*GuXWExF*5|4@Gek^NQY=D2j!%eY}TyUr&^PiQR3Mbc>N z+R}x*^B4?f=3e?gyZ28S!ZF{qqKgV*v`siy(s>1lP%Ny(3}}+Dd%KY3lzfHGn)^boh5%B@;>jFv!X#-V3Lku7sY$x_?7# z6KdIYf+DIqiDe|cF-`f5!DEqPU}N5U0SgV2;f6A^OeIR@JfiDm;k&U8Y9XQ%wp|*k zjBxU9HhxGVvseJeR*h+^vAlj@=rP9!%G-)e3{OYJyRTVJO2WU673#S@8@w0V&wF;> zijssxD%`e#Z@;z}R8i+Ze0Bn1nHMVOr=Rz#0}g1A!P9z|B)ldAB56I<0=`XuY187q zXiFR%9T8tH`L^tLsRA15sE zbRC;A`8+2PPjfvMOS@u&DF?$K7Q5)Wnu(wxGT6LVko&&MZ%9fAlZi47IbyrX%(}*e zfD_51*V7nXmO=Oa6OQsI+(Q!liX>SN%5CICFX13Db*0D%i}1alxW_TA?B=|?l8hE- zpCj~Bspf<>x-Lw9td338jt!l`P9{;+wz9Of4V-c6VfeI7G82m24G@4YQY$1azF_6i(cYMP(ak`)CY5fU1sJFPR#r>hW5Y2!e6nZyVb5H9a$# zsak^evDdYBRDj?#!Pc?@jFpoqg~PEEE{{7%19yy8RRK7s3wOlbZ}+Ik`)>;eVAZKw zOc%^Mv>KRK5YwDeWmx&5?VNd)#92`Y$nsO<D?r4MMlcd_@*g_H4Kt=kia+pj|GsLix7Z2+f~vrA#sP3+hz};gH-ukWxSgN zI*pXn>0aD+Mj!JzS1Gw?OA~FfJ?V+?8XI_#I*&u>jj9ZA{Sw1z$}m`SC*%gziOUv7 zj)PMJ>pRZIjC|vw@Ms!G8NWMi-3Zj=_bvBo*PC#j-j%44b1nDQ-^3N1jFn4d*}{Vu zEpt8ymvtbwYBv$tv}ktlOeehyA@S>+y~D<%DUX%EEjnOCxG!$DKIb-$Z_9v$2WwVE zpvU@LUa1C)A+uG$8xP9LmA{>CVFc5wj#p(LHp!E^m;e#*A>5u1}vCw=Ei8hHdbV zQ?_alF(Ttg70H)ipj)3D=e|pW3Fu|WM_~CJl9a(C)K~LuI6{+H$P>saBKJX{sf~D< zA4YV?$imjkA5fla;nsw$HxQLF`=VPw2(J-x9ZcL8gLvRsaGSW(z4XnxT3X42-E!{Q z-|IbTG^OhM*7oqZ^Q~P1#FM9vnQCOhxkS;8DJY}ggytprqHcH9b0eL^9MyRf3tvH1 zQ0Z0@p<7&tT1Tf)&FkTYW%+TPYUgCuAF@}bOH>-A|F#~$c!qD~NgK4`r}TSj&TF35 zHX z0unaRXy`MLkBK&VzQaxgIgrkmvyNlRB(hD1 zMmyY9A}pBVY1wug&UFGe&kw=@p)R^?2Cgl%JZ#59ut zN;YQ6UEJTil#>73ux%}W1khcJL3Vc4a}MD&ewu4r>~etfeOumnw>HKblasQhE*p15Rj&&0-I>=!`d>DFx=;Ly^Cg3UAj$`z{5nz+X8BbIa$QwFkZR3v zZY?F;iG>SBwX+DA-})0ga>8Gw#~<7=;g780-X71Eb+|seybS%a+-@Qi*|{u8Pu7X$ z9c1Ot%ROA3szl>0avT`yD_7x{{|Ff?>w`v3yZ*=Qh@V%(W4UY^aN%(P$8 z$Xxe6M9lwHoyc%K_qa66N>VSe*s@#8OAGv-xxi)N~ggoN>C$`uiq6sLP^G$;*J=0Un4WP3G(b<+_U8G_x6W?(6A@kViL6w z^(R#FO#4y6DzU|FahM@&$BJ7LTpPq9jB8x`6GJ$ z)4TLRfH)QN7wW>=c0ztHSj(Y&bt8)d$(YdN?yj$(&(7K1PC)OlRcXVMXdn9qzT@fk1Q)--`D_lEsegC>wi^OK4Wrj(^*@|aqo!V@P zkTh?k^|~EGlufm|HkZ;0fIq1Cf=mkiB3zSAGx|*-Si>$f!=}w(Xq~`8T1EurwUuw_ zz_p^q2}Ny51FjQobqN?$`K!j@8@}(!aPW1}M7*caSO`ZK1@$+h4Q?FoSL2CpN6M6jukK*C`fLj^V7UU#yGk)uT+o+hfA5e+p z199O0^i>jY6T&Z+GbIxexdClvHbO`o=q-|cWOBpblTEG9#VyJ7;L$L~N$d}8ucSBB zo)U)9uWYza2w>!`1M9%%e(CygfW!%1uxK|2bBBL4y+vq=R`2RNw%3|>Ez(nAAFCMC z_TJnHW$pCXdw1fTO=wNMK5^2$dRA(8Ya>cM()|d;TFVcHe{T`M#;FS=2j_b?bupT!bznna_L-zaoRPDN7;JfB>I?YDzBG42L&uFVhl5L}U*uU+0T^`MMk zNu!D&qynGbzdSSTGoCmgcrrfBN~op`!3Wco=Lh%{JOeq{TEO|zf$(0l923Bj5Bt^8x9t}k-pg~3yf%Y9+lXBSIi_g@+-DlDY)%Jerbad~ zLCmj6in}U*FA}A5ZW6B2=R;ZBQdQ1`8J5FK--LrA0gc^iK<^EDueP|WK&61p{Q$Wr zKftNw+9-}!9F8!nn1E#lxs85G#|wVp&iJeFOq8jMO+6Yc`Nto0+GFfu#6Rfvj$0z^ zMI%o*&2eaASCbQiL^3}a^;%wnQy8l)D<5~TKe+9#mGu_n{C|wSWl)@17cCqJ z8XOYb-GT>~;O@cQgL`9*dyvK@Sdc&b2-!J&l-Ss@@ zoNa5Zz0b|yW#Pv+SXA{ zk*~m{0PM{<*%p%MK2d?$mmI@6Ki^#=Rg$wbA_icv+h03f1i)W{U(CWkzX*`{u`A)I zVQN=S)WRBw$gUL%Z?=hd&9xmfYJ5<8pKcHgA-0IzPYzLz19ZNE-jiA_e7G0ky8$P3 z1IN9je0$WZ1iCVSd9ZQ$$;R#H5({hz5iX~Qs+-~|muy49&dKttZy<7{UyMX+6F=jV zvI}_Wx9mD9BxedC3{gS1)OQ5KK5cTk##R7}0wRX536JKd8sv;o{lJWTo4OCC7|x4O zHee%EyAJ?-ali2c&8X2M4#)N;DB8DIy{|4%5rD~b1OAb^kCxMf9!XY?W}0;D0G4l& zFft7sCGMr9J;8hYbp`of7P^KMtqCW5m1~q@02v8=e){EaD+`3w{ZtJ&jtW8uf0~Mi z3YnZot~srZ`=lw%dv>C$l!QAu(RF~aZ`xB;o%N8lX&Ju zp->_W9)A_6CjVLW4Jp_P-5LtS+yDU$APSVS&IX+CO6rAd_ofX z0%4I^HM_}X&vF-9oUa;TjA|>S%0$ogc6P7rJrHWW1_!}Z&xvOMr`cC~rumjru8fG6 zNp%LU4B`s4-ZTuBE{YcMiTlM$E(%8WS<_mWr2bo*Mrg-v&nBhk|16L`H&U%3LyMiU zlQap8^qPzG`|Rf-SVd~-frZF~NOIwM2gfr(1OEbYy3u6|7&#=dYFhHqK3d61dZLyMNEiR)D(4s1d1LdAMp!x-BAQ)Fx2gxEZ2i_ak zn8K{`+W;&J#{L8tmJTHglW~q9j^v9raoEB5zy@8t;Ki7DbLDUz14=owN=+lE>@eZ& zNg@>R4{teYmd6V?4e!x@U~4}?T#O6F@1kTyX_#|}y2TTd`)Fm$bI6=2uHVV+f&{|! z)z0LGm1_PLE3y_tMr_>Q*;>UmvgDqS=0nPbO?F{XMOKX$^&I!I5+?5j2Xl2W4R7@q zB|!=dhWv~WLFeA3HP#B=P%w-rh_BYmTp>gJj*UCy#e;>N%ZNahI2$L&OKL?@p7)h~#zXSqod~EPP#{#g?H_LNI%r4s-3?Cv4RP-*YxJy zwYOPkA@|S$Bu@Kf4K_8tEbo=fqfr#+Od$fouJb~%qwrDkE$EFb^ovaf7!Z9NfWaCo zV15TNjs2RMt;?@pW680xX#SlHQ8Oy*1-qrjhAWQO1(wGzCVBn*;WvkI1Eo3hj`fLJ zG%d%@Bx3qzPfaY}drK!0XK+w15b4<9=?*qs(|z7TRPCshBACiK@Ad2NHA*V93t4Bs zEIW=#!?K#80~p;-(N)*So;$(SkOMnu`PbIUzyXFd-z%dmy@@l8lnM~#!3eH9VX)36o{-M z-tg;60R>qX2^o`PE50xA2s6cak0n!UBp}mFCrc(J0Wlm(aH||5LCy+xola`Z(mWq> zxnz|j{lJ6X)s{U#$=&tFt&ElAdL)G2}APO1b(04KnqzG`WV@ zalcgShxKA7qIdhUrEJ*wJ^dHW_SUF#U%BD z#Gkl|T)Vy&LW6}B4B0&Sy+~9qVP+~U%HCsm8CJ;7WI+ObsQCtT<7pq+aiY-tTONN( zTDh^O2qw-3pIVfulU7Q@5}CR5*LeKY*{O48c4W(Q3i5NC$N#n6p|vm;?(al|AMAQw zf%6!;uA7~QC!%A^AwbWGE^HdURjrh8Kd%OrcLC@$bctVyd@@7r^%__5QvcElc-PPL zlFAU6`QF^3Ov_WOdJdCwu*;u{l)$kS|Xwb{jFiW2H;1`~K ziI1HMrrx=LcRhiVuiW}2VPzwq?w_?szaMp^biV`WuS?xes4j#MnUu2@58cOBiaoBY zyG@M@Ev>D`kyso~jjy_CE0N*qFVgJWKE^ei_6x|)%(_)+kBYG%q72tJ+JRYcZTNV@ zc{_P84&7Rn>24I_^&4*`cq&c^45k4t~P9lb}nKpVk6spQ^yql)QLpJ?rokuh*nO)*Bif;D|{S zK_n_&dn zHd9k_)E$15ZMGK`LOKunb{zM1_-)Wq_tLz(qj$-L?!8n0VYVf^8HA|Z+7$ko9*1?; zD)J|yZ~;ELR?b_8M&V(=_f2mq=TQ?aWy9$QXiKxe!ztNwRog3~t{Ft(6Brs?XnM{> z5oueb!5C}OSfE|EwDiAk*EaR{_;sTUN8e|P^j7T@YJ#i6HhhYAcWPiC<47k*J%F8bKqfyW zZKLxTufwU;T2>3}$3wQP?0?9>{5OF{4_)EiIez;fH;)d|`LcKeSf5@?z8$rC??=Sx z^k_d#8y#VRf!hZNK}pKnBH??xt@zJ=y*9m(^`gLhR@s``(qzWsLy~w%^;4BNqJPck zbDp_+9Z<3Vqtk1yEe^G@l2*ge8vg9u2wn}H@C(83pX_OIS|%xWhvH_wd_4etR(nJQ zmc8rb-(W!tO0Cz+*s5X}iJ6h4;n%wL@z* zeIny0#c9~DGev2Z?w@?@l1#z+8HC$ZBZ@2K=F+|K-a4ta!!`U=5(!ZX^XQ-|r8_(i z&m`CKA=amf;}FCnAU)x+KQz7jEtg>-fGs_B@+2gL6MnuO4IlMAaxHvNSG%F6E6bs# zS|9k$6b25xhM8F~f2%g&Q7!xh*$`#_nW6pb;;L5Jngre^)KU15=*^(JOi*1$D+|Yk z_<2$imx>G$e#gc*1+7|d8`X0ci=!6$7ytZaL5vRUQP$}j3jyJ6(8+}j9(^=6Rddhptj2&aqCGhxmb zDesNTnvT8%**6k~rzCsy@jlqL_#gNlyL6k|=Y+POa+ z2c4?y{pa3IA8(hUj@hnbeXuBjFL zu<5?%$i&y(d8S!yt+!{S>u5pBkK;cobgl2X`!$sd!P?A28gOLUpa?)A9ziBUOVxCh zA-Yx)z9)~_#G8FPJ7>JCt}f6q+%`G4b8PI(`c9-tt5Tf$8s)wXEM!G~J)v1;ioe*X zacbAz4 z-C~0^WW@0H%G|QIJWt%i7)<)k2rHhS@2leX&1->kRfDQ3zJY=hgU8_q6_Q^`awikq zi8Fo|KT$GeOu=a?sdNl|u2qqDrHbvx)_SFt^>^3r&1;0K@JCfMFD9#JXuNspC{j_t zjAgNTm!E21$0*wBrgoN6ztgZX1!sw-4#|jSSm#Yhe4FJbAZN8YlJ017^jN0MI{9N&x9i|rV-U#bmqh#5sI8nB=~xmedZJe%lT}jU1kp0X8Knl+ z4(`J_J!tN!ro#E|)BZnaY$DgkyXr9f0KRj+Yo>a$42D^Q?iT*Iwg#637F}25j$a(y z7uwaEI0Up$6z_|+IBwTfW+P*^XN_4IIRX5_1thrJ6cwT=HE^l{T-uNqlX}UV*;rxt zoOM+tqoFgL=;koJmaPuJANW@W5&mICJ<2?7?$D{_0Y-Eo6-w^0mQ6~gz1wKS%`pS( z0R{qOZwIVBh(6k4}& zT{M~Oba#~>k3X;+a_SB-;97gt-YpKY$~26MJOgizJlct7%Jhm_K-iA0M~&_6Mn=}? zxemOWTq=u=*C$?cdv`XW20Zpxk3a8Fr!4o4hEHMqS*Yu(i#7E4EsI>N$yoo12~=o- zqre*!IpD*c(Nk3%k(f|tkam50`nRl^ z^TDuY8TA3u13bddCGc|Fl8F{NBO3t&82sz(R6^SVe1l$-N+3DF>-%U_hMgq5kspk} zjrh?p9|x~5YgsveovObQMm$(0@7C7s#UJia=MF;-)qJq(m>%R3=d4Die(FySvH_qX z>Z!GanWdA>U6Rc^fec0o(Qblz=Q=BLKasf=Me?=0K2KR-`#gweyTbCu zGM~nfT5IfY<#_evNV(Z!qYi@=Y<9!L5#LKCA% z;94&ixMsJ&eEPr5mBKa=1511U>mnGm~BNWmXih{S-$ufFH@}5X~JSXaw@7>h3 z^F@)X(5J!y1LyxKM4==k4eawBl@aRBNH)r<=Yetyo$#RRfopZ5Sp&H5rY_csCtjP5 z=J8=C{YHjLn4@^!FG)8@I&~aA*}RWO+g3|tOR)OIw2t*~+vc{poahUhS7Ei1p@T%V zRt5_>N0$|SCGVaSr+9!B3EuV-$9n0tGd&2CMZO>6*sHn0^83|tpUb`(DDV=|4Y{QE z?iILJ<@PkePA{!|c&i=wgLbHzI8)K^R6{3zc(3j)Yo8PiomxS*maY}J_in$8XkUOv zFng#!_Lm-4J8s+6^z+x2FFZcK-u(m7Z-u@Wi*IZt_Hg_=&=Uv7hrJNVJH+%;G9vOiNM=V0C- z6=8Pp(2pLsWJ@7+v`{B+WR12C9k**V?Q^`*6V(mSN%EFaD%HD`OtNIT-W;u5V@h(7sQ4kK(L2 z`gIAwS3)vtLpDdoA(~qGf>B*%M|3x+B{gfuU9&%IG^7lqOF=oVj9^#$U6jSX1ggb-wFOio+|MY1ukWN36tgiW#m`SV zNlPGuULh_G;WRbxMh6jes?G+u6-h%(a~8UuPU==~EV~zN2!C8~{?Q4nor!hGf>LLJ zEe(J6>9-R1ua}M^A3Ld7Nc3j?+e(wDj9Ylb0Qx_?>qz6s=ifPY2Em)`O%>lQ*z{Xr zk~Y}QV_zN2=Yu)9C_R4b^97C?sg{0=s%`eq;#;8;?@3{(g#0#%R*{s2Zws*9DE7oU zLSM+5hO3&MTrE0c%<8+w9H}fk%B*z%5rh4e>Ai%>&Unc1zqFvR&O-5j;sJ4ElI@#I z8(O_}tWuFdNWVc+WT7g0mjJ_QN)F*$kd#4oSK}5VfvZ&;(YFZZAA3NgxnPh*QzVFY zjE;Mqd=mk*3({AK*rNz|m=L7O8xV{zBk5j)ie`2Jc8V@o2q+3|+Tg|R9aUd0y zeBP>!H(JM+EE*qaIT&Ro!IYmFoAGEJxBrde4P(-q5V5RGZVePoIZv*S>I7KaUXS4u z{U!RPvo5q|aMEz!8qUK=NQaO>Qw;H{caSaZqDwho2$!A9PWJ?jR=CLBUc<#TpeEdK z^Pixbjl{cqdY0*N{kJC0IE11D!{6}vZN~O5kQH}#JP#sz)NCS%jec1lx{r+tSZmX< z2OC^oXAm~mZ3SO6CINveDuRj+?q{18g!o7G&<%Bqd2y|Nlwr0t#DKz2r=0ykj3DX@ zF%F#)s;{rF?n;BB(cBLo4_@bukgN5lp(Af5t@0_e-HESq2?q;E$53fD`Fn-x8Y<@o z`$cXVp}nsBsdFLYnNpqgFd_$}@0w+56NegF247d<8K=DJ{v4qHog?xN&cspQ3#PM@ z{quF!r_pC@mwZg3Ew6VK<*{BN3^c-0EIJP;L~2G_Xo2#Baj=&ZJj{F%rVpz1By=j9 z-{QH;lB=Uv-vbLs!_ud$bf!3oBS7OHFI6AAVm{hzSF1h`@Bgxb4pQ3Z_|~oLCJ~`R zYr#fsoxaWQrDkDfd%oJLXV`CnFNNc&&W0rrzw~Lrj^xbS{@t6;JN)!h8AKL&=9@;7 z%i4=~(Z=+$P>DV6cm>K1hgu~^J5>iqcAxj#Au@YPx?dpcrFZs?B z{=DW3Xz04&{afbm1SjNkjo{%o`SG_ za9VCfonafWsn@~ZzrFsPq%-l)qyM!-?``33%AapRzBCU*M*MhBdy$TN<*_>WK?V8`y^Kj*DIw z6`bMP=$%Afex}WjT3LO(p?X`Y%d=y6vzC;25F*(0?hNCVnV+z}&(E_HX``nU;YH@g zouWT(uEPQpkVwjUkLmQ{ul@|^#=*jtb}Oc1o98O|OQSf`h3rlSGq1(K?JrCHuPzmW zJvidBU#MksTxoS$cAsT9g`tG2XuMb^Wqo{Tgopkzn+s;Zdjy?$uqmEIHHiS=SFE>m zEb+L(RrHI2a!{@Kd%fC^@uVntEFU%q1diS@r^FB=Elrq2zhHl7;72c7{&m=N{q-${ zBuUQw`OYYYdX{-8}zr#sVpBfU>fuS?XFEnRKOY~`M=F9#^46qKqq z``vjG`r>xY_ZYeTE~>rwS(|84@NdE%8gJfHLCwex+&%jDZV(5WLZ2M_&O@xCSqKEFNJXg=2|Xug>I;Ecd$5sEcoOFGB)A@%O=MT+X4 z>Hitr1lAg$ydBORYkJzAt9s6 zWxpC7h>4%U8;B)1RV8(m$A+hhzlqot{KiV1Jds!J$1CpZ?Bt8F3~>E(XXsxSV(v$mlCCuOd!u=tGt(Bx}szKxhe0?6RrAk zPupf*`}N2OQAha@mj;T`*AQu}q(US?0+L%JP-*EQW#29)Ht9Y@rl~OGAW4abrT;Fo4V-7& zS-8=i<&BTXWBJM8cB;Gw@F3*oTSfJZNeBtgLgjq{&`{a0}rhay!&)H z=o_EWTdAI~R(ZpYAJD!cMQP0(Zrenpx$GqObp`*G z`6Ozu#Jqws4R*vv)o;1{!R8$y(yE1b2`0r#LW3)f?rxNab8CMkf*Ir>np-C1g!k=aDKzbigXJSM~3WKxt zJ0^5gN(6-0#*surkVO0bUdoOR%(d)Sk;5{eHrve(rwc>z;1{O@=p)40f9&IbU|ASC zOm-FUaLBo@kAg;+aZ}-1cG%i~>~Uo1x{ZBMAd1yLOc@_JxRsQ!EBi*N1&IMmc~A;G zv>0{rf%`RlqAP+LQDSrH+*Hzh!HTiqfZf+`+?3{RA6;p2_N{S_2Zl-B_KCzJrGH4m z-Tz$Bv@W6arm<9Srcg&$xuMpGB^8gb*FMhvBLgENcRg=KM{6#Bxb`c}5toh`O2rx3 zp?NRO3PstbgcGDDtR}gx{90SRq;VHH&m!$lkRLwC_%Pi?qya8n(5IBTDXA$6D!Kg#NO|-zY58+t_ zUKZkvALtv5FWO-YJY zdrI7T4@(l*PV&CNqN3N$v~2;GW#@}+&PPOZWY^PNN_kR`B7=6iF$5oA=mfcypm!Vo zX&!lt71=-v zl?l6@JYagjNQ#nkLR$`4fKIDxXtfhJSem(B40*T5H`^;XDL0NyQ^SlEb-BA*>aROp zztn+Xm&9T}h!kWI(n;%8tm2aQ?dw}QJj;+SO6RH!*SaaYc>O5L0Ce2G+7bk>`0e&N zS-mq6v@2kxWKzLZs(pj4<2EQqGpo~f%vPRXUF!RQsE#!^GfVH5aRotQbLUGd3xY`Vv! zxlZ1%OGiP|Vy93*jfAtQ+0&r3puDD6zob=#oepgp7e$4r%uZdFI5+*DRoLRfoxD*% z22}}1O~S&c$;KkjW6fvYq+yZzLl8yqf6GT$t`}|pHG?nMFmytiVw-4xJkCEg-QT6% z|MO27W*V2Z+Kr>pgSU2dr4e$poXcv~+!?&8Ydg4BYiEpZ(diLsfD&d*baN;rfz*`?FacFHy zje_?IssoX9w5&^rTzA7;f;b64yV?;NT{ zu#0L_J5%%sTE^uve9zBJBsh1WRgRW3klTKjP%$lP7Y*rc*8icjAc1K1_zU!)?Fs1< z;Z!ki&H0n+I_XfY74!{x-z|In|2qf&91a%LKnC?f`cvoeR^6AU>S*5gqsDQzb><4&m)s_2 z%(6Ehl@u+tk!|;Pqerk~i2qeL8LP+Gf31k>w+q2iVELQWXPdwZm_u5}%G(^tqG&yp zLRxGHcRDdfqEiZa-||#RvK!m{sE9Pz9bFg}rNWMHmV;Y=L;zc>NEltUfU3Fxtm^%t zy}3qhW-xpz7x=cAI70E|t~$p>oTj^}8QtuA8*@5-b+17U-;%wkN9jqgD_&3SmZ`{? z9f%QECch73rl2cFCg1FnFuzLpxbNO@vAmdI$cg91aHP}2@Tt?boh_L_r`;EPPm%w} z`Nm8W_VvCA#qj69h~8H#fJy`IlhZ>mwAvoDfyd}kxy8v z(4;EVy1$HG=F!|KgnKL1gg@&PCf^C@5ak0YH7hFBR>m2d`xh%;GG)Z9rJW)$MMluh z7R%Ba^)p@KU1%<347yV%0qB#MP70fI88X`4Sk3_q;HDzw*H9IK($<%3m*E-?c3C0z zLT5%V+@fYLDeM}sEn$u3$o_fbC9_X>BH@KyrerH$L}&Sz z$>D;#86RW;^~Fj~Y_9yEAm+msb7U2<6vr3f4k5j@dI8CnB7?oFFz$KV&kC3#GX;mE z_D3hbSYx}@l+pIT$P)6=Bnuj=kcJ&_i*J}r-mBuE?n|Uo<|dw-n4QI=RnBI1UL@*` zAxd4eH7hjB9Rk+1CX3AQOj}&(8BVX>0R$|Qh4+46hx-9y0o(*Z@gyWwopmi1|Lrb&yd+{ptj63gI$LNkNC zYQel|K8^b=>$lIUN{M&uQrkYmb7NF|7)`9yU(M!OqUJ=!c+BQH=Ux(w-lh3HP*!bH zPEq^@R@L(C&{fQeBUeHi51p+Ru!N&cm`Vczv(jc!7JBq?lwreOFG2$!$^ZwEI}HN0b)r~Mu9 zlJlA3VXE&b7n}HYdPGT(eWxk-0rn@Uh;~NM7^hTrOQx2NKq}b}-K22jy!V3%Ak|(% zf9t5bYF+!pqP8=ioSkRgiK^ZcK?%khRGb3C1$MYMI)jU4U1x&`I3E+uFp-6520mNc zV9Orl8`u}d(Z*Npuv)uUp(=eX&RZ~7zb`FkIF|WVtYs#dcwW_1{P}kBHuVa1LSO2Y zZYXN&^S+D8B*K1Qb2%&~k=OUXkza3ekND_HNrGpJ z_|v^~0#3_s<<^sh@B-h8ei(E)^vXLU&|MG}54-Gn&J{>xN54m4k8zdB8tzNhGEBb@ zZ@*}oi^SJy_w${rGzs2_^tEi6H*?PjMT8=ZQBTaiN(8D zGti0S&Apse-N5`D{LFW@n%a#{Cf}xQKIO+~CF7ug=Z}_Krbo4md^G?)Lgf%KHv`|x zX&W5pFSxn+6{C8RByoyfpQj0XwC*||K) zsHxeK{TSQ59y}w|#d`b6eO$c`BB2}b<>cyWMU|0d;`BOReE`lZjyGR;NF*ARoKPqE z1<@r@iVR=FJO_|1lGKx~5MB|l=I_vrW+mtVp9 zBAZu0VLzjgx=nLa{HD8zL>ll*`=CEe0mC*Pjm)S@QHb5a+~O;P4md_ zxU46Inw(zrn0Pxry5`vtf8P{+SlRUH{*GvfmVsffNp^~nKy!}Rgw;iV&gv;`@oi^7`k?~Vr}i|kG%)V;T`^ge_QRqkwfgwf zXj(x}*hOoeBcrKBP4@`Xc`Q8BJ8;-5h0i7FJXs#Qy&;JfeDF-1*Zh6y0ojzP1CT>( z`6|UTwJtMFWf7U^ek&I#fWm$ZBn|t2wEE+0JKmAOEui#YX&^6bZ~zo_L4=XeTK``} zV|&12o1m(w3A>>OvQdnLuvy4qZpdS9$RQ=tqWOy~bp`(R1!t?Aqk=ew-n%q!#SQs^ zHbN2Jq zlCnC#MHSi&4p|95$UlhTn|lagOT6v3j;kYoVajT@3|O@1!JAueQflN71jqE3*_Zyvz!eiB(AO`bAc*b>+x7Sg*h-!BGJ!rQ8 z$~K}&=AgqMbBAw;qeo2yU^kd7`t9UXS6ntnmLJra>knUY>bk6S```vOzqa=1hU05|;4rA9j3vEeh47`8clrC=0Oxqvp zf2rmF)e{RejN#Icv)Zug=c9D<_DXS-fb5=JrR}5(<&zLtz5E=iWo9ac4r1C&xtDIS zrJ43&AH>`yhj*B(p#b{#U3a708zL>nI-rCrkxi@@#YTba1a{$l6J;XBvDWHU!PMBJov-@wuUw9Ihy?+&9wCJ)vU?126Vv<=DHIas0F`^g1;Bj_j4Mkgti< zD(bTH1Vmtfg6oK{p02g_`0GiQ{VFoZ(MP{;D|z|A1`2XxvdA6)_#B2C*=iguwYj`h z8!flrtt+Ce6o4m`WI%!uR^h|57;U7aK95x=Sv+m|W>tyQEkYc)5LGGJ)wgMr%yRq*2Z$U&2& zgR&YMR*7!Jv`<6@-scJS#l1bb#+dq7h#4j670i}r%wcgsA6QF_Ut>XU<1p~zvkmX~ zW}hGrJOze$j#qETTxMaDs&8!OdC7Otk>1)FBYyY~(8}fsW%Zm-D%}+SuF1RcVb)2I zPwCsXb#H{kxIRIxUtLN_#6~E_5RP0?G&|@9$z%ZFm!AzcfQd~3ra{t)%PWbn!mJ>V ztQsOJD6)dy-{IOH6qiMgRBe_eEW_mWOI(PW7bz7JhXUEA1|=vp0GBaQIq)_Qywy0i_HC!KSbQ9uENVN- zSPJ2U8cwR+tyCjTpZTgp?IUU|E6mz7CYiuEr*UPuO%!?@AP)w!CV?tS4K{Hp;>2RYvkDI@AavZJ`Au;)AkYQ*2jl zlA9d~)$_DgKPOlEjozI+WbY(LAnmea7IK8@-PMOMY!-%OR5bDtr?XVB;87L`@*jQF z#-)>q9vTT203A)SXV2d%cv_|sx8xu%V%W_(!xAl4f1)%<(<9XmS3k}4OkWld7~aeJ z+9J#%*q}l?mVa@z9D*(bjj*>Ox1g5~o~w52Qc{G}^x{0Dr^ba#n_!`d68g~%DOkfB|i zLME>_30tkj=ShMYpM3sHh5Q3KuAND(y=@S|Z@Vd8MHab_B%58`5hSV38D~%3QJ=4Y zv3<$@RvehL^TN9e!?-_|OFLcP?>rR>2INsqloZC_e6v!kkevcZ0*9N?Jg2v(^X3eujA=r#pmR{b0j9)X=RBWa$$iNjN#XOR?y^&AZ~ituja#7XM_LBPngVkb z5tV>lHRrc@<4ELw-(f7?0O@%krt|@>tYA$(BUlR?U#!FYS{6`c?l);(QQM0LQ1X2^ z?Mr@$BBO@bV!K9z{BL?p3JlBX`kUiKB1c<}+w_n>b0bvQSILk7g7^?E4j|qe3zXY> zCcW=M(EwysONQdg0*|cFo*@r|oaIY9?HV6NPwQuSlY0}Wpi8tIxT2iHB{Y15lXul9Yf^g9{0&ZQ zem%heg&=WO@|d@oikR2y%Y-AO!{I#er%Cq;nj9?9dv~O9=DG(9-|y>O7>dSxbTSLmxqZK+yja z_a#UAU50j7_@6a&i`;{iry@aJm+GeZ+ixR4Qcem3vDUY0m}FiE)T0QCG>L2RGK=PS z{J=Kz4f{*`o$$1Y9GI&ec%uh z%(@~n1ykF^=UE*TM@hery}=CxC_CR0tzM^MndVPwPWI4VSGGH3Ah5N193N6`~gX#qpzpL2rICaJPWVbF_5gO3Ce$5AH8AdE5}F|qAa z`H(^mJP~)lr&Yf6A)lN-0{$}DkpS310zO0>#%Yp|i)xGW2+TXb7we|2G(i-QF>M79 z-jkoa!P%@7pb}V#r#FLkOS1$oS+^ij2Kyg;(D^<+rEF)CDvac*W#FE?tsx*k;r|V% zUi_kED@E`1%U*di#LQ0o=rojvm(ub#UFbn^$mx1cYh#Fx>6orVg8ClI?sCoIR^QyvRmB@ECp$$`xgrj0)FVNIIMhn4BsH$AZR%{ZMFhXJ)A50mxpl4#Kld` z#b9;ilAhMT@$S9)wVyVR76^_-Sf=iGKal7j3BcUaJk}=RK3c_(&B0B`3guwDogT2) z>Q6E2jY0ll=965|r+tQ>R{Fhmvp@C(Qcvg3BNh>W`4%IK_qCQBq{eRr;I?q;u3D>;PExwLh}`@qxJf>XF)*4zfEKsq9P^uSY!-PdMEeLeJ%HN*^> zkr$3+C|2MDw4@t#B2e)!p=n-o4*t3fIyrKTgo6rUJ z@-t*QRXyiFW%t#^T-=g&a8B(rQ3BRVQw=-oD*5J@F?=0G+`2uWQ7#rD!Jka7c=Pm5 z0|oW=JO(uZ3R!8+SPAx*DEyYGi3@-w;l71uYLA-fm3~j%+%MgC@h)%Sr^LbV5+oj> zbMMzT(4!6MbKDx;5eOKO{P!8Z$ir#-o&DSMW&?Mlh`z4MO+QI(dm?s&J@MWVQ%w;w zK_1gIMUZnHIjTrC>IDWDxkxhBmpSBEt@q*z!AtPtXhFu`3ov9jvij2veJdyOs34Mn zkzfe?Emk$LFFwu&QNddP*bI0O_z__q#RXr22SWZ;^eM<#n>zlhc*f{dZd4m4-l0Zo z++FlV|53o>rsH?Cqz7z_K_QN9rdP1mWSA0&fUA*)G#Lz)}&>aYrXHvn)6$)NFarBpyq- z66z3gzQV{RNepu}r!qP2)MywTjJy(>N(+TaMIsmCIh9PN{nYKX(M-{#0+EW8!uG=z zBa+@~9J_KSN=_S`O_};YE3!DkQIxT??KwYNXh|lpgi@U zcHro9>%x6Zw<9=0;67u{VRZoTos$FgMO@);tezc$rzfgKo0KEdBF=?C$xtD1X3^yv5|SV>PcW=b*4 zcM^9Bd zBMa3k+{y3My3QqpK~ez}s9`>H+bJ*4_QjO7H( z#-Jk=6mfi^GH=eLet^1m@-7y(ONF$~uz7AMn{??4MwCvz*Yh(PYJ!-)t|q)(7xI*6|M zDa>N!R}GFJI~y>hL`u_UAL`920cvM7C{jnqJ~)yCeo1WzXrb;+ zA2k(Ase;n9ws9dpT1)P-j)6s2b^C#hFGMy-+wsi~7wT;29?2!J^D*%wnYIyu{q`$y z(2T~kT-r4LRjXWiwIOMJitu6i-dLz0oW%03GT_q{VQHMoA7Q4vLe^qSr)T+)s}%L--g$RxVOxPUbf7J7xf8|7|vY> z#Uf*!-U^L=w_CjtUPh}jpL6;J$$e1>viD-fmhIG7()QXKJc*?_Mw%T>F<>rY`OrmM zF-(0np>zZ@THP`HdGa}pJO&AHK5OKc1{kowB;dv2Et1rKQ z22$_px_nXlxI3?rVcrhY`784zEDxtfC&{L^pZ>R2hxFk8t*}Vs#67IvY+gBGxq5Tn zV-=7RNZ%{lvA*f?6VAlqZ9WQ#tc99nu-_kxioPe)DRvQ^PjwxyL@oGEmar49W@P!X zgAE4BN_Z^FANzANf;O%1wDw)foC)XLSmnJYEE@%iQHd*zydM2qoUvK_Btd8AvE@3p z)X1t z*m*C`n|gZ+)wgo!nm;xCv~Lq=S2b_Zqb1(A?fpbg=e1QlxF?!1_Y=Asd{nkw2Q3gN z(df_X=w>bm+!Aa~Nd0uI-8z}?V!6+ymuxj1wv(M^%zmlOlV0>T!$+I+#S4;s9wkiMdeq&+=KLIMZY(KQdBr~cC6?!#z-k9rKwT1I z&cLDM;mI*&?>YAS?mVoSobw0j>;2=sQt!JPOi_lptOws`y!#ZtDq#2wY|_6$1Ecbh zt9ra+>>^I;3O!-DM30l0o)~d9CC&&tdKbxf8+kacvsnj_A;E8&k%X?Y$m6|L@ICqH zX(L6SP-QXikeC$ibZo5gzxz4Q=lY4LZn(+Bboc&|CALA;k28&r{Q!=|3_YOi#g!i8;TsV)-sJxM}z`%jrWIVwxSg3vZWUp(SHoPd$ zREOp1xScuD+SX++_VU(YWKqDpl{x>nHN>MNxa7H*ZHsA+Zbf-g!ai_)Xat2Ybp9HvgRQV?#&lqtsUZ7h-=VT8PIAi0=<#C+w1G)6*J@`?jQ1ObC9; zSSjd6>nHcCz(z9a*c74flu{F_q<9$$E2!EbCc5qV$}xwHcg-t$0V|@XEauZCXa7C2 zuuOQL8W$bEll`y_{a+esH45^UFetxB53i~vi4jf3>~{20_%T^!m@)-f-!)yxG%!&~ zBw?(wmxxTcM^3(nc2s3OOfR?W9oHsFmecpSn*$5Ky=C=Ed#;aba2E3lS&4EAhMBPM zq3Ts)j)@~ywfDkR;AUz&gG%FVVj?uR%fOZ@#YbhqpAttNGd^HborGK5M+M0eFx$Iz z2bay@1f2KPSGzvrU+ev1x?|PkT*BnP4K-{YR7|NlqNiJBSz8ehseUu_ZDAu51)Bi5 zp+s~}mu0=!;lY7sW;5nMh+w#r9qi9SeW7}!dhs&?$gHtBL+>dSgV|Sy8P+t`c=+q? zyhZbZh4V^zUGf8^7?P&;$D{1?1hb>68|oWIB1!ikuM+M5QgbP%MN%!K^>Z0^JU@~H zQa&PjMAymlbKkm$)SJAH)kVH1RKMqwGwn|-%fRe=)J2OfuVB5AzX!2W0iVq8pu0t6 zM5=xH~azk45zN5COT4I8R_8R_EiTx9w&he;o~^lbXi z04Qau9Rovzvl0`B*`>1pK<{hM9_2UY)j*zK_HndPhY66xc_n03jISt*^n7~*ZFKxF z=WLpQ+5=3}N`(A;C?0$_MbEfGf#qHmFH4!+Mtl#HGf}+bum`9*yaR})RX{*{ zPud4vKampWJtfrg$q#GzwXwsS>6qjn3Pl>;B1GSMroT+Ey!TV4JTGX?LB^AUU0%)t z)J5Ve5~fE6eaK4iGw*Hk!%wNMM*6~@I`tkKHB1!D+xaiyB9JN}4v@wBg-;5SeO6mx zDwl@a;+$bhFF(FbE0mP0#x68x4 zB_kKr8!*Vm;-38%*|bJ&HtHmglVlo?^8saym$}wF-jZrPqVwl1|;XAp;&>FWPgO7cZL~1I7Ff zHe1Igg^-jy(7yj%D;nj@lEx?+QLtD4HNeS=8v_dq<#-v6mC=qdpkH%@s&z-xV~hAW z>+QQQNT>o%oq0d&n=d(8y_w|$18k)&Z@9Qc>H8c;&GX>U)VElKk`Xp8&VfW zKWZ`7I2*_Kk#N~aU<>1N4};g|j^B+oz^1r*(9n6$%m6-z%8L88c>UOviuu#)d8qhY z?5dT7{nPSJepHwjAx=z#L;M!;2P&2%-^FX<4Ae$gXpV;>e=fIy=iDXG5ofd7|y5AWj5iu3#=FIe|mv14;A~WwR**VhL%f30LA|)tV zzCmYSpf*Opqry6V$(1j!DWtAo6(kAimy$CsQrF6+7Xw4#F~3=V$iqUGK^H_JkI*qi ztjZ?J=2FVm|7z=-!4OEgSrPoM*TzU@1cP}8^U%8E83 z1$K_s{o~*^(ue`+qkjJE<^G|_&KdazLAj`~a>Js@#tea;kbeyVwsW1yp3OjZfRf@0 zAz>5BeypHT^7gf*+x?}DvShALpPJEAD6F%Jf$5Oo?C>_z5fc$~{enUCtTiWFx5^!Y7jbi|KiT%y)k+Ndm(! zr(Wm64dG?6!=O+2ez(0=ymyU^Ax4?=nN~}WFgh1|)EQ=QqW3FtL|Sp{Vk$G6}14;n$vPWBpKBK+Xx;hjumafHV{)*kv{${<~+3q2)=B%ea@x;wn- z^Ga_~ZnCJqe)=hPd!1X%44cb%u=so@Q#$J z6{zFzYKHA-ef!r6s<)$2@ek$8hRMEbr_2K;ZjQ~064`auO#YNwTy={Vf6IXn>_Wa3SVx?cQQ?z1~qyR<#O?WV=u3Zf&MoE z3w&h;b?IE=EntuT_pHTxynwUtK>0w~+r_{OCN`NJ4M&GB5amiZ!Q&%A->^p|gx|uh zxtIu#VsGDtjxZt{2>wPQc%lV!`Gw?CUv@wI)A$_8v`cv&^p{x0@@7<$u22nQw&gH} zBf?quFIz%RDO=O^#}?j(`IE=_!FZ2xa`9fTv&mzpYr0px7ZZAP$eCFJwhq4x2dqzr zuN#;XS^USFLJXH4w?xmDk?Y^0!PkPgPL5bsFeWsYz?*!P-k4cA+=CSqeE&B?ry!x1 zQ{?h?ZmyEDLp7V@x;SA7`Fu|Vk#|o#Io^sa5z8Ag`w#Yea#zfcPj8* zIK6cgXqw4SBO^f+WGt)EElJ}n>5MZ!mYkYnEbX6#@F;^fsBS9gz00ZjBci@#Mnd#8 zqnTnbk>%kU9M@(~{mk!w&HD(tA&KBwxTZC2``QppuKb&9?N<+dgv7>#n~*2tKIKg^ zi@m5h_u8^heGu(tjC32|&Kjb2X{aVAm5cR|_eW8+7CCkCpOEZ*HR?8d+HJ;v^zD{$ ztNry(IW7IynjWEq-k_=v<9(jYV(ApnxA&sAwitd}g>F6Y-#hCmjrv9N^)0XY#;^|kT1rAO|%NC=CVp)!7zJG)*q}e$Ym?3b5XWJ7)+JUhs zYln$Y4eoXIT_Hx->ydCf{Ivq1pAOlDMOquUA#6ugCj1}2hIDeSOM{gLr79HJIdlk< zdh8dW2Bswrx$SR3VVV(dDY1YIM$W^wTWZ=0E@SY3gpm&neTQE9U{wZBG>i?^azg0@ zxW2}9(oN$`r_X{$Dcacr%oKIhG>2>Y`4gbF3(E0kod*fzGNT$#*!dMoEf~R^(ND!F zFuGq}sH_T4D=PTJ`Q8yV1ojgItt8EBd^V*G3oXE?_eB4q>D>$=pJGe({KfkG?Lz^{ z@i(L^Q48m+WrJJ40&CbZ@$V9wC4*~s`wk^qIk9pwRb~sLP^suFYN*<^Ouz-Whu^peMSoY?fqAki^wU#px zWWdZ}$IOSmJCXG?P4{$P%I(!STRdy9%9Osv9ParZN?rw^mxKXlucZM~(Af<6cqu9V zZvwdfq1Qo)H|1_CW7WOpX#B(2b8wo?vie9u9}KP+1^vlP_XFR$0n!n58dC4Ka(qf? zHR_y)-@C{uNndY{(k*ZxF`sXB3 z>T!y4{^k=_~EO~|z)cM$(T3L@r9{l%L zSlqp9POH!vl{?^ya1Idt{&`*;=o}VI&GPV<2w$72C4u6x-7tYv7rHF~3+n2b0>n|qC zJHP+pI1cKG{6n^(xFndX-5*zM%aY8X%-2K1I)q_F31;k=vyj@_XvFO zfPZsHEN68;yrhnc*B}eGvrM0*Wz+{eHp%QD^`WpVDxE8I&dPBne&J%>nkPOjxYJ*6 z)1H& zqr6D7*%Ox)jz41l`PG6FEQjd>`VY(6Xq|X53uL@5z`7*zxSN0Y+EqKX8!gWEBjcTD zU0u|bicPD|A=w4cNalsj+XmUy1Ntorz+=*o zG<857?!7^yeVdey46Q8aM=*2S5cD0c5)0M}@jOi` zEhrz;B4Q)A#7hz4z?;8za_0rD@r$~IcII=g(`6!)L@xRUxq>c&!ij{thjC$|i!9tm z@9IVZ3*|Eb{d<_kE-FBSv%|CupVsDY*V4CeebA<}yKPEIBU!psTn2lvXKkxtxwZUk zwYgRUK%nEFbt3eTQ&ZcI-+x&8(W0?-QxqM5?N28_AuaB!C6WE6t@i#6zBdcV z3=#|n%m>xkhIQ6Q{H~nCamA%ZgIr9i#G?KPrMB*bDuyp11n+40^^-iB%CJ9}zr_+e zlDN!|t=S<%hT8NzX9;s5st>9P&bY{~>Uh4W;u;Lk%$j$EDj2meRY#E%NQZ{nZvKLW3{#P{93%!0yy;TSF#ShMfkmUXcHog;{SnD%ms`{thtazVV7d zcoBmV!($$fKRjm-{CkbU+@9hqej@ruDl%rmU)`*s`FDDP`RWB9wB-XYJMqg+P~;b^ z#N9$PmLUO>U;#W zts+lKSDwx4e~|FEfqTue@@vrFR4UTn^jHBy^C;qeWm)s?@J~z8 z>&w5j61<|nvcOECx7A7lw=T+VLipmC>dFqreLJ*@yW>qUn&yxxSq^d zRG;8&?~bpOzzdr;k!1<&OWWSs4_~g$#$puj{}JJxDd2;#mVGhJ;B8;wr-N&&G(zw+hbe?7BjzEw|lO*L2nI=nHPxPz5<7J@V`h8RDK?!LJNT zy%W~=Ddu~}bInpw47zScU&di)(`Im5(q^nQH2Cx0`%mM?>f0ATG3u_gc;Y(GL=NGM z#EAR7OPQn0hjBN2(D;Rusf^=>L>ax7dQ8N&gHS(wUI*SRCMs~?TK!?*6qsH6kE;DY zBRu&D>L&0^=pyY>BLL0fnP#qC{e0WGCZk3ek7dnZYRnQ7cF-!irde?ah0pySs;5sxS^%gbi+!bi0=WUIQ~c&pv7FEha+NT!z$uXkk9BV zXTQo^?W{!V0SbRXb5EP}seWDL6j}Y-tIN@F!N*pS-9`RpCZBho#1)W;lq)Ta%8mcE zlc6Imo%AfvIWvV99nO`t*j?FYl^o$CejSb8zAmf57pajftb&m`Gzbip@}lx@Y_0Ts zE~lN>Agw^Ddu}g8Bg+(`FS8ApOw+Z-YOI(GWeqeJjd3O#@2ZO-I@339j;8{{`rFoT z8^gPeHdSj{&!i19bGF+A;+BQ(eZe`W+f&K-(?nX!d1B(pOOeAjkZu=22O!a;arBbo zeYTYDxtI)~*@}gWu|DV_en41GIpI3Mbt;WO?*^Bh2L1_B8c}t)GE@7q(Buif@=zK0 z!A?~44LB1?rVT>j^+{v-1fqaShGlDB2>7TL8^z%B8NXL%?pE=0#ZUsl*By{sbmtzZxc-vkW7j^)0DeX-7E_e)?n29#J0&t z-GAw7F>#T3_PN29V)XLMwUdmo4GY#a3kPqq=qT0HjT#TM4(&@jhQ8_j3LA@n!`Uis z;pcbqiIN_B$|dndA8HS;3>{DRgerZ8x(*C#$hx1(Dwd30NvQ|TTWvp843Np*;UWsP zTvc%q;1{nm%7FGq*tx0kjVz3gEA_XHgZc$0tnn~3075sv{VhAo>01+IuV{waRxJ0+ zjECM9>lZQbPU-M7jt5?d9YkNkq!A^hajmt1Pv~C2+?Iq0*9glwnOe%vZclWfQ2rQ@ zT_7gs+!Y@(}_*;|P3 z1**T{#|wcU)IRg&Zi*G0H4fc%Oo)U zI9Z%D2ouM>==FO~rd~2{=h|iWuTg(tQn7a@3y#ua6p=Jt<_ihr5>W3$K;-=r6OsR1 zg#VENV8VZ4WVa10!{{{A%gs9kxm)$i)!3R0623O2;nWk!uY5wgKtDbysO|``=-31r z%Csgi*5tKrrS_)I!=6bxMv1un$UZZF#r0D{gEHz}*2-SRyqN2c7{!a1j9KoyyINP04eTi4C@> zg)BKUqmuE-J@D-P_4&#_yu)X6EsfM^d($|e%A;8;Ny(=0t`j*XhgAdFU=_4dDAcD% zhyW=U)HZ@w?7B#RTnAp+w=xZ=z&jX{R0eqq>>Z#0T8L^82d!mJS+s zRU|hR-}L-X$?ES1@Na(g?I*bC1}NMQ5AhD7nB8EeyQ;rv=XZV6a<%W|cxn?_J)zf} z>HhQ*cQqw(^|H`=?!lp{YN|*l-TSI~y|xUuAjp6?YwqqNzP^dBZ#IZjAimtYAina+ zS_g!}uGSH=VO%+#=H6h^GgcnWxKJf&@)i3P?i+1D5or_dH|jUV+leQ|(K`-ggmB-r zs-rxSsKzc~p5i$rVsg6Z5LT7N1DVroG9->q^`s!{-kUi;ad==!on`QlbcJwZgMP3Q zX2S1@{DAaK-&unH^`lRth8n8=o|s>Ul|W?D#>1JqC#ON zQZ?cOD|StDJ>{PdIwU~rF|n85&DL(0#{{hFex4@d2FJ|E8N!`-JLO}9 z-L^2)`+D$rW;goJX5VylbH?&UD)-{ka1G`m7b{359lNsgcKaVo^y(xs4=Eatn4Sk8 z7<%eO9R;hCt&3ncj`+Og5rVyxsOT3^?x%U$ujm_aO7S*R+ypFf{@(il=4v@@)YNxN zbyamEIkgxoF>`h~2`S@RC)~huf5@`+pogdrns%7^;&1c+0??augRxaZmSUI7s$bx4 z?rzliyWp7q^9F!lC!E$X#)2=nJu9M-@=6bZUOsvay&EiDAGi(+s{u9|PIa8rJTwf#d`N=>5sgodC ztLmiJ()b!{Gnou&-!`TR%deQ;zOjcdD*S^#IE3N`w^jdFR$4^d^CB!E`@JmpHa*CK zRXzhPMTRdFyLcRAMeVcR`NZQ;4MicB|Mc(&KqM$xLaxPcYe%J+q#S~xp3QqMCwGHO z8!GT^Uqw4=bcRF@1e?eVm;_A)ufQDIY>mlh22Qe^LmmakOK)|v_8EFhO?ROf-A!ZQdJ$7#%EF`0!#cEkBEZeT5cMSUl=bj9b! z`_8u8q&ZJ#%6Lh`@N`oI=BB|*8v%aR2lcx%CC}^3n)$oySueqK`@<2w5B*+(Rml#1 z^LUov$vt>>573okg7&Mon=VblfG9J zy@H_M6?5$f!ErzoUYPXxa;P`K%;ed}x!1g&NB002xq_=zAVoEy&kIaY{)XLxEp$i} zEdB>b?J#=ZxCD(8vB7|Bdr5EIx@+$1oS|Py3+f3=BUr&u;s3m$ck<)*MP(zfE1M-O8g)$BAC#ej#+L zdUQNjSJa(z^;}k_V|TLv-zziy#M!xpckRUecJ*84bj^Y9MH+%LQ+n3hJMvJ-aF+Su zRu6*R=@2_Pv`sB|D4gB_S?eu;Nbb49e^dTg^Cgz*(aOF9{D5`fM?8X%+pF>oCX6mC zjex%t1YUO@ot9gt?n~6!Hv7o9>?qHAT9|$Ul?VXLZUK}cM3dtXABDZ2$JC78PmTT0 z>OPBbnQi_&|LK3@<;5y<`9|UET+t!bv#ka#!IpC)O|MT!uE2C#ok}aJ(g(OA~1absx z2W@P7<4AW05=nnC=~fuVzcsVY{wJaK&#SqR_Hx;s-W{a)FpanMvg}r+GUBL5Vq2W0 z5$IB#P-;voB?eb?xL$JbWQy&-FO!iv?fq>)hG6(Q#|~xFpY-x0$zQh(a86mbv;4C~ z!4C?@S<0;83)0_@JFdT4)E3K|XXN(N)w)wF;$VwjcxormlM3ZYBTkyI|cicW^U>A8eNt9*gXEpbQz8F*uWIbN3 zw;lcKa`HX3KZVoIMb)mH3Gj~INa0YrTJ+Y7hzC`&+4uc3Z@av+S4iYVl{2!t(r>gU z(Po}_dF>~@>X4(e!xK2BU_UQY7Uk!CjOLCgsnmP=T>#nk!is8SR0Lt_yKlG6G+H~K zEPIX^(Epncmw3~E0gyk#Wf{LRx4;!~a3Jw{aVNr$NLrh(_k;MjYrkc3S|<(%^MIn6 zpDIPO@C~Q?^QSJ>n?o87QT`44=)SHaPH94^=f^y~QFDo=U%b2~XrrU0z_@Q(p@;p* zrPkBK{xwIb41{BuQDdpSNmUKhar~+;`&T$+Shc&CPv?E6FKa-ZFWkLd#O6iz)i%Wc z;GX_R(M|e{D|<>C@FY|4?gHzdk_%g#^&;J8u6sqGIqyU?)!`%6!<%K+4YIQ2@6eq- zq&v5!OMJ+`f}t^qE|J-QhZFz&{o6g}K3bb0qMiOnqOq^%&=3K}Bc^olL(93?az$QO zv+mjNU^wbrOkksL={C02s0Gb%r)lNs>+m0VZ1$FCB)~g~sxF&;6dRz>U-!Jk+980k za>iAFAY0N`wM1?cz|Ld6{x?O)V0-G1zxb_N36PiJXGl91DMB-?R-aR?GRMruWkt&% z_j8VETmU5jH3`nFZn zZg@EUke9~L^lUk`aud4&WmYj0e=@H`=7ufMSKF+wnVAytPHJ6L*E?_YJzT5lPY8;u z@L*Kisv-IJ4mfCVE^9VFLPIp9SVNKl{zJ9<_|8Y4tc+I5{P*nyL7QE*7|-}{W*;7$ zTEgxt>xDd_h-li>{Z}n_X5Dc-TnM#Y0m$`n{eYR{6Xo9 z^NRN8Yd+6GgB{?YUJ@EfR^`EznOO~C0PkB5e|qMgM}aby1LW#3#zXZ z4YxB7uZ|$v_%6P_1v=3Wypg%OSR~GAs|S3`$zcAWC>!OBevNQ0d0SJY5Zz7a@uH3? z2U2>m$nXNu;_us{lo@cjeT)C5 z;y|J@`n6@V;7?$0R|BVokmH>dy>V4G$8V-7cYluCjRfMxx!-d4c&-n`R(`nn;&Ru0 z)lbszD7G}qs$17J#8;s1@0htodwPDp8=a`La!e=YHGSFC_q_{8=5=|Eh(LttEGGW= zbF(Q=6Y1U)#jQ~U8)*jl58yiCPduxXM8Hc}jzhMq{k!YE_g!OnuO*$evO5i(8)+6!CUCMV9y4F@r0wV^AK3 zcI4gN+m*q5u))-tHaX-$}< zw@WeN!=~@kgZSDi|G=P5M#ulP3RmeYhqq$#`w>=){Hn)^xL@a@c!K;~snzJPkBZqq+*5B;TbSC98ARp%bsWAj1jEBt5l%wbHc z0jbos5+iERyarM(c129kBvLP?B%#FHr1vD|5rGQJF}{@zlzZwtNUP6pe_MmeNESGa zcN082fy05jF82x0H=0F)47ux(7@FH=8H>vf^{_MDnNnKDzABpTBc^*?D=z2T zfEuw>_lo~iOuANtPS4-9Q&4PQlUoV0$#B#3PZhozamu>j*i8!^QwW@=0%2+JKy$4!sR$~?A0Z+y zVV3Tc6rUC4HS$+DSK~Pzb=SG?PW`K+#F$?J7!Go;J;4t+ChS3QYErGEU++be`r%0( z2N69hV!oMKYLC}~H3Kt_<+g5<0z-UpjGNic>OlKC!#S&C^V}x}nk+Ex1(6|l8H9`o1uQldnozmap4-oEIW}O}zvrFpARAMUwmu)qW19v+q*>&{H9rLDn7P zg&1j*=C>fhhJ3V{!pPbs>xuU> zznhm{rP8%!X<|OV(+jH@fn1A~O7ywa-F5 zRAl7r(JedQhFl;w16NrRH2hFTyXA8Y|A=StT)FG@ss9U^HDQW-VgX7i&;z!*@xS2d zDsAM>yM7NcV-qHTQ8_D71ZqIah4sPt;$JFB-5f9gyso%5M?F!}HO+=|z~W@lJf!fg)H&HUUWyZNj$4^H)l&hgQwIM$5L zjZ@Tg(47dAnx`iC`$HnR`ATcwz3J=~4H%u+1)^z)a*xh>s5q8KJ*N9ii`vdwp%TYY zR@oLBLD4Y5XIU>+l0?n$%4fySAJgjpE|oG{t}k&8)?ux_V%CXI>RG zT^G)I3v7%AU?(*!C6PL+*x`?aCGEw!6@O&#E(?!mS*xVC=8%ehvM1zO^}v-ey*~;jHRL7IwRxh=nUS9Sykd zS)G3ZIP^%_l+N^ThjR+Uk#v)Y@#CeIgR|q2A7t@YWv#MoSjWB&0Z4=8Bp-61K?pgS zQ^u9%eYRg^Io)7_+#h$$*Xv6pZPh+wod319Hv9f+GAHN!I>YzlGIB*L+|}q4!brC9 zaP-vJVD+$tVQOAers9GMxt1ymKG1hnN>T67veMx6>m@iz+P|qdMU+7tHvj*cl>ZB% z2VMuW-0{k4Ngdu^I^Ja-ZO!uK;zcL1F&~BdK`=sngy(d_f2}(D*RBg}5`S#Nl!&)X zSTda113tIG+kR?_CfO~=H&eAIxkIrRb4ned2`z`OiyhTp-R|HGs>4uhWXs-@B3*es z`5UbEeP?jgoyBW!wJ%LWmH`IgGhLhJOEJAY(70VYCSf_B2wSO*_{TvvqHux5<_B~W zPi_8vb3cs3J-V_d*c|n0*3H_OcGa^p_fdB}liL80T*TmOz!Yz^_iejIPUDOj^As-R~dDYq52%|AY%EgyXce-F2c9J6~CfSLTT zH>ab-5_PX*>iEXBwOAzo38GAtpVS;r=*xJ~=?)=HAw*9G&ajr^#5_I1__k=|ff)G% zYu*owOJ06JpWr4hKRg+o5qpWGzod1ah!zCO{81L4DeY$ay$@*IRzJR4W{Od2;xnq{ zIIqVRtDLyrwFSzoaD2Ld6N9@M%Lnd+yD| z!Z@F~rupyalGE|@(oDOTR+jmE*uw}J%%|0qxG&5Zil4-h&V3Bs4H(grwv~`}Ypj27nlHfyHi*f9(VMVmADwqYatmG# z{tG~VZ?A#6Q+GYLi^=frIA;wsTo#-=z3KvlrBQmR9Y%##vi9Iy*I#ra`l4@_H4>ud z@4gXrIMDtB*$?pH`{;MgKQA^I|2O$nnui-7Ua&dZqBL$2omZ$ee&x;F7ZR)NRi^Om zB8vtoZw(NUOHnjlNRhQw+E+VwF;23U|HRGNivV?6-+lhryiBeoU3#ND|ki`y9OAoZ?;BIp4xI{7ZevcIo0- zm@;=IJkxe>bnAu~jtUj{j5epVE%WSw4O1 zeo;KoPuDTVy~*?fvlMtoT_^d&H@X!CvoW$+hOq1amyZ^$C0&+TStq6394A(&f9AIb zESG3YUmR?!g?KpZlKS{zSE5o~>y&*0mdP%%U=Eiut+KLQSgZCCb==nCr7X26uCM`_ z)2(KJkJ|)ht^V@n#fed~#IPMR!+`gu^WmsrX~wMil_yN7Br;Vu2;SISZ&z#M$mK9q zDu4u=>d^A2Dr@l=PD#@E!4k`$=F<-)_=uI2{CE zfR@i#`;)$hVuK};51B_TiWHe@e$uhkmu2nX+3Y?zWgg*B{=1|^KHZTqkbT@MyV~7@ zq}p6bA)0c{E=IIZG&($d=4LofFB+P1gkLdzxe=e*pKf>f)U%|C_bN%W^~w&wLF6Sx zZ{!y-jExy zA2m#k@f!B8`z*3HcQ-*v!v?&dV*Q}!ja#OA<$`SRr{nPqKF*923}7IeuTxGY-sSXa z-6FK+x=-L1Va&op4{rJwHvK<2z%1(VGm->Bc(KFAe?8+ZkdsRMSg<*#(_FflB7>*` z36g12vrd4WsBi+@7ICKGx{0t?Fk=~OG&Am%f1zIV!AdERXm_fw?J=jm{w{#e!IH%7 zz6X1=3IY75gA-mJw2_VumHR@3P)aL=eG)z##EmxSuzyjwR#H1H^3gRzJ+R6K;}Tc0 z=Bi;O)SbK|ZuqJ8#yCo_1%W8nC#c4iot2=&qB<0AIu2`ah}o+w`sMrO!c@_Y)INKO zlS^XyyKlDbKlGg2wrZh)G(sL3hk&2LfVL@kI5nZRB%iT3{uc53b?@?w37zh~-4N|!1(iYA*5=#tiuQ<y3+Lq2s zTQ(3NmkiQiehPZa$fNX@MMWd7hLkLpRee!ty!KoY-BZZEUGeUMb7gfFr}4$73}(@i znYcw>I|eDExy6U)N#)08e%K{!R}4BisM?KQimFQ{g?YyxYRo%M+r%Hyd}D zGJXJyQXTZnO_u3ADT6V`_`1I=gDs^B9-}KW3&3_dtpoMM{<6isk=6F z%{y!*dhm56OvX0ZxQO9};hG8f^TV7`j5Al5adf ztp;Si&I`kE9{{PS!xJ3(_#ionXD(b)?;MmE%wD;NviB;6w|xxD|BICmmQ*nTy3OR< z+4YHnity?u+Y(mCW`2__a~{S>DSp?}e4kmfw379aPVnrVG*^+&;~TZ9jd*32Npr5X zA_zlCf6M5lju57Jqi{1rth;8EWz)XzY`2+q&Fsg$*ElRVfl?Ba&BdA)F7DgKFDQ@^9GusbiO z^=&Oc2QIJ`Y1Hz^9#PA;DJ&URF(@oIme#PD;X7t$j{orL20)44zgga!yV7U{Xu#Dr z6EF6*K$_6P^#ljo=?6X(MrG| z$F=W2@U^|JQV`?gYvYbG>8xulIXLi)NZF*{Dc9=#qS%5}mk1J((M%991k2nVZ}w-@ zo#n#e)#7SEv*U8Y)nYcBepFPw{YqsRei7!?@-n)=bl9N*8GIj(&&*k)0*c`(!8_zz zs~SWX)@itV+<#aPqk>r5+U;pstqLiLW($%1!W1{jK&Boy$t$u}<=o+wWUX z@T=6I7W?v44W)&9tfSugZ~XvLr2RQZ9@!|O?S>yU-cB-(S5t_7 z*{iOl6t}_CYh}aYe^4e3zvZs*%}eLMeN@86%`@%?bmj7jMz71l>8rka$&HgnN1anz z6~FZNa@{bX(0c%VAq4$sAYG_(^r=@lrEzjktJ3hC;EDIO~|89j`eR! z5Q`ygFheLzht=T;;k|SGlvPd&IRiF6r^MPC;Qz}MbMd!2o*@8?=S)^)GD z&N=27c7fgoNQ$hkw!Ua$1t1)evdL;|u|c_WbU zXni7*_S+FHprkbb_5IKvIAO)x%$t_ww%<47%XXj`=qX{x4=b{A7xwvGzYsXI{p9Cw zl0@AsqVLf|_*#n;bzx#(5F0U3-@at!@CLh4UC`Dypy#*k$^6-H&U;Jml(j&i`lUs` z>A^nfG6Bjw2MrzaS>k3u*01iSTjZn)oQrD$I06_w6A0x4PQ`d9!+|#tDN@X8sVeNw z8(%6`Tz3y$t1O%=i-}+D636#vY#rqIP5+GPTTI)}9DARQWJC-4nH-r_A}VUEUyAhP zS^BwrKHZajU3Ybtbw@^k*SNlZs$p((w}k}~H^ZgWVD%RZSIOwMO1F)?SH03SSWCDGonx{AEc8Yj=vgvJ*7!gx!I@Wj~bCKh|wBa6OeDNOL z={oj(-_rAs0}YqHoJai|I;6a@y*^BnF?LZO-)`d*&bjq5sUD9U&o&^a5Klb9B+(n5 zNRT6Dm_uuDWq?`TUA0uPsekHlo2fb4jg^Y}PG6c5?d`;2BK6X-3DLVQvrn4M*wmX4 zg(HVg{d3i2jC1e1h%t0C?IC5HK=;E}XH>=2t#uFXD|-nY^~KE)>k^ZOh>-baymQWH zEjtXj;7)q_UL#u=&5BqHSy_51T9Eg%u7u628r671vKs>m)FBTk`1IGbAMtXi41V1Vvgq} z)=(8!XVP$rega5>K7F&ZefzZd?7}r;{A=ZSw-ySb#1#cW+a*n|o_;sLPZGA8_N1F4 z`!g-8K1!8dar0nQF}TmDvKOG|R%`2j3o`xzHtfuu)pxDc`yJUhFr)9k z>cpN>!x6-z@sVOZtC^pKA7@g0`mAeOhg(rf^gg%+WozGWS!mCl7!86k%`8)8cQ^%uui*HiUYO}+elKv1u@4fC!m)532cKYKpA>EhejOqv5Nd@_yFv`U{ z2uGh1ZO<8C8g#wK#~{xz4bdar~M@#~w=@IVDl=%TXa+fGb|fZwmKwhQ2WH&mC(L_%J`b z0M?M0pPZ0F0S;t=u(^zYwE6V=DjFOuXjy*d!AkSZ<*aR!p>dRyi68mOHrDh6XkZVj zx~XW>yyTsmF^2Bb(azstBHxTbFqfuj5u-;vnx2cy5)39?L)_1IDktq&c1f<%&J*z&VUk+C+$8*t=`*KsxJUsI-Pwe7I&h0@DMV%a?V@0Oed8yaC zsMJf-+h1zUxH|4+&3R~Pc_3DLd*BT;E+rK!D~^ZgvJZ21_!`RsTi_eG-f-3N{?{Nf zI{As-s>r(In^ddEl=YY5hF%KF7+)(8>>TQ+KQQ$dFEf#$e zf}bG9MD%zb`eZFpd&ZzLf8xuw>EeM=b9D2wG;=#>fswb2Wf81>o!)-n4|w@MAT|Rx zqWrqc-M&l3ld!F`%F?xyNpWAfkd|ssXE(N&NbCGl1Ua1US=V3MbxU(e#U_~ zxNcM3|7Ij=#PTtKi7iX1{mOILBkH4N?97h>e*i;}m1Eic1s#7y*ZaddL+AWXvNIJg z{E>U+8AbpE`tTRz#_YO4m|t`Xn$>&74jP1g2n3cq z1e$qH6Qtvf!>y zR9e1%hTPvJ$mJxg=EgXY2jO>OhA8JY89bp{hRt*@5hk0vZ0n5FogXF>SG}ki|@x{C(dO36@E)z9_&8IBC9z7P8a)Fd&;`SBQ*mL1U=|am-OpHuQ;780cOf z2-cWySSi-<>e-^qz(pDMsF9P+GJetuIVGG$FF(l4sUAPu=^O6e^-2Hb7!IwEgKi6x z_0~IYB>yg0f7`r}n!o!jm0JJ=lRWDxD7e#su36K{=cy{F=WFR1XuM(dU#Tdu`gk^? zP3O){FD^ach`x=P_iB`JLd{5UYNF6_9FT3OI)`zV^VVP(%}x8kHVhU3>U->!z)ILO zw;y9!I~>u@Jy`TIF9pKGzK`0tu4sfy$QA~=C_a@~+*Va)Hq-cZ)hYSGgOhnow@)&RkddCrz};&4z_WViJZ4n1fPh`)BoMtD)TMqglA3ye zG7@h+ME$XL4W65<8rLlayzlw&xBi;{B_L<9nOU=$T9wp*;__aD@KX|{-rF9{1W)nj z%D->;ZWmKJI0y5fX;%F?^7t<5tr;fah|XNU76Ol^LDn!tSn_w#o|=xOLE0$>6Vvy= zmfUq0_0|_Ai+Jp3a@J_yBR+}jcRqzPm4{Yk)5 z657uT7S-H)-{(e%qpHhSaJAiqp?VmA2t{V1(T@@L#fUFfb}bqZZLVHC2(r2lzi``{ zN#5|j+)M2{s}^UU6tx+>&FC>4q$Cj6CD)am0=rM+XdXoZ4!B)qH=QunU`@9E)S_%7 ztw$+E)zkKTYDpHSse<5y8__tNod(T0!!x73c%5-@-1>Q+7lBz$kF1Gq7hEn{q)|#K zG}+=wxUdA>t9u5N`^x%hTw8H7MPQjyC4Jir>4tYOn%y@a1aJxuf%c_kzu>LQ=@<9x zbOqmHZN1;>CmGd<%A&=I*^e%n#Kw8V?rckN;~*SN%<2w1*+oT+4Q`HwHEBIdk&K^V z@zNA|Iz(`nI;seC3G-apR;gjDd@y^jFJdh=Up?IE#-oMkox_o;Sf8~cYz%spfLol% z$7?A;nl4zCv3x9jqyD!UgQwe78TNgkfSIwcyk3dNvE~EQG9PKon-C?6p1N;^sW%wp#^4qt^<(<$553iN-$65H*`LmU%v>epRwdhrV*>f0(J2`A2(e zzR3jwmjMpJBX<4+EiS74Jj8+|q*r~+ zL@Z1fsRX|y<0s^S2qBPjv+eG@vu}lIi@Dhvcp*hK|kArlo+-_^v=k&ax=_iMiDKA)23le`zqJ)`gyce3t@eW3hK2=V+!#QZ8&T2A4 z)9kl1-F~M6KBJx{y>Y3{bGUl>?MY%?YYlyUg63Bw`S*s8o*8v-|Ckzzk<2fuRTIDX zCTZPo&!(=HxVgR;wPauf^a<0e#A~zM*s+T@I{^iBBOg?Z z`VINpvwCsjiC>W2Ma#xFv%;x#NS&l%rdK~vGP9F zT`|9!B~4fbKAXtst7Nmii1n%TnelOOm!x}jacaIcgvmI&GaeS7t*|lNoW-Iw6%OE- z6>50dQEYpWPt!cRI3{KxJrHy;Nu4JMp3h8S&F!0=aJHfsIqkXd8Bllz<3 zT52~@yFM0oHuq|ZF)e+^iUdsdoRF@x=;1pu_YXiYFFu>!)(Dyh(XgX~T6BhD2cymR zI(z=|TsS8!T_@S-ZnE>Wv+wbJzA*S$rE4d0=LhrQrgUWy_+U{3OX1b@eACF!9R~dd zp^30)W!szK^y;Fs2fuJ*8`t!(T^Jb@kSMRW!m6#v4~|aMOB@m`M+B)(1~$3zHiE+H z!K^$qVP}Xf0WX>@HHj$gpBnNTjX3V=?!uaSOws0<^&uIUN@MWww<^pex1eNY_#d9w zAF1}=+N#1B{85C5_c<>5ck#S@&qFG^IA29isJ=KY)tZC{5_q}pk)O)w^qDTwKusC= z>TAH&-36r<)i0tsQQ0sViKC(R=#93}QDo^QAYi>MR9ANJ%qk}4v=~F{K{;L$b7p$A zlggcXqT*L4QvD}x=Ag#YuOLyl;`jjbm$I{2Gy~wlH;(X@Xcr;cqMONo>M_0wP)H(X zzur#l-A+%WaV`8Y$(BE>ysey!$yX6&VOIV3Rr%B&oFAmvWxYSS9L$R!ZNFrIB#~I+ zB@GoRH2Q*RJ4qe|U|DyG*ryGsJ3eF!KeHIauk+6Yb27*741*I!cVKc_h{fqPR}#ir z;`dUk4a<6sup%Fp_R#E^JO<x<0IkEG?;ik(?4Nea)` zEg*?Am#bQ+4yeJbY;*jcZA)n*bnImk@xC+2W9mjX9*%Jk9En$m=~y<(Q-bVuf$>Qb zlujLt@3${AD<4baU@x+|(G#kEpY0YB=S>3VHE-q(b!pc#s1a+^@ectmM%zm`!vs~* z!utUa!lN72b*9L$@+BVQlcx`>77w>BH@CRHk(D0!es_jnEZ!0LQrR!2-eU~CXKIH^ zsD-H#aX&k{WM!i`8()*L%!67$6VVyzm4FUEXsKFTab;pwM^Yk>f!chTO$XVcD> z>XJSWE@?k`c0^(~Z0rT33(+scicm?j)=l~-bjfB|63uT-+DXHhZ&8x8Z{hmqE5Zcw zStWfl0$wY?>iOV|KxUym@DqelL4A95nq`y!$gWyk#$Jp40gI);AIS25Y%kU1g+T&NvrVDr4bCP)}tUrsv985vJ`OTQr^9&qiep%prSTpWp!Jh=W z!Vma#ej_!^GL^FbGJY|@+cppc;}N=SGg}JjM84a-4-d-3hXjm!9OcadxoOI?y=4N= zM@i^tr<9MH$A`41XFptDAF6CD$GfB}g0q`Yv#)SO?iD%k$24b@27ux0iA!%x#QPB& zF5jQ$1R2;;O=A54D*)yj_a&4Or9@DV3nhX%@HF4&5Zq zKayUTdD&1iqa1+Zk_wqGr8i2{)PTV>?*UMBIh)IK@J*^ zI=7Ac#>cG;*Mo;Vwo5+aH4M@@h&lVfVQ9&4YIeZ*A@UcwZu0 zC&j!p&AO_x15VAoGU6y7jGu$?r2XmMm8;JshXB+*rnni+qBRqqLLttN)=4qkxAh+Q z;ePdtlZ+XkAXR$X0N;O|STumm-@DR#@qNV1oyIl~>jk`>j%e{1+$~vtc-wIwIw%C` zx-EH@&^{~ff63BZ-EGHTROX|;02!<n$=Xy6#j*@g&1R1@?7YThsQm+T)WYfU_DDL`Tb+y^8&(0)t~YNPg#6ylGR2e)P~Ikp8Zrkd{g z&7UqZF&$LCprj3g%!n@0)V8eB9@bHm4;G+{Bx)~6FK zm7`>`)T`c0smCJNBHupr^15BoAA4Cn&yBcan}fPH%23+BUYoon_sH2Tes}M?31e`+ z7hDS6VJV4?j+~)ceNwbB=Wl=@e$M8gc3}c8wPz(`r^(lvB+36#=7}E!mxk{2=1|)G zoOp3IGLxewS2O#h)UF_#D#$l#j9)ISq40nLzr9|WU}yy`s$4ocvX!IdnveSKrYWS%}?pKO*5*??c~j!+VBkP z@1dSD!w-%z2zAC=CMx>ukPZI~E{bM>4FF0mlsYZV%|EyofRw=l2DHi9p$K#E8d*mO z*iE3rdq!+3Yyy*n?D4*G`f|bmHiTPe$&VIa?0%-9IUq=%!24`F2$H1FFodTpjw*uH zE1`NlmU?lXc^-uUP9ukn{BVi1)N$3X7*TH*C(5pC}r}tT(kBc z`&pqfmW6CaBwy-eg*I~ImT=|nM}64{ebFtPc1MOSMZ)uIz4t11sxjg26J|^Iqx5NP zJV9TS^Ig-l9iIwIM|kAi$hA1+*__YX7Eg1RBS%c1^Z71rN3{$P^$!Nip|tctg?Q2R z;9U=^^VTo#=jPl;vPyqbzb-?!1dfdT{8r=QHR|(qOS8bmJO0C3td_sLGHNKdz1N3L zCDBGw`!DhH;Lk8@tE_%y`Nma^Qx%7WyK^iM1CLC?mthp(=bZ$8vJMvnHKK>H48D>L z{xOWi;dJ4(I4xf(@1c>n*3oc-tmgyrc1X8Q(_5b!zqSi(pt!eSgG<6)GUO>+&ff-) ze|X&dQ+li?AlEU?pT&$BPbA;eDM<#YpITr%Au%R`pFJy$OeD2a-hX)wGx*W89MY|$ z5iSk@Wo8B;ae9D)AE+lWVXJ$*3NjH;}EjFHQ$b*wi%--0|0pcm?-l5SO z3@G^N)LU3bQnH6S=o%@_fZi(r2^zldX?7{hBtc$#{^JCimnr!UCF>>h02M=GO~HT9 zzX6rCJC7ow#J_@n(V;qztVQ9SN|wpznC4oUNa{ni9cUk9+n;C#7B04rp`L9hc_s|+ zf6TmVG$O32;A-Rd^bk2-#6+orkB9?e9vB^r7ls|KzJL~uwa}VL!tUp#I155_mFi(H zzeU?XC32H{E2gDBH2Pdrdz-I~FVx&Dm*HQAB+cAuUT!2Gxg?jhM);r@qC(ho=}JWv zQPoSk2O1SCPu2IBU7ZYG-`y5sjCB%Qe7XKAVdj@XYRDy@@~|kvO0Pb5;-(_tN%J?^ z_1pZ@5fprpIz+&{8H|TYxuqC$^LrG*8?}*};n((mo~0BN`kNqRPb~wYDmMzAbIz(X z0r(I7^Y%H^SimU%61YP~dRP^Hs3=HoQIL28&{xg}M*++(?>rcfFoY`rFE?w~3g4jt zHj)1BCalF-l@ptQ3o=(q&jGL3*}Lpbx$&11KW{QKx%aGTCOf|nJ6yU+FfHJ0$92fF zHZvKJl&$KJ3Xokej~Y{Pgld`yQmF2HWnIQqWh4@e%drDPT0LI)`I$v7Ms)I@0?vq> zF52(nR_sd~)7rGKn%BUZ)WvL=t8CzXPYIt{f09zxxaYdG#a?#Ot5OUNP4#yHALI;+ zv&zY128YvuhA-08ir+x`VYNqAeCp0A+AHnAlqUyN$;R&S@P$QA#7kFr!$PLKSS&e8fWw4eZ=IafBbCi zh#vY@ubk!P($I3D*JlYESj5zhQfIQwCU4V4 zt9`8BQEL;A7tLm*@3*i3RfC=PKJ{S5kIvtE&X3#;P~1(0eNmTRd<)FR-E*%y+r-#>8fm z?VDtNJK;fW_}4n=z~KQngL-8EwFL1yg0y>}?*3wf^UCCvXe!6hkX-ZHFH2q8Z=V`> zns5t}wP;cX2DJe7g~~~O=7foOL7@dQhlhbSZuktI-tGO$1dXrd_U}^0WxqZ{J$QSt z%~@fH-M2dL4Aw5Fjj1gi;TPoc3H7&&9Qp2=T%mpNHlrGLn(y$o>D)4@fcmswz24ej z@6z1OOJ^vyb>aJO(p>x`u!^Yh_~d>@`l z%tgG2$*UF}wo{YTvu9_kJ!L#22jA_@Ev~=O9++|xb!*{O`Pzd`R1k3%J8kT}_-M1T z3+>50Hy{Ttr9gU~`O!sG;#RLeQJI-udx$qq*nMrUW3BSQzq z8_?sFiqp7STjSBn4`R1K=LYxHGaCcxiwCKQIf)Y9rhAQk7%w{`@2Xy!dxQFR3r}MG z25{~r4HbfiU5i%s*slO{3`GBWAO-*n({buM`sz%z^Gvm1h00n35!*r^qIH|J%H%R2 zU%zXt%#)bEJQ-&Z!qc^Tr>m=k2ss#NIy!gv^)^=PY{>1B!RJIBKm{~*sL$6cy`Nq~ zK0e}mYQa8RduOfl^mrc6pwV~z;2j-w8kz)<-$?l=aDV1zk-m#ziRe+n^XYMpw#1uZnev6yMYGBl@9#g z1*>b`)zIn6@3rya+3FW~*Fv$OruN^0da)rf3^PKW(S%jZAZRR~QOqQwE~ul+3yZY7 z)CmZ#%+FeJ!5hbkqen}5?qH+LiM?gaqz)vvlCD+7PqmT zpzb_?BdNVV?>jb|jvrU~9y4@hLv~U>Qx_TjZ4&se8uJTwhoFD_fHm-%ae>W=;7wiy zxG3AJo2u$?ie#?AX3LgKGhYYRcoJg58lvn7dHq@8rWFF(mWaXOJoAq4`Jj!Qpo{(w zwI+{9!h7b@1$|Z2S<3TlWdg{avP^vIu1=$@+o!Wt42bSn&)GPd)J%c4I4`8DIT&vn zSP=yGR+PamnhT}44y;UgJ4**@Rws+JF{g#FK#_@TwL=^GDaOz)A9609K6bfgvuzO3~e4mJ8vfG-`J zg%clYQQxTKUR}PDuUj2@l`N&jZdNfVlb%tgpzAZ7MwQT+*xjpFnX;5fw~3A^7Fy*C zIfGgI?K2&bV>Rt2fvR~qlEY1>1DF_zs&i*pTKtD9GHKXv;tWAdZVGrb^KJy>-Y3lFn`LG3x1W_V zi5IfU0KpCDT?poWqNM@%c)XQ@44wtO>sn%!x3xT%6il-|KwqhE5Hi=uq6Af3x*|O2jfCY=G%^?LEnW)-l z77S1;WoapZrKkBYF=+&F6RWzYxsf;Hr~fL@)<17E#1~LzD>hcXb{YseH)$;yY!KMz zq4AbVG->qS&j9uKcFSM@qygeBN%OA!qi&(8K8u8B-B2Srym+{wbNl$jo3I4LDW0|# z4I%?-J;}Q3TahUSA3RflfFG6OqBRI@@mcF$rbJ()ETmggucXD|WBvCT&WfV7dHW#I zqmCmrZ?RPGG8G~Ty;=I3NPWFVCkDEkxP6<=W{Vr$xH0LhVh-Sx>y>XKFV;MM0lHbx zBmJDkfm(uld^Dlz;p7u^y|Br|_Usr<^CR{YXtAd-A!L`AbeO=XQ@ww%k95GjYCpBX zHB~;)H*-AZY+)jDMRP!_f28eoQ?>x5>YJo9-}Y>6%{KRbSbN^zD07ls3V&5W4WGY?UcgJb($94{AfH|3 zCrF%T_PL#|kII<#a{qsgjuyY*kN&s}y4pl``CW`P=GFUjbSmWsbqpl)B_TWCX}n8_ zWx$hK+_jQ(JCM4`a*O%BXn6cJM7d{YhhIFyk{wlh^l%Vk+Tx&rSaNq%}`q#|QJzrg1SubzlCqN2SNQz;tNp)|*WM;hV^- z{rD#B#uHONllfVc<{!!(T;t>b;JafL>Mp1Z-x_rYPh+yd8n*Zh$VRIH8pM2>2~ zScUM8=A^5)Cd-{u%-Wh{iJN;S)lo6pZ{+F6+{hZb-sv}96W9CoKSG`BpiO~y$>p*G zef{%W9;oHoY#674rRPoHVJ(@ri%nnP=bIwCPK1J5Pc-5gq^eWwSSvR29Eo50lX}|Z z38p`$my6tbYa#dw^fR%W9}2HNW!EA)El=kY99EsZ>xRdYr`9Vc?+{aven3=+KVwO^ zu1VvjLhxp_>H}fy8>eBniF+v_)|}#x6T$^wE=UfgL4cV4#`5p`10-HHP3vNfDBr83 zkAbn)3NmH#sRx3x3B3oiGkB!a5L1TFcseA6$xo%tLya49GtLwtC~c}$u%>U8e4?%M z@X3Nv0t7W0BwS%Xwk}r1+F3m96K~ZYbE3bLB36;EzE{j} za?N4wSV?JZ7W`~?gM^QZ)icw5FQF(8>}k5kvS6kXjKe}e{R>QJ<^WRCRky&;=LLD5Nw zpJ=ev)foV;s42ZHjxe;p={>VZDH4w~+ z@_A`USJBRK@~uttm}s%!It1hlbCq`9NaHtI%~4tYZ(!Tt8qvA$g5_K%_x2FvVhC%0 z+0&%Ua;5snclgJN@lV5wzcHy91#%A{;i)7SMdvIBtA>zxC(&9s(hM4^qTP3Gs-4I+ zkcmtV`w(|GhsFfB5MiVu^Eogn5n}A!P4x>ZgyZA%WdsqmEA)4@PG^5dLDciRMhZV`c195$5W z#rOW5<->AvN5k9=z+I$jKx1}*nw&;t#S)Ea8Z)gb%Q}gb+D&WYP8{C_UOZv<5v+R_ znK|Z*XB@^uf4`bUf*^h6=qV9%t@s1NAvNORl_u1SuAP?>{KHO>JtQldZQnPYH=Ik| ztkDOJ!!?42i4`-MNoPKzd{fyrMMYg^m0Eq-yA3pNh5K^(Vow{ho!`S7v;|f!9>kU2 z955xB17{6o^Rv3=%?`zc%}-lUFZVcRK0KsTJfT@u-JYC6T8B5vx>wU;gqqL3RRpZu$ zm6Cv@kejMhA8~YnZC`%UzE(V&LZkZHsp|6%yR{wo6KAir{x+SWh4X~o-Wc@%FdF{> z75}b+hP;?H-8%EZGF*0Cy=*)Rx>Ut?VyxH6VuE>prbpI~y7jPh5WHI$5K@5H^}K1^ z=Lbc?Nuj`}r`itub#TQc@$(WYngBM*334e>M zbJA1lJ$`vCi*42hgqq-zbNsMXnq7VXkig;doq1~bx<6u!FUi<7`W#wb z9CJ9wWck$^)0`z=I&!>6tWtGz7O6K@IUUAS>gB8v#iS?7#wynKwh$)Nd@G!KR=XuH z&#s+Ps$TDOx3EUsAO zvrDP>NgPy3rG4=MCPsF%j=7i6j2`{WBU<*oH!@G2jVSeq@B<|<*&kGEzxa&EPX6hr z^hd8G;A=nhw?A&q?(DlbzA|Xs3Tmt)So}(tr>4bZS`;bKhwr`LrpB+{@ut0e)-?b1 zVx((+?xP*Y*^He%F;B%w4;B)4<}kPCktc9naC6Rqu$ z{tCR?KQKCKU;m;rU17Qs1qRLYy>;Uusr zT&nFk_nyJY%Tb?R`dyc(+xAN$AT*WIW5Lj2y4E%FAwp!sD+^or-b*MxRNMM9Dw$l0 z&7v>5)ZD}km34c+&;F{wj>^ZCRx^o`3q@9bh%4;-YNdrIE13JgIM;|rSxywNn3 zYz+K**#nB%)tdxJ?Tn^-s#dyxl;ZyQ_5K^xw~0LP&L==;B-o~tIS2&k{n?f1YF~ZX z!^=ls;u!Agje2>L=|`FMU@SE_*UJ28C5N^mhK3 zCNrKy%=^c^=)kw~{|@J}G}+zu`6_c{Jsjp^me>_Z;3IE>{pNCbmvMc6b{Oq+>_GBx zmzvDYfT<3lrL+6=VZ{)2E`6Mqza)j7n#~DTz(J7fs?e~mlM?|J-aoX=vtc%*PUao< z2g--l+oG~yXNMarSjWjqs0ixi$u6R{LWjPZ^{BdXq<4#osE|0$UGy|=-Og~MvB=FY zGPEx9kM#FHDj>5?+}j^%d~HsoA*V-O#-|ZmOa!t4Y)d_S+XHDNR}yZ`EN%`9cY$(h&_PslQ!IuR4jnY>$zzF zq79ng7prZ*RxsIFblz%z<0`n}&oa!6KKX;?od(yYNy~r13bQ!o_u=T=GJgY(ck@HC zM!$OdTny{xQz7@fs+Cw4ALtphz;u0^kyZQL21yP3tzewhdolaGn#X6eW~o!VaW{Hu z@$`|Z!=wR4*#W+UT{m1Qg%f(O@zxA)Jvil~_>j!eRHWcSwG>~$|6YXtLmE>pMt{7} zhTAjaK_^}~ug%3K2^v`^UfQ;m%K8*yZ&kg%@tO~hHV@_p@qZ>Vq;NiX}_@$P1toezOw!3Dm-;=uw zIO#bMkC5Y+Ocm0i_h8y<({)VGYs3-fZZqakn;7sp73G^Ri zvpc&}82C7i^oIW}2Wj?Mu17QT%b|#-%eAga)`sb8p4aAH#DsHTr>eJo(0&UNzUl6; zR=TW}9faSu`Wpd}GhIS{S^PaeHgJIc$DCy|_XwugyH@eOxGxtyS|;^DqbwOFUt@zQEs#A^U>)#9o7YRhYdplyVe5 zlxK8)y5?jo;ZV-|@6olBA$$s}KN!uw`L9l63kH0-)oN;|H|xRi;FjFH&o5l>Y}MMF z{903zv5v>wEx5)O0R-*Ez2Ej=lX&Akdw&?4__0|d^>lb8Qg$Ws?K~z3cOON3T3jM@ ztc!n_mOFwKBpcVk0m&7o|7%?S%j9zKy0v{Tbvu42y2#7qbXB|Fa>^29*5rtTczEol z41*CENCNQN4{Q%&?NaEN_&O-w<=$si)=td?vf#Y6h7tK^QN5}jkKQKT{mgW~P|3Vb zHj-6*qmC3|JQV$( z2D;zCzaumAqRY@CD=~(1DQER~u!r?y!kiKcwwj$e*nmG&TKa z(l+zK0bep=d*-}7BRL;pNFW}nZU%PDykU$spQ{vZLBfnzlby*BRm~5_+W*fPkNbZ0 zJ<{`wrqg-({yJfwt0s@O5jv0%VG$+Qd|?&DC_A;5xR)T7Td(23u`w?3nJ;hKVkNXR zql1=Jb1^+}N9kQh4?AYDq@%|bgt-#&e1XpL;S#bd1ib>85;HknbRaROj%Q@^7{w0b z3l%>f*be$4W&+`h+Z6vu@INNZxRXAuYFeS?BQE*V_Y2)dG4mv z<9u-p12)|P-x4V#3%~m-w5vW6${`fi7=IqO7M0;cG8e(-w`YI8u>!iRh_(F;eeuUp zV@i0Mg*4^W0y+ytTSxZZWN@EW{uwt*ByufHl$-51UGsl&B zS0@$S)Xk7G&VFDkkp~4<-gCc-g2s2R-(2 zDMv`7=Fa~)BhyINAEkXmCKs2N{QIZ=%Srz~U5xv`O*6vF9klPsxyBgea_DN_wjJ4G zS}&{|Fhjbc^nJ<2Q;@XC_GcJ#hdw1q=r-YwBTyWHKK;Jd^0xjRy!thF%5CK^U6x9W zsC%ELr)bxyegbj^x=|tt6CWP#OG3Stp^Nc z*~J*rG=Up7qtTFy?~qdq1_E68@;|MUA&uIvCW#aK`}q2gdHvrXnv+AD$9#VASj=P8 z&%2wz$;ZKc63SsBuN}hiVjnOgf)RSei4Gyh)&1-x{~u-V8P?>wwE-%!0Y#-LMI<30 zB3qQEgqlbZkgC||AXN7&0g<+qrbmi^bO<0VNQr>b0t5(ELJ6Tm$Yh^0 z-yFZ0YtLNsCqMEh&--SrXRZ5Q_gd>TIGk|iiTB~>tzF>1C<8CuYcR`_w~;xRXN+&1 zqfz>ryr{gVht3^ z@g{B#lm@nMb}|diY}{Aee#^Dair*ce9QPV~_}c^if18)TGMha!pRAU=iP^AcE^5tC z4wrw1MFhBfJ}DPGK?#e(d5!am^6&z6{A}(5ds@y^BGQ+vy0!f?OTVIQ_{0{pAgR;z zxs&CT0yz)*4_eu4LV~Dyt~al!Q0S~eZGTPrdJPC#(iNxl4x!_2^D8DccYDtSXOt1R zqrn&*%&=q92Xldp#nHas@7}2!tjt2=%)9OXfB%eFTr;>~kyLQ7)`Z<(%E1oW%}B;O zVaE1b+B!`ZjKjyZP!~U+>*dx^I7T$T!OjK&ug5ty+-oJH(M zVuQh~o^HpUlL$EZ0(eD@uH2cAj#ZVLMqnx^G!l>xkb_)97Ul#@$Qk2UEgU?x-|@e_ z?95v%PZ99ieEx27Wa#{uJk-Qr*bSKQv+t{m4%{FJ*W`X-U%+^hTQpU@rX&&sZ-5}}ro7MJj7vWB+ z`k(Bk#(@j9+d8-z+ypi;+&OnB+hOZ(GC1Kcw-=gjjeOPo@>a9m-uK8@4cullKDX{ z<(}u{et-{pynW7NwqdI#LiCJ^|G1sq>Y`F>neeHbM6E0$R{L|23Hx3V)n#Ltj zRHwovCy47?$c&_CCZ|smlQFoN(WFXdbp@tvuTubE6Y3JWQM6hRY%cIM7kS{1aqF6) zg#m433AHN)UWE9l_~@8UC?0{39R63`Z%6!J(c25aZg-SY3fTDtd~?)`-CdGA^`1F& zz8`U&^Pa-+m1t?~uOL^2>zDLr^_=y?5`~A`I@5pD95&yybDZ3GS_R7yz0+Aq=?iSz zC=+QsCz?b-kPf#y5UR;S3p`tA`uXl4Rz-oitR!*)&wDYg48}jp0$&>)ozM1T&5r1U z)kl=!v!-qu7U`ttzujDYFK*qzxgGnc^Qt*3qIqM^g=U3T?`7T_-977_x(4b_%kenb zy%ji1P5nM3;k6+CLVgR@RK*MAtL1e{$Zse<`{bAG0j*E#Q+9Sl^M0m%XI~xWZLIY> zvYnRl!Ipha0eQdp<*{g+MR+nADR~DM2EPLI0E2|34EVuX}s}bC0pZ zHPuL${XHhdjbpUGH(QH++-p}mz&N@*AZeHINg?2}M#1=(z*dS&s79Vq?Gln=Jh*GI zBkKgV&GjFqYt9(_P~9O88|5xbZa787BcsP_HpS99!#mTd2H9nx+kb*t$>W$Z8cDjr zcuCyt9t7j6U6lXFLs>C-%8HkjYgm0>9+&VEm6&Q#4~?qk4>VY7X~=Qj{5~lJV804a zJy^s+vSdT^=6Zb$0M6on27GA3d|tbzC*?j75%M6dM4gT6zJbyjdaXQ%dK=DLA;gjv z4BI;WF&WRLLdojv3tXs2szP%!atwL+mi^YrK_th?(qYSQ9a^3{T(K` zryn`qkzBs%Bf)=bGnO_&o$m{Rn8Q0eIf3w_@KSGiKrGGT`U1O^Tu>txHmU{i0 zf&*37K#h@GT(S6daW{>f+HRddo_QWV9*rMZ5~*OR7jDO;3|L)w2O0P(kS%YrGg(jQ z(P3@p9H@K1Uc$|0fcyK!6P}M^K|~uNsq@~}l2fPsJ7Pq_eJbVP6nO1`f07?z4-({zJk>?+4_#*0s}U_Y|Ie14$ahQou!^;7rzVYM+dE8}8&DTD zj-g8i&HN2JRm1Tg)~B!GzV#Aq4(=?~nlTUMO_&DkFi|k(CfsVF#1^>9LwxpHsTN`L zR9(x*$Anyvd4P+GHzFH*jwb+wHga_Q;CSj8!yNxc0+jlCNI#k zd*?=z}(CL z?^N{+c;UI66)_A+iJzYqclmXHWKU{K^JB7BF`3 z5jJ}pokW|zAW%MQ6@Ns)#k;uvZ9Xbj*+$`RVr0baV{CnP>{r9<8*ctkjdjoGk>UO* zRz~%^vu%h`93ji%pIa}G+J{M9JczeBY&&ZwVOKoc3*R)RB-QBr&~@F6fMM2oru&r!N%8N<^>@fuBfwXq;kn!JtGzS>zmZXD%$#Pm(4U-<)abZ2 zBG65Sy^;6)#*nxkr~PLS>){qkgH|5z5?fcmTY{)T^#b#!Cz07=Z2 z{xG9SAMDNiF6FjX(7@6g{0WOWl-6Tdw`ROAPMIYORa?dG@8ELgs+#6S+cQMJ&G7Eg z#MUfOo38E*#|NG^vard2w9RotuJN0m`{;w=Cd~eL6Ek!vZF0k&m61m3gx#-cm`2cJ zdps(+Vt7)*uPLDj}T1JnoV0Q%kGP~ zwf$OgtD=|sL7nfoLa1}3u@oo;bX=mK&_}rtE8@G14T9xYpV;U(w9?c8Dc_o|i~otFsdjjXRls@8I4RvI5L0V-~m@Cp{60yVx?^tHE108|FII${3w0cb!_ z3Il5^#)@ot%9LSMiZ#oay?nWPz@Ux?2hWAl=YQq-4Xwl;8<6x)sh*Ec_Z=$Kg#W0w z^_n6kXWuZvp{d#|Ytjtnm_{&CXGEvv8cp1En(JQXWhnLeI_|ogd#-*sBrV*y%BzOV z77gz1j24|;h}-&icv=S>ntz8MSuZhW2BAas6os8KtAUwW*aQaH3_m&2V*R)`Hr; zIpA&mP;CkDZ7%#;ur2wfocI0J3l$SpsUPvM)u5iBqabP>9)Pscq*I}~B@Hq?TP3e) z`$Jo{PQxBB;Lz6Ao0Wa5qdL6i>h~?H;W~UaQgE~|ZZx$K-8huCZ#7?+eH3%IgyE1| z4N!&-hU6Kn0w-&K4Ky=l1}v+mvn$R-kxr&&yals!1`F*VV+*wWqa0V z;9YXmo~lO{kVJ9a6Up!nzFYc_sW-Qz*Up4LJPMbi%-A5Y3Pv^el?>v!rxm1x_<)T+ z5tp_5z@{C&=Y>Ej7iIyk@Vo#prGr}>yu(xmTPnYl;T}YM?AeL(Cc4_l4jX;M=(<0E zN~o1AXXI2(@{0QN0TMwuTVH|;!Hq`?g`FDn3Kb_Azgw%$OBRaZIbWV)&F*=EJylLM zMsl()oGVN`%xemyX|9bC^fgK9kKj!QYwwG=Q}~`sAdeJp>JrIa$`*-ffj+`ItNN%m z#M%C7-i9ctG9KChF+J%f-o9_+@b&Ee!li1LGLnY!=Pn!NT^TsJ{|jRte%*V|@G(J_ zzSB#4$#3eufB zO|esLfU360Sdg`A{ms~GVq=?)*-G}N{vns5N-;KPh z!)gA50;nz`Sq=GR#S#{bh6aLKb9$edq1vEoOG+I9@V82wcsG;JcU)sColewZ%mG!F z-BWku#87$3o)F-x^UBF*puthZl4)VA1a$jKg{xzdX>eQ#{Zi0wnGxKFHVgnk;neQE z7ILPj`&21yxy=v1E*BhP7bps)6Y|u9y7I$3=WZJwTIC208D+SOW!djnU{Dbg!Q}eg zE2eo<4v?k_{axw>Ks|r$^6oogHm~T=)#WFUiS_EUzm`V^RNplJ`paZ zm6kJ(!!T-1rOLF(mCaTa`~Xn&z5N|9>!;0K&&2Y@w8-UG6%~VTTXz=;$!;sGa+{+*xx#bX(o2Kqvb6gJ<-;ijJt_!adPb48~h;a(Ro^5$?Vcj>|z$P6mnrcRNL3~TRK(c*RuD& zQ_jp~7PAbjB<`F7zR5-skt~;4CC3NX2~i7FcYdQ!NM}QeJ3Fegu)ZHy2Zrf33kjd( zJDzwe`YG?g35oDn3y=uTV(LkKBz0c=+y}{@S0t5q#!|*MC2xpNHZU38t%17X&~;A1 zukRGuBW{yc&3Lv)BadxwSIcdTTvQCrQM+w0A{`^P7SQR)9m*=Df2DL{sM>g~{qICm zCqCuf+nn~iGE?Es-bBq9KwHSjCW}?*k%~Mcub3w5G-4L9?WW<>H9H`a(RtaaCsOWU z(cE6Z$Pc6o8Q_fNGvWm=#`6q?Lc9rX2NYn_$djzn?fU1ACkhV#CKv1%=gbJg?ghXI zAA0nWG<0vIz!p43>P+tWEZF*JB9CLaEH`?pW8wNj>;7mu=zOJ*-jL*O5VjcBKs`I4I zL&?E9nsozRdZX8ko9wJ0u?-F4wQ^o+U>6Tsky8!YyQKF%##m*HXoi|`#JzEp!!A|H zy*`|Un;#+{xfGZ4c>75K;^O*>asss6B}lbxsg;L|wU+ssQb**yKFSCYQsMc_F%_N! zQ)$&PZZ%`6TuF71t&f8~YmMC5CDX?qnY(AK-%^f1(RT@dWv*OWZP zsgH2(2^84l!mTl}9FR3(gyPUDtLFr1j}F>x{*?>yD@I>#S{35H%OTXg^D5umnm!H{XfC;3!gm(OLZcbhHa;Glg3;Mu<7*A^hUbmn>eaWWIK<@vJOBA+TQWfo8Ymnu{375*=8N?gKW3iPVMO?Don89Y zip}Gue95RcrSFuPgvs4?me3ZzoS>!bt;_XC)VWCh;H6usxAs8`6Ki&^3B7s@pA{pj zuAn@R`{Jpf9r3fDfgIFRe8-<~WJg(3FJH|AdVfi7bshG> zFWYvjYs9y4CS<)Uonz*fF|(;f;b{#TjOUFVv0cZi`p-4MZ5@Gt|(}GRhchZ z+%?PO>@5I)L0sn>suVG%G#9f zD3ZEZe6?}5)5tF}r@buMbxy|+hwh?$;?Xq&;Q3=sWR zP=)soN}P>{PWN|N$#i{|TS^JdS4*cxifWA9ick(JqC7iFOk=n)snoDB=iajZW)b{s1XVEZhT4E%0G`S*nMb&7@zFT08DpY}b&x1+AS1jHnsyf*M=?~kyyS_Gb$es;^((^BwnYJB-`N zvFlH8#cv>(8+x4x&B^tEL%+@+g-glIAKZm&0bNkhoy3wEJ#%-D-!ULk!b=N4%o+T3 zmW64iBE+YR%<#M7#^Rjf=L6ovt1P?=nEY|>>+z&8YD*9xHJHvHdW&!&u`Buqnp4M| zYxMLp@w`)%1mQkILkFNDTPT-$qVdRD2t@T;!WF`#sXI9G;-wH?sfM3-M4>HO^GP~Xmt+*im0TK*5^1?IE2S1^q?~OsqIs{rt#JJOoIN3P7xhLG`a?efdL5i|G%h;uT^F-iV z0+K_>6`;rv2Z!@uSS-7e$I#$zktQ=~^ok3*cgOVxyiy6H-V&mD_{!tgrg(F6LeuA) zeK&)D#<_84%|?l&uLS0P6xO_!E}7VFR@Y_M8FkclcEVlT;+vIZx%j&k!wya}nqzMp z&hKO@Oj`t7J~VWlrjuqYsK_hv8I8Z1-g!i|g+0AC(rboy1`f6hDzpjWm)#_7lT;NM z9i;*XJ)jKJqpl8D+ubzb7_9sF98j}QHLpBjd}!a(M-$P0)b!oUI-p~ zC8}`lAWLK!`6Iv$2<-(wR$CrxZo7D3BUn9lDJU~N@is<&4rm`2XEzc^`cyIhrAD4y z>u6TYbY1_62PJz3q6ObbTf0^LOMNhNf+T}@>s`-$Ub?f_VY>Y_+}3b}*13O4d3)*n zRa7~^QE}nA?BZ#_!RA_WuqJ6VN!TTKq{4M3lyP_-Y$!u(@ppRdJ(MWx=HFMPbfD~m z4xlcPMHLRzn-phU2(_?9+er@jl1}^);|V#X*YdN);6L*_lU%6T8rAsST~)cD>CJ8b zrY%+pzM%#&UV)%sc~5p0zRjrsKpi#7!B!>Uovn`B1e`uR;73(pdv5hD!&S~}&mN>Q z){jHvsBEAxXbm`kMuebbS&#ojP%U0lBClhe`S zd7Mrocl_|(>7RfhUv1dK6F41a$As@R)%nr+>Ku3(hIyx)-(K2x0FICFCY`X4bK^Ok za>u86mX#lPDV#M0VT56fkz2~|!&J*spe-o?KMAeRb9W_L0~BTo)7I+YAcxQC#1^yY zyWR1Ng~?*;dDii^n_taRd)`Sq>zCL2F3AB2C;D{ba z(~_Xlx1#hwAF1X0P?(_&3CY#x&E>L55*S?@6_}5@N8{I6R63IFx)?CZGuzx@c6()6>U-jJE!_UFcH73~cxwTVu#^gE~5aUQd87s%?4 zQ$;Y*^x=wd9aN}&UlFx*suNq)YMJSr$&hj-@SZ?jiZol$B$b>-L2o#&hMoA7zNz9B zm?Nqu49<&eaFEoQ=iRi&8AycJk z#A`}bm$u)symu1Gk;QSG2vHA`l zc?ftFEF8c2u4Li_(gW~3_`@nlRpJGPowFG8E3vv!t1zZ@hlX?sDWR zI@*SkD63K97PA)QBPk`WnY}%geR?}GR!7ME7{8F}q4sw!z_(pjfcnzUj-3(F+73dJ zf>*N6q9X8PC=UqrJ!H2F18Y%6qjU}LK7QK@rR1go6#48DED~s}p=2}) za;=@Y_^@2*O@|%6A{r9ZmIsq!cvA(g(lrxPHmIVAMDUG)>9=PgE}A1mo?Ti9oW>F$ zO?xk{{Iy;!IK+G8d}ni1-~DAjM0P3i4^2auEN9{Eu`Xg;its&Y(wNMiL24Ujq#YXY z6<10lUKkOe`KAXVA$wnW;F}aq_YvZw^ohO`uuCFvk7-zOZ(=AYr!A`5@`Br;UxTcY zDW-(>SUS>^KB%8|Gj;kbz3qCvlM-X!KkH^kr<+2-_0i>-Z?%$D4hbW7v%zJNvTo_% z#l(+sJkj^cX5SuWDtzv<*zUwvI6OUVTqG?^=u3ZJg3IYx63X2U=rbL5|G@!;d7dGR zq>Xqspb3wI2Q-#D&m!L2l!`QM!q@n!X#S+f9xZVY%E;|_cU;Sxm01q3N=&Omrm)lr z!F8}+W>T%GbMuQes`TT6;e8A1XnV`t{JGsh3yQ-DO3_K<4dphE#?A`3P23sz7m7qVTHZS)j0A4?O4xL|*Te&%}fOtLXH< za1l$JcP6AWUetoQaTBVpxkf)C_%I_=MK3`*ui?*(BF5FzOVe^nhagQ`i1EK_XFU?a zqym%HRaPs6oyW~Dq5%^FqvPZEde{lIHoxeaZ~GMl#3LQ&`iYj3T79FM6F<_9nhqD3 zzcvT3>nr+oRx-aklAgmdVk+*DJ(?A$#{c9Jmn8r&!7o;ysFiDRNkttSLp{-c^xdsU znP;rB)((GnWoIlbfM>3k?vQ=4OJ`3bR{#1f&sz>7rX8ZoA(~lS0v+E#o8lVo0`f!N zGcZGR6d&NI0w_Zu0wvFZ^b>l)lsN9COHO?9BjZ`j`y}`4r zA>qfn!cFN@)6)4lTSb2`6K)({Qqq1BB`QB7oppz*^WN_NRP3WRi zeP9%J((B_7*G3oR@{@Rw_HA;VGmj{C?y+DJ7q%x%44bIytiaD7vA3>}I^pw4@^!js z(J+jFU5?9?9K33I_08U$$2IE;WhYLQH-_++MVdnB0(YV(X1{VFoYjjdIuja-gdKsY zo$-nh+3bpKAFIfNRqM&R^qcbn2TPG3HT|yiOC@(p?^<$Vz9yS8T5{yvDLmFoT4T{3 z?nBva0Q5|h@y1fhJzzD#ux&1mNxUyKwRUjZo( zV##RkK1XY7ej78RSru$1v+umy+tX0A_qMKcS#lg4L#K(RE`^2&yCe&Tr0kKR?YGO6 zLUTG=d-r4bsy>J0l!cv!U0%r4vAT41xqL{cRH+43#;G$hyY`|ywpMYUgmbxC}HLA=UTOB3e|;LWnW`1)P3kXy=ty<6mGGV}y-?JIwAMzcL(w~V~V zsGsfc70g$~7lFjvTzMF15e*|NTz8rQM*ODu0BA`3ggfcDfQ7h|uv|q~4M7)h7YvLJ zlg$$u?vR|h)A5Hu_^SmCx0Z9t1}y)bzOt98ILofwd@Wp`;gy(R!h14jujg6Nuls4s z`EGlcTGg&raQt$XX#FfuHN;lts4f!opuLaxO02X%VnS2YbuNYa`Q%QsfC_8bmdMyC z;Vjgt^vI^67sl+DMkIR6kijL&N8!f=0-eRHTI_n@Y-iFSOsY8ynb_SC*e;xuD{qG7eJZnf_%kB5IbvSlBVCe1b@W1X|wqhSGWkyrX< z?55nB2HJU6er=ZS#KI1AnueACRe9%FDR6El!5}8ID_#d>z%*+du9ad3)FvS1{PZmQx%=G}^B>s5^rNQa^-Ri!pF|FiWs`4HqdNp!QRqkv8n^s43g&{(z7kpBhwlD$?*5_CxAcrA zx03d#8aLvz=(7try7|NfRi&RiQswQ16&E5)5r%PzJ5k_ zmv&8|>oR0leU9pz`n2Bir0L-cP4$udFW@g?hS37SQp}RitVM$~rpD61_k+(N3#}2W z-IAEq3x0vH=9IGRvDFJTwrW!G#N1M+f2=;S>b=rT0=;0kX@;|)X~)-IMF2=w8jgzh zY_IWG^nBL4L)h#cMz+=v6+QiI)q*G#aUIXa_|=f&?(4lykIN$7(CI!G}$+Pas5r_PMhOzy23m`E z(-!sP6Mj}VtzXOC#&$fyq!Z!cC);As>}=a2#BE+f2Z4)278@Z=he`Q8VDR=^n5_3p zi%bj20!o@@r}-9kiQy*VUH9jey<+EBnnq`3#dr zTl9idLI-zus^{{gTeR^WEk* z@>ww?DQKq!I|3TJ%zoJEXEj4fIsG()W)^r)z`i1W-MK!mr*5ffQfAl1)GG1Tv-MD(C=W3>}&6CRW0_fn5%9f0lk4OfH$wq6bOY57c5oR64bhGT}I;BV`52< zt%NrAOn|agzHf{s>n1eZt}>{sz21ADhdz-l*Wesqb2X*1_nrdP)+h__kQv4c@k-Gc zCt|&)&jv_l`F1@1)17sMYxrhV!=LJ$dsiPFYQAVKVQ+1Fp~9t1?IOR8bYxP?UF@y5 z%M`1cWw%xWTh1%bJL6<3TO`~>nZ)E(K-qpG@gmbzn1*qgdbz8T42bN}-z70;`>Xa2 zyaBah*8h}~kxqu-m^sjAK@(T;*vk;RT5JM0=SQ#aB8<>2R`*_RCV5-Mb0|R6DGJ9u z-A_OeT~IY?k|Ox4*LFe#Lvu5tOBR&35KXR_!Q84Rts6u7I}zsKh*_h-Px$G|!9wVo z9M!!4cOlZa7kh`(=YNNz<|VFK?gTkgz^de@XF7tnrno+S4ckgc;l0KCFqS@L!(y<2 zEhn_v!nPAu7HO9+&$6);e4~kXpzk@fPY%3^q<576aqq;v*#_O{k>1Og{^-9baWV2L zuY0s^a6Qwp&G^!x1+C{j?J)axB^@{u61YG2MMQLO`KS_=cRpNSBsJ+~2*~`@1VL0@ zl6&6#m)C8J5c|pUXU0Re>8jm5?Rs?HUG29Zxi)T?gmO{$vo;Zx<}npt?^%bFS26wV zJov#Al61w#q!QdJFrxm{y#^-28P&-Vj9>(EscKoX6R|Spgab#A~jri@ooj(W1NdseD z(u8jac@C*7NIa@$SmntH3wQe?yM%aJq$yQKYJ}H6{fp0&i;TQZO~S+u=W>)4V-@Ub zjpnPw!24==3pwQy0pJ(9b&tk_lh^z1!{&>Vxatpvz{<@kuoMeJ@-r!{-(K+KRu`bx zs-ktDe*5$)iQXZ3dY^P0>GrDP_JJk8W*@g&6+*&EtZ-Je)n2W~B~7!yOz;HEen|Pv zLgp>@FVb<6IM0Q=rV!LL{n<%;pV&c~*wDI0bXL8*V=lg8fwKy}*V2&Zihs7;I+e%@Uv?ex(vDDnbw6`(+` z!HEUnurLkz?de1MP=*R>H!4@3th5_Cx}Xp@3g!+-GZ>S$Mgk>R5UE{^&?`% zob_iryVK-Mn41}RxBXchGdAnbF^k{4DkG#hpzY_sv5$%i`T%6lWD<_uKfWsJ1d;RNVUisNe=#FJ0yPc2AVEv{#Re95)%P)KQw9^su zEgix&5=wtc!T!8mpq9QK-&@jkJN|B+owOoB2cX4|FaeyNc180&(N?h}PCRs5#-Fow zf0rC5Up53fB_=^?W{d9B$S*sLwo&}alRxEny@{a3@7P(w&aDz<;*=QMR(e{AcBb0H zrrM?bvC1oxjVj*@Ct{|~7wOp_( z#!hj!$KO{*IVU2&?DLKI({8#3>igUIl~@#ypkcZgP0me}BoMQ#DL^LJbblK`D~~Vw zRnb7Bm9oV~44#LfiL4rc)Rn9VZkQ%2-Um8f={gL*juTE4l9nKul-%du%my<;# z)s6c2Nt-S6&jqZ9qBWv>NoXVDD5MJkQCHec@eKo!SzRthVd;J~7CJQss)d;?LpPPL zH-ZFCRjmPi;IlH_rtHo!?&@;qR%7dY`!_{>> zH^@BRjVBCtoaS$Mu3scCixeNp@z{3GMJzro+D0^SX>F85+Gg7wyaJnI=P8hz16IFl z8MVc|UKo0VsGs;)c-V%QeO^wJ!krK~1z7I}6a+7>foeFH_BD3(uiRQsNIZ3kPE4BT zoDSS+C5=SmZ}0!aCW$c@ow;R~_=9KWZiI4>6o%UAv!p@@#`RA{nf7Y&d0NZU5~v-@L91S=+PhCDqTLFrDQ7(QR=6L z4PG zETjW0l{o|4>kuwQdx$cVh9TL*1*B&h7oBBbQh?&+k~g+`c9~{X(t7QUH`Az63o$*V zz87UBQ_WUXal2WBh7xYF4k8pBMt*aSy6Sy~6E)@#3-J!w3(3jKPwuIfm>3;O_c_PQ0nbYE4% z&Akq@?FE~Pjv-AquxzHsjFZ^la*cxDL^W}(p7YLj+0JQO3faLk!L9Eu*cK2xzpACt zxXd@R5{u8l*;1+{d~4 zoMX(EZ!cI5ojUHS8K3D3iLv#1XLh#i8M$H0!dC_#9$PNwn%d7@;I0g9>)Ol@hwl$V zl1%JTJ-aIdYC*xj&^y=i1Im~qZ@w7mjV)S84@Wn7G!^GUWMIOwJnbh{J6gmQbFqn4 z%w$bM13Z{?qj9H^``yW<$ZO69_0IX%<`lg}M46jnTn`kJtAnKUg^ib7$YI>!_0N2Mo>?J=;tT?ES z-)c1IQhSwLjrT1RO;m8RrhbX9cXY5nsO_twRWD3a$VNP(tRYbfJ|1MR9prGD{y#=! zmXKU;Y7aZ}a?l^YnPb9Sm$Fx6#p4DVVm#OeGa2+_;x*FD?cBfUb64_>0?*HS2W}w8 z|LQ{jHD}j5AB2bIK{ilLUbYZ37K1vs-HYd;Qq!_}@*rY}XA<{$vw6 zAA*uO#dl1mS%6xM<5a0KZr;ATC9ofo{o3rspDd+uhht*m4 zT*sK^g7c)0>O@q1GBLPD)yk9H2`WJQJ+oi1RjXdG$}LK!MO|A@({a!mnr;m|{EL$V zw3EQez1(QBo||(W=6F%GM+n^i$#cAlQIFF2A!tj(U6-jF@e>rIU32P^GI|6xdWG{z z^1reKj?elv?|I<;6i@3aLk|ZyZ4OG-+ZCem&#Ez85Ca_H(d7H*XOFe^5x`SZMefl01HBbGzhH#wN9>}~7G1XrwtAjxS)D~Hv7XcwiN*jOc{?2*>NLA2m0<2o3+K@ z-<>mFT(b7Dg5ED986eky53A}4Rkpe{tWol{xO$#PzL7`;v8_8#s}hmToJ+jVt}v--%a^L>A~ z{Z+=)w zKL@|KliYD8|99Hl#_R9VI8)pY=&zb@v$#3MFB-Z#$HW5vh$fssvg>#ZhdnjmFlV*@ zeJ1dLj_)0lX-QK(Njuik8WvOqLW^JR0&GZLv4NdBc}cDG;bh!fxX08LqCJKaGUgC| z>4d#=BnsO*1}K*kE^;aDTrQOJj!SOi+;~p-m{_rCH{s^f@0Mjt54o<(Gs?n18Q*oz z*3VUp)ef&Dd{gpsyIKjyY? zROhmAzO}}y`)!qN&mAA2#$w8PcULse9aL}C4pnd8JV;z|L|YGC7vXs*8XHCj0sH=y z11F3*k1cFh&${2*(A%f4of@sw^1)2rv1f3pbpFW}0(uxkUT{zyfU#am03RDzwx)JM)Y5g8V_ysaAIv8q; z+XQZ03iva5m{Q#G_MEm{aKfFU#8?|X)#ciS;=z}(ZJhZqi1>o|Om<41Kq#lWl=n!n z#-}Xj^?UQ=ch0h;q3!@~&8H(IEmjpV|mQKB7} z%czk1E3Bh7KWsjs6<>+%j=})6n}YyWgE~mKPfmbs=Tk?AVZfw|D0H~&{dJGEu>7sv zL9%LFr9U1CDou}k8d@Pa8W&P+TW=A7Wq z)yctY+5t8>9xb3UNla)6a%Agg!9nCT;6j8@VyrI$gVMxH88p>?WX6=cJ;(-hNHtpjGWq#+g;^ZV z>>2V5rR_rnlQLNn>AqGsqIw@1At zNu!;QT*@n7ME0Z=^~QZ!e)q$-80Ls?lsVGba4D@6g7;k*S!p$?SBSpnls`yS?6l@c zMD|KWVzAt-86SvmB`fDfRXJ1cEOl?y+2l-YUuqhK@r+$Pl4QHojmDR_;By-N*6=@0~@Nc(IQ=@3K=9Skji6zLF((u)d$NDWBuJ#+zuP(v@$dqN2}esk}v zZ)Wby{O-E{Br7ZDob{e__TKNa+q3n~JgSLWD*J|fkS~;ZotxNxOs zm+!W_irXLw*gIb}71^0wE-39ONyYC?xT}y4>F3{u-Y2>En(fpmiC=ym#$|4pHm}~J zPM;3@%T>)hiMp2t{uV)33vcz|K0-Hm4FR>ilZ-i{#h zdVX^!!VDSisuHXc5cFQ={*5s;-Uzi?6B4pj$YXUaUeD2wKq4;Qbiic1(2$z1S@WBm zANI;@T*jmH6&hL`2oJ=`vOpSAaqo|G9byIR0>xXP?mk+_CYl%}eA@xe$8pFwl;5qN zs=iaq@?aeC;2`!2Og)omUOCMUMmK}aTx(Vf_)rOv$QywHN!A7(qsK|a5-%((q|4Ry z6RibHChQ!1_5{$VFL`6EXPA_G{(HrI0czzx0$*;GVr7bAv-T?DHftFUxi+)C4CX{E zhjLR|F6Jx*CeD`CdS$&=cBxHli0ecG{pPEI%s{a$iIWC7+PZ0JQKkNZR9+*7g3?~v z+##kkz4OF zAZvE@aKv}aza)~a<$Nb-;p*PdNlmOQ)7rP3%5gEY60(xxGCbUz@GzW%bHuU;-|Y04 zAo5?{GQJ^}EU4^qx-50)YVUH4`f`t&f@-O@SJIhZ^t3?wSXN<2V$k8Pj>kfNOuY@v zQLJG2TsQ~RwAJ5YyR-RMLIdY}czLP%o4jBFvcqb)!x$y-je{GisWIv_qmd-%q+?|1Qzj#j9AYm$bS=%#^_Ry_BH1L zK_10m9_C1yiec3#?wn%4@j87546R@OhA=UEe>@~^T|IB1-=WsAzbShHlDWWzV5z=% zI6Ybk`*rw29u!@o3){M_ldMY}Lznw&YRm4xird*55?hlbu}GTC^{&l5WoWyu+YufM zpSJaIGRO03F8&g!m%MtB;`p|(k>5D-zO0b7uL2*W9?14=QTW z)e1hZ*9=<~C>q=(eu%3G+SzJ4M9SYyw!}37KpR1p$h|#}LHjkS_FUNQ`)Bb(uPyh2 zyl_e#!xvfRaHSF|;e+K)jV4dx7ZIi7~WbX+Zl=j$xt25Tv$oEI@y zx*@PVN;a85jdV~?53J7N{1fnOaic5n)N~d14|Gk2>Q*FdONO4gzp1ob8+hJ`BLe8? zg7r!1*$2+!R=EO6T_aeGF3WM!*QAz-y)lCN>Ni7)jk#Pny*Pb27aP3*bo1fgt=VV5 zP8HI5)VvfX<}4Yx3B4)E!B(36W~N-<-Do@Eqfg}LI)RLq%MSD zi1_0|3O*?Qfl&-5f%E0_9Zte@Iz!uf?HAn`I0-6jcF~n5~()K&ou*K<@^`98DiTepo7X%&-%D*wff{o zj7v#b7f~+dp=rD)3okE&;ji@`nyy;h%E4MSp}vagY=M`2b3Plicr#eS7zoc|hEqEO z7ke`^9c+HWY`f}e0l8C|jH|rPNJgOh5L3mveCS6M55zy;E@v|QA}N#E(?s*KT#NM( zSSylC9XPt7sNwsq$Ac~z2tV@ocl1wT=_2xXft0nRR)l4F)ZU;h9_U#w3WRWOc^ zCkCKKoU+iDJ2w=Vw2lyOUE1tpi%zTntic&omDQ_utZ2+Hm&B%Bif}bg(W;{Ji@o@B zKee^pW{=K=y-jPvHDMl&HHztdnbW>I{{dhRg}2J!E?w}fE}#QoAb3W6YDP+)R>GNm zg@5$Xw7Y>r^ADvRuFEVSHW4dOX{7cK#jv8`t*bOsqV`^tcy7jd28~w2^F#hTB3|;) zYt$PwG>WP4V#N#1QWbUMq-#S(n}!_JN%I6UE_tfHD!1-?l9CaRTz^E8jik~NLn?BO zy_BI|g>aL?tQ-}&$=ME|6~tPsM;Qh(v=r_$)97Y^XmOz~gKDG%ZF{&J7imQYi(t-v zjBSNp_iY2vDCzlRcOZI`1{{D*VXUB1BIW4*#R%dO<_h8DZ3|@uYr5R^J4j;WSVyI( z`jV}Z3cLP{J3`9HFAN3x0!-F}@rgR~i|%g!GiUXNDqHw~vvK7_y$K_}ee}6hz!ZG^ zVN4tC4&*UsMo5x;&dRs8p8B^KEs~~1&OEc!wjSeJgaEIS#Ane89`4bdd*bEy{p#uvmP8ejsPt z6tgyB8V0u>vC#N8#Q@sj%fWS*KZ( z`spfb*G$|l+$|BO7Fmi?F&I*ZsA~+3W&&XqE}0Ibjxf3clZJKqyp){raVC$SlWm%U z_HQ6eC>|JPz}9GwML?z6^jJ2uv4&B*Ah06HOybU=W%U)lm3Caf17F zr88~Ap;rPs6j_+mYcJ=97-&2j?#z(-d0XD4lgho&*L9W7w3A)on?g~iHf_dytIOoQ zG;kZx-W2RWmoj96IzY?2^yn?;6ot-y4|LcP06F4P62XzR%RKnF+dqXbmc>+i^Vb*jaXG%eq2)M7F9&Mldod`&+zD%=G)>$|};0(dQ#N?5g@&637_R zyId%y9u0MVmn_w8aX7m)cUbX6w(~MoeKDv#_Lv$=JV`k6eFh(h8%dJ2Aw<)_0BXFr zj_V5v+gtKFpPsxZ!sKzh0JmnrCOq5d3cI{FJD^1DOPyzf`g>sVjAEXxN%+hSsm);^ z)Rf%+LN$FsftwN??=yct%q^UDW`q1=jP9m-wsASt_*?yh$&YM9TecpaY@@kw^JT7> zG&Q)0ST8uF_7=pk#*j4%4%E+7xeKc2-c;cWN zF&{e;Od_|~&Lp472GOPNRo3C~KaximI}EX4BF)dmn=5+d%Y*dJe$eTF9Zbtud~k8k z8BxYEwgVsGRGSZT5O`vt5@l}bGqTMF4MoZXkF3FzuuG5W*2>PFN_DNCf0D$%<3TGK zg0hQWyXndoAE^yo&LU+7WvpgGCiOF>Qu=(N zx85v&a8`o#zCipk(mg50q=AqPc{MNRQYoSO76DKQW8qNF-VGm|wul!ERPeFHYB z7V!cMZo&wggv`Hi+?u1$=#RNUUduvV1_%hY#&@N-&ufWMl+c5MonW*Ay(782JoUvH z#xx-p@spgUK*8^dsKOzVNNN7sSHpGb7@H8p8MlEct$9Ss7AFZ}Y$}!G;#0JLVgSNXBnJgF z+>WjiJxUYa)zR`c@adE6wg{!GTkf=kzKq799_EOIVf@oe&81U&0_%SqdUf0b*=oWD z&t9(OKn}N#EBS4+lpbPCS$a+KMG+ie1rO);t4p^j4XPl?J(7 zE{s6!jvOHWf$hzg$l`c$0`8~Cksa`>07q}JR4Y-<2l1fZ+tE~V^zDEIy!4&p@M{}? zWAWGGh4BYgiZ`A9))k}P8sbFVp|pJBYTleqG6yLHYgqj9%sl3*Cplx^V<|hbvWTKs zCg#nK|2f(cBaaqf+s_#+6fWaS5GDz~S2~q&H!r4Fb{k>K>~_(ce|($S9tREPARzxl zI672z(Wgp5kwDW+fVI}+G?~6Hc0kBYc|c<0K+4Hw>bU9YR9r>srZxLqJHivm;N!t4 z*Y-v^O>av)N5q3h9hHCp7xKies}Ya5IEx#u33IIXroXZe*lt0bbw`OE-JOhSZAt#Cb<(&`0=1ED%578A*VSmN^~GcD3X5PY{efiDi}TK%956O5p!i9bEkiA-}+v>GIq&=TX=zQ1JW^bo9m zlEHDlAfmrbE*Z}-_!%Uter3}Y0@_Z%$0Fs%8W-pb-?7X0MwN*hu$Bi=+5Xr#saY(x z-Yp96#okFyxRI@&M?!ktYx!JH61GgYjdZ4DW95ZLvfR z0#ipRHQl-j0A5wS-EAauu2v15%7IUU(<&7p^aIGeXjTtwhl|A5A$xfq1&PX+vgd2M zuB_0Zyq=GTqw&#uvTJUat>5n({WY2Rk3D0pRa|=pmS;^%7DM6rIoPzENx}6(1D{_< zS!HLX8+5q!L$8yXzmhH22oWqL{OyACOxHUJsOOc$Pxofj+^L&?(%e+M1KK6LQv5?x zWzPz&2Bn^$X;yp*GO!^$M+y2^v1jyB2yzjD<@z~F37ke(6mL_=^~8KM3N2S<9JW{^2p@L*@kL_N-$@~6mnBN*{yBvHaFq^ zbvbo(^SdeufrF~Tr0Bc1bSEqINBW(WM;Xz<{l1DV;1kXmWOoSrP!zANGs?&R*{%!Y~ndzo~()^Jil7l<48>HUQG4qw;^;$b*ttQC(|Rg?Zty+DP-mnAZVcOhYocN-I|(P0Kw%$w96V{|E)} z14icYw5*dxk$kmlJQGZE-es@%^>h|7z~8N@@6buD-WI09esreDK)Mf}Y=xW4 zPn@|x*`SLmg7(AF=1gFCxs-o*7Z8O!f4n%9*~yk^KFu>u`J!RbDraD9%9c8<0%B)l zms08kGCO=Ugo%k%ghtEYP%Jj;6cH1hU!-djlKicy%&HLDAd|_Csfoyd%kf^H=p!Vt zAD6o9Ql;{*<>wvici|Kj8~kIqAH%CoSzz9TF6pH~I>y9%6oROGmk^d1^IftN1xA!x zHn{OpBbmt5A+`!~7A%v2&D(yJ=z|Z`r*$$Y%a(QR;xjF4ihMrU2CleFO*}bHOTw>6 z(B?H4+47{pv&3(vUijVt5y4IS(tSwU>K@!>wT*7>VjQ0_%b`by$D*(i0yXwfZS8oKn`c3bBUJ3I1v7-FFa>u9ph}`82 zzJwdDz5e6Rq7=#TyWi9gbuF z!~?r%1JyQ0CP-5sZrqQM-6ZrpglU`FJJDtU;uJGAztkmnmiq0QYdu2pB}yx2_Ke%D z9*`MTBiI&*1h1Dy7)dH7T5VC9_N|A5+PiLhfr*He+yOku4hV7e?R6qVj*5G^_N|S2Ih4WMlYj#%f-v10JQ?AEl*fBKLvNByK!v%s zrX1gA^}afbLro!b@Hyk>h|_Ta3dde~=(o5OGq zk!<6@#>iZjCk35saXglO00p<~c}>728{#C?}ud_zmUlo%9z(B_SxU$6j(EGd z&%g1YW5?j6ayH{O_U78L3D~fFHKVBujbbZwti}zQzP7g=k5WWwua-;*+Dox@^Nts@ zp*L)J(hBk=RwagSUyXa?j9aeUP)f_@>}SPEIo|>>5raLkCk;JQe`U@6C%HD`AZlKF z22#WmipgBg3}4QO>`e|g*`J+)usPlSS#Gukf33^J)4wi_Jp0qu<4tqk4I)A?StliR zi&EZ+E9-q#hk-P9Uzbi_t+=B%p@7FM%-{`tSM<$=Aah!UgZVi^KkBoxdt7YsmHf}t zdwd+tr`rs0NzpJ`RwQL9^X_>;4ui&7R1R~^IK24Hk87q&N4=iC=ZQX~6k1BFy%7LX z1}+nI;52}J;89BN3%D-Y#I|iF-YNc`6~JAa^lk(*t^Y`dWI&08u}03tk71cS8u?F} zTbwL)FZ^Jt?Ard@6WWF6WvY!5DI&{E61?okTx27Z1y)QspH4-X4oKXXu#q#bw{|-^ znQ9{yCxmC-ELgkh>LuDT0wh&HGby%)Dl@+(nWN9Y>X&zrmqPn*X74 zC9X}t-s426WxTga?qbbWoRn%4@oQ?qMRBVlZUhogK9lV?2l1oQ*3ERD_O~!e3uLgw zzC~?`(0dI@?%TFBge6sG`v1D%li44ro=?E}pUBJfe&Vsu$7jK92CbYpgm}oiE541V zK|jkX>2R|&EV_ZEA9b$xDvDZH(#CihFdTCOiy42K@id1=bGC#Nwd@T8OMoM^~%%8ppWX* z#g(Nd{l}?x%R9q5YU8#$Z1TlvEt_g{>ecRfI*T6br_#v(5M|g5Z)Isd)XWEB=GSyj zn=S{(fVkyA_S5w4!Ti6THj__k*1?0EzLik_jA+M|NO7;(Ft}Yh6ui1{0Y#gXDdlPM zV~;H@=bneA&g1inCt3ttDa%2ZCM!F)y_zlymK1Bboj7B+bdaRW`{>i3hXMBYfa-Bo zR7^a6cM_i4NMXAzg{zPH_RIzHZsx_@8_{hvE3ts?M9CZ^gknf+Z84u4r*08MVIht2 z>wsQfzNW`p^|!N(SW@szKM^f!+MC9Rp3Vp3Lih%S?GA_@*W{fIO2dNQ2VnY|IrYdl zcFM)$q1pb3hB*3V4v${ax)^7-FD^MTV_OYhE-w;7j*qL8wu)jK4pHp^5Pv0`Jw@N8 z=`%bj#!3CC1)WT{IV7qSQ_IgL589}pE?YH|i*_1mQ5tXdV3&QO78gRIP!^6pDp`7I zdUf1S@r>?JMA4`YiDen*Wb+bv!?D?OkVz%Xvs3Y=*jCa>vd|DW*<(B+L9SsT=Zem8&X` zh2%V9`XB|m>tLSVkTY|#x<#ekw0y57Nc!CQ+{!}JE`)v6?@5NtW>(7gDxKky@72_~ zkSexpEdY|nggTUaoBF-J^qBQ_ypk%X@13JCK&K4O4Cvk~HWQMg+?IKKPm)DxwF4)cu#x&n~ z*6#deL(Se*%g?cQ{R|SS-P)7)@FSAGdOv7<60-J_lXp7~7a+RUoIc%xzGL7_DYN`S zL8hI?EvjWpJ*-wXWu>XN$(QiZ}Go=wI07wNn$S}0oKIEw#unq^J!n>^Z&{a zB)H6<$FHC?6-y>~^Zremn;ZMoI4gwre)KTzFn^77yy=Nf+z@W%DS3N3l9Xsl=1X8gnL?p2R(5f8OSwBjSINFQ)HD1r8_cPLW3f^-pR~I)bqNh@Y&TY{dHc=&=+}{kT{ zWMfl6f#pGB*J6G~KK@3WHb(z({UlL8m;MhIz(4-;FTcV=@GpFCk;PWkghB2SStpols@UQjqr{ueWJ8m!9lRv?Bje|70*HQ zZbs8J+csmColPk`kQA=$<+5fpapvjpTe+?gjNk0kQ0P=XKptsBIyE72Rot7kXeMYv zz+^vvdoG&!k|o#n%U_l3|E`dK*HT##qVywgW86!p-?|GR{V2jKpne0a+0eu#xpG*A z)O(dgBkwqL`})53Qmm1XO{uK<(31rFbWClIk*b-xbVV)%`uc&Es&goU){M>C+1wj< zD3F+#HTjhH^IV!!d(o9NL2N5M(ZPAU#Lp>PB~9qJY)q^*o6m|cetb?tfm6zVS>Qh! z6MD&~(osra zg`Y|jT2hNO+VFF~PS#Ux6JC4ouMt|xO3@gmgWQCLev-)q%HBs0o>m+dz0URWnL(4x z=F-$A(;%muX3NJp4IAtS5igAUw`uWe?h?bVXwgqIQho1#q|?9EKGXj9va_2e>SYKf zI0;BeNy$icwRDspIHbt%769l0v@mFemwN*-;XG^VElKuZ3U2ntVm9omV%(IU*a<9C zE&<}`K{p~^4~zS03OMR5PUX=1%ipw};Z7yeB-w)J(~HClp-wjxWV z?ds4!!ie5_#~a+_k>|RDm(5aQ0vj4;Ad!ORvZjO}l;*VatMN{S0>NHz1~&EwsqJzu zK!=7)Eo?bt>x9%RRe@XZ^`8Q5MNcr!1bJ(db>CI}3lyaOYFG)vh>n86G{Ey8?BlGI&uve)Hy-3sO_XyQ%Fsd^Q5n25i{=cfzv9Dh2>R^+>DECh z^J5B4jthu@Zf@QP8b}%7>aqV3E&fySaRaLuK}!*1>6!QCmq%HqhZymmlZ6GmX9@YA z2z2R#-*!UOx60`i{&1=L*A{+Fv{;DXcCYf!qAhhNh67G)_JbKk7>50KK1GNdML64k zdP**Lhp4m17B0#Fy27ge=!oEEvoL03;a&eozW4aK|_Z+5K20IW+r zSNNq{TucOBPH+a|9|rI}N5j8J^}mr(SHs_$R2=UthnG;-L9RDP5pUl9$j2QOj2-V@ z&VgT%!sE)pq*0{1SGfG}-~37YK8M98%t>dh<3rVK4c}g9t|~01jdJ1UMw;KEzYMVsM^IVa%TxLj=XD?HGMD8XA{I1r`s-pewd@T zK-j{f9nf!b@AItl?*njs88Km$CC#b)=ryA^A z21jB>)>?Y#Ykn~~SuQP~D#6jka8RaAuJl_LUZv&6Db`YGq=M}G2_fFO&Hm=x3Z2&m zG1D{JnK}FmfQ^w0NpzH#;c}WoX_|{PDumDchu8#=fYea=ek}Mi_b<*RRD#6MHDmab zc=o`fTn^FZ;}RE9cHi}nZtN9HgEDAXCRc+2{$20@DrO8eEyK(jz1{Xd1N{qe2}lD* znOV=V)QpG@(hj{;g_%E)^-;xr;ioem!zjez&$57HPJcgVQCPE6%5M>9jpU5?S(AyK zb-N^X+o_4UHztvj0-_0;=;ee@Tm{{quO>vC(p5B}BgLQ5?EN>PX~o|EPw3E2uLlpjJ2GgKI)wnbZo)7Sr>{$GunKgsdV=Z*9HJ86yJEPonr1PyNNQulCckf z>BtcKd?XpEs(kUq{CBi-^jAMws^hn#h<@&aC5Hs6-aeSrM10JKEju?;21H2JA!U>9 zI?;79M{o}Z!x+>3M~}~a=sA^hqu(t>fL~`Oy0zer%YZu#yUq7uut$2UMvaT~(F!}Y zOR{Rv9F6&OP0qX?j)_l~Rrto#{a%9r_x;)3057*`rYK5xRUvQi50jz-G2A!pFtrQV z{LE%}nJsY&==MvQnZ1lteAV;Zn>&d+PPwLf;VJ}#2?>HEv3vDYnBvjdztO+nJ4X0# zcZ9l)yhQCch|wX6FWHhj#Tz0babEE}P1x;ak>RbFiIuYlZ?3w6XUU%BX?Ci(aR&@N zNt+J#j98D3Ft)tY)Yy?rd88kGl!_%b@Sw_GWolo7H9Jfw6lE76CB0w>q1 z>bJ2)2L>kMmI0-M8671(tkQ`!Pka{pc2p?q?@0>q*@U>bIO2_)a`RnXqYtX(^?RM| zu>*M$yaNgTikpAhWQ}XgBx#)&YGS={MNr{NwKRJ@l#iryI5ZrN`99k@LnG>$hC{wZZ`}-=kjqP#1Nr>c+jdNwH9K#S#KwMi~ZHHT(40 z;b7H6>ItP?{eHqVTQlAu%O8dx@Ftj*%qa(WWTKTL_ALaS)fZ=~dWIke93%%{1J``! zzhGtjx#LyBW96JGMJf+9ZNeLM;(Z!760%9wSEDlvn%6V zn#RP>>s!aEHN?9aVEn)r4;osOpni?63+<@3soivbrJc2jt3m@QPNjff_};HZk9>6G zc;y@d3KD5}{U0%+E9vjO4AA&p{`XUUxbdPv>3ZcXECNCUd%o?<>4N(-#7@!Bu z&m^$>j7k6+g!8VWs5D>j!LeAqtHj3A`J%*GliXWX-`bo-Aoep~NJi5(gG z7-f=;!?(1P%BJh#tsHUD~6_PlZRUKw*UCCx>-sKfT{pu>H_t!xTfQ)czP?vwdddS=tWe>6TZRssGZ1$o*!X#@r{nS;F;CoY z&4GtJKmD7E0lo}2`P$dTg~pO{uCuY}b!z6RHV%deG9VzJcUrj-5F!@Mn#0Or5d> zL2d-;9ggaMffC+51?a@DZgsy}(>(Qm{$s{*amZRTWZ0OekXvyIF;-d!!<-)j<+kt) zKlq4MEdzjbe_Ld3Gh=+{;zh4Z`g#)j?henp-3q94qK7EPk%MJE44Fj$nrKrvJxBc) z?`n+@@2q4~gQ}8$!Anj}m$h{ zphryk=cZKK*Fz*lVSj740Lgwd?7dj&sbF_0(9gV7R;4g|kUZ@xrG$+acu2Z8)oapw zh`nNx%)YulMShpH&SMgao#vO3#K1)~PmijEKj4;qSHDz%QZ-48E1!Yrhx!A?HPQ3h z$@}lD3OLeA-`6SC$tX=;Fj_3eQgq1{9(~eueBpAFIXjy1L}BPyy8$~=P{gBS+I0*M zY&2FpK`l36Vl0kV<|X*2Axi~r?21*ngmu!vUs;#N6Xn)lvR*z}BAoCHaS@+;F~Q%q z*U!2xn(XIj3?TB(#QqA0qG+`oOuylT?bM+v*fzQecjgN27c>*>{&J&2_t7W?$0kjZ5c z!M*}MY>FWQ-k@(h zaDBM)**dB>E^fz1Kbxo_Yh)`zBROk#wSjV*fK|c&*QmZvq4MCm1uYiL1$)7Gr=h%% zD|MTD?N!zNJu4nGN?WYDeH34@-J6Ue>zPkm5;ER!^_{H|nS0*1lQ_)F4kk;hD+sgO64?>J!(b5)Ze=iw?&#1Hkp zj-xIztKe6^IbyPaZ5s8Bm1D=W2LqHw2seA&lA2z$%@W%Soo;M@(kM?njD!P2_*xPJ zwNl#$6b;M9cDU}i(?3FTLhk_jz$Nx-AbMl zog6g0dfoiv>bKhw|Gw^po*!l*obk7N*^>|(RUNPfTM@P_M5C{i0&2=5mA~3h1o&G! zC6tajq;QMHf&vX%gUq|7oyYH#JY59ed<<_-}R zYQX4GDDAZpM&k=Q3Q}zO0#52Sf6rtFG|<^k6?s~h$*ERjuZNr8H1B-n*(+BUg@{e5 zGZ=0>^kN1%VFaq9$@iBoUntg(O;kybdk^Q1nK7R13$h*MPLx(_q$vMq*k~Ww`lLTI z>vO(n(VwA6Db)||rqNdy!w>k>O9Amh6Hm0MBhMoppJGEEKGt349iB0Qn+gG+kd<^T zYJ%!jrHeAv`S|(k-rIg%9#V&Xh3W7~6c}8d&A6=aod_L0c>HAYxexqdw;7kMd0z^p zk7J@h(ToM(4#Runj5sh}%(yZJI48c_pWJ<-%J;EEalDd zBUD%=g$#V^3`-$fwZ<8A&njLe_T&0aV;)jFp@xQ|%nVojy6xZfu6Udc9ERb`ln**% z5HTOw6oNl`oT8S-29BQ%KXk_z2x4`*yQ1lwyMsag>vpW}y%TYw4;LRRG#3G%#F+~m zzuo<+>a`UAY^LPVx~Pn%#;bCh!2MngQ~W6MfVM{pm%T06Rc(@C8G32hX+!kGYXdGyHtU(r~(r`x#V2&6C=V+Fi(t}$ zJ7zK5HJlX@3-~d+erf`|H;9N%MsgGtekB|x*EHI((ul>G;;DZIW5d~fU+#Bs;9Tb^ zT5CS_9jn`)*=|nX$lPU$X1lNsp;{KJ#Mw6-N=S-bNVegV;FT8z#aW0 zjYD9Y90ArJ<4BH+)Ru0i$C9nC5{gV-H~4ursQhzA+k`%M&Q83+7&OC8l0RqXHRIM3 zao;##KiZPj11-l^e9z{cnLsL_QTDc2r6rC}tzx@A zkUNt*B|M95)Y*%nqC#onpIRUuZ&}~wT*jT@LwHh-x6W;1OXI#C$2E<+)W4JpEVwbg zB5PBLr_R8Nc0lP%r=rQB?cY(0o8zV~Y`YgR4gCq(0csJ9Ej~?j8!st?GE&2XnBE4h zwS|k_G%r?j@g2(uqdIGQj$T3MHmv{nGn?}7(=e4P^L>H$LF5jixqlu+sG*WgN>g~i z`~D&oX;3Qc^_UDUtS;Tj(@5CU$M%$9B@$d`uE$&E&h`F7hSrPs)XeI@&@X+$=KN-M zjRo`)8{La;oQho8BmGL?JL=8mHx-uS5_*Md%(Oo^tSLiBJ5oI#$CMb(Im9;#F-m2R zWz24&tC+@6M`122zG6uYLIrpCes-e--5a-p?zb$;qyVPF|r z)pw8jT$uV*zB-M(^nE8~ zLu}I)%QkXE%v)b+;_5+riP=hOxMOc>TOw7rozV&jaQXq2IUWmg5jyV`DXz;7vo^k? zYuZv%4pCBmBloe@(#%O{x%GWapJtq_c5#K8qh_-YGxkhy`4vxq5L&^}t0|>vMMpsa z_jU0K!L6ixFq!z|aM^*LN9G-FScrDTXzWu;&E%q_? z9p{GdN8w3P>k`fsEomJ`ovnv+4`C39$Tr_F%}oF|uMc0X44?P$so7P$H~^}Ag&hX& z)U9RTXHpSK+8U43U7CYVJB5(u<}6n%U)EQMfS~2fT-PU6M{iS!$G2+UR1J>*mi$2z z%1iEZZLP<9;ljv136bV6lc(>f|871;g&Vm{1~=wm7aNxsiu&lE{^% zOt{ev>xWM-i}r)IAri0HLd{7fR-;h>!iyV(+kl+Tg1VmJEgGJ41_>@)u-$6&Jq287 z9k(G;r9DU>vx|`xOFI|-*s5L3W5}0V!^U*ZL<<~MrLJonGO!N_u4JgqG46G97N8hd zYGh$8dA;-2bJO4LchIHIfJ2^%BY!pQwVq#I;k<|;_2&q=vnT^&ZXSM(lCiCiSFBGY z(tY5H&U2==v0c&WK)Lt1B61~O8_j*8l^9bxrTMtLGoG;iEk!t2 ziW!_B2se|E$7TT!=o#$?JT{P}?6{RQ&pDv#p4sx-HeV@abS-&20SZ;xz>Jp7S) zMvkS8bDV<05{0 zRORkZ_PHDSoR*U2FYClG~Zb^~^z_WcW z{TS_lttdV4Xip^)?ypt+UYOO5)^Sxn{(+|LVN!1MA|T=C^Tip~UMc@?kB;qtbvDtv zNGV1vNPfspd7-iU>s9QqDabrG5-Jof+hh~;P1VjXU(C?g7&*h`Cn`kbu=;?^XRJ;=b3 zKE7IJ0%WmYI8ES0_o*rfY(jFOn~76XIyT|!)+8Kcp5W_|ug<2MG$k`A6StPXbAfLt zT%lsgHq=xz8r6jMlEL;YlH8BqK!`}me2ux8IJ?{QtwZdmxFuc!C5AOp#TX@HLTW#V z2H$4oC{0o3;9{(WHxjEMf_Sf4)^daQWU{`E^>`zVxco%=J@ra)7eRTpHTUvpiAX!? z>si9hZQhuJ6E!CQcfVEHru2o|xSC3ox*_Q@wD^%!js-2;&ZsmwTQkH%5Gq1qnY@#( z);SSE!8D@arXT*4h*?>RJ&^nPh+2!2hx4RiZ7Q9(T?Wc6c4m>8V(`&>SO8_R_=1{z zIwMjGc`w6}CkGe7%~jOi!)gmuz);I|H?A5qd#XdFNV?vzwWG4K+LO2I(4z$!>=I-R8P(WkiVajV1q^a@{L*hkKPdms=4}_BHY!!i$r-wK9(CO;){!NSUoHn(^wo0QEzqM)!G*^{T#ZuB{j}9F@84>2p)bW(FcTaGaTQG zGnOJzT;kcxs}k6Za$Bb@J~o<_0&Qrr$6&Fq7`@vKV5uQFm`(xW=A+oG@mqPKsZoTQ z9#3NsEcv8RaXaC~ImPB=rvZ7#!5=-C@=yu3PAll{p_yNo8D~$`LuE96-sdjIf#E{0 zfuk;mlNn1i>pjF`s(tomhe3BLqyIOZ7HnmvAC(%)SZ&g7`&5A#n*AA+g)AqIFp}tY z_TPSq9m12wbP8oAM@y{#nzgaujN&$oayyN+7t82DgeXFa?q#Dl2#qjnr|;y3*S2yP zZHa2E-qkGyfb-y+a`}gwGt}AHHWTt~mQ(w?iN&8<0FILz!uKE@XxxKhCcCZTJtEat{ z+RAC}jz@)fw|9x3Et!kI%>C*6`C8SzuZ=zHn(SFLXMnOy;WMV11CFIecZv6kX%U7y zyN^-1Ad~kYY*p@}wr0cc`Z~{9TkVVzEg{e?o`hti&Xr-Bbfm_N_qNUK4 zg5vD<+HWYpvbH|_)<v=J zFTzFeqnU?!ch%}us9f1;XXU5oBRN&s9}~2+l4QeF@`Xl*10x)!e5s7KX>OPu`gia9 zIM$a6U4-(g#to<7Hd}C;sFe|88x2;jyPQlB*H2?~EF#FXqP46!aC_k-j~<%lN%4kt zV$GmPYSOR>hN}bMRRv~U6ZE{rLyW5FSb{M=V1TPS+5z?b*`U6kYqd@~=3_>x1z!@( zEjBf3pv&2JkG{Bz*B{lX&ntHLR3UZOwDYt_`2;~D<>;v-z|QsRdj>GUT~*u;?oH#; zh7x$=Pn;ZiBqWWpyKYSLR(HPc0?eO8Oj@|nCI2jwM8 z35U`)HOKE#&0xX{%;<&dvaJUsr#)bk&$08Q2ZY`Dmv6mfHKCxc^1iTZT0qux;E5 zD&10ojFHkM4Wp$&=>`Esq#4qobdHjen6$uX>8{a?P+E{29UGnRyr1L!@E-5++|T!Y z*^dAI$90|8dH&8dnY*OAC9#U4^pE=jjXL#V#$1ckcGkv=TOA)6lJJsY)UN!X z%jqgT5LxA>+VnW=lLyO;n8%5Q@o7cX`y;%?I3-A3V#n4i>fP|Xa*>48S7RimzH1rt z2l+8Aa=z6sa#7jyD}A~oM!3@I+n)|H)fL$Iq5OW*Za`Q6K~IwAsv@kL67Mt~vq!(; z-jnxzc}8znjeHj9=%q>0o-dO*r$8-|SWYA(X@|W>%J(PV>%aT){*sb4Dr$-;{x&K8 zk3nbb$(JDa3J(ItCj> z^{GQHde&=0keHM;*m^fhax9%omWpOd5DgOkwLdRSjUi8eBMTmYCEeffBTIu-Fb5WF z=Hd7iDJNCXQD6P0Vwom|nzj%nrNCgG&6TsCjE>B)?XE|O^?24N_8BO9mL1fF#g%4d zK5NS9ns+l-r4w-|Ys8V9aXgEC-j&H<>s=IA@8Kl_GH0ElxuR8WFGt0seWK)YB;|7a zoO^~5;7td#g~N}9AYd^AXP1kqy80+#kZm%}k1QKjm12a@l}!VFM8fQANdIDSHHJ3^k4imjpiSn0j>Xs0k@RbOU^%DVb9<`*hj$srnVxue_*grgk$ zCV15%Ne9>+-xyMcKI)||!0Kg$DyDWCB;Y5I`8Vt6JX7tS1^TF1^eFkMI!F0QhR-EN zl}G5vHz%w|B>x$>hzmHTYg2d;(gFh3bVkHgojdAUTdab5l|%6Y!6YEg`{WSChq}ml zbBuXJPm(>1w7bE>V_n(Fkg}I3fRyoq)Lv`k1uhOQV891bu^qur(M=(l7_cW2{x{f4 zl>sYm){?R^k~oqBE}O-uyH~*2Gs`2`*lm1MMM+FDLqJEXaPvsWwQw@Hyag*ouQkR8iB5M>p9-k&^66S&BvmWwm?Kg=M$eq^ z%4Tp>){=WBHyibQlefd{#cE_DP%;o>jitVt7?l9NIM ziBGS0Mgd!3h1c`gUU#?x1JCS6sSwkzjZjfON;s^$xegLXJW?qE8}VAj7ZIRU0ufhA z=FKplF_A-{r%^3TVOj=Fn>!`pJl05JW3(s~f zJ8chjkBJnJa6R3D2I2@se0I=Zg}Zfyt7r%JKccC1sjW>Ttps~JcCoPjCCVJy{`tO+ z_3=5sMc-tf2l;xt8E`U3d z>+q?!sY2>nI1%Ubxv^KxfZtMf1oU$~4bu^%hdm zUXA8Rr(Tevb`9x9-($b&l#4%aV7?8$+NiW;fFpm7kq4&9TpuG&bY?PG(;K)N22V)L z1$6xHk{srB);N#rh}f}kA~Fs9&!ATvDtmi=ul7Nsm$eD^vTI0M&-y#AtdeZ0lmhES zk%@SQT7o8m)9|(GK-3ST%Wk=ErT%KHk3Dt2W(|qQMt{)viBenCuq_@c)RTY5l%;=H zm_%%dYim1Drw=MfB2OkhdCeK2tNKm#euz@KMDH#?sd%cwGX+hvR-nCMh0_E>@u&1= zc)V3V$^~TUDUH1&lCk^9f30p=zq-|l?};(YDtr(!fZK2_QeGvg+qLHsTyLoy21EK)4B>PuJKk+ytDB6Gi@@ecRkeCRbi&u>E-X9;e8AalBS$Y;6= zFsdnb*T;qqnG>~aVF^`-n_~O182=7vG#En(rp_yi`qt*VXQe!a?zxg^he^X-Um9)! zsZs+5mDgX)H%=2T!RHNTLz4THuapCAlS#A@daV81AJLZ8?+(({kKD>TKSTSzHCc*x zec`(ur07Udfc36CojLi!I=8Cm*Jw6Gp%qebN%!aZoHB1K*SVPM5RL!9(hx@K8x*46 zu8hCPKdg8+z_WZ)0l(0ki@Uivsacq)d(J?{P$&6|DVK+X2ao$LS5ukVYjtP!9W@DN zcGZvSPXW)L6Oq#ee_xmA4yh;aBJU+%Nt%nKAaAP>rBt;Fpd4HwC1ttc(__jcRDEZ| zYzKVDD6R5Y{ewEFh40d-nDaJHh|g99#jL{gR3$5-AXo~WbKYv_wp-pnyl`TdY+EOi zw0LFCT|`^(VquD1Ubx&U zMv+(~)R)1x0}oZO7VsU@C@%e-XMT_b9)*OcMF0Jq(1pMyxcM|=?!|L=yL=yz03r>0 zYWvWZMS|yg92{_IE)z(&@+NDxPnABRwHt&=pqzaazxR>N`GRr5?scM1 z#-{7saeF4$I6l)V>qv&PNr@Fslb4T-q_!2;hHSWF&N5ML4_R&& zBQxRO;xoY_bR=i1>49)v`4{)dZX8g)hiH*ZKjgYYl zZvAkVt_+mgvD2I|+)(!J(vjI8_=1RujY)2#c?a}mv8$lZ~A6^rkbB15?r6q;}bYnqNw=$w|ShW8*^5K`~Te8!5ziyW!B_(xl&#}cNk+-x*|&!j8Y zJ@JjN;Og?Yqi0y(ad6J$TSv3sz#3FSUF+Vz*d(f9bSw`EOsit=tE&qQu>GiD7|`l> zVaCl^WNb%9Sf{;*5sj85zYT8^FC#V-bMhPWq1s_R?1y-_cNk!;YpZr&)huNQ{FgHQ zq^hN&YPGdRHzn_q%u>|!vd_e_f7lH4b`Bcn^H6P11;%4^ znQ+e)Ho?7Ymq}u`#afcG3r4}{AiC+nm#8(&Y9#SmtD<|qzKgB>d)`T7mnq}OqRx{J zA$1l{)OuL%=8xd5UiBhc_VG%$#E1HJjH~+1Za&{=lhfY63??oMwCg>i-=?~4^?N(0 zg?>IA=p`0G=V#@t8PU)PCYCXZ+>cE;{y!Wp^yFA6d58ZA(R7|hea>RUqFANSd9xwH zS@JS*CLxLw=XKOiCD!Xo|LI0|+7MSgpT858sE0O$p;bcy`+6rS3w>#_wv+!(9ePH3 zsJBeg`jA|5E{eqHoD{#3f?>s+Dea0GF%_d6ecqxZPKp)C!LX90M3P}s-b670~;|Ih@{WIkBQ(i1Z?^7f1kRqDHM;J+CY zZ`m4>+57cGYkf+CuNB);dFati*+;+-37bg zW+RMF^n9DIqL=MH0h_sT0^sx0{fm-H<^emyUuE9O>hywS2e5X~%xc)(uG-C36WHk3 zW-7v5bs?v-^c*OLk0?Zo9Y1V1V!3b!@4pugBd<(xnG_ z7;T)jFLg#M{53KqH04K1Y-()EcLUasWSJ}jtnJcMTHQ3&4~@IEKuK`b56rk9?92iZ z2tbRQL&bxyv~uzUrsmN1mnR$A&E`HMJAO zy1*)62$R$my=ai?3A1&Tj8Qd#7VhK7umfh64-F-~Gms;Ew-^oYyV2#F(IcQvoZY|! zd=y?Db{;9#!;Ar373`ej8BRg0U|ji$e>y!`CS1PP`~G^l|vyCePOKk-Z|3(cqHwcy+ZeCPwnG6->%kv+!L^=>@vPtTEcWxswST6}Xw0 z{bRrjVE^c$UW=v@T_~}Q9Yd6)+t@45<66JyV;#}BOgqT~1?D2I{Y76^QP2(CG8NxkwATOv5&0 zc^)VPlG4BWaTUXk7>&5_BlLI>1f$ywHp(rdQ5qAnoa-^rcl%ji^_Za|+b=%jdb3g( zZera=rZ9c@HMVY#V87Kep=*?&I{GVH`?jrgH5=ov9xAsu=pfnd$?1VaXtxLraMD;Z z0Ftvz)_B8!%h1JioCiIRzK|6=;XNJk8-MnZ=jravPqWyKz@>*d$syd~^ma?klBS1S zvQ!#xM-V5J`O{Pa1cnkvG02Iz#)IRq>OrB@Pm{jL3}M_vL8n1QD-xeEv$r_n9B}?= zvwJCUL8h<()T*JK6E_BMIiL)ec|h6#rxJn_9GhvFM684usTs1U>`JvHY-|=N8%_4i zcWb#ha_V~@{K0&}@y%JtJ@vyK#i z$j^b;oqDke$$2E^v%5dWV$P<04{`3!a`J`DViA`ABp7N;t|3TzTI_Bnf*8JOd2w95 zOt6{jm6g82?49Jq?bcC36HlTC3Zu)0mH+3=PUkjkai@bC3l^W-I~rr+TOZLByf*&t z)=^=y^crtN-H_Bj|D1)L`tslJS^`;LvaP<}c(x;h(k{pwdZSPNe!i{3oiT+pfidIj ziALL3Q4Um(;@6~W{v~(Sj?3F2X+kE7j7asAo*%uvlqg!1&BjO05`j}e&ke;_ZWiKM zKuf?SM<{#0UbB4%cMC>hyhvBkolutCZ*Wz7g&so0-lgO>!A#-CpI8Xm-QVoNT6I{p z*?b%olN3_R%$A1aMK5YITrk38YpRo}cxPeeiT;T|k-$ZoV~Y81^dfMMpAEXVAusdp zlGew+a9NdFq-2kbOU3snbXUq*iXx4Z?nvW89yXqNn2nRaHJJL&Zx)N|Slc-od@eNw zZ>m`gYHnGuY<@g*Ldt%Elu756!N`q}Dp@vI)^?;eIHdig;TRuPG#kU?7(2>OF>`Wk z=D&5AF8%>D;ijAQ1iaP_sl0IEow>+CLydmE*SiCoDd+gqf7~~Y=$Yc;g}zx^GCGq! z6b@sS9zL^6l}1#EHCHn}99YN32-2`enkam;SK)mmdcN1EVP4@feGJzEFrUw=(Se%wj<}dt1(*J(TS!aI!g8!V}?QztL z$KqNeG`sSNNvU{G*O{0j-_%$!Iwz7{kcdQTI9lk-mn!K{$V7u|fj_snw9+=-bJq+M zVOy(VP7qG^yC1X~51Z1V)U&9{S?Z21J!k;Ib)sm3>|d+9BWCGKqT>V-!%+hF5b+>J zc_0pRVJA(=h_~aro<%JIPLaU~<<0hb>&NyuIor&{$j4{+D592-nby(VtV&bUx!81O zcwN-<{@)AUM~X1jFVN!^4uhu-=S9|#iFe`$BB$apcALSo)Lut5uXy_VY8V`4U;dnq zd|N#R($DH1G7}UvEC(NjJa(isde*;ql)&(%ee9dZkVkMmSKUE355Ki8eE zh$UbLPX;vFtCQ3l>&|h7Bhe<7G&PBcSrr#g+Z?+aedT>2b}Mr8!OwPqq6IFti4CO} zqpwKX^?ovkXu=I+AEM0(q_(lK52+iw6@dtu*Pn}{??^+bKF+ z#higNlH|sK3?GIDw7_Wf#V=p1h)eqPhJEyZB)R`BY4n$r)Z6;iCh5zM&;RogG3G}* z9cv^>Nm-%M4sXOEYMmO71QlzBf?(XJ7pSTfp%O*BU^wM!qT38*Z{ZM`KFXnVZ15#R z(||WzaVRMWHR4?=awVR&#=Kl7uvoU}#(K!6lWte$4S?(FZuK~?IS$he5!owD7fQVe z_vwC9@w?GO4l5s;N0L2jc-f+M1?%_PaeJJ$N*R0S{RlqCA`<{E;LF_B5{W&AY&|H} ze7t#FDR7qPU}lsMG})I9F9JwLlL#G|ZS?f=Th7kCw!C{ELqp-v@)Tu_SY$&^^rNRZ z6Fbx*0^UWd)~}g}HB;H(Mu^$s7TmO#c8&^FGXwC|!MIN#s}@;4^+NzS;rp>VvSi!c zT6!B}x{G)XM99OoQQ|^FL7vgA zU6pvG>^l1j^jQvyLQg7yA6Wci4@M*JMKAH4}h}R?8Bhua#HLSkhko?y1O)h~uF8g6+P0+mO zj+gdn@)f6D68H=H1=8(`uWNaazkobeP1BS5wHJn(>vUxh7w>hsVRTUcL;l@tFGH2k zqq=aWp_-Vb_4Pj+-|LehCiz#vM-AXH#P+5+caZvOAS`iNNBduo`%nbz(! zW-us@mc8}5Q4`Z(VZGcLDvo734EwAn-l=9_22A7O(!KnwR-{>nT1oLXaXwz(9kSQ! zwr1iS^cj~cF?-D_$tItSJZb();nNL1-zo)Uc&Oxd*rkL#JD1iLXm{`@mtc=DZkke? z)Uf63(7<>|65O>|5`7pdR~5#<5DlHyYm+a~JCC)_JG4B-h+l#ie2-U+6i zs;&^sqBEL`p~qI0(t`MTF@Y@y-S0F`Tb~BT0EAq&?(`h_ssBbK`8<--7_cbJfo2B{n(MG2}*46g=?m@JC ziZ30$Unbl6Ys&P;)~SsdFNJiu(??QYb@3TVl)6$urzPssmmTZwx@XZ=?TAAxny$$f ztSG0H_(8ITY4b*SkZmc3$|$hw`YY9Wr!`*8Z9fohI&JeO{h+7cZ_s30^h@c6riZ)dm$~W+!6Qp zquIVepQkN1U|YN)f-jgu8lrn*C)swxYbEn1uTuQIW5H_{Kf&$X56ExXi3sT4)T_N` zQ z1mZX>ONu@iHYKPHX%v(W5gEFFV0t}iy43q=1JzGP>tJ!B{6l^xPrTyIh%2<+8q9y|5ScvIJWoCAt^`&BM|1M+3X(_Uoi}2y94qJ-C^E{rb z&&#I4KZp{z`xf8gl;yA@o;gZ;?8AW+b0xc9RT#l3)<1ji9Qn?l1&kjKAEKu|&s5__ zrVWJ|)cCZSHIHkWBDaSj0XOXL;ac&+BsteR(GipPqQZvY`)%u9v&w4(Oa$px+^?Aq zU9V*sWGx|MBpoDWB&9c?#B)TjB%Z1!Sz23?OAc%4%W{ED zBE_%5qk2Wx(n|z3&gbm7SvFb{I?wA?W~RqjUy(;j zLm00m#eNbzYr9fk88UtC-NxmmwZD#Iymn7K<01q~<3{o@ z0?-!^E}&PGHbR$xN_`HOeBjbXS?h^VSf=N5b*UK3z)Q@DUt;=2de6c$0%aNrghUm| z#4gLH}#>UF(S(%7{pxQl#~W&~D_%IAg<~x|F+=gbMI8 z3nR>TwasYiA@LBh6d5!To;F?-vQ(H4De)l`y()S?O36G{PP=?H-2j&;*Tlb`6gl7Q zx61IVQqy=+DK$LdXOUG3EJF)4-KZ|18n^wZlzltLTZbiM%xHW`{LVjumjlk@8a+Vq z*vCJfDVT!ND>IkEmShgP9GW8ec0~bKJbB-kt%*gtR9bFQ1=#K_gn_jhCz^D%hJV9P<@_vJ^t!!%cyqrfrIz8#u^JDTJUddh&d z`|`(2n&$u8{QEx{UI(HtV3o_pX|wUOf7|qLMcJh)ch(f|#v0SiEUOLoS4z&!Ut_$J zzPaPgGcnsAE_$cA6Ka376wg-+)KKKwk1dNgYqd)eDh-8uEYqOfHYLXO{d^^#sgGWy z2t7L+p=4Cf={ZQbvX8%r6#8ztq02ev6LvDl=2JS9te0dvA69`HQ&_%iEF0T)((o0V zOuM6{$c+WfkM3iBf7L|-h`-(?>I=3hLhVj<)+%;(h2D7;F7W%-NeS9(Rwa7izvpCQ zMu3>(;uSg%$-U&&`unrwLuIy+mY>agt!O{6qdDFtoGfOw#(ga$mac>QypV2r!w2rV z*c6%h3>ltu-kNm1XF@_Xq01e`@w-_M!R&xMuFMZl8Z1?;XQ?sAC(9Jsj}2k`^5XA{ zIUZHMFWJ54^z8}cELU|v9N^zB902`#Mgp1 zBP%2MI~)A?g28A=yZ8A zprK5kP<7PNXYN$D{vPmI#Au|%95DH6PZ?iH_GN>-YgQ%|lqvYg5RRAFPqV7MG2GN~ zLJ3$UyzDijX1{7ILSR4^zx)1KP`qn`$d9I^=tjAAp$>aA(^Q)lL=Wm^)3i{tUo(iyE zkXEB&FdU0?>J!2GyN~OW2;pVgnHB>#8N@7bDec|psl+JQeH$)f`?`6GNMtXROR7&{ zbH$zTLnRH4k6@Tnps4qZ*m5C_>1k%4$2BVH>pyLypeJF)Gm(T+KKXwX3CeRDBXfYasWyy4QjzY3zhbl45nDX|05<@03~ z$hD%NgW_d-`oY)1w6VduIhs6U%a$UA2c-e?lAQdn`+86HtihWd;y*BiR;R~-^9Bbf zRF;sJzJN=+W6#TDyR6>ACbDJj-6|>MYD;YGV&ML#ZOHhK1FN0+w-x6&ss`#h>OUjW z(+W;G{Yi6vv1GIMA){`60Ozb{3Lj&noHh}g=g3>IR;r@p>KAYT*mQ`UFM3lqu9IR!VQt8 zO_}`*GgJ2qvfL*?Y#z`-A9vAO8mQG*z6$$VLSj5%4dfr`FCy>(W3frU zjfJhL-1+cC1%zflz3BWS-PkPyQpu#3)+UmB!ix*U^>DSI#=eTGCQ!2SM`tcesc=_e zX71AYsF8|f6$Ririjti0mh==xX}u+N4fF=3hX(XUMSJ*nTtzw{}#)v!@YMQ>Ek#C>{pKIgEH0+AJ?nTTP<1AYr0p*+{H zY{y!7lqh=Ar&8CtuWh$GmJ!e;)3T3@P6vJKf;}QDjuFXbAIAlIF zOLckZPwaExX=SSc+4~|BpN^C;t?J(bmnBw|tp=W;u*|MVjIlX(Co3CSH!l4NKsRaU zEa@LS0kRog80*hmmRU#7X4_Pe6}LRy*O{__#rS+O)DVK#`?xU(5c3*di)lAPfAXr1 zGvn`Z+AV46`0C41B``uMC`fty@y|{xgKinAV7rolcHOC5%i_b8l>*#_- zIat+S0+7Le3sQfHK!`;9p89iSX|hnq~zn>azro>*9)@|QYR z{wm|z&W8}OMKGLzlR%K5crpA&KmvC!e>F>vXe1THr^O{a z^g;N+-L!&L786Rc1*|PqvNQKGSpWyi`s-BH*t(QW%#V_1LH$YlA8(LmLEh1e&FRS(HVVP&Wms7miJdPI8ozc8Mv65z^P2QT_2ea84F1vqxIBp z;_m~H>6f8ew*1uz+J$yAvS<4;dEF08L%TS?V=*nY%ajE3*$G$$_jLWLwb~iLK@0ZF$Cgg7 zeR2qGhZ`~2fi{1G5YO;*I|N#}wj8udfS-G`Gi>-frr8hKknrGmaeMk|hOB{;MHz!5 zylcP1HZQ&7aw2F(4<-q-*zCwH;SEt4@bXVT{bF%r=ZGBkCEh@6X?x%msbrmy#|OGx zVodMm9=3dDzyI$4K|{P+Z4QOUjBXxi_g@}{2?ZDR{dYa17}6Pd|M=$DZ~KS;@ndI6 zW7XdLeYa9}U>*o&^gyOeSv%zU{>9{W=KX!sRv_Cb; z$fuZ9At8}5AanD}d^HBm0{GBxk{X+h_;BaGWtxJ4Apy)%GD%jF1c5JLun}UO76y4* zv-B;9SBj(fW3LpcyBtX#sXM^iu;jbT$iSq{q`s$Hzak3Yasr=>T;BKw z@+m5wCrB(|-1EFkXY~znf1b_7X609UX5s_F7rQ_JWTJIcwxb{K7 zT{H`8OP>auaRl}=N<}+BYwOjEgBls(m+{L9Ir_c*35MH+MBU6C%+LYOLX)j8Q^)J55`oC6uoPQuUhSpya z|L~V3z}G(C)Nsw_#1k29dw#9I`Dv>Ev&?_^N7?+?VWRG3xLjjqJd$4Z4MV=jqf(ZM zpUeer4JolMj&$-ra04m~DN%_GX$uBg_Iq0|l=FvNfYPf(7pq34ejeridaSwz6f_~* zHRV-;6+8ZrE>3gpD~prhQR88dy{Q$n88N%PmW~yJhA2DEAzu*dOp)+fgH2T>!Ftz! z?WNEzQ}kw{XBn4_)w<31kn*T7b%(_%*mLV{DYrnaVRymHmlSPq(Drd6Crb%yb26is zlNUcU=oNW7d$nOASE@Pn^XCdnM$Ta2%DE6&9+MywKM)T{#E1`+0fLycLN7ijzoZnv zCBsU@&3OFQ+7yq$`f=a@1te2#J~5>pF30wnql2TVpY2>XVK>2JwkO^*$l6Xt)4pN< z1>0)RY;HVyk)-QO4lcV24P+Q>lL=Gs9dia_K`!Y_{RW_(Q>#HD^S4fhl5gJ=rriG( z?e}o*K&pt9(ts6@qHzexm|@b=%z+V>xI!gUi;_eiq;7zCAjQI3xv7y2QQ2rP$w0w(=d#HA+1EE`R1gSc1 zKQugKuUF>l)lT;ntyZVgA9RVFt=UO;yXb1%0kQgajnmM&Kn0qe{`>8D3)gJfI91_X zYj@^DPPl{kg5r8<=jcrgLynqacWs&}v z<=dPKJ6fBn!u56(s|*X^SBR9O*ZccqDgi$Eo|+Cxvnkc@bh{EWg5_dc*89RehemO- zx`p07{<(TzW#ciS5BnqLe%_@_#Ir!k+MAfCjut&cMJg^Tg`SZuyA>@@+=w9lF1&3R zWP!P)7lPdKYLjBoA8M|=eV$7a@L>=2QPlL2rhvn(RZE>hb zJtmE=uuHy#Dc&_2q1ms*iYn~q8~!`9Y?tcGc1NRm!)C+EKO|EdGvgs6;ze;%9P`mb zQgp&Sr>I*7eSNI{hB{}INF9$>7o)~t%MXtEf`m6djX;7qH90Z0@>We&`J(Hb;!zKz z?R}E|W!f93NF{Aw$(k0FT8ejer0D=&E+{n{4U|SLtJz2NznWKn=9Dr zid5U4@dmo2f8|zFUQGF!+Iwcn&>te&&d`F#?9vsFWLC93r3xH8#pD^PbW=oB=!V*R z<0iH-Q>nODST z0r-$s|wz)yD=6$v~z!TjyG#4VJ1Aqg}{q1aFmA?1Zw$kw9KPV%t|sxdnqY zr2dB_<2^*##qv^MO}_-^1W|f-O||-;Oeo{L9W@IE_*1fou-LJ13M2@-Nwwk!jW`u> z_d2!+n`mFqL{Z2giud=Anw>>Ws;!?Z9C>)~9K1~wTj+-+2a4`9Ul+{R041fbY{gCJl)UeQ(sJcg$QQ$EvfSF1zP?IsJ#=D&aiQsx zt{HISt<jh%AXauhvh%Nx2L|`Juwx2 zb@kV%q#}-$K|a*{_hG@ogzzU2rs*fv0`@tjvCgWYY`QU{NDkiNr$jin`bpvR2$aY&FUci_e0r#nDh?QUC4U%n+zOS@2= zT!NIg5*F>8s=TG3DY;3oXeeL*8rx6AGn{qtP~~+1i%7l7H|7aOV#d#nUW@Jw7hiN z6q>HUugI8d30Gn}p0habE3i(JVc|)EG<9fgwx1g&8uzxQS929*A14GsbYt@m=p>Kc z2(hnCU3A|5a`LSv$$d0PRFBVumq}_}0l$Grd#w1?A2n&$%@^v`F&Bu(88JJPijd8x z4l2RLdb^7*+?)?2R_)Qv*;TC8$-;50|Qx2VrO7hI`cY1(5ft)J(*-Oq4Pydt0s zzT@}bALU#|S5{Nk7tiV#{%1WT!%BhvV3|}LOLl1LRsB&glov@dRh*sp9Wz`f6eJ9i zwx8gsel+*9Nyue02W6qkZfPhVx!zU?LR|=e3K8}xR3Y9*>MfM3`#j`#(RAl>cAgCMo4~Fgv)rs|zW__(9&xB^c!E1abZG0kcoLy|FpIQK zcV7&3zm_UQ%nO3|rf2Rz0wn&b)TxWez;?PphG? zZ8=e;b70~wj{&y%KA9n1m^}OVd_|!0#FYs8OS3tOq+kaJ;P2WSU7x|8d1W4tAoqjp zMt>q0q%lT`?d~_bAyz>xt(K@vw%sPL4qRUSp;i4o>o44LcISnIsf>V>$v%d1!(R5Ee#E;COxVgiwpz@c&ng^FX+ip5&F zM}nzmXn{?n?G81{3Jz->PL39+>wA1eze2>p)s&A>rp;>Ia<0=5TBljOCFr{`eR`6M zQb!R6cbXa+{{Ya!#a@9nJQf8naycrjtcN5f)*J59TqDIMc$)6FKr`B=M&6!LI6@Uu zZ1sJjO)@e?aWt~6J#B9BAZuaNBOr&mYG?NpxOt zo+G49;jGnlS{6qh8c{s!kvIY`p)R(KNFV+}yLFs0@tm9<)plLL+HsU{C;f>O#s*Cr zqvZiqzp0rs$%S6ecfD#wm+dNWV^@9)>A0|%JMQ%GH`%_2ne681I*@J!%wHXU6yhbG zh~$w_-D0Kzs&EV_&1E01%ukTu;U&tVd~!Y$SKI1a2gVFg1Ucd`t@BFus(xqw4Rjh` zwsR-jO9*mqJDnb`A4#4Y`%T)!g3|pHk@)CVq^2v1uHIVq5wRlRD(tUZWs{6YW-{~1 z1n4}0W;P{o!GEji_<15;0-2TX@oFC#7f>OAEDulqQ65uwzBy0qjbM1zV(^iRYP%Qm^ezSEl(1z^}?@}vsudVAp!{&Jow zAx8!&O=dN-{d^vz@vBOv61>y_XlbiT)b9;HZO} z3$+q>e$4g?1w|A+!~XIZn93JR>2sy5e4Y?Cu(>wiX;%LR;5!AixRI=!;JfmD@9#Sr zRRVt_MRHJ9MITqu4YgXM8L=jaoh9Kj>ZRHbjbMaxN*id<7i)d{nL;Q3oP|E6x7@44PQ`yZdX4l^|7XK(?bWQ&RlFzB zM)I+3Mu%mjpA%^=En$T$&Z#yype3>;<3PI~OZG>K4CfHnpZTSp)72w`z3KViF6xMn zV)8G}L4{!a=Zhq?;{msr3m68f4EF~Pn=!_NmP|LX0ZeSK0^#QEW4D^hYsf!)qA7Li zFmJ|AnV*NV;`z?fEzc^sfzIm+;}G!lUw4CX6+^hC*IC1c&&vRvXLIBn>RM^M@G}iwbr}h! zpJCqmJ_Sqy+#d~df7^2Hb5#nRq)-$nY>ab06dtsqlbS_}i{n~(B$;|FgPRBH#<`ly zg-j&f_h{@1MaDzMQaYXqaxRNR+RgLqc4|3o1Z?L!ZM?)$P2t`7&AS@rHj7i5mxpWtk_EvnBCf!av2+yKTO3uA?+fiKlaW zQJ=rSY1~vy)J86PkgV+@frc_?OqfH(*{+`1=;u}*V-lR7_-r}}b5?ceAM@>#vquT| zZk3IfdE>0TSfcEo=l}Q@1G9Yfo{$Q@YIr3Pk?DYPiyR5MTgx99Hwe4$qH;5sz0u;i z)n9+rznmJkidKcG6H68`Dggt zq>y#7)j5oLTTFR2;p_bkQpWOFEU`P`K~33fToKiD2foC3{?CjnAS{N-^lGFeOHN$? z*hdBDAN}r$_~VHOD~yWr$eVN)nc&AskGlV2pjj=A$Q-{XwB6Eo1hiiR1}G!3cGU*- zbC}I(DC&0nmTp;rE4Nl|7KVA-KV+Uil48m;P~P)qon1y8u56~1 zBr7|SGX?}50hykzLIO|zD!W}3iMHAMABfI!4=Wu6V3r_Jc(Fsb=hbeM6hm@jq4GDsQxvhFY9eKWMhoFpL$@Guvc`?@pMRKs_ic zwc@Z=;t|~O5!4x`O~w%Sme+rUz>=mp8el_1U;qk<^+=kXFa!vOaJUnY|8Rh+WAcRy z{aLb{8U2_NNqgc9m$5lDYA{~l2}R5Ca~Wf>6)yN{yX=ED!zzIcgZ!6<#$Xcn{AYlg ziFWAtUxcEza;sY*MkNx&f!fu-xftVvZaup1{UhLfO4kL*9N2ea)nBB<_ zvYt%0jeOT@V=D?0)jkc`McptXVFYcX@2-scdv!OH&q-Qbnl@4ekbOkgFtoLy)Kb9t zpEd6!-(zVCCRKqc8FjBNUp4?{x4 zUfZZXZwv1?e#_)J%aa46+|xlxcAAuZ%bd=Gd0laA<^-bK*W_-$l$@8)x9dXJsaDW?Gjs&PWuud0NwK&NKdA#$exU#^Q-j`)^zMPOBj%E(16JIE3&u z)3Z%!D~O+6;8`v7`CToyJ07|g-6#xF%Sn}YKi~vbM+~t z^Td+3((CE`-M5U>QDVrcgUg~00YiL$;Qp&NVQi`msMV=|w zwEOg49PpCGU+>&g7F{>u;OQw~T9(eC6^PA7AZHUwagy>6r=7%r;s3+jdj>SMt?i-- z2q@JGNDE6;5ClXd^aMo2MiT_2g;6hIlgv5h81H!JTc0N?4s`L)ril6nEH}krupgN}jC@3lI;-<* z)HmPr6sA16;v`!3$G1PGW-nD6)TYsEOxrJ_o#HyIK~#xF*&9PA^&_M@V(oCBw;#+q z!Tko+`F);oxg2_b>Fvk~_i69&E*v7&$VX@Zih$J_xsH^Iy@AN<0-Q8YJ^m8Ft`3_DPoZu#Z2He+GX>*67inclr7=#NN&^ zf1G9gXmStGf+?m^el;_m=j>a8aCk9J&BjFC9{$=HwC#^fd@^i=JdE7}X=-6`*rLEH zL_{2flnI?@{&s7fa0>d(n2Kv_xHyEC0qKI49zL~IcZ3>sSCHv`wb!PoRG476oNEqqhr;(p@F z{RC|v0B@Iz(j2C}B0O%%4lJEz5pRK9)@1Vfn>m#mEn|1%PH5ngAu8-lXf(;TjD^p)Nx#7l0Zq43>dQ zS)WXU=9p9`*IuN*d}Jl8Ck{Ha|A4Zw$)J;EK=?$TGvsolSY2;E-Ng3FG96G7LN&}n z!GX-H3}@8XZm~dc7dP+v;zSf4?_E3n22*8Z!uI?&?Kp$06tFx7lqdnh0dT&|oV0*E z6*{em{n6f=&q5j^8O82D{13Gx+nXMdA7*zD{B>_(lEdk zGLuH4FZno{wgs&!9f3g=Sf1OQb7Em&y7^9HDplDR?36pfRVb2w)vqIE;I8#V3b2_0 z#@v%=w8cp~w-XsD#o)Td8-KPdyV80l!0veLGZMiL+|>7vGu*F9ibMw&?h ziy>3Mh;(Jo2}H5w5Kfxz-+&$z|k7%2DZxiV#hbW zH&61uAs)-D%j&CysVGlo5@L?}j?_WZ{l`YT$G)o5py_Ix!Cvz(sdXOi`0)PnE|m+5 zcc}brvS(6UB3kaq_sQ%nowLnDCE?C&o;;D2?|4F$%JK8*HzZB@+0|S3`M;`(zi0iz z@ULH*f{cVew;!qmECtek)%}Jc_{wSCgWe<#-r}N%L#=VF$3x!V>Z%NQ;sZ7fEl_#k za*chbloyo_x-dm9w{V?Fd;qvfDZy;C$TYQX+JHwfU~P@m6Lq{&ch3TTjU&SbTLi6i zq2#%U_CO(<#YN>qOvyi~bc}Oc{9Y8PP}S^K$=$Y%&F}sgVs(*crP4t{-Si+Oqt)=0 z1xe%Fo^t_Ll`DnE#iGmEG*5(k5rFWP%vgx$RcAKP^ql`UR^yZO4PF|P>! z$D>Q*3Bkv^pRt?Oqt7<6%L(<%<E&$m1+A&odXu6hg$m+ehDnIuRQ*(8YLs=eHRI3Q* z>9Eo>Y`j1(KtOo6ZWB=ED`Uj~SdDR><1*k%#N)ax#@oBjQ8-@S92D-XlR7ND5uokl zY~J(^0QP*l7s{%}5b@o(s$28MsRMObn;y35Wa)P60dEeD3C;qL(N&M7-27B0<8_q% zHXum0ZwxHDXE^rK!ZQpDo-H_Pl1Zive2NH|lS@Q1Wn9dNJ+fuTOg$HD>wOm|90 zjS>qY2S8|r7N^w;x&2_JRX+J9cHwKYyo*D!jzm!(WT;*64dG0{QQc{)6^ay91Tx>^ zo)nA{7);tI8q_VQ;1;pywoppuuaLtM9UrwitS9HIT@C0Ley23vjwnDzB!g!q7stTZ zZqR%_#s(hpE&Vz!6CBhy(OTZAdQPok%=rPR&sr&b%CnnVRKsIKFO{4X_ZUpRT2ZYi zPfYXeCDfI^t6lZeqpI^c^RFGqr&fio_WV-x>{KO8%kZdZ5S?Uc2W-J!utA;f<;46n z#WZKaW3|9rMo!|r)>m$=E#HK*Gvjd(x_g6 zyxLE1L{7=C|FJi=fBi)oEbWg~7neu3%|^2g#`ddE>%~i!;)XmW$+Dk@^o_SxlM)h~ z`ks@OpzM%cIK%U@pHmCeHUH(bPacLUY4+2M)BNxJNT9wuw9l-F2we(QZ4hZ;HjhCK zeU9|Fi4xFtoF*l9q5rjd`iJUjN->^YFJ|xde>>1@D16~SN=$E5e>nrr^|~z0bku0& z_C2n5c`K*A&t$MX9IgyyRv9LJsC$EB1g<3MD;K#ac=u;i4|~6^Yr=(wkw*}GURltp zv0N8Xj)DQv^t>X)%BnqpxJ%7)S$h#g&3I(7(zre8(GWk_l3eO+F#4ouAJ{NBB8MrH z;0~mg_07~xkCZWv!_0C7M}|o|6owd6mqC_;$i}*L1D?SEzVNQnoglrIuPEd@`QUrr z-*k!zv#8cTdU4^B6D2Yi;r83*@I)4yuk3dQBTzP9k~8`ecqks0k0^si7On1>JdKGu z%u}B4bfi6)cScF3WFzs+)`)jSUCD@YF=ukcLTG93BVGRXIv>0m6tSg9;W!H2>(40# zNR4>cj9?XAs0xHHm`UZ&uM!TH>X2aIYz8TAYhN1=x1IPSoquR6h7F(5|c0Lsrd0Q4mNwBXNCU*bEDHSC^WJvh& zAd0N(+RS@`$%ygAQzz#8CIwY$?&?axCllN3`b&wNH;K~Rk&+c=C8KwNOu?V;K7BJX zo#6vG4==G-4=@Q#KY`PDG<%W%O}~x8$$Qs>LpqLaL(HJuI6Q$LL;@}|s++uL5#e|# zOtz&rj7pMs*h+w`0KH9P^WG3PqOepFP}ZDh^VPM+!(1{-CYfktoANA zyXoXDCee`NV9qwSSi4ms>+}uIvdhPGkJugknE6k3iMg1=`^rfxTnAPH`>S|Q!M*1? zg0_OkOqWk3%u3w56f8ckz`o63&ud#1%Zo~6X|>l0E(pE~%yY_2gY*c>>}bKsFZzhl z>r=P2>Ox?4awlAH@7?AWm zTn5p85m*TEWSNXs&wlL-+E!w5Tf3|#K_zjRcrUOtTtl#~-z4art*5hYYWwcz(15l{ z1aDK+q2094#<_q*<8_$u*JI*?VqSUA$1{Wo4BkhFKj~!@ENH8BFJhNHh3!gP@(e*? z^M^pjhrmvPib5ati)Bj(YF}ZB=;r1x<+t9S$azZ-i>={eOev$cU!d~7rOC*#oTu{2 z`a~?4ixHx?d}cNyg$R*~#6t~OpOEK!T2M~$*88uxAX(1we!~WUY}M;l-aaWM@m(1% zJm?m)MXGsDBl%q;ro!GgZ_mOk%|1;Zd(bL9F56MxANY8c@abUbl27v7G*e_?k$-$! zdA^IWVBNrvpz>PL9_3B6qd+9FpfP}zwj?36e;uKM?lD8wL6@c_Z8m)qk46a=>4*qH z;_1ulsqQIr7xM%G(=+zuoNMogXS^Ov^6U<|owsPco%|-*=T_w5d6Zdyldk4OkYtvR+dD)jo6^{eB z6WsSa;L}UC=Mk^*y*vL|_nALQ-^ZHwKG_&3rt7|YOzZC%ytT7{2=A#(DW_el{5psp zk5Yi%V+;9Qwv+O+H&!6yCbTNduK;QF2qM~T-ajOgP%uT^f_=dK(ivU3%j^8H+{?9!TD6A z_6W>;@U`TQI#wo)0*+^$i7d3);KR1a7dVNynKJOY5}>ro=u1UJ`Cfb&G74X|6$hzs zNJgz{eg>U3)R(9GEdk?WyGzw8GR1XCb<$4D<0WU2E3%ZcVKZ~#TYOoK370TV(1NMz z-cbxUU$v4b_oyT8V}SeNnx9AGXbQ}va`VT^q3QKpGrwx0+t$>}QYL@B^G&R~b{-dl z1|+#hBwNgQg$&0k)q09r9b3OuYx<97i!Ym8cVxU_)tn-Tl^o#`?+piDws!}bD~A&S z>}>M@zKQeZ?cP5OFg-w^1{93G4yD(7q=7BeHvv=+=w zV4Wd-b>HDokjL|u0d=gu3o!ZdJp|yzsLL}Zp;w(;k)fvDf}0)bHq$kZPV5P1GX0?^ zYfj$ipx#m5%yfqx`$~psL-8d!|uF__e+1N^Wmx2dC*jT)$vg)g4m$C+3ryd zC`e&pNt{Xd+J9My_47$u@yfg=7@7D8y*{-xxlgTfBv+eEu1u>F;{7I9)|GEmB5373 z)N9P1nsDIP+L@;Gbzs2P#S}%nByzBw61m9!ymKo(uxetYbZ<2fd{N7%8)hq-R&Q~z zXvq_;FrRq;VTr&EcbtG%aa-VRvki+r=AFs%X+%#NFUfi(-p|p+g}ocf*Z(Zf7efaF zXQ{8S3#;nY9mdRX^&KB@$u{&+Ic6ajpsJJKr!sUAHFtB=H3g6H2aX$f0&mC3p>p)9 zfA8xAK+rK}Q^yAuUTM~C(dWFV>N2^}rY>`JxAKrnDWhcVIKCB6RhQpj^Bhzm9pB>a z%~yW*Q<@|43N`|puw*aPe;u$;vLrxbjLrIb6rhKNC;7H@D}S#a+N0Bs)mHMzTSQK! zUlJl65)m@`GX|>(s3H!DpfjP0uGfbb!flV1mTwiHRnY&|$@^_Zb;~imrWHGQ_6+Wd z(v`^Lkf(-Ce76hwg4+wYhP~0u@TXd3!$wMh$-D zZWHGs+u^zag;_DZYmu_#3ZGaW*IpUqt6K?u7Z8-^a>Khhm*q`q z7i6X$QBsVlfqM()O5Gr(OOS+c04azV!~gn>()%|@e4XU27n z%!^k{IBuWQvXFmPMG`$XV>o{^acy0W50_n}QR%_5zg4ZwynNsaO1iknJ5$t%Yf70F zHT%xW51zL*Z|%q~WjeTMJW9G4FsNryH7w6uZQ7!(TXjgMgyXl(b#G0Rrr@eQE*jZP zicfeWkhs`L+^q^bkzKC>9ok(kzU|23buNt0grW}(r_tZGQMVqT8vo{p_ukyJp00|w zVAT{VCh}8N(4xkIoT>8sWrv-x=rfshq3>4ao^$bQY$bKAv(3xc*MeDihI!*%bHP4C ze)$+4aadXUxTWFZ)W9>}hPLv#4|y7evan{Ya133IP}(jP&v5rh-Z8VPCKg!&%((xw zw<^z7Wp}c&rALu^N##A5Rzx_Cd%+?(FBojc$}sWNghk)A@I4@zj4pW7%20!mgq!VF zpX4<4)DZxOZ@yFF-j?(UJu}2Kw3)~`UarXVt`*UAiFWA1-V_bI@{-LwoCz_{XqT0` zRZ;!+Ov{an=TC19T-&wbGVF}^xW%1*8SvUa!?ODt^<^Re-gMbCDlao2XvkOX;1B1x ze_{cE2`3I1(=SgGqtWE^Y&5{dwhz(d%KhZjudLeLg5IQ_Y(ch;t|CbYk)642 z0((x}83Y+000Yi|psmb0vfS`IjZn9KY-uJ_PxsS#hbUZM&07s+Gc~o{@{r(Ndkmm7 zyN)h)GjDBeZ+n^s-aQd?oqI=mH=Uz#=~qf%Xxp9#np~LR+ZKum6&B+IQY`%!;<7&k z>WtL*rL{Jk1dYkwC6qbh@1%Ywgh5pL2&Sh>ScmSL^#vZk>kpEFE#1{U4qWfQ4_|gZ z=h?5agFGwC5pln|#M;uIC(-QI=Xo~i1{^_BIp)H=vm0&u6Ba&Oc7*O-t!*dVe(Ns` zM)tpVXp#Y>IC8087$2Bv)I#las}^ujr?6HxlkoN2o(=r@`}M0I)s)kde1O?ybPoFo z1wRK0z5VssJW(D?TEuJYESu7(dX2y4V{%1oc$iNCm zd%Qp6a+;=!kQz+5(d)ze(>4e0>5 zvBsn2D;amVZkD+@751CzSYUI*uX2kAj4qp+S~L;W^^5Ws>qu{{S1$ECS{ykchP^*9 zn{J1yBI+FkqXqYNyQ)ZKB^xOa*HH2t^!-Eg)lzrQNf2zTEUg`rKw9u^@sjwsqmJr# z6qpE?+k>w}gU;k5SH|_j`zqCCUh7tzHE_B(f7U~jU1KEjTX$@rZ4TGl2gE!Wl5{kJ z(ow#$*9dEz>FsYQ@*UP}nf5T>zfwI4lO~lrsTLCTN3|t*A9uFg(Jkk--3p%=yD0D7 zDrkEz{Hs?4l4Y&66C+8;-SW0vYU6GvvrvcS>O(XG_fsOLN^%fRe1Xh!3aiRI?WM*! zD5lca2!$BDw6CPa42L021bV3OZiE)0Il?38E z!e?T?S7+OO_@f}Y5`wC6yWrRGc=3UZ`_#&lWX)l8E1*Nk=wTi#xPxvAQ5Ziqu>vI( zssL+AOg~M$suJAvC+Hz{2ezRi-*6vpf<3u(jBYC3u4i%xAB<5yy}J1afP-y{8!hQ( z_NMCTpXh?kL>ie8Dmgb7uDSarT=Lk&t6GoqK}pET-6g)6Rzx+MLJm~weRM(ux`v_c zbTn_vrKmBjM{`2l=bl^lrKw8t&#sWH$;C8s@tS9@a4a3g4(o!>bI?MOIS@T(cA954 z$zWYIF2bod)+VS>uM1)Rmn}CrOj=7#QmdKRt%D+BkgbAS%Y&t!1e_Q(Jhhh)4iN!U zm5O0ar<`7DdtSAP^rL2Lpw67DCuFHktR{sAg&%cHa~}_Q^JYPJv#3q(;|9n8Js@0@T!|g#pI>Rp+t}$zA z@ScD!X*B|}u{+)HW>N0FVFQ7~bt;AuRVxND>lPgCmJK#kGoC(IwBPr~d=7{-FZ4W$ zFzuXXEQ1WaWd-uPY9N7j?v-8x1WbAG4;|~I$x2U#rrGXB93gS4PM8+iKhx4A3I3B< zFZR=T&tjO!{I66&+UGwP(T5HK#G6F_du;mv?MPBclH%~#U@Vj#I@ETYe%*7Be0BYz zNBDizJKmwn#eA&x-B&w!AKEL-6r@4Kd^FmGcwyrzH9^EiHmoQ0SaCe1elHp_H(f0+ zlvcRPFH%=>U(|COK9fZExq7@5Ke4%cZJ>J>AKe3AUh#y#GjVW}1>+5u_ zbpv%icSV}CFvM8mXEDmTd|QX>R#K1HvZ=jAOC8W+#5_oiHjH9>J9eWzHky+4QG=WtWJazn^@ArZMG)o zs<6t7^9=H1faf-hx8Jbgtoo-JjtkG1w4`zF9!S&q5f^9?Sr1Vg?Jhd#T0gQ*Zff(b!oI&e-Pm;mI^mhHmN0D^3DCcd^wBA0qg6;G^ea zj$Wp<>0NKr3#zi7?eL=5m_fa~*lz&y$9CfuCRlmHt;a)sh^NLqbj8|wC@ja_i-%&| z`w6uS1ux)He%Vt!fv#m+vpeQ=iuf8@cVZ1o&nN30cwPdb{%Yu4{cyru+O98iDr10` zZc=qxR<$RrtWW9)yb-YfYG37O|Jf0)Qj$6|do;RahvTY5g^!g}E#h%r} zH%19by3{0Gk4l9vZAYH)3^j#6Mi`UwY~?ng9}?Hn7vEFOUNK!du@?6g5TU>FYHG|( ztoj~jMyX#Bh>cLqc7~PIq(Y% zM{9S_4U57Y>S5$@wE(Mge4Dm;wS2ZsefleRla(vwksdIjY}+U@oAn4xz9?@x*_Gb= zXbB9T=H)j{iXmhM^!Dr_ZWwrI+iM9_z3JOpQ^{G%oLpArUCY^?AX(djA57p2&h4ik zp50G(99xUmpW~pqJ)upIJnGi8-hQ2Nn2e!=O{n!vroMH&#t)yEb5BStn*w#2lO-)T z-ucZWYs|c=SsdW~BK@Bp^!rf^-ipwNe@{v^*|7q)Z6X_r?hm!L{&?26Du;_ZE_Atz zEk(ag8_QcUl8;NQK*jvLkHmcf%{@7IE>9tnAp0~9*KRhov>uhWXLqh5`zjF!&{4|r zz9_+dQpC4wqkhJf0{`flSB`7sP3E>jvkl9^p`hBhqe4_c9UlqCzp(E$uA8$ihUoJW^klvT zK4(8A^wHw#BNwGlwz@O_Ua)^VZi$BU zj9sFktZNp-*Cy4kfY}Zo`A+!0>cpPbFudqMCJ*adE`;mPZtbv+v7o7rc*)wg@7e1YHZN>}%_ z=Q(Yz1eLV3tQ6_NL6_L^eR9f0%}{)S>;$>Dii#g17`dW&Y%xy?%Pjswj*+fD6B%l6 z%O)!MOa9IE{JldpSgpM3%+#ViJ6gT2{Ozb>zFJcxO; z>{vJW=a2Qz15{rM7#E0<3m=O#DJW%s28HscY_&_= z!BnVFHox1?w?%TiKB4K**1oaK{-frv^ZmDDAjHte(3{i$0QQgnV9HrLHcet;;yCyp z)CN?>E_6M7T3=P!T%xIiHTdGECojz7+v%msB`TyL1CoE;XJ9l^x|r(; zx=x4aVRygW=08f&f6Zb)A3cAMxK(r?E%>*Y{{3a91mMMFLv8Gn`~aND$;TsFSo@If zkH0TZUkP*IjroHEzfghZ$eWNfCGR|lmhq6|3V~Uplahe05h}8_{D+AApHu!XSB3EI z_Z-B1xCi~~>-*;c3M0iORk~(fyC=O0U^az`KOXcv0^k4M5c~H*;BG3s6bgxSaY-^y zpI_+uwBqTd-vwA291U*&Q^>RI-zDk)tDOJ+lP4_XPoc$Q-mI{{wg{tuL~+dD_mbf( z_XU8$cyKhtG4&rueus|#8tT7aVnPnXp?deBqT7=9|BJ==uS@ffPn)R*bWOD}`QEYV zuQC3AJZBT-x6=Fn;i#;(C|=5qI{%#6?Bb^3eED$&%aZhchm?GqN^`c zay?!229p0a^Mp4H#t%2RQkWLTL1538Vz0kc)_=D~_{a2lz&{4BW&d3@c3)$Xf=MmR zwLX@z5MQ7iPH$ysW<^BCmBF80i{crfwEu|f`D*>pvFO?j*3ASfI`}e=sdy$4;@LP| zatnSvEEU_oJ)u=SK$tKh%p{tA5WNROfC>xR%watvub zg<(B3JkAtm3L5v^5~}fhV5d9yWVU{DQUSFTq!_pauYEy!aM4V~C|>$0#3Hc_`Ojhe zOKJVz4({mu-dj`ev%ddBZU>*J4S=5tg+#SSe|mB7{F;h7OWqR7 zx-*f>@6oz(j4+qS{hRejPW?<((O2(-l(`Z zedB88`K`@-mxsz#Mn&d|UnK)}LS6cCC5%UKh8dL>G^=^g|v=SNtihQpMbd$h7*P zqLf;{Y?AlZ3&BhY($j!i$io!^xvaIan3dt3EnGhpTIpVT-O{jqnH$2wA!T>8+FGvd z(mr>-{LwqM69s}adV9J471EaeX* zzw=o57$<2-9XFjMsi57iOe0a-zW-Ap@>CD$Hej?#*XeX(i9 zg)F$P%Vh6n=kLXi?0&Iv4G=`$?vn0GQw{7ih9RCbkAC-tq`-w^oj`uGA=?8FrQ;US zD2)2km}>wp27SS=F-w`z5OGk@rur-A=+TJJ`CWvbdh>ecZokNLJ0MClKZ5Auw4Y!O3CSa%RP4cAIQL-U1HN*u z6O4&VGZ0p2u-jB4CW1u}vBh=g zNbS!bWgdr-`z_uR{{NeL{g1{#lgbnN6V6}h`I67yw@uUT`P_y_bc-seAV_Z__vV3NB;2Gcb$){;_3@oMMks!8!Azc(OA9$|W8gojw391fOIyNYgp(XWFI zmejkH>)IYA$RW)5l%x^8`m&4;b_2ad3x+p=pNj;l{gG^&oNx%$Z&Wl-T8 z>d9>jXxbr~zA@0e2k;q5N;vO)5U~J>^3VoWcb+|nK~ugsOA)@>FtsHGw()qeeY54d z{-7RBZ`)Y&6DP`0Vroyr59(*AMHD)Rri(RfOOnPYKvY^ zLVI%DM>~Y}#QW1JND#%%<$&+%I1$&7Is!)zjZVGPm{3iK`-UWP-8ykiXXubT52Drd zxGWw_7N^!A={RW>W8GTwcip{v-0KFN&l5jLt2rWp)*-RunC(u99_Y?wW6SZBnDBSh z#E$#ID4JH6QujR1UXC65T0PohD$Pe<_pUl%rX}g^c9$0FX}n~?MK+#Dc`FZb;+cr* z{g&zN-ezCEh=WfWsB0S_1Xe)FGuK|!99o5GXgFMmKcmmw5|1c#ux+?N+#=#L?MHuF zEX+WTs<+oa`*s}<9(9$nwe!&nGd0<`-l=2r2JBjg%XA){03Xad* zu#sW0H&O8->#gjdP-JYE3yY-$OP>JTgK1Zc-@&fqgy8YqPuMN09l{(Nga|#WwOaN> zHl9UCNNfYOgFGaC!b)NB`^np_Z>3Fr?sw;1f3GiPQMmDz6;fnYRRn)po7J2L4LopM z2)V#@d6)On0_1;Yey^WmIvda+`dTLRAC@5RlaS8qhR?J2Yh8qY0l1wF4W7CST>~gF z+J@BHeP-l)`8lJbVln0VtMn^?IL}blO{R1f<`@rm-B)atjMo@))ItH@gPX{%fa+kk zafaOBs13}K8zZRMrb#?KJ;Fy=yqz)%+T2M5B#f#ZzoGY$+uqx;a zyyJ72#j*Lw;U0d^Zh+Anc&XK%@hPYHc<5qFuRLOF;%>2ixDOaSW^4S&H}lVU%4cnq z^LrDf{GsGdtpKik$8}tfLd4_lGqdXx?${~7cjAP9Qph2oF*7#!@%&&nywrM~SNU|- z^)Cc4pc1qf0T~*`*46v0J=?*XKd$hA{U{onu|&=JLZ7i*r8l6-Pn6ac1)mIwA$;g* zgd@i!f_S9wo%@nsEY`+(e%Ufy=Dchmg^7Jd%gpC>9B!fI?YT9fGdV|b)ZaDq_Kp{{ zB3f@Km-|~fe3#|vjXk%h6Z--)y%8R;PmM(|heh;6_DGt!*Guyt@i%Y1{39fYYL%fY z9x8w^I%;`!Zegs}c41`@_DMGyX$n!oihD;BTj2n#ned3LXi%lJFSvYxJRD!b~5(gnN2Q) zJztC~uhgCNUNaVeq!ywOfeu1-LAf3Geni2O9Hk-^c&?JfrT^h#{I_1|4d3&;4M|X6 z%0u11?Ej1UYLCI_;wX*@oA8-~s!l2UTCEEJWN;b7y62=M(Bg;T$=3mNzzR`niu;(Y zC>$TnKn$q_m;j`kG7EeF;M13(!4YNG$%bG*wxCnHAG0{`5X#4eCGo;Q%kQ&bHKC>g zwiiD}PMVjOt|@8nc5!a93o{cKm$#)){G3U>6q4%o$nLn5AJm*zvXHelPu%wY`UjK z>i*y+19G|?nB$itKF+ui{ME_~T;Ix@BuWUYqwpKuHuemOF1x{px^d-X0>@_lYB(Xw zBTK7dWqg?D^yvyGo$|~#OLXuU=dKQ->`?HWTR+RxWMG+cC)oKoQTm-fpkcDaS|b^x zra3ysBeB^p^ZbAg=xjexhs!)2f0s%^I3ZEwB{>pJI><^;&N9tQC*7{V)+nAx6kFkBnTGFaQe8B33VvYn?6@07#e z>`P*c3;50U3;Sfd+=%5}XrUGLs13x9Whj?D^u#G4WmJJHNA_h2-(}?1OG|XweehJ# zp(5@FP!w_V$QVt{YrxP*@xcC^LiJZ&&)PaJ*h-8$)*N2P^ywlkIR&74Qm`8HX~?b* z<1O{1p!5&A7R5qlQ-juOFr6)fN7$*OiS2w6YHpO0Oqd_dW33 z&FDbg4Oh>N9u4>S%*E(<-Ds5Qali_!I-u**bsD$Y_!{}!0-GlHL%cF=cNjhH*oFV> z$tbk)yok3Loer~vDq2FhI5TdwiAtxz1JD@1x-rf)RTEBh{ziP4JONdWR{6_Y|4Ul` zwL@!#5r2=H4-%~1RsOs3wlZZ6=5zU2%4$!7I9nXXvLZ-GWiF(^x7tVf=mHh!d16>q zFse!PIS-L@R*p^{O4TzMD~q20VF+kHyu7u6q8%}ZEuHINs(AP4!)$pw2-N_5-K5;a zEq3N{lfWpK*dBmVcQa zIry1h`e;_ODHjQ}ql`d7hlRyO7LbFA8k1oI5TJt-_fe>fak}uEDDq9R!vnP_nQ01B zLn4&bB2Gr_nS}ia_E(h@H`%O|9v|!##V&EZlSA1VKMDT0&e$aKd?0Ac-#<~RzV!fy!QviA%hO(OdnB)a<7Mmcr&n=)1@{F7 zSIRB&a~Qe>o=D6ewr)vx<_E%DZ_Iuu*D@U}ZM^_7Kh*R0TlntE}jB#YbdvU zx(aG9=x}z9g+rmioq~xEIdh;;Wd|4D?U1L2Tt<`~>B4AVDdce2Cs|P!!ktaR*xb$1 zfDD!LpNZQ|+br*NO&GcN)5a&t9<3{u=A4VCpB`JiVm9yi|zv2KD+74l14u@x};F!*RcdK61{?!5DZRurr<#u#nC9aoFPl!1OR zuiW?7r#RLb0)b<@63N0fU6!<)lRw_Ew7ksFk#^sMQAq zSk2Cc=@P@x7;_X*!hV(U<&Q7z%nIG5p72c$Tzj=xx;~`F5*DBGLYc{OsSj>@OmBhra0(eOfX*^kt<@f1H9)H#eDjJ zuDZX<0q?$45LvMw>z6-yZug{L1beK_3lP&-o@!IMSw+m&Zsn+@%X9ng5J6?1*iV)L zUxAr~{Mz(jMdynpp9|gxaZ9Ti5bo~WsEF8_n@_iayt{D2xuNI&k@c(8v7sd)E*H<7 zUUzd$sS2%WM^a+G_MzN7@nsr{ynzjCiek;}+e!>~e0M!EtPdm=rfW>>Ur6G(xO+0& zyb?ZK9jvuxe>&ECD>BqQRvv@QM7*o5iEMx1?>GFtu<-g@N9iYe^~hs#Mm1O0*tMP2)s;Q0np-;qnx7~G8_jOTWTJ0vZgn3{=VSmIqKgp?D!w$&g;y+= zPFoDptz>*@-?*yiPG+AQOx3qC$a61w`*5qTOG^tP-o-N-QI%gE>pj0hu7_iFVG2`i z^S(z&V@ujHschem%g?+#K-=GEzI>sy@`9haZ2zo|?PZZ18E{mS)_Gpm;L?s4D!WF(;6Z6Vt?%vV_=H5_UtgAz4ND`*jL|S|4AeJPvSRlt{>X> zlNf^Eua87>=`kBIF8+wv{~bRFafu1B0iN;Rx5m0m$UJAByqYg4#%6bALd1_l4J;$0 zf4p3LoQHt$2{Sh^q7_hR|WV2XwI+M$Y1p*#w&me&OO9lW?N{2F<`I%vmoAw4WfF=+b$#@k-3{4p{UJRr znrj~h$Jq{lUXsrc9#2()$x4l|zk#92b@*qqc8KV%yNAE(M7v3K`THDj@`q zN~1fo%^}eR0bk&jrM0bOLbH5wYBd*js?BC+xo6>!1?~R+S5H4__R;$VmN2MTJ=ccR zOv+cZMe%qU(xVwQQy6%qzQ?pOz|OVN-_A@6;s~#_Q zF67H4!vzgWL^tDl$ujc)1%XLr}0+O#oznr(L-R${RPAE*QZQz+%wJh8zW0y z=&=SPd^~fTVsVkncnH0i4&W8%|24gpBYFIh(b!Kmv#7Wd{L%1{G4?0Rk;i1AEU{)q z;t0H0hiEB<#NtHA5eWgtVKjb(CiJr|F~i=R|5w;bzi&k;sFH|PF6x(p4|IDh1QzYj z?|VHMrP13&oLjg-a}Bm;sJEqqZQ#^zK4V-k^2Ck?NJjTI&(X2Vvi8#!>*<6{?;^`e zi)5|UpN`x^3d=kXA5tGX9jRVwziB^OuK$@08q#=vlRJT(A~yd=FzXxnr%MdFKy{8h zu9q>NIrUCy?z9=L-ly{Tujb5szn22yzQG9BVF-j z_Exf^3`z5{08U=!dtahwfOu~XoJ_@PnlHQ9pfsqCspaZ1-T#*V#A?Oa?VOy~A(d-s z$887;L_cI6=ofCP`wG8UeSBz#ybIPyx3ar)0>i-8BV&8nAgA$qWEA3d3KyZ%P7_6u z!_p(NN!p40u7vlh>HX9{%D-rTxN!5PN@pD}UcJZm`P$yvL~>7&)Xy;9?wob%ET3d^ z2|Z?ddk4Bakt{-INRte{wJtH9fWBGvE`&fxL58VE6VMEta$pG7wMfh8hmXfcRGsaR zYTB3k2!)Y+lD{c7*j)V6NLnVxWWCR{ci-Tv;mV{xON>#EL%T_3axZ6!7@r$dELp}s z*`#EVuaU?9xS{*Btm1ZwoPmhRb9y|p48HYi$I$};ED$s^D~hskegBJXzflj_aM*57 zEh9;ah=8Ke#ZO&btY%yNn6*%)8{}cs_53foh8v2QoK@w@;uHl`#*!??t^xkEGE1d& z{q6Sd>zuAV(nPU5>7w&2$i#y*Ff92!qwgsU<5w#H@vaFf+l^u9P(hStqlEMh5C^M2 z%Z~0b4ZCFD!=Jk#oO^8NKj}ZyJL2f%j&&Z+X4WT9%2}*6zqx6Mq4gYT=eLijg45dr z@_M<_vlO7J$Ies-gsSmO;opLnRHwn{&hmYznUPffwSw}$Or*aa8vokL+7x_Zm6Xux zYna9U+i6(8&lKztSLu{|*%25P@`FLGg5|As#%1KUc?F|UIhb-`gVw7ox1szWL*gx- z^D#e`Uw*qPf$=f*k7Sh@u3W3D5u~!5&u!!oFs_Y=FFYM;5CfA#UoBrtN)T`F4~nyU z1hN^?Rr3UXUt&3tBVB59yJfvABc|lZs0EJai2OOSN72>Z)UJM8p$DB5QQ&Z;{-@Ti zwk$jrTpw7Q>wi9DXKUI)#Gg2(U9^oCERB;}#fMXvs` z5$y9E?yb$+$l`;ZJo~GrX8dA)kd?h{=SRNLkI(9n!*qHGsgozY7$Sl3bLB0qaGG zBZX%C?95(_Rlq;i<`vSj+$NTU=-ek>Ydv;U_1T|xdtt3NS1EQ?%oc3K{>fXPB0}dp z=b?1K>*bw>{UxvTOhq_X8B=baY^n{#-s{AbBwMSyRK&s0u)hWV>p^OD>FS$BeXy=M zXXK!^!`+LTDDoh>3PEcyV)?QJ0Z6pvd_T=98TzM2gvTkO~o;-S0C z>^xlYMbEl%4n`a#bXbB&;!AiF*nn!KF8m50skZ11czzEv@fCounJPxtuSeKirZ#7r- z+q1%Z+Zp@sC;ReOm>AfA)6Y~K>>_ll*`t9fcg;>`b;pAQj?(Eih0RN8_2~324he89 zD3zvf=pHx<{2ZnOdgdXd82ANaOFd)yM_1g6+0}>X3nDH;wf3|6t1`^;-f90&O~t?V z%>SxoNN{BBo_;p1a_-X9@AAHlGzMWAoUOeAUlX(6cLfD5L&iR)0_LdvA7$c{A;yIv zPY#nX3xOBDrA$3T)!ccRKR=TiQZ5q#+O!%dMq}Kop^IL ztozOJeR}h0LuZqD*n(Ohxt~S2c!JA`lQ#z&e7MUM<%ul&CNv@3+qp0l;ooRNd|7Wj zAvgY(U*X5=Qcq$hd!URf=5FFWDnSj`%D=d&(DtZrYd*s z^g(CTB#;r056;~%hT`N3RMS~_dC+P;DkoQ8q`Hm+AR@|{ycMOIdmYvY4kfPf75ByJ zu0bP*6aS?jfaxp@$NK98u{Q6r^+L28lri7?jST8-|Xd_V|DIyZ48^ z-{*P0+-uER^XXpKbzj$c9KZ88iKN<&|7yQL)T2Y2RiojI{Z@tLJMTkMU*eD9s6|bC z5E029F5u(p02<~t#pD3`HmI*WbfGBCV&KiiaPiNda5#vQV*q|4ep=KP_jB!=S}v|K zmEQPs#V*6Y(XUz6{Bg-=^ca703vgc44fz{tarx0>%bDV>uks=B@dl7FgEDQ4iY>Oh z%v>&c@nDqs1oLrX+vnJ(R<9s_Cm|`Y_jOvl<>Sw@xsBh z#!LU*O*lk03Hy@b<{?m4ZM_yUA= z5xbKdM1NhVpN_}|P#rr{=Zs*aNmk@55!NztNX8^)Ad@5k+q%#FH-+tz6fbGjh&y-CY$U9TCvRRgQw0Z;JC}y)_#GM0| zJ00JiN%`)by@UAKadg(L#kQR+$_M+7<61QQ6S%gzujs)yWL zS1y9=l&o)^3m4xYjUII6xihniM zfcvQ^UjOR-1FCOK+B{@TD@pQI1`9_dfu@1=iKG4QfeNm?+S#$(|P=3~G1dF^M|c2TM* z_ATG;tGYo8(6u5fF7evE*?V@YYJKwpdsp$o6p|2XS{4vGG1DmFc@kj;D>%8Zq`yC( zxL&BMK}d#HJ`BBBIp^P{AI0mTT4wJbuQi{k3w665)`50tnDRhwf-aU;apC!c4!?h3 z|F#*0M4HO{U;QiJi`@OMvyPH(_eQ6#>l zS0iASLAh`?YldCI$Yov=s(}I}6Ftjyhr^?2dz%IByU|LO9C~-%SWd+aIy4~rmb9AT zpRm#AC&WgjLeGohOppnaY{%ERK=zPYC>l@y8v7!*JUd529p~TR(vb{Fer8p0=xqBi z48@WxMOWFGx9`9KAjZ%`=@0{dIGvxJ;+5k_LtmSg_A+UJJxp||8q>=Ra=E*aQEn&o z;T?IaN{P%eOPWM{(kV6`RZ;yy8Tha_TQtOQqH0?PVV4U3R6R~CM*u**1`Hjx_BIv% zN@q!-SU?3?wz_g4zd2$wh{LsT;&BiDT|`MhxaMaTQrC%cOK^)wjoVv^@i`CtI=pT? zqmJT-Ktjs>FWz@MWHN}miJ1rkNchHgsip3yecipg^1uiEj}GwFr*5fpKqd#>>Pii} zv3wq{O+!c);NeaieAa%gV!Ru?pi}t$bZu^}Db|Rv1t1OgV=sB)1#&BU`W9!LBTEP& zVYBI}3%p#*@MKEUpEFQ-#(FR8I2TY0IvdORQ64b4;wn4BJFJI(x8~d~ypW4)bfZ9W zlZ`t}v~mrDm=zsn6hrqK*QZ-CU+3&{OBzx6@uJoG0y9P;W9vtaE-%k8j$AVBp z*u5`v2X+vv64{wm`Qw1&>ttJ+Nq_LgSaOj!GF^BzaSZw`*|f5)i-a*euQWk0xpYI- zpL0PUGDXoDqJMA1<{ig&umi)S_%!@}^YWh~P4=A$-;zfX4*thv9;}Zz1=mHhME~Q7 z147$F{En+nO}CNETNHY<+HNO$jvD(5MMni}Qe@7Q%J?6y+JRv;C7>DxLlafqvDhlnf63lW_BoGY;SUSZr$*b04;kSD;} zG_@rEFGdzqM}R*yWt@@4Kq#}+hw5$&w z2!E4Q;XDjl)nwBt^z`B&O?0PWSH5xtXcch6I^3{!v5;W*3kg#4QJ_jgA;qY@H6?a9 zfqDmC(O!v>wYe)6oz>}Rt9m|VeXOqS&ROlEt8gReolT9Ms1MCWvCv-^BmKdfa<3P^ zHV9PwjXAY_hjIHeAa^;e3(HO3*PVGNF0&M`W_-ZP9`vbVrd^Kr@`sNUsh9M$|Jhcg z3*^W2?194c`-Jdkki(vU=#)L*mFjp=yLG7hXhTMV^nAK=NkoE#f>mkgD3OVSxTUL~ zCSKk)Jhe#Km?D5Us*uQ+H2aINZnB(MvfIZF#6S0((;w8BG~mfCnznAQw-fq?g@MG6 z$a+X)(e=Cp-B*5xoW5EoIs9=>!C*nC!!98lp9>e3GBQh`ja?Cq->J>=$p1a{3Dayp za+Uak_S>5qHiaow{K#;_bv$S<;&FNxcUzxTkf0g9Kt$Xgq1bobw(2{iB=lQ={X$Xh zMS5MuKR}o3UlgJ>!7h^=izR_~bZY#=D)}Fry^Dh`Se(5XK&##mik{5CYQGunSlt=J zvQ_U|;W)P7ZFZqI<^GbUG3JW79Vw8tf*VtSGTwnUBX_&rV(31XUpd^Ls<7D*R$$yB z8VoHe+MUy&N+YArEIQgUgfvRKTS@`e)?6kBDyB+CBec4&?BxvGNBxmG_jngy z4BHg^_3hNevqSa=96Wso*sQL+0ch!e&@_KX?g{|XQ>lbpZr3B%`GKCMjGZwOE|H8V z(L&L3Y&HR2FMm0HQbM!3-?O9F%4LvN=eAn67~CFYpJG3`xP-KeHs8f;otWNo{efNx z&zDeK>oq6bu4)teHn~MVpU*IkMR^01ttE4^D_Hd^E|%z*TvNsdx?Vfs=+ti?Q5+th zw_6{{+$naEIn~NI7P$56yGMftlY_3 zv_stVtm93GL^!K6r6S1yVJ$vVAmdMmGAx(`+Eb8AI9rYLiaor!HjKV{y$)C*`JAr| zd3WNrW1OAa>2(E)rM^mBg>oXU@CRO#6sbleTvE``(2%iFUY_p65|c9ThKD3Ex>O-+ z3PrEw`Cpn%I(PNv&^7gD-{L8?#n7XBtbrGU%5^>6GR{(1uWV23cEU(bXcl2C+clVDodifwi2YIhlluM;bvf@zhI_2_b)}SrP%htJ==}ZB@1)y+6q^ z)6RlExv9qvH!iMw8?i<^!`hH#O;l=g2pM`R>t$joa^1LMw9%x!e+t-19z^6TwNRDq zpLN}6MUYQm_?zwy92bQ3P{D7<`7`4J?c-H?l*DQTQK!Rm`-F^_aXBJ6EIDxVV>sB zQA|eSJsYeqhIEv0jg@+tXh0&r7nzCk%x>*5qo+EMhvDR)J?Ijn^B<=i)i|sJQppx0 zIlLn;!+g%eyMim?*mWLWu2f1;1~GhWlqR+3a711u$Z>(saU+X2O8}w1)P(Ar#B_5_ zVAprDs-19`$RLM+pQdLwnMHCKRx%ps`bH%7@(sizxNe*%d^dzJ_2Ajp|D6TU=d@`` z-j<8|5*Pe{W-9%)`9-ok)=p7ZI{k{v!b({@RM^#SBdGq2yUizwNeO&l_;X<+;USJ$~M_E=kTW$09uY4>zVK3fe>ffa?Z`z}0?Z*J|6rWqR_W&m~7K zLw0Rtf%WI*Eo@kfcca&)8X-I6m4bx5{3+Snocon@#{v5Torhza6=oa~N|9U+K}03< z?N>LajTm=CeNvfgU7c>iC6Qc!#Q)r8`qv&ZFUXtbjZ+Rg+y(K}@ZuKJ-@GbG9;7kc z5d5+5G@K=sQflq%SW;49&eNifr_@>{+j|p>D*G8t9W^%uK}TS7)=T@BqoVtwaQ^Xb zQ>vz81^8hVXe3!VB=JO4MFluUWKGpXC~?Z{x6gk?%py;+CWnzlH)r^`8H*J6;5YbK zE`D_FP&hf(toPWQjsZ<@65``W)6*+)Mg+&C3QUZI4?}@lCyV~acW4ZqitjyOi@`3=eXD#Nde0REdjw!`8V}5z1|E#=Q`kPySF2zOd1m>#EMb{!0Q@jLCXurt9#>> zBXh0=sH zgy!yLvZRm6KasvY4zyWFMO_f&c1TM|5Ok^-j6A|tffogHn9L=KUr`-?gdJ>Jn?8W_ z8rK0UbC1f89(9Nz@D#wR=~B4#2IGERoFI2NAc?9${%n38jK;Vcm9T6uNbBL{_ql8Pdc|%_^pS62~DI(a7Q(=BJ3pS;@Y^~la+Bo*xXnq zVPfEC3BVl|KJR0vi!3C~A>yhlBS@N9>~#ea@N`D6>FpNZ=<8$Jdj#2z8|-bb-z}S6 zY;_wupsJKsmq7s8)GxZarH~(+n?AEJj@vv)!n9M(#ocnECj@NGt7`YHvk1*K62}e( zE6{GI?ea&j-#wmEG%qrpy4U$7=j8(LKK&J`8N_W6v>MFbW^5I*-*cGzG3?tgh&283 zzY*;IZ>q6@pP?||>Hj)_+Gfb{C)bSH$Ieg95nI(Z*~CYYj9 zTN&TKkwlTm{FKmq&aXarmfGbaq{4kvjX5XNEo?!S&A!oxJXI8~5Xlq)#nS-2;qoY_ zX3hDIQoYkc-Y*S}XB4A5X4#$kBos3e-I40Lel(7!>cezgSh<^>$g02g3^;X$y1lu< zvroK`%F%S6rC7_nHkA35AB3lINs@2bOmkLuSspy^0CvX`NcZcJE#)0VU?uADHQkTF zw}J+9XJvm$Z%0MdQqqMgm1IRnogN2Nz~B6Qz)+fXJk(r1@5T_+w?{gBxw65(Yw){6 z4)$@rDabYO?e$lOqy9m3zx`wOHXXzR0HmKa>LHSMZu+s&fxW8R&+N;kZsyfoUI30f z57E1urD!-_VPgFcYA56Zm|f_V2v#po{-{cpOH{(+8P*%`ekh?B$+K%dt_}rbE~bZ= zhL@v#_|2>_TtW3Aepl5b2H2Kk`fb=l)ZCc`p;%`UVhG^se$OWlWlRV-X)J7e93Ojr zzSa`J2y4DsndBeD@UBB^bp5T1i&T3Q+e*(69PDMgQZxt=>I7}W(mm+LwNr8iQ)iYt0dtgoFSGn3H(GN5s6sJY(?W~PB5<5#GdZp zkGRb($u>}he_+M0bOBLph^OBB3~0vJ?X3z82Xo8r62=jBM*u9B*)#NOY^^gsJu2@3 z?(AEdhaRQ~=VYVt6i<$kw}tdFB)d-tWlm7Z@vv)k^!Jj0X8ElNzmoUDGj5D*8O*dW zlRJ#wC7RIvj~Y0ayBN}egM&toCBo_gN%4e!L_vo5|@uzXc@|Byw9 zHtfw6DualG6`tN2W@(?W1V>70x#I;TdTy0|ub;UA6A$82%#CHj+ zB6u9?wwcmI{#6wny6!c1Ivg4$$ypR^z6(XkAY!V-tYyb);COxE2OS@W)HO7H*h`(c zW;%lL_h0Z69C2Pb?S*qSzZd1!?#R6)8JH2a+i2%MAA8R=s0uz_4#nfu(6Kasmr$z@ zv*y-She~`7eK>y!RwCvNr9##K?;v4WxV-pWZb=lCnlK& z&1{VoD!*g5mK5+l#k2~22;jRZ2Hf95U?z`*-JAj5g%}n-W_Q&j>^O-EbJbHy+v;ZH zi3bl3UOVQwCp5ExXEva>RgFww{_PT1(6IRK`5+0*u{cUNC|1`|X8`(`gmJxDyw?-l zWmL25ssimzUh@?{O;Ua{3<6j)yqL-MxJt~^G0zZhVdNC#)fX^+)%!*RfrJ_zot(u8 zj~Uf&QoytYO&SjugIf2-48qEpJLrcIOnS^VB^i$}hOK0NyVGOkIARp-NyFG65S}=B zhS&H9fJT~%*`FbY51z%GU03{lnj0?UuuDtJJoFx1SE+{RQAAfqr$!gte!167wMTUMSEtuFU4zNwnlh%qHTW9{rIT~s zlze&(2}c3npTgfCsG)= z^^&!7cnfJ3`ndLo$FEz!c*&>YnllA8+Z}j!@Z)y+ew}AP+8>2Y-hO<0`fCdA*ix|k zl|5TX*Dc^jW=jAZ@|G=p^7%<4e&)%@sPHz(+V5G80}{^^QIynl^bA7`qA;uLU&n;C zDOMk(!Iy71|B29py=`AF`}aKkQeJBt*EW6B7DuBmUA?6OCmT>)GP==L3%eaa_oAV>dsa-=7o&4PMnWE{a zuq$Gv7}1UqRd*M`w#o2MRQ=LG)0jW!M1X6oEs7kK;V10Gao z>6*N2wr5}{D2?n8gBhMidwOx|eKEq5PrGgMn$fw)^z(RVv!fCVzZ-gUu|6T`ja`vy z_!H2R>P+nS?KJ}>nTBD=kLwX#W%na&FmCEh&3cBM{axHF;%GVA9%~{yy-nipQK%aai@NbIu&qID#Wy72a?HNwN5>B63up>x{Kq7&F^Lc^=`!q zTSw3Oiycb(^OG|AyGj5e?7gxzx*{lzTS=FZ2I~gj@0p5WaE{iuH4sNL666!7yV7`G z|K#4H6Wv&u#idcbc4+TefRW`e0qXk zPNe%m)Xll8Vt;U6uFf61%8U!6?OmI#Tr#V{N56I~c2{ZrO>U_YB?Ec%rvT!w7-WnY zYNv(GUy$Pv^G>q|y{UxP*)CnTpP=P2N3@{s^-OWGms@`_EZrS~^IVwicTAfu(1qGZ z<#QRXFRm%N8wwr0EJ!9xgLMCFR z2I3lR*mHPCxRg6wLkNO@pOPM4oi9UUcl`g6DrVsvW5J8>nayURxSZbPk<5tS4&a&> zOZ8q8>#BZ?sUlMBpPdw5Gqyh>(hx{28)>;&qz;>0bF3dUMBteWul#H4TCn!{X>K9= zaex>WCR=Z>x#JkMGw#3B7Oneg=CbV5#I=nl`_DpVQ{G%4=XsZma?@Fsymy%2c7=I; z`5eG5hxOA)E47GHz`hOP`-^<{_SQLvI3* z3mcNd&CneYSk`%I;LSg_%;!wm*w1ZJe_X_TW-YBW0WPVLuPOFpCmy+lblv>%Q}`Vy zN;o+0NP8>%X|hJga!&~}@_Y5i|Mc8S2<^$&lyCrj8rtx*|24{9h|=HwY`NM;I*i1; z7&HvI*X#gpNp4M4@;fPlJ&fJ8MqQVQ;T+SPbzViCx`{*R3DOoKD-8 z@QHCjq;U?Wl%!N7pH;M2zl14U#+}W74Mt`!2TwcU+8N~kcd_u_nj1%!{}K!?N%L=U z)rc4``LVdR>=egIdQe0B4K_z?hs2pG)frPC1kP%5HxU#o$;W z<|?t$_{M{yV>1FML&*;(HodAq|CwHK0B@b`_0u`kU||2_hZIYOIoYZAWFQ)0zSWk? zAQG?-0`-1;K`nZ$`{DTTo6k?Km|?Mk zTrQ9(!5fRepdt--dCx6%5{1f19NWl#PK5Ul@850>E{9mL5Rt-TPzvu}XyE4%1Q{yF zxV^>uHT8i@)$Dl)9qV}mczc|rFya?B)>{-_-4@`m2a}u?4G$vGP_AloPI7s_5L&1-QZdNX{<4N#^(N(;-rn?IoU(48m-@crVixu=r9&{qGY;D_sQeI$KgH*^5=ohH zs%Zyag99%YW*jcQtgcprV6|tznpU^XJ|K*_LVxbGggG^n^nYWN=s<)ALbw!m-T`tq z!a@!L*xzl8oa36tCg5Hkmd&*giQvJ>bmzaPzJWHYgu=P2od$Q1n-m4e#p;j9&GhGn zj=DDlQUL4Q0OZ?1gXOD#p`HJey0}BpaG|$&NYNMgpWDBo%>zcfPg7WA&e6Le@XX0f zCr89Yxa(<$5^*Jo2I24D(@rBs*EdTR$0b?9JAGK=uZClkV5R+X80brC$swC?hP^}D zbd_7;j_s>s&yl-dd+L4}hiKzvgdXKKpS-zyBfGW9Z#Skn379SRm$BO&xAiP%x?$61 zr@H1cx>q-;**Yv3I>Wv^c`p>ZnX6zlCo>0v6nbV16nJD{mc-A=;_RrMzY*(eD5TBf z#Y*f%z~*r+s>!S`WEyVUTNN(hKb{`}+YHtMwHz7fX^8&N2_v^8AQQ23%%1hzmdEXS zU<`Q7!64<_*w;F6Cbs0P{IAHymIIXt0!|`L%gFIL&3^RkVsC(-?CyG|;xk9J2O<8( zH0#~blU2*~-V&QVC?|SFUcE7qt5HgW#owW2jO(N|w0r$D13HcCCZy@&cL_XMf4TX; z1m;F^U($U;hVw^=7E_g*)fVFt7AT~z#`Xg0U%>EG4G7-r0Os7H4nohG(PLsF-&M0; z?}_p}Q7iFmJu#vxmw|p4%?2fx5=>pS%(?E#eYYln{!3h-y7lJDf48Qfa-vnNa%iEK zc=(PQSeW!6Fdv3{V`lMi%k~hbfVM-qJl*UO$Bn89j5^muQ1(?Qd@bZc$$3r`Ey?w( z1vk~IL__g1l@^bSi>rLmTA+olJWn^d`OOvA_TlAX0+7G?mXmXKS_N+pNnt(ydH#1S zzbi{cH|E37*py_;68|KGG0@6LxJ60Fe|zKdw%7P`Qayf|NKp>bjbN+KjKZQ_sYjaw)5pwcKzapm(rZU^zwYOeQE)px%qGf zEU&#O;U{m=01Z*Ac9XV-b9GyCmtM6VonkQ$^Tsj~A%OKsfNA53(T}8}nhHo**@yeC zeNxzbWi=>~PT-A8W{QvZ%VMQ9OtOPG`eJ<59C?~FgUolx*kTwv2~Sog)s{$zc#R_y ze;P>lJqJXI=vvEJmKe}cw#pf~!&kk~0y*IXUAr0OX7bDDec85>Me z`t5*;`Hx{^9a9Ci0KV=%aUw3>c0&{lL6@^4nGoNiokf=+kZA8g`W&u=%2;MmGCx!H znQh!_^S{*P_h~y-8kik#DYxoP{m?k1fAZFC4;ci%l!cz0WD&3i@nvfD$f`vfs@n_L zM(Rd!6(wtJL@%K8Uu5i&O3Y7fZ!lr)T}XaD2H>+L2mg`a&=~&Wx^Tr>=i(n)4t?)B zs8Op>eD@Vu#e|+2*LIUX{vIO=Fr*`wJyPUEtr&}oOt>{o;8HS!J=%M;_#j){N>WMA zw#Tu=wv6;ZU+zogy@)Q4GgxQ?+VF~R=W;$6Gv;+4MQDJVfv{B~&2fc0D`ar!f;r;( zM&iPKR4?{qTj2wjoTpI`UlcO9Jk96r+(n2C0=)?aPL#n$oI)`YisMbXM@rXXj%v^- z&PJw4Xw=i9{r=;LxTN4eSWAyn$;Iv8L|mJ0@JoMIyd+L#xyxr4N1MtMn+ZiBk3Cg& zwAl-%RPytkdt zuMmQdUp-5-;Q`rhxvAyqRs=UGR$-&zZ+@4-(vH7{ls zqWV0p$I`5YR&Vv9)|`)LO2=6mQ>IB3HLQ4lJX7?2==tY`pn%cp(&~{W)sEppJ@NW^{iU z7p=2h(`MM3Tg>_T5GT#N7UZTI?gupymyjyZNw}6(A`T2Uc|VU+K3!wU2W2hWWm_UbbIIAh!#oT)9O_Z#^H6!7c0;`;PXZJe2MSigdGXdsjLE}VX9U>p747- zXKB4M#Ts-A_d6y4%2x_6lm{iBJ^s?cYmiaxDBxGj17vD148_qxNu8aYaLDCp({F9_dajW z6l^D?Khb3IwXw;uQiyOW&it!96hd9-^$acTE2Hm3A7T|Y+KpOGC|kvlbDm{%-vMLS zH8v7y<`0RHuJ3+EyD*HeU!nz9FAA%N@XT(2`b7K$tu#|1tOpDgs zvka*f5pFS*5t%bNEuAySo;kNr;{V>`MnqY3*mpcG#9^?+F!&tWDC*k#`bH+m3phI7qlGjsDJ-ZqZPq$N6R1nf2~{?&t5dGybdPxz9~Hds87#{fjY>3iq+? z5Mto2(HQ@L39*VDT#2RByXx-0)4F zb?;|eO#y9qX`CsGoTpFRO4{RWZ<=o_CfF4Vp}I8YzRP~euGYnyzYYAe(CBRXJ!2p1 zNT~nJRyEtYgVDf4v)6qwPm_6Z^WUBB^)e9`KW7*aVCXMKas<7UkVZGRzIv8G9un2$9EBvMr4!@TQwPjp z8(SyR!X`w-l6k7fbEXEMYm0I?E`CH^GG1EsSIvVy&799pUnf{&`qyP(oRJ9}PfWfz zNm(D2W+?{3Nw~<4!Q@JGH6dqn{@YShhJkz!p9L>P^YZBk`{GGPFHdi*--tL*A>XC)pr)TeLwT}yMDjr4UU_3b2KmYv?X#@-8GO+0S| zNOB;gYeIw0Fl3k4yF2LqW^@})RXcNuTfBsO9sGiWd&c3-Vg9TpN_r)P^Vh77r&^Jb zh8ldsnktFpG1A>1ZBs?8;0=n^dubBq8#aO7N3r1I^|eo_;}<())wuLZ>*NZ}w(=1J z$tdH#6$5Qmy?8%_@b z%h4QE<_Q+iVcxW^D)g#c5_k_8xOS#K_M21&zMtOPu>Eb;yH$2ZNesk!MMHSc-n9L; z$`hCym)8G9sMc= z3;-&2Iw3ya)J17EXV#~=^1O>O1b0h)E}yP%GJIN?)zKaNHckXcTS|u%?Q0E-J7UTV z-WY5ql0)O7K@w7F7&65#s2{_VhDr~bk{fRVu`5Bv6J1K&J6hkBvdQ+SAoPB5S1+h? z>0a)RzO~-tIfLTym5oSBVqHEe2O2H6$o{BcKTw?g!qvw0>=MZSzz8*F6Xs2KcP&`;y;g7kg{f>w=39>M-IP{qlDdVd zmHa$5Y9F%LAaI(`E2)mJwbZ_w_I!a<+RnKyH>VE~5)z(VzS#KceE{jm?3LS;oeSP= z-(yzRtw4ks(o|EW$S-^2u>Z6;2@l)o7IRYuNj^<3PqVNDiPi)Db>JfadwqTGG;LGd@wNx?WceUg@UR_ow@C7$?{YwrP z29+sifTj;!nYurqJ=W_N&n?2pP9}V_>ls$ksfNAZ(16R)!$Kx;Buv0Lv}$LjzT$&W z^)FsfC{B%bef)Jj?X#ogai2`FIGvsa>(398T78en9OwuhRQ{fvQId6A_bD%6{hSAg z>@mMLrhBV+6ry{mFuWHucK4En?V6g5=v4qV}0oT+E)*esNG*&bt3^#Od;kaeC z>=EsIg=IcV;Vkk0RxqygdR*AtU(+~>8fBqmz$s!GZymEZbU~)Lq!OM;<6JHNQ--Je zM0-4C@qX8V0yIN92%XaXZF0uQO%PfU(vnaly{{E>a=bbMF0JhcwK!!`qzILcc+so5 z0TlvhMrRw`K&|}X^qt2a@Rp4aIx<^);cW?MnL(a}cE?hSWq;m-=KD+pUx;|fUcD8G z{AOWYu#8YReE(lvLd5P6Ex|0^{!{Ep-XEr{^iEI*eb-cTN}MX>k&29tnk?)^NEkhJ zfA!=?g`cX-5XwnNF1gQX)JLy0#>d;Dpx1jP%Y<|wp0QyoNT;A}@60cf*Fr&Crd+AM zD!bh4*{SlFH0a(=-|;dLpwT)CL?$L-M3AT`nYG6=L)NOHAsVTji(&aju+XLtg{0ZEdG}^}q<#@il766~q^wc6N^p;A zBdw)P{cdL^s`~8o{)U!V+)v9?A>F4rk|i^=p>8t0&mK#@doze?YV~mHW`V!8uLl9@ zP^6RMRPqrf*|chL4Vby+`W9UHoC7ZYVEHWTvj0eBSsKNUAuCxk+=^mOlSn@!q9P-y zn>YRyVfb>O0|d-v^ULbDr>4q^;Vthc4FRdL=d8wO6y_8+r0$Z)y|z zO8bxybm50*%L-$=&7(WA)<-@^zxef|F9!d>ilUb91U&G+53LTU zJJI3fM<@Ru8aB|5{|QBF&HVvf|>4dMbejKKXej-6|HkL%Dp0tQn;7a6n1@c z*uBNyFYw$)h7C1apVB4_%epKLfq450xEBLZ=28_q7LC=4ZA%AmJKL)Xf_M$Qult(` z(hB|S%*udk#d6G6QpxJF4Al}Hz)7emPqd()alzX?oKvO!0) z**;BJ<(P7klvQySfwO`ar5jmJS2LqnB!~M_&DbSlzkq*)^`;?YE-ue@h_2U`uJ(KGAM`bCPPfjw~GwS*lu79Q<8}BGjm^BYm*$NSu5HS=b zXx7UA0!5XQsyd{`fSQS6q#EwC4OJhyHe$V4RHd*yPt`wtejXHsATTRFCivqs)6K}D^dRgcB7RR)*1lVgO(F(bj_o_h0{v zw5_In%*XKTeXsKE1*-NhpK5Q2N7{4{u1@?Qnt?drQzADmWINM|Ef!k8e+_Z#0j>VgGJ3W88E;8PD)NJ|-Cc|;+FDSSmr-|B zmCZm(vQAL8e3X0bFCckb&%R!qCTJEOqCM5>=TAkPa7TJMsIXT{`UwH#c#iUPF78#2 zQ$=Q(s!Fk?RSOde*!4-4ALKWnso>JN>Y zOiyBwEY*4prMVZOt`@-yIN0Bm^(T;No*RC7a%!h6j@w)lCjftKgF zfbx8t_^67#z1HN=`p#L7G=X7M{l9S6!_GT^(a*0xARCM##piIsB1O@t=J}g{i?k~h zn7SEA=0~f;d!=3(|0qfh5TQZfaIEbW+i_1-Tg{8;fm}iiMEQDC_n+1$)0N7heS-T# zln|M3)etw|M5n8o6;8f($(ldMjm{~$JYwyX5_r9bZf_^~W%dLbwMGh~_2V^s)|a@w z#$oF48I>LnA}ym8$hG4J*;5`5*3y4jK6f-J54?JJp1zGL$_NBXR3fTXJCbN@T$2XZ zs#7Ye_=&Vg6N3@HQ&QOgrpR~4S*SZaTG=J&>plNf9L{XeD!QW!nDsB_q8nIkbenig z9q}Zax>)frpjZn zIfrNMw~*ZoAMd$1Tb#!3z}*I7&9kQ*#Jp*)s`pq{%=`+vND%ic&seXm{pr7b>VdhK z(ym3YANU-}r!AdZ`bvi0Q5UKFFpGQnU$Lxni%~=rWC}MI$1o(nlk%hW@?O!d^EnN? zy=ZBDMxVhUwols*L7RRB+=iNkpO0uB@a^~?V0$EP6ECD$&QDe=aN7Z52yEUrz1Z9G zU&Wey`T8gW3RAW!q0Vmf%O{~OQqvOqvYA45i%WsC0cB>!yf=K?5_I%t$~VZMXyn;Z zGfpeU)kL2f}u7L+uj;?NZEfO=93T5;y;1e1(sL~Eob!=kEgyfUS18=X6q>uZqH+%u-2{hzDT)!YE#xv}4CWtFC zBkEz}N2%Vg-!b&SXk6|x_&+Y#NYK%otxkx>drct!c`75z=GX1K zX}-nhabyEiuJs|g0HOtUnFV%`ZbsgS#P>9Ett3x+KXL0@TtDxh)2r~ z!eF?f?KI;}ZvVZ`mb13NimQ|6_Hn&?BRZZ|ZH+sY_4OatNtq9iu07aZGgizun7s?+ zAA%JwJ|CA-iy=A%${*=lzD&q0Wdx8t&u)C1pMZlts~zUPfE`wUB!QfdvbZPSvhWU{At3dJ&aOEUW152Y4&KiO|we7n!a_rY%dUGp)$S2?vDph_$}e&x-}8%K=Y&xz{iR;NT0Ep)y#~xzBms)-0+srG z-@fd9Dc$!UAMb1x)31PTF-U+ZakW=2TZzJq>&hq1PRr27ekT~VvX#lTJVQH+MlXk~ zpb#WnxK@6>mVKiCr)x@om1qKEWS(w$ATH2gFU`#53po^)bz4dzK?c6L-a)$#C;8BM zSynl#Pt`ga2L@e_I3Qz$msNku6!?ApFDo}EB8^*VuOZ5bh<|pSBrk4|(NFV?a!4QK zX>YDfWFXNA=?YHr=246cjyfXMq6>)KQK#BEkeeY>fIk@RmBGfwY2xBlDk4C?t7#H) z!k}P;(wB3BJPac1sB?S#-BA&h+JX+sYd@#`nk)IgM0Zep&>Fm1` zq{|y@J8H$4>W;D@lQ>uC9c}?cemFni`be2mN1{LJUpz6s8!)7~kf`EDV%`WWPwRLv zfvo;z(zqT7ZNwZSrM=^4x807Xf;e%@39R%0%QMN6mPYr!rT?s|at$&!5di$lllpa@F??Gv8U-oMB z0D%~WI^+==1D?ONkmRzGMfmLJz(75coaJ%D{J)XO5O-=)WLhkxxXKbDteUkSN0b(k7+eou`Gyi$M4=Us_ z69dXC)SOKpNPQ9NYTZ=$bX$dDHxYXE~q1Z z(5>+(@+u!M0nu-5+c#~i)oYII&Ewl3%l zXy%BIZ!U$nwo6J*)L(ep@@g(#qm1{i$J}_9LfL))^kgKV^qC3R{tC&5{a}VwSHW~z zT$P8JtmGIZze-)F?B&y1xom~C68P0JjGm09T9VAujB6S1CU>vl+tE*DEMGrpf(_x+ zV;?^4_CUL(GYD(zo_TiR{bEi9qrhW{Yi2H>ia{WH_>1T{SA%@X01#XE3x4v-?)D^~ zg`ME~IcAdF#sE=z65E9qvxOD~rijH3l$;fYNFKjhS4MLybm+eILVFcCy)rs82-{j^ zr)vzhcoj~hfEXV}Ud~DefEI8sRF#%nQaF}$>JpzitDjtxJzEsEp-P&uQ97h?6^Y3@ z(IyvZlm$(*EsxCmHo;7F=0OhFHQ|RO(Jo2QYS~GZ0|C7cLAUhJgkln~{otSvoV`~O z^a9L0P`nh%PY)Js-^oWPOq&0a;8+yCPR;u@oVY`Y0sC@1^c^tk{*&TMlv^t#noNA7-q>Y^lER=ReIThQ%wX z&=m`a2AJ!WByC%nWm}G5%9TZeN7++AB>I8HF9WkOhhiEt)h4b)K;l9sDD*`0Y)WW& zFBK}?4fQ66I9rf1C%aF_S8ATKU<&7AYJ?~Rk)9TN)b<`r+Dx7%>f&!f+Mp$7BY>w_ zvPhbXvz+osz_x2usx`eJ6`*);f?H^J)5hEHP{-(Sq`jc{PUArc_T32vZAwg}2|z8| zJ9(b&WYPfvU+T-)doTG;*>z0xqnyS9Ma`wBBu>igO|bBt9TX>gPA({#IKY=QYgPIn z&o8yz2`AoIh)wwc4q6$D;ivg78?U+(WfqJNH;e7$GJ1w;5^GpT$$on^@3~y7IpV!N zyb3(H8ZGPoj6zqfXU*Nzi`1W4?BxAAM%5_vrsx@k_ZqKxdGGOPRGMLsEYrrENTsIO zFgX6b-4)_$?z$@Un`2bBvm+R4yl0IX1U*AuMW&ff5W45Rxt&x-V?{)^W0qf*{9wen zqflaWrUY;^J_YK~6 zm|0Ue`?}gnud3`>`6rxO_-t)5Zo`Wm8)U-zjZ=-7vcLJ=pnX?mH8y%j&vSoo6_mG= zVJ4qvs#-=Ij$T$y5{1=kUVOvbzgy2iFqm46hO@dY1TMP#x4apk65^srf;jM#){dP3 zB!QMBisDt-xsXi{FgI8MmTMl^lgZz@O4@Zv=5c^kBr%gP=@ZCn@yP#`b|6E4vX55g ziPq|}qHtXbZ)!K*p}X~^U`FQia;eLv?LJrInpYPf4KU~Vx{ir8XBi&3`BGW9r`V?lS}N|%wS`L_AXHEp7iWZ4)p;HGDvrz=UAs_R&H@?z(h z$lLX>;z7(^^mVEZTAf(_5=yyIh{jT;cF*^519n7&o6hK(4dFXRNPoiefKmlaid!{Dc17xr~6v(ik`4X;@_M3J>tTg!ieKr@u5Wb zkxDsaE#>**Br~#e$dh%ffdXQQh%><8psPpSF?VBqcVk)HCP4#zD$ttRAx@+#@{kft zJIU)dU{$--3r^onNKU%hcfCGjeyi=^b|?H>I1d(L!4xSnG;nRzKqHKJfWMQ&P>|hw zkr0a#etY#My7_%iAbr+IYfn2)?~{hpzFGojz1!Ny%EIa9RnQiIwAyv+%xx~k`GAC5a@b2S|FTYCs6st;7y7!1_B5F7sa zukQbo~6s(!8 zpz?A6(a{aOjpdXxWynzCZkTswPwQssnNcatt#%hs~9(zarmF)hGJPA^tB;yp&<_m)v&cPGk*{w}wvl_#6u$5*dBNKV+2Za|DzpN6c!@`} zRw2UeyB@-vfyq|RVH@#Jxo1NUv`Z#M!w?Z{_|~j96fbRn*+g}=vYP8PQg&=cXg@zc zkc&b@piq%Xhr1Ru0*^lH9yr8A>n9VIWz{O1ic^c}06cDN|6`h{kN{Ewlh|^VrtTqN ze2d0BM2!WUo8Vyejkap+EdykzoOF==qu1|Vk8)%9>A@5%YqG8Q*Z9Z??rlUT4@P=S zNp88!{(P7(uFe}skto*jmy2| zPelTS!DozbTkliy<)S16rj#x61w93Q(ouM;&+`o4{vm_~k4EbTS{0${tD5Sy^>g5c1N0Np(+9V|2AI|YsFA?|1Zt%!+fgm#XD9p3 zz=7@Qg$hC36FBEE0WBY|oM4Fi=pJg(xrS)R_1q676+VKE>VBqqs_0uOeNbZhV_(T+ zzBsRJZOoB1`I;wo@w$fYg2vb?S`4*3SgAeu%q9oV8|b$8ZMN8O#NT0QuF8W7Na2s( ztjEm9<_ZVskhO;*3%%hxol7feFTYnax{pAMqFd`i)h5E|5~!3R+ zVR6XDS{1^w#`8hhQ1}MD_;+QAz=KQkj~NwQ1$U7}A~vXje0vTB=Uj?*nuJta1{6zt zz~ElT0W+=2zNg1|kn#odA%77r?8D>^l;;htjYiO#T$%6I14*x0pddkttuj5=u(hn4 z@!28_$~xA_zxK2sYQ9NWOxz3EB5vU5lb>|ipk!KVYvMVRBy?;jEC5vo(a+SUf?l?3 z6`$I)S4G-xywUOPBJ+dvscLop$JlG zEBQfq1YL@#f<80q(xbbbAp(x1`{a@|<#WJvoAaaxjR9nqTw&DOVu8^{xA*4LXu8Jt zb1Q6d`uafHg@qdWO}EK_w2O&vVjLV6DG)`S{Kwyryb zYZV__*@9IL_c4dM0lB1>%wm{RCuP8V7P^wyQ2K4R<&oM=<_}aP9$qN#q_ET5p@bGD zDdY#i?brD0IhWQb*B5AnzJE(djIs{oVO9}N>6qTMM+3-3mbl4*2{y3isny{zcPlI( zwz<|c+oI3wVSHbL=&JYV?vzIy1Hvp4PfMkcwlQGy%^7v))t15GromA&QqKBAAx#5R zux$o30oznYoA@|V9^#{(UCgHprC+Vxi6 z1NHX4vUn980|B!FFsDtk>yyHis zKIgv52M_Z6noj)3vx7KcO@GiF8oO+bA#&Z|4Y%Ue8$N%9LoP?Q_m9V`<9(6~} z(5g*ntz;+`W4^d$xr89x;rL+L4()%74aAZc(J1rgq7>}H?O3?Qo195$+MXKYq+{50 zGHAQxjczgQ@qLZ=liK5rRjki5s$_%auX`nC3`;x8_w9OogeE}Izroc@;L`^+XsCnd zdjF8bfU~~NJcwYEk04CWtKlty!H}bIx*&G!5Yl@x0kbB)4X>-Lc>F#$5%l!qn(kY3 zQ%DB!iqpMU;tm=#LM(Y&ZR_Gj?Sug>IA;_Nf%4cd(O$b0;8SI)Me3qOK5_Gu^l(rj zw`?B}SS>`$A(AURlN#$OiJg46Z!0*G*1A2Y43VPrAcT1SO3 z@{N$bUG|q)PGYyFkb7NC9h-{Haw*z=%DD&)%*I1smY*-bGODErtEjFnlaYr^EME{* zw+@RIF-jtF&lOXM+|>U{VR6eLgpa$j^)PctD~DaeYpL9_=^g3j6&8c_p}-3ioEJTK zNAImfK05_h>0`)NoR;O|ZzWbEV(`Wf?ydvKowBt{C-3AkZ+G#Bf2SKK<)paXHMf47 z#ra+M`lPq9fIZrM^=Gq#>7=S2RH-n^WY>SnWVR_y+^NlFe`yQEC#~xG-}9HFDfAys zkcV2ElIr^sHyw6X#uoOmtK-zRA0@ikt@+FMS#{tXvKBKmuFR6MM(W|JI%E>FkVKxekJVb`(4Th`kH!(q{8uL=b)LMN4wes9qj zpCMf9IVJiEo$Ukyr+Z4Z9gc6#Y@dZ?`?5azZOj<20%xG!%i1%YAU(tL=fXF8&69`F zyPr?1S&*mOpJHN-p2~ay>5uGWybWTZU#fmOWQoifSk@?zEJ*7HVGwv#ycz+w=yJgM zI`4X*;VIxbeWSO-ckHU0v1%O$LNGI;w=koia!XE|POfNyYV zs-&UHL)xJGa!UdhvksH(rOWaN5#A0YUIBVaMD5nO%!#!$<7;)*GknO;^r0|a`ikM< zP|()k)F~9YrzPq6`IY8i+Slh~*fCokR=Vm#tE;aJDu{gR19v7Yk%&h;=fY1+M$bp? z!dU4;{|HHeJkPLXl;JX_@YXzBWp^&;hqyW- zs%=~EPW70TeB8Y8 z&AOM64yBaBHav-gK5Tto$llGjUakJ22Ima3x46vXJeES2^=g(-k>JC-7fvv!Lel0fJ zL53#Jz8HH8qX(?PKIy+YmG6Nklvr7<`3+b-R4K53fY#r%-cYwrCnRUt%ZCx4EWkT2 zHMZn#-E|WU>FhOcqfmIa+&=rm(PG}V{`%DS_#ftZ(lBV|TC07oyDg%Yg!Z+w>2hm^ zuQB7LX3zbd)iq!`E~CCWg;GpwSWgRk>+_1lLTH*kb{eDpSkg_v=ZVedp!o} z;?(>~%rG%zS2{}`e9>cHuI`owJ?~6E4rg$LAYgGDs`cD< z(hu>z`d_N6{#16QdK*|C-3nOa_tkk`ZrJDxWY79}cN4XahXlZ-^_)vA_z3S#e|E7h zy}`SjYNw}NReUER=Y4MuB;ef{j^*Crb#OPg-E3Fd+AcmhmP&rVFcIv2BO3|pv4C&L zvOgt~%XX1J%75du0@OxXG((zV0=cdKw1odw16HDVbaItcFooaOmQSML%=@V|;c9OD zbON=pqh}CPn_1BcGd)*H^~BGzv|Iyu7LQNgQXlwL@fM0qzc65bJDg~-cmKUut#Yv> z%kAc@Gn^C>H}oX%+6?RUw*A2TD(#Xh0$D=w4miiQfudg)r2V$u5oAHhCgaBqUhgTa$S|FdT(2IbtiYYovlRDZ6&&iF?j1 zQ|*vzU}cw_Tpi6zU&UGc!&EiT-%)d6TPkQEUX-#QzvB)dSKG~!y`Kqv{etP~3#2nw zKM(FAi{{XtR(j(0*vmbpB{ulg)X^|)0D{}@fBIa1$1mmSx8n^vC!)xHWOw%B(QD6G zF(5Uq+136csEM83N?%Idli$+{ex5?q&G>KGPb(C{HJo*;OW*UmXl4|do}JHKit~f` zhX=A{t;AU>L}=5jAq@igGz`y@s7gLag8+3uF5e@@>8cmp_@j9!-SuS;WKilJ;tFFreQyAQ3bMpC`|Fp*7rRcjzGO)Y40`77>CWdmt~N%BS5YVLw@gx zZk)GP-1yIiRH`mr2Ci2x%bKga$k;#sDwX-0D0F^4(ageXow-`=bXHbkd$sJ3PYjuz z%oclM1)GC?F9z^mbFe^mG4eK0Mg^KU5PO2x@)~#qkC>VLRr=*O9zFW?fzJ<^Du?I@ z0KMiIm^~o_sue#g@w;=nRB*ybSwPB^9+IA4{x^j4h;&qlTRq_xgFz1Sz%=jmG|OMP zx&J9SWT<@lxkIR8M-))&@~0jDH=+BBT-rS(eV$%Xx0ooZZQ8P>=;N@rG3zz!ah60S zM)0cO9~=8eMC_l3@Sj$4%dhIX$c-CMw5+;?J8?MN!bW)_wBkNo(OsjW$9QSt`L93y z*QaqIw^a1)Gs_BWQd(BMbwJFxrE|wVo@&zPYazDfTZ7j(cIJn6XMUvacKw4b{`Mh> z@Q~0>(c2>Ct2@YjTCFo4CZl8Xm!BB6y}SIn`TsASfBHWn6{nSzmCuG+dz{`Q5D@Gm zk@gGEW+P7pug%1~i>w+r{?bhRum--h11*o%AJifFH{uABx@|VJES6cnhQmW$L@ht`7!}6Hk^$ zrh^?O$%U<>=qi1Pd%SQv*W>WH-vGZMht=NFeU%w|FPAwNvl+{ZqK@)YMT?SEM8J;y z4-9XIB_R@t9Z(8c4hx%?bxS55mRp=GI&O<}*r$#1e@`9!Q=kn6NDAtKQMhBl&n0i- zwplzT<0RK_rC0u475OiI0cMyuTUesSY~S$_qzTDV_8)72ZW|^*&!i~u(D2_<-+$6v zeor#j`kw@@M!(g)qCRWS4}xapP2KrlWKi3WA_v@MgyZ#;r6wF+flE6~-eqQE`(BZM z>@`uGhOx^$yTAeNA1cTIPZ$fxcI|fdH_i7IzjH?T{z;SkN1^$PqdH7VE-Uf;;Zu%* z`V3>^RH#13n`R66-Tvc}uCEAQgrV+MG^x~wI8l=EN2`CG3#LmPmJ?IS~Z zcmX(@lVl16#wt&7`HIdr)wQa>;Z6LBdH{P>K6uqI0SL0~phUc+E2{@vNK|2uMD$5x z&f}i@Xd{D^$HvQblB*4FnN>y~vFlfUrnKW*CQ6dmci+BDuY$>YSzG-#mR)=%kGolpOMWrtc~xtQ>Q(#h>e7`{THhWVBwF*SCGvZFdza1e zEFYI3D|Qs6Knz#_uBiSv4&6CnNBK!O=2Q`#fLz@lDypXJZ@Ps4pmT%brCUKOM+18G zuluzqcAR%zPlyMF6A2 z6RypAuOonrYFg*$QvMf3@{9LYUY!%kb5=pb0L40L_>Dek)R^0aEdLz)W|9rbdiEa{Qv9kGV4wxk}m6btF4y={e8KT{8M*<;`Do+-M`+ z47D5hW|F3idI|(3MHku#;r+dC?HAko_p<;ukZfOCA;+l(am@Pi0~DtG`l1;^ecW%k zMPf|?SG0Gpk9(`~>EcA~%lfxbY^}8H(hi>}FI+f24MfQPX*bw*AJw?0RT+T{a5CI>Bv*9ZUk>BnRq6i&JgbD0Y=c%>)6g3meGkwRtJC7_o4W!UEsLTJCpP_;b>hXb%tjBCGfiNq7+8BX;t$5b&T2EQwR9kRXqR!kPdQp3y3 z!1b|+szqVN-q7mm8BWEfW4Y!Z8rY~`(z>yQ7#&_>=o1@Ra-NAi#{!#@e<4lXeoOES z=k3Dd@vunfX%H!g!CP)BT#gYA4&W&fjV5qq0r_5z@x?b=nU{!fyumyYtcOj(R(Re% zu_GfRn}GhZRmi`qvETSi^%+-=Ox-GqH81!=bI@WDWy_76pH>%Suk<{s;o7;&I!Z+X zS91>!Jf5}oYojpkFY3_fQ~c2_{9<==(xb-J(=2XfB+1^w3`7@vsAI`kzt>w}(F?gI zU;b@J|DP9yU#g@(G?d@tlTEN-D$aZ*vcWiNCvcA`A@}D^tOEQknbqJ5kIX*M2+!E$ zn06tBF^+3NitZLEhc;$ULQ@gmz`@cjzAtrV?Edi<2Y?KkVD1kbr8nZR4SN8=137ma zvI-s_Pji4NNoYygxtT&t&_IPsL&w&KK#ui<3(cYBV-Odv6SF<90bSM2`f)F@I z-uwluINc!Ls$aBV_Vzj1X=nBjM)oi8fPjFhsaS)20*k-ujpHd2MBqu5wHe~6xF!!!FEcf2?rK@24*HnciKgTZvd(_T+oYT~B%Zd1uh z6l`|(Bl1tSg1G`BY9uM+1&-(Do8JGRT@;B`)qG%Sv+S^xva{ev36dz-H3A}M1q9%$ z583BWK?^*em*tSgtD9{1_{khg;e$&_n2oJY&xrQ-gRNdJbe!UJveMu9!s21Yva;9N zA9OF6s|Dtmb{yC0LxbvB%5dj{N3Yp*Zl9Me4+*Q27b%y<264CC(mmGVzGpUcYAxHM zKn8e^h16hIlNtN9?Soqg#h=!eB?!c^+ylTHPq~1_nR^Bg59c0hJ4b&kVtM*N-fS_< Vp_WlY=>+g|Pf6`gzJj6u{{cwn3@ZQt literal 0 HcmV?d00001 diff --git a/docs/source/figures/gallery_thumbnails.pptx b/docs/source/figures/gallery_thumbnails.pptx index ac3da484dbb9f703dff20e507f3ad38302cf4f45..5ede1c4b5be06375ee434b926575007ff6570004 100644 GIT binary patch delta 115777 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 P7X0kMH?E$80{{L8%$n8_ delta 3862 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 From ea6504f6c49e78d7b3d0effccef2bd1fe19520ab Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Mon, 11 Nov 2024 11:55:07 -0600 Subject: [PATCH 58/82] Small patch to html repr in #1100 (#1201) * small patch to html repr * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * comment request --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steph Prince <40640337+stephprince@users.noreply.github.com> --- src/hdmf/backends/hdf5/h5tools.py | 31 +++++++++++++++++-------------- src/hdmf/container.py | 2 +- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/hdmf/backends/hdf5/h5tools.py b/src/hdmf/backends/hdf5/h5tools.py index e9156dc50..765ff6aa5 100644 --- a/src/hdmf/backends/hdf5/h5tools.py +++ b/src/hdmf/backends/hdf5/h5tools.py @@ -1609,22 +1609,25 @@ def set_dataio(cls, **kwargs): def generate_dataset_html(dataset): """Generates an html representation for a dataset for the HDF5IO class""" - # get info from hdf5 dataset - compressed_size = dataset.id.get_storage_size() - if hasattr(dataset, "nbytes"): # TODO: Remove this after h5py minimal version is larger than 3.0 - uncompressed_size = dataset.nbytes - else: - uncompressed_size = dataset.size * dataset.dtype.itemsize - compression_ratio = uncompressed_size / compressed_size if compressed_size != 0 else "undefined" + array_info_dict = get_basic_array_info(dataset) + if isinstance(dataset, h5py.Dataset): - hdf5_info_dict = {"Chunk shape": dataset.chunks, - "Compression": dataset.compression, - "Compression opts": dataset.compression_opts, - "Compression ratio": compression_ratio} + # get info from hdf5 dataset + compressed_size = dataset.id.get_storage_size() + if hasattr(dataset, "nbytes"): # TODO: Remove this after h5py minimal version is larger than 3.0 + uncompressed_size = dataset.nbytes + else: + uncompressed_size = dataset.size * dataset.dtype.itemsize + compression_ratio = uncompressed_size / compressed_size if compressed_size != 0 else "undefined" - # get basic array info - array_info_dict = get_basic_array_info(dataset) - array_info_dict.update(hdf5_info_dict) + hdf5_info_dict = { + "Chunk shape": dataset.chunks, + "Compression": dataset.compression, + "Compression opts": dataset.compression_opts, + "Compression ratio": compression_ratio, + } + + array_info_dict.update(hdf5_info_dict) # generate html repr repr_html = generate_array_html_repr(array_info_dict, dataset, "HDF5 dataset") diff --git a/src/hdmf/container.py b/src/hdmf/container.py index 8f961936f..4ee24ec2d 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -754,7 +754,7 @@ def _generate_array_html(self, array, level): """Generates HTML for array data""" read_io = self.get_read_io() # if the Container was read from file, get IO object - if read_io is not None: + if read_io is not None: # Note that sometimes numpy array have a read_io attribute repr_html = read_io.generate_dataset_html(array) else: array_info_dict = get_basic_array_info(array) From 6cf77526210c5cbd586a77ed55258e510d436854 Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Mon, 11 Nov 2024 13:28:42 -0800 Subject: [PATCH 59/82] add string dataset condition for data type conversion on export (#1205) * add strdataset condition for dtype conversion * add strdataset conversion test * update CHANGELOG --- CHANGELOG.md | 1 + src/hdmf/build/objectmapper.py | 7 +++++-- tests/unit/build_tests/test_convert_dtype.py | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4f0fde80..9ac153581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### Bug fixes - Fixed inaccurate error message when validating reference data types. @stephprince [#1199](https://github.com/hdmf-dev/hdmf/pull/1199) +- Fixed incorrect dtype conversion of a StrDataset. @stephprince [#1205](https://github.com/hdmf-dev/hdmf/pull/1205) ## HDMF 3.14.5 (October 6, 2024) diff --git a/src/hdmf/build/objectmapper.py b/src/hdmf/build/objectmapper.py index 493a55bab..3394ebb91 100644 --- a/src/hdmf/build/objectmapper.py +++ b/src/hdmf/build/objectmapper.py @@ -21,7 +21,7 @@ from ..query import ReferenceResolver from ..spec import Spec, AttributeSpec, DatasetSpec, GroupSpec, LinkSpec, RefSpec from ..spec.spec import BaseStorageSpec -from ..utils import docval, getargs, ExtenderMeta, get_docval, get_data_shape +from ..utils import docval, getargs, ExtenderMeta, get_docval, get_data_shape, StrDataset _const_arg = '__constructor_arg' @@ -212,7 +212,10 @@ def convert_dtype(cls, spec, value, spec_dtype=None): # noqa: C901 if (isinstance(value, np.ndarray) or (hasattr(value, 'astype') and hasattr(value, 'dtype'))): if spec_dtype_type is _unicode: - ret = value.astype('U') + if isinstance(value, StrDataset): + ret = value + else: + ret = value.astype('U') ret_dtype = "utf8" elif spec_dtype_type is _ascii: ret = value.astype('S') diff --git a/tests/unit/build_tests/test_convert_dtype.py b/tests/unit/build_tests/test_convert_dtype.py index 8f9e49239..8f30386d8 100644 --- a/tests/unit/build_tests/test_convert_dtype.py +++ b/tests/unit/build_tests/test_convert_dtype.py @@ -1,12 +1,17 @@ from datetime import datetime, date import numpy as np +import h5py +import unittest + from hdmf.backends.hdf5 import H5DataIO from hdmf.build import ObjectMapper from hdmf.data_utils import DataChunkIterator from hdmf.spec import DatasetSpec, RefSpec, DtypeSpec from hdmf.testing import TestCase +from hdmf.utils import StrDataset +H5PY_3 = h5py.__version__.startswith('3') class TestConvertDtype(TestCase): @@ -321,6 +326,19 @@ def test_text_spec(self): self.assertIs(ret, value) self.assertEqual(ret_dtype, 'utf8') + @unittest.skipIf(not H5PY_3, "Use StrDataset only for h5py 3+") + def test_text_spec_str_dataset(self): + text_spec_types = ['text', 'utf', 'utf8', 'utf-8'] + for spec_type in text_spec_types: + with self.subTest(spec_type=spec_type): + with h5py.File("test.h5", "w", driver="core", backing_store=False) as f: + spec = DatasetSpec('an example dataset', spec_type, name='data') + + value = StrDataset(f.create_dataset('data', data=['a', 'b', 'c']), None) + ret, ret_dtype = ObjectMapper.convert_dtype(spec, value) # no conversion + self.assertIs(ret, value) + self.assertEqual(ret_dtype, 'utf8') + def test_ascii_spec(self): ascii_spec_types = ['ascii', 'bytes'] for spec_type in ascii_spec_types: From a8e496b637e9a84bc2ff0c0e24e040682a6a8653 Mon Sep 17 00:00:00 2001 From: Steph Prince <40640337+stephprince@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:40:11 -0800 Subject: [PATCH 60/82] Raise error when using colon for container name (#1202) Co-authored-by: Ryan Ly Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- CHANGELOG.md | 3 ++- src/hdmf/container.py | 4 ++-- tests/unit/test_container.py | 11 +++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ac153581..adcbd6bb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ ### Enhancements - Added support for expandable datasets of references for untyped and compound data types. @stephprince [#1188](https://github.com/hdmf-dev/hdmf/pull/1188) -- Improved html representation of data in `Containers` @h-mayorquin [#1100](https://github.com/hdmf-dev/hdmf/pull/1100) +- Improved html representation of data in `Container` objects. @h-mayorquin [#1100](https://github.com/hdmf-dev/hdmf/pull/1100) +- Added error when using colon for `Container` name. A colon cannot be used as a group name when writing to Zarr on Windows. @stephprince [#1202](https://github.com/hdmf-dev/hdmf/pull/1202) ### Bug fixes - Fixed inaccurate error message when validating reference data types. @stephprince [#1199](https://github.com/hdmf-dev/hdmf/pull/1199) diff --git a/src/hdmf/container.py b/src/hdmf/container.py index 4ee24ec2d..a61dc19e8 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -303,8 +303,8 @@ def __new__(cls, *args, **kwargs): @docval({'name': 'name', 'type': str, 'doc': 'the name of this container'}) def __init__(self, **kwargs): name = getargs('name', kwargs) - if '/' in name: - raise ValueError("name '" + name + "' cannot contain '/'") + if ('/' in name or ':' in name) and not self._in_construct_mode: + raise ValueError(f"name '{name}' cannot contain a '/' or ':'") self.__name = name self.__field_values = dict() self.__read_io = None diff --git a/tests/unit/test_container.py b/tests/unit/test_container.py index 35d8e480c..c12247de7 100644 --- a/tests/unit/test_container.py +++ b/tests/unit/test_container.py @@ -180,6 +180,17 @@ def test_set_parent_overwrite_proxy(self): def test_slash_restriction(self): self.assertRaises(ValueError, Container, 'bad/name') + # check no error raised in construct mode + child_obj = Container.__new__(Container, in_construct_mode=True) + child_obj.__init__('bad/name') + + def test_colon_restriction(self): + self.assertRaises(ValueError, Container, 'bad:name') + + # check no error raised in construct mode + child_obj = Container.__new__(Container, in_construct_mode=True) + child_obj.__init__('bad:name') + def test_set_modified_parent(self): """Test that set modified properly sets parent modified """ From 8c3eecbc312ef0431bb7a2ac178ce55dc3d4da22 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2024 16:22:17 -0800 Subject: [PATCH 61/82] [pre-commit.ci] pre-commit autoupdate (#1200) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4122f041f..0cc5da870 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.1 + rev: v0.7.3 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From c71537a82ce9095df5a5f06a9e90d2a78cc95982 Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Thu, 14 Nov 2024 10:58:14 -0800 Subject: [PATCH 62/82] Coverage to 90% (#1198) * First coverage removal * fake removal of __eq__ to see what coverage would be * region * more: * more, maybe a bit heavy handed * removed array and query * deps * cov * Update CHANGELOG.md * cov * cov * Update CHANGELOG.md * Apply suggestions from code review * Update src/hdmf/backends/hdf5/h5tools.py * Update src/hdmf/query.py * fixes --------- Co-authored-by: Ryan Ly --- CHANGELOG.md | 5 +- SortedQueryTest.h5 | Bin 0 -> 2128 bytes src/hdmf/__init__.py | 26 +-- src/hdmf/array.py | 197 ---------------------- src/hdmf/backends/hdf5/__init__.py | 2 +- src/hdmf/backends/hdf5/h5_utils.py | 78 +-------- src/hdmf/backends/hdf5/h5tools.py | 120 +++---------- src/hdmf/build/classgenerator.py | 4 +- src/hdmf/build/manager.py | 16 +- src/hdmf/build/objectmapper.py | 50 ++---- src/hdmf/common/table.py | 4 +- src/hdmf/container.py | 49 ------ src/hdmf/query.py | 121 +------------ src/hdmf/region.py | 91 ---------- src/hdmf/utils.py | 91 ---------- tests/unit/common/test_table.py | 4 +- tests/unit/test_container.py | 12 -- tests/unit/test_query.py | 161 ------------------ tests/unit/utils_test/test_core_DataIO.py | 27 +-- tests/unit/utils_test/test_docval.py | 78 +-------- 20 files changed, 63 insertions(+), 1073 deletions(-) create mode 100644 SortedQueryTest.h5 delete mode 100644 src/hdmf/array.py delete mode 100644 src/hdmf/region.py delete mode 100644 tests/unit/test_query.py diff --git a/CHANGELOG.md b/CHANGELOG.md index adcbd6bb2..b0639630b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # HDMF Changelog -## HDMF 3.14.6 (Upcoming) +## HDMF 4.0.0 (Upcoming) + +### Deprecations +- The following classes have been deprecated and removed: Array, AbstractSortedArray, SortedArray, LinSpace, Query, RegionSlicer, ListSlicer, H5RegionSlicer, DataRegion. The following methods have been deprecated and removed: fmt_docval_args, call_docval_func, get_container_cls, add_child, set_dataio (now refactored as set_data_io). We have also removed all early evelopment for region references. @mavaylon1 [#1998](https://github.com/hdmf-dev/hdmf/pull/1198) ### Enhancements - Added support for expandable datasets of references for untyped and compound data types. @stephprince [#1188](https://github.com/hdmf-dev/hdmf/pull/1188) diff --git a/SortedQueryTest.h5 b/SortedQueryTest.h5 new file mode 100644 index 0000000000000000000000000000000000000000..67f3d7d6cbd59ee6452fdaebcd21834eb191f551 GIT binary patch literal 2128 zcmeHIF%H5o47Af0LM5cKAts){6F_QFC#Zmdu>-sS9swKA;3@nHlK3nk#!i(;D)lao z<0jVGz1QV>oX>Ld!&*wI2vlEhjK+#Z=epj4Fz^O^8X-*nA)3NILHu98!>+2xd1`z` zY@GteDVI!{bpPGiq<-uC;d5FJW0$z%s{sc aNH=r+3EQ=-@!NG>P{sf_1Zp1`Vg3Opl`aJU literal 0 HcmV?d00001 diff --git a/src/hdmf/__init__.py b/src/hdmf/__init__.py index 6fc72a117..10305d37b 100644 --- a/src/hdmf/__init__.py +++ b/src/hdmf/__init__.py @@ -1,32 +1,10 @@ from . import query -from .backends.hdf5.h5_utils import H5Dataset, H5RegionSlicer -from .container import Container, Data, DataRegion, HERDManager -from .region import ListSlicer +from .backends.hdf5.h5_utils import H5Dataset +from .container import Container, Data, HERDManager from .utils import docval, getargs from .term_set import TermSet, TermSetWrapper, TypeConfigurator -@docval( - {"name": "dataset", "type": None, "doc": "the HDF5 dataset to slice"}, - {"name": "region", "type": None, "doc": "the region reference to use to slice"}, - is_method=False, -) -def get_region_slicer(**kwargs): - import warnings # noqa: E402 - - warnings.warn( - "get_region_slicer is deprecated and will be removed in HDMF 3.0.", - DeprecationWarning, - ) - - dataset, region = getargs("dataset", "region", kwargs) - if isinstance(dataset, (list, tuple, Data)): - return ListSlicer(dataset, region) - elif isinstance(dataset, H5Dataset): - return H5RegionSlicer(dataset, region) - return None - - try: # see https://effigies.gitlab.io/posts/python-packaging-2023/ from ._version import __version__ diff --git a/src/hdmf/array.py b/src/hdmf/array.py deleted file mode 100644 index a684572e4..000000000 --- a/src/hdmf/array.py +++ /dev/null @@ -1,197 +0,0 @@ -from abc import abstractmethod, ABCMeta - -import numpy as np - - -class Array: - - def __init__(self, data): - self.__data = data - if hasattr(data, 'dtype'): - self.dtype = data.dtype - else: - tmp = data - while isinstance(tmp, (list, tuple)): - tmp = tmp[0] - self.dtype = type(tmp) - - @property - def data(self): - return self.__data - - def __len__(self): - return len(self.__data) - - def get_data(self): - return self.__data - - def __getidx__(self, arg): - return self.__data[arg] - - def __sliceiter(self, arg): - return (x for x in range(*arg.indices(len(self)))) - - def __getitem__(self, arg): - if isinstance(arg, list): - idx = list() - for i in arg: - if isinstance(i, slice): - idx.extend(x for x in self.__sliceiter(i)) - else: - idx.append(i) - return np.fromiter((self.__getidx__(x) for x in idx), dtype=self.dtype) - elif isinstance(arg, slice): - return np.fromiter((self.__getidx__(x) for x in self.__sliceiter(arg)), dtype=self.dtype) - elif isinstance(arg, tuple): - return (self.__getidx__(arg[0]), self.__getidx__(arg[1])) - else: - return self.__getidx__(arg) - - -class AbstractSortedArray(Array, metaclass=ABCMeta): - ''' - An abstract class for representing sorted array - ''' - - @abstractmethod - def find_point(self, val): - pass - - def get_data(self): - return self - - def __lower(self, other): - ins = self.find_point(other) - return ins - - def __upper(self, other): - ins = self.__lower(other) - while self[ins] == other: - ins += 1 - return ins - - def __lt__(self, other): - ins = self.__lower(other) - return slice(0, ins) - - def __le__(self, other): - ins = self.__upper(other) - return slice(0, ins) - - def __gt__(self, other): - ins = self.__upper(other) - return slice(ins, len(self)) - - def __ge__(self, other): - ins = self.__lower(other) - return slice(ins, len(self)) - - @staticmethod - def __sort(a): - if isinstance(a, tuple): - return a[0] - else: - return a - - def __eq__(self, other): - if isinstance(other, list): - ret = list() - for i in other: - eq = self == i - ret.append(eq) - ret = sorted(ret, key=self.__sort) - tmp = list() - for i in range(1, len(ret)): - a, b = ret[i - 1], ret[i] - if isinstance(a, tuple): - if isinstance(b, tuple): - if a[1] >= b[0]: - b[0] = a[0] - else: - tmp.append(slice(*a)) - else: - if b > a[1]: - tmp.append(slice(*a)) - elif b == a[1]: - a[1] == b + 1 - else: - ret[i] = a - else: - if isinstance(b, tuple): - if a < b[0]: - tmp.append(a) - else: - if b - a == 1: - ret[i] = (a, b) - else: - tmp.append(a) - if isinstance(ret[-1], tuple): - tmp.append(slice(*ret[-1])) - else: - tmp.append(ret[-1]) - ret = tmp - return ret - elif isinstance(other, tuple): - ge = self >= other[0] - ge = ge.start - lt = self < other[1] - lt = lt.stop - if ge == lt: - return ge - else: - return slice(ge, lt) - else: - lower = self.__lower(other) - upper = self.__upper(other) - d = upper - lower - if d == 1: - return lower - elif d == 0: - return None - else: - return slice(lower, upper) - - def __ne__(self, other): - eq = self == other - if isinstance(eq, tuple): - return [slice(0, eq[0]), slice(eq[1], len(self))] - else: - return [slice(0, eq), slice(eq + 1, len(self))] - - -class SortedArray(AbstractSortedArray): - ''' - A class for wrapping sorted arrays. This class overrides - <,>,<=,>=,==, and != to leverage the sorted content for - efficiency. - ''' - - def __init__(self, array): - super().__init__(array) - - def find_point(self, val): - return np.searchsorted(self.data, val) - - -class LinSpace(SortedArray): - - def __init__(self, start, stop, step): - self.start = start - self.stop = stop - self.step = step - self.dtype = float if any(isinstance(s, float) for s in (start, stop, step)) else int - self.__len = int((stop - start) / step) - - def __len__(self): - return self.__len - - def find_point(self, val): - nsteps = (val - self.start) / self.step - fl = int(nsteps) - if fl == nsteps: - return int(fl) - else: - return int(fl + 1) - - def __getidx__(self, arg): - return self.start + self.step * arg diff --git a/src/hdmf/backends/hdf5/__init__.py b/src/hdmf/backends/hdf5/__init__.py index 6abfc8c85..8f76d7bcc 100644 --- a/src/hdmf/backends/hdf5/__init__.py +++ b/src/hdmf/backends/hdf5/__init__.py @@ -1,3 +1,3 @@ from . import h5_utils, h5tools -from .h5_utils import H5RegionSlicer, H5DataIO +from .h5_utils import H5DataIO from .h5tools import HDF5IO, H5SpecWriter, H5SpecReader diff --git a/src/hdmf/backends/hdf5/h5_utils.py b/src/hdmf/backends/hdf5/h5_utils.py index 2d7187721..878ebf089 100644 --- a/src/hdmf/backends/hdf5/h5_utils.py +++ b/src/hdmf/backends/hdf5/h5_utils.py @@ -8,7 +8,7 @@ from collections.abc import Iterable from copy import copy -from h5py import Group, Dataset, RegionReference, Reference, special_dtype +from h5py import Group, Dataset, Reference, special_dtype from h5py import filters as h5py_filters import json import numpy as np @@ -16,10 +16,8 @@ import os import logging -from ...array import Array from ...data_utils import DataIO, AbstractDataChunkIterator, append_data from ...query import HDMFDataset, ReferenceResolver, ContainerResolver, BuilderResolver -from ...region import RegionSlicer from ...spec import SpecWriter, SpecReader from ...utils import docval, getargs, popargs, get_docval, get_data_shape @@ -85,7 +83,7 @@ def append(self, dataset, data): class H5Dataset(HDMFDataset): - @docval({'name': 'dataset', 'type': (Dataset, Array), 'doc': 'the HDF5 file lazily evaluate'}, + @docval({'name': 'dataset', 'type': Dataset, 'doc': 'the HDF5 file lazily evaluate'}, {'name': 'io', 'type': 'hdmf.backends.hdf5.h5tools.HDF5IO', 'doc': 'the IO object that was used to read the underlying dataset'}) def __init__(self, **kwargs): @@ -96,10 +94,6 @@ def __init__(self, **kwargs): def io(self): return self.__io - @property - def regionref(self): - return self.dataset.regionref - @property def ref(self): return self.dataset.ref @@ -189,7 +183,7 @@ def get_object(self, h5obj): class AbstractH5TableDataset(DatasetOfReferences): - @docval({'name': 'dataset', 'type': (Dataset, Array), 'doc': 'the HDF5 file lazily evaluate'}, + @docval({'name': 'dataset', 'type': Dataset, 'doc': 'the HDF5 file lazily evaluate'}, {'name': 'io', 'type': 'hdmf.backends.hdf5.h5tools.HDF5IO', 'doc': 'the IO object that was used to read the underlying dataset'}, {'name': 'types', 'type': (list, tuple), @@ -199,9 +193,7 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.__refgetters = dict() for i, t in enumerate(types): - if t is RegionReference: - self.__refgetters[i] = self.__get_regref - elif t is Reference: + if t is Reference: self.__refgetters[i] = self._get_ref elif t is str: # we need this for when we read compound data types @@ -223,8 +215,6 @@ def __init__(self, **kwargs): t = sub.metadata['ref'] if t is Reference: tmp.append('object') - elif t is RegionReference: - tmp.append('region') else: tmp.append(sub.type.__name__) self.__dtype = tmp @@ -257,10 +247,6 @@ def _get_utf(self, string): """ return string.decode('utf-8') if isinstance(string, bytes) else string - def __get_regref(self, ref): - obj = self._get_ref(ref) - return obj[ref] - def resolve(self, manager): return self[0:len(self)] @@ -283,18 +269,6 @@ def dtype(self): return 'object' -class AbstractH5RegionDataset(AbstractH5ReferenceDataset): - - def __getitem__(self, arg): - obj = super().__getitem__(arg) - ref = self.dataset[arg] - return obj[ref] - - @property - def dtype(self): - return 'region' - - class ContainerH5TableDataset(ContainerResolverMixin, AbstractH5TableDataset): """ A reference-resolving dataset for resolving references inside tables @@ -339,28 +313,6 @@ def get_inverse_class(cls): return ContainerH5ReferenceDataset -class ContainerH5RegionDataset(ContainerResolverMixin, AbstractH5RegionDataset): - """ - A reference-resolving dataset for resolving region references that returns - resolved references as Containers - """ - - @classmethod - def get_inverse_class(cls): - return BuilderH5RegionDataset - - -class BuilderH5RegionDataset(BuilderResolverMixin, AbstractH5RegionDataset): - """ - A reference-resolving dataset for resolving region references that returns - resolved references as Builders - """ - - @classmethod - def get_inverse_class(cls): - return ContainerH5RegionDataset - - class H5SpecWriter(SpecWriter): __str_type = special_dtype(vlen=str) @@ -420,28 +372,6 @@ def read_namespace(self, ns_path): return ret -class H5RegionSlicer(RegionSlicer): - - @docval({'name': 'dataset', 'type': (Dataset, H5Dataset), 'doc': 'the HDF5 dataset to slice'}, - {'name': 'region', 'type': RegionReference, 'doc': 'the region reference to use to slice'}) - def __init__(self, **kwargs): - self.__dataset = getargs('dataset', kwargs) - self.__regref = getargs('region', kwargs) - self.__len = self.__dataset.regionref.selection(self.__regref)[0] - self.__region = None - - def __read_region(self): - if self.__region is None: - self.__region = self.__dataset[self.__regref] - - def __getitem__(self, idx): - self.__read_region() - return self.__region[idx] - - def __len__(self): - return self.__len - - class H5DataIO(DataIO): """ Wrap data arrays for write via HDF5IO to customize I/O behavior, such as compression and chunking diff --git a/src/hdmf/backends/hdf5/h5tools.py b/src/hdmf/backends/hdf5/h5tools.py index 765ff6aa5..bf8f86bb9 100644 --- a/src/hdmf/backends/hdf5/h5tools.py +++ b/src/hdmf/backends/hdf5/h5tools.py @@ -7,14 +7,14 @@ import numpy as np import h5py -from h5py import File, Group, Dataset, special_dtype, SoftLink, ExternalLink, Reference, RegionReference, check_dtype +from h5py import File, Group, Dataset, special_dtype, SoftLink, ExternalLink, Reference, check_dtype -from .h5_utils import (BuilderH5ReferenceDataset, BuilderH5RegionDataset, BuilderH5TableDataset, H5DataIO, +from .h5_utils import (BuilderH5ReferenceDataset, BuilderH5TableDataset, H5DataIO, H5SpecReader, H5SpecWriter, HDF5IODataChunkIteratorQueue) from ..io import HDMFIO from ..errors import UnsupportedOperation from ..warnings import BrokenLinkWarning -from ...build import (Builder, GroupBuilder, DatasetBuilder, LinkBuilder, BuildManager, RegionBuilder, +from ...build import (Builder, GroupBuilder, DatasetBuilder, LinkBuilder, BuildManager, ReferenceBuilder, TypeMap, ObjectMapper) from ...container import Container from ...data_utils import AbstractDataChunkIterator @@ -28,7 +28,6 @@ H5_TEXT = special_dtype(vlen=str) H5_BINARY = special_dtype(vlen=bytes) H5_REF = special_dtype(ref=Reference) -H5_REGREF = special_dtype(ref=RegionReference) RDCC_NBYTES = 32*2**20 # set raw data chunk cache size = 32 MiB @@ -693,10 +692,7 @@ def __read_dataset(self, h5obj, name=None): target = h5obj.file[scalar] target_builder = self.__read_dataset(target) self.__set_built(target.file.filename, target.id, target_builder) - if isinstance(scalar, RegionReference): - d = RegionBuilder(scalar, target_builder) - else: - d = ReferenceBuilder(target_builder) + d = ReferenceBuilder(target_builder) kwargs['data'] = d kwargs['dtype'] = d.dtype elif h5obj.dtype.kind == 'V': # scalar compound data type @@ -713,9 +709,6 @@ def __read_dataset(self, h5obj, name=None): elem1 = h5obj[tuple([0] * (h5obj.ndim - 1) + [0])] if isinstance(elem1, (str, bytes)): d = self._check_str_dtype(h5obj) - elif isinstance(elem1, RegionReference): # read list of references - d = BuilderH5RegionDataset(h5obj, self) - kwargs['dtype'] = d.dtype elif isinstance(elem1, Reference): d = BuilderH5ReferenceDataset(h5obj, self) kwargs['dtype'] = d.dtype @@ -751,9 +744,7 @@ def __read_attrs(self, h5obj): for k, v in h5obj.attrs.items(): if k == SPEC_LOC_ATTR: # ignore cached spec continue - if isinstance(v, RegionReference): - raise ValueError("cannot read region reference attributes yet") - elif isinstance(v, Reference): + if isinstance(v, Reference): ret[k] = self.__read_ref(h5obj.file[v]) else: ret[k] = v @@ -920,7 +911,6 @@ def get_type(cls, data): "ref": H5_REF, "reference": H5_REF, "object": H5_REF, - "region": H5_REGREF, "isodatetime": H5_TEXT, "datetime": H5_TEXT, } @@ -1238,29 +1228,12 @@ def _filler(): dset = self.__scalar_fill__(parent, name, data, options) else: dset = self.__list_fill__(parent, name, data, options) - # Write a dataset containing references, i.e., a region or object reference. + # Write a dataset containing references, i.e., object reference. # NOTE: we can ignore options['io_settings'] for scalar data elif self.__is_ref(options['dtype']): _dtype = self.__dtypes.get(options['dtype']) - # Write a scalar data region reference dataset - if isinstance(data, RegionBuilder): - dset = parent.require_dataset(name, shape=(), dtype=_dtype) - self.__set_written(builder) - self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing a " - "region reference. attributes: %s" - % (name, list(attributes.keys()))) - - @self.__queue_ref - def _filler(): - self.logger.debug("Resolving region reference and setting attribute on dataset '%s' " - "containing attributes: %s" - % (name, list(attributes.keys()))) - ref = self.__get_ref(data.builder, data.region) - dset = parent[name] - dset[()] = ref - self.set_attributes(dset, attributes) # Write a scalar object reference dataset - elif isinstance(data, ReferenceBuilder): + if isinstance(data, ReferenceBuilder): dset = parent.require_dataset(name, dtype=_dtype, shape=()) self.__set_written(builder) self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing an " @@ -1278,44 +1251,24 @@ def _filler(): self.set_attributes(dset, attributes) # Write an array dataset of references else: - # Write a array of region references - if options['dtype'] == 'region': - dset = parent.require_dataset(name, dtype=_dtype, shape=(len(data),), **options['io_settings']) - self.__set_written(builder) - self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing " - "region references. attributes: %s" - % (name, list(attributes.keys()))) - - @self.__queue_ref - def _filler(): - self.logger.debug("Resolving region references and setting attribute on dataset '%s' " - "containing attributes: %s" - % (name, list(attributes.keys()))) - refs = list() - for item in data: - refs.append(self.__get_ref(item.builder, item.region)) - dset = parent[name] - dset[()] = refs - self.set_attributes(dset, attributes) # Write array of object references - else: - dset = parent.require_dataset(name, shape=(len(data),), dtype=_dtype, **options['io_settings']) - self.__set_written(builder) - self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing " - "object references. attributes: %s" - % (name, list(attributes.keys()))) + dset = parent.require_dataset(name, shape=(len(data),), dtype=_dtype, **options['io_settings']) + self.__set_written(builder) + self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing " + "object references. attributes: %s" + % (name, list(attributes.keys()))) - @self.__queue_ref - def _filler(): - self.logger.debug("Resolving object references and setting attribute on dataset '%s' " - "containing attributes: %s" - % (name, list(attributes.keys()))) - refs = list() - for item in data: - refs.append(self.__get_ref(item)) - dset = parent[name] - dset[()] = refs - self.set_attributes(dset, attributes) + @self.__queue_ref + def _filler(): + self.logger.debug("Resolving object references and setting attribute on dataset '%s' " + "containing attributes: %s" + % (name, list(attributes.keys()))) + refs = list() + for item in data: + refs.append(self.__get_ref(item)) + dset = parent[name] + dset[()] = refs + self.set_attributes(dset, attributes) return # write a "regular" dataset else: @@ -1503,11 +1456,9 @@ def __list_fill__(cls, parent, name, data, options=None): @docval({'name': 'container', 'type': (Builder, Container, ReferenceBuilder), 'doc': 'the object to reference', 'default': None}, - {'name': 'region', 'type': (slice, list, tuple), 'doc': 'the region reference indexing object', - 'default': None}, returns='the reference', rtype=Reference) def __get_ref(self, **kwargs): - container, region = getargs('container', 'region', kwargs) + container = getargs('container', kwargs) if container is None: return None if isinstance(container, Builder): @@ -1525,20 +1476,10 @@ def __get_ref(self, **kwargs): path = self.__get_path(builder) self.logger.debug("Getting reference at path '%s'" % path) - if isinstance(container, RegionBuilder): - region = container.region - if region is not None: - dset = self.__file[path] - if not isinstance(dset, Dataset): - raise ValueError('cannot create region reference without Dataset') - return self.__file[path].regionref[region] - else: - return self.__file[path].ref + return self.__file[path].ref @docval({'name': 'container', 'type': (Builder, Container, ReferenceBuilder), 'doc': 'the object to reference', 'default': None}, - {'name': 'region', 'type': (slice, list, tuple), 'doc': 'the region reference indexing object', - 'default': None}, returns='the reference', rtype=Reference) def _create_ref(self, **kwargs): return self.__get_ref(**kwargs) @@ -1570,17 +1511,6 @@ def __queue_ref(self, func): # dependency self.__ref_queue.append(func) - def __rec_get_ref(self, ref_list): - ret = list() - for elem in ref_list: - if isinstance(elem, (list, tuple)): - ret.append(self.__rec_get_ref(elem)) - elif isinstance(elem, (Builder, Container)): - ret.append(self.__get_ref(elem)) - else: - ret.append(elem) - return ret - @property def mode(self): """ diff --git a/src/hdmf/build/classgenerator.py b/src/hdmf/build/classgenerator.py index a3336b98e..3b7d7c96e 100644 --- a/src/hdmf/build/classgenerator.py +++ b/src/hdmf/build/classgenerator.py @@ -4,7 +4,7 @@ import numpy as np -from ..container import Container, Data, DataRegion, MultiContainerInterface +from ..container import Container, Data, MultiContainerInterface from ..spec import AttributeSpec, LinkSpec, RefSpec, GroupSpec from ..spec.spec import BaseStorageSpec, ZERO_OR_MANY, ONE_OR_MANY from ..utils import docval, getargs, ExtenderMeta, get_docval, popargs, AllowPositional @@ -195,7 +195,7 @@ def _ischild(cls, dtype): if isinstance(dtype, tuple): for sub in dtype: ret = ret or cls._ischild(sub) - elif isinstance(dtype, type) and issubclass(dtype, (Container, Data, DataRegion)): + elif isinstance(dtype, type) and issubclass(dtype, (Container, Data)): ret = True return ret diff --git a/src/hdmf/build/manager.py b/src/hdmf/build/manager.py index 967c34010..bc586013c 100644 --- a/src/hdmf/build/manager.py +++ b/src/hdmf/build/manager.py @@ -490,20 +490,6 @@ def load_namespaces(self, **kwargs): self.register_container_type(new_ns, dt, container_cls) return deps - @docval({"name": "namespace", "type": str, "doc": "the namespace containing the data_type"}, - {"name": "data_type", "type": str, "doc": "the data type to create a AbstractContainer class for"}, - {"name": "autogen", "type": bool, "doc": "autogenerate class if one does not exist", "default": True}, - returns='the class for the given namespace and data_type', rtype=type) - def get_container_cls(self, **kwargs): - """Get the container class from data type specification. - If no class has been associated with the ``data_type`` from ``namespace``, a class will be dynamically - created and returned. - """ - # NOTE: this internally used function get_container_cls will be removed in favor of get_dt_container_cls - # Deprecated: Will be removed by HDMF 4.0 - namespace, data_type, autogen = getargs('namespace', 'data_type', 'autogen', kwargs) - return self.get_dt_container_cls(data_type, namespace, autogen) - @docval({"name": "data_type", "type": str, "doc": "the data type to create a AbstractContainer class for"}, {"name": "namespace", "type": str, "doc": "the namespace containing the data_type", "default": None}, {'name': 'post_init_method', 'type': Callable, 'default': None, @@ -515,7 +501,7 @@ def get_dt_container_cls(self, **kwargs): If no class has been associated with the ``data_type`` from ``namespace``, a class will be dynamically created and returned. - Replaces get_container_cls but namespace is optional. If namespace is unknown, it will be looked up from + Namespace is optional. If namespace is unknown, it will be looked up from all namespaces. """ namespace, data_type, post_init_method, autogen = getargs('namespace', 'data_type', diff --git a/src/hdmf/build/objectmapper.py b/src/hdmf/build/objectmapper.py index 3394ebb91..c2ef44b5f 100644 --- a/src/hdmf/build/objectmapper.py +++ b/src/hdmf/build/objectmapper.py @@ -15,7 +15,7 @@ IncorrectDatasetShapeBuildWarning) from hdmf.backends.hdf5.h5_utils import H5DataIO -from ..container import AbstractContainer, Data, DataRegion +from ..container import AbstractContainer, Data from ..term_set import TermSetWrapper from ..data_utils import DataIO, AbstractDataChunkIterator from ..query import ReferenceResolver @@ -966,41 +966,23 @@ def _filler(): return _filler def __get_ref_builder(self, builder, dtype, shape, container, build_manager): - bldr_data = None - if dtype.is_region(): - if shape is None: - if not isinstance(container, DataRegion): - msg = "'container' must be of type DataRegion if spec represents region reference" - raise ValueError(msg) - self.logger.debug("Setting %s '%s' data to region reference builder" - % (builder.__class__.__name__, builder.name)) - target_builder = self.__get_target_builder(container.data, build_manager, builder) - bldr_data = RegionBuilder(container.region, target_builder) - else: - self.logger.debug("Setting %s '%s' data to list of region reference builders" - % (builder.__class__.__name__, builder.name)) - bldr_data = list() - for d in container.data: - target_builder = self.__get_target_builder(d.target, build_manager, builder) - bldr_data.append(RegionBuilder(d.slice, target_builder)) + self.logger.debug("Setting object reference dataset on %s '%s' data" + % (builder.__class__.__name__, builder.name)) + if isinstance(container, Data): + self.logger.debug("Setting %s '%s' data to list of reference builders" + % (builder.__class__.__name__, builder.name)) + bldr_data = list() + for d in container.data: + target_builder = self.__get_target_builder(d, build_manager, builder) + bldr_data.append(ReferenceBuilder(target_builder)) + if isinstance(container.data, H5DataIO): + # This is here to support appending a dataset of references. + bldr_data = H5DataIO(bldr_data, **container.data.get_io_params()) else: - self.logger.debug("Setting object reference dataset on %s '%s' data" + self.logger.debug("Setting %s '%s' data to reference builder" % (builder.__class__.__name__, builder.name)) - if isinstance(container, Data): - self.logger.debug("Setting %s '%s' data to list of reference builders" - % (builder.__class__.__name__, builder.name)) - bldr_data = list() - for d in container.data: - target_builder = self.__get_target_builder(d, build_manager, builder) - bldr_data.append(ReferenceBuilder(target_builder)) - if isinstance(container.data, H5DataIO): - # This is here to support appending a dataset of references. - bldr_data = H5DataIO(bldr_data, **container.data.get_io_params()) - else: - self.logger.debug("Setting %s '%s' data to reference builder" - % (builder.__class__.__name__, builder.name)) - target_builder = self.__get_target_builder(container, build_manager, builder) - bldr_data = ReferenceBuilder(target_builder) + target_builder = self.__get_target_builder(container, build_manager, builder) + bldr_data = ReferenceBuilder(target_builder) return bldr_data def __get_target_builder(self, container, build_manager, builder): diff --git a/src/hdmf/common/table.py b/src/hdmf/common/table.py index b4530c7b7..84ac4da3b 100644 --- a/src/hdmf/common/table.py +++ b/src/hdmf/common/table.py @@ -775,8 +775,8 @@ def add_column(self, **kwargs): # noqa: C901 index, table, enum, col_cls, check_ragged = popargs('index', 'table', 'enum', 'col_cls', 'check_ragged', kwargs) if isinstance(index, VectorIndex): - warn("Passing a VectorIndex in for index may lead to unexpected behavior. This functionality will be " - "deprecated in a future version of HDMF.", category=FutureWarning, stacklevel=3) + msg = "Passing a VectorIndex may lead to unexpected behavior. This functionality is not supported." + raise ValueError(msg) if name in self.__colids: # column has already been added msg = "column '%s' already exists in %s '%s'" % (name, self.__class__.__name__, self.name) diff --git a/src/hdmf/container.py b/src/hdmf/container.py index a61dc19e8..864b34ee9 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -1,5 +1,4 @@ import types -from abc import abstractmethod from collections import OrderedDict from copy import deepcopy from typing import Type, Optional @@ -467,21 +466,6 @@ def set_modified(self, **kwargs): def children(self): return tuple(self.__children) - @docval({'name': 'child', 'type': 'Container', - 'doc': 'the child Container for this Container', 'default': None}) - def add_child(self, **kwargs): - warn(DeprecationWarning('add_child is deprecated. Set the parent attribute instead.')) - child = getargs('child', kwargs) - if child is not None: - # if child.parent is a Container, then the mismatch between child.parent and parent - # is used to make a soft/external link from the parent to a child elsewhere - # if child.parent is not a Container, it is either None or a Proxy and should be set to self - if not isinstance(child.parent, AbstractContainer): - # actually add the child to the parent in parent setter - child.parent = self - else: - warn('Cannot add None as child to a container %s' % self.name) - @classmethod def type_hierarchy(cls): return cls.__mro__ @@ -913,20 +897,6 @@ def shape(self): """ return get_data_shape(self.__data) - @docval({'name': 'dataio', 'type': DataIO, 'doc': 'the DataIO to apply to the data held by this Data'}) - def set_dataio(self, **kwargs): - """ - Apply DataIO object to the data held by this Data object - """ - warn( - "Data.set_dataio() is deprecated. Please use Data.set_data_io() instead.", - DeprecationWarning, - stacklevel=3, - ) - dataio = getargs('dataio', kwargs) - dataio.data = self.__data - self.__data = dataio - def set_data_io( self, data_io_class: Type[DataIO], @@ -1022,25 +992,6 @@ def _validate_new_data_element(self, arg): pass -class DataRegion(Data): - - @property - @abstractmethod - def data(self): - ''' - The target data that this region applies to - ''' - pass - - @property - @abstractmethod - def region(self): - ''' - The region that indexes into data e.g. slice or list of indices - ''' - pass - - class MultiContainerInterface(Container): """Class that dynamically defines methods to support a Container holding multiple Containers of the same type. diff --git a/src/hdmf/query.py b/src/hdmf/query.py index 9693b0b1c..abe2a93a7 100644 --- a/src/hdmf/query.py +++ b/src/hdmf/query.py @@ -2,143 +2,24 @@ import numpy as np -from .array import Array from .utils import ExtenderMeta, docval_macro, docval, getargs -class Query(metaclass=ExtenderMeta): - __operations__ = ( - '__lt__', - '__gt__', - '__le__', - '__ge__', - '__eq__', - '__ne__', - ) - - @classmethod - def __build_operation(cls, op): - def __func(self, arg): - return cls(self, op, arg) - - @ExtenderMeta.pre_init - def __make_operators(cls, name, bases, classdict): - if not isinstance(cls.__operations__, tuple): - raise TypeError("'__operations__' must be of type tuple") - # add any new operations - if len(bases) and 'Query' in globals() and issubclass(bases[-1], Query) \ - and bases[-1].__operations__ is not cls.__operations__: - new_operations = list(cls.__operations__) - new_operations[0:0] = bases[-1].__operations__ - cls.__operations__ = tuple(new_operations) - for op in cls.__operations__: - if not hasattr(cls, op): - setattr(cls, op, cls.__build_operation(op)) - - def __init__(self, obj, op, arg): - self.obj = obj - self.op = op - self.arg = arg - self.collapsed = None - self.expanded = None - - @docval({'name': 'expand', 'type': bool, 'help': 'whether or not to expand result', 'default': True}) - def evaluate(self, **kwargs): - expand = getargs('expand', kwargs) - if expand: - if self.expanded is None: - self.expanded = self.__evalhelper() - return self.expanded - else: - if self.collapsed is None: - self.collapsed = self.__collapse(self.__evalhelper()) - return self.collapsed - - def __evalhelper(self): - obj = self.obj - arg = self.arg - if isinstance(obj, Query): - obj = obj.evaluate() - elif isinstance(obj, HDMFDataset): - obj = obj.dataset - if isinstance(arg, Query): - arg = self.arg.evaluate() - return getattr(obj, self.op)(self.arg) - - def __collapse(self, result): - if isinstance(result, slice): - return (result.start, result.stop) - elif isinstance(result, list): - ret = list() - for idx in result: - if isinstance(idx, slice) and (idx.step is None or idx.step == 1): - ret.append((idx.start, idx.stop)) - else: - ret.append(idx) - return ret - else: - return result - - def __and__(self, other): - return NotImplemented - - def __or__(self, other): - return NotImplemented - - def __xor__(self, other): - return NotImplemented - - def __contains__(self, other): - return NotImplemented - - @docval_macro('array_data') class HDMFDataset(metaclass=ExtenderMeta): - __operations__ = ( - '__lt__', - '__gt__', - '__le__', - '__ge__', - '__eq__', - '__ne__', - ) - - @classmethod - def __build_operation(cls, op): - def __func(self, arg): - return Query(self, op, arg) - - setattr(__func, '__name__', op) - return __func - - @ExtenderMeta.pre_init - def __make_operators(cls, name, bases, classdict): - if not isinstance(cls.__operations__, tuple): - raise TypeError("'__operations__' must be of type tuple") - # add any new operations - if len(bases) and 'Query' in globals() and issubclass(bases[-1], Query) \ - and bases[-1].__operations__ is not cls.__operations__: - new_operations = list(cls.__operations__) - new_operations[0:0] = bases[-1].__operations__ - cls.__operations__ = tuple(new_operations) - for op in cls.__operations__: - setattr(cls, op, cls.__build_operation(op)) - def __evaluate_key(self, key): if isinstance(key, tuple) and len(key) == 0: return key if isinstance(key, (tuple, list, np.ndarray)): return list(map(self.__evaluate_key, key)) else: - if isinstance(key, Query): - return key.evaluate() return key def __getitem__(self, key): idx = self.__evaluate_key(key) return self.dataset[idx] - @docval({'name': 'dataset', 'type': ('array_data', Array), 'doc': 'the HDF5 file lazily evaluate'}) + @docval({'name': 'dataset', 'type': 'array_data', 'doc': 'the HDF5 file lazily evaluate'}) def __init__(self, **kwargs): super().__init__() self.__dataset = getargs('dataset', kwargs) diff --git a/src/hdmf/region.py b/src/hdmf/region.py deleted file mode 100644 index 9feeba401..000000000 --- a/src/hdmf/region.py +++ /dev/null @@ -1,91 +0,0 @@ -from abc import ABCMeta, abstractmethod -from operator import itemgetter - -from .container import Data, DataRegion -from .utils import docval, getargs - - -class RegionSlicer(DataRegion, metaclass=ABCMeta): - ''' - A abstract base class to control getting using a region - - Subclasses must implement `__getitem__` and `__len__` - ''' - - @docval({'name': 'target', 'type': None, 'doc': 'the target to slice'}, - {'name': 'slice', 'type': None, 'doc': 'the region to slice'}) - def __init__(self, **kwargs): - self.__target = getargs('target', kwargs) - self.__slice = getargs('slice', kwargs) - - @property - def data(self): - """The target data. Same as self.target""" - return self.target - - @property - def region(self): - """The selected region. Same as self.slice""" - return self.slice - - @property - def target(self): - """The target data""" - return self.__target - - @property - def slice(self): - """The selected slice""" - return self.__slice - - @property - @abstractmethod - def __getitem__(self, idx): - """Must be implemented by subclasses""" - pass - - @property - @abstractmethod - def __len__(self): - """Must be implemented by subclasses""" - pass - - -class ListSlicer(RegionSlicer): - """Implementation of RegionSlicer for slicing Lists and Data""" - - @docval({'name': 'dataset', 'type': (list, tuple, Data), 'doc': 'the dataset to slice'}, - {'name': 'region', 'type': (list, tuple, slice), 'doc': 'the region reference to use to slice'}) - def __init__(self, **kwargs): - self.__dataset, self.__region = getargs('dataset', 'region', kwargs) - super().__init__(self.__dataset, self.__region) - if isinstance(self.__region, slice): - self.__getter = itemgetter(self.__region) - self.__len = len(range(*self.__region.indices(len(self.__dataset)))) - else: - self.__getter = itemgetter(*self.__region) - self.__len = len(self.__region) - - def __read_region(self): - """ - Internal helper function used to define self._read - """ - if not hasattr(self, '_read'): - self._read = self.__getter(self.__dataset) - del self.__getter - - def __getitem__(self, idx): - """ - Get data values from selected data - """ - self.__read_region() - getter = None - if isinstance(idx, (list, tuple)): - getter = itemgetter(*idx) - else: - getter = itemgetter(idx) - return getter(self._read) - - def __len__(self): - """Number of values in the slice/region""" - return self.__len diff --git a/src/hdmf/utils.py b/src/hdmf/utils.py index ccd3f0b0b..6b5900384 100644 --- a/src/hdmf/utils.py +++ b/src/hdmf/utils.py @@ -382,8 +382,6 @@ def __parse_args(validator, args, kwargs, enforce_type=True, enforce_shape=True, for key in extras.keys(): type_errors.append("unrecognized argument: '%s'" % key) else: - # TODO: Extras get stripped out if function arguments are composed with fmt_docval_args. - # allow_extra needs to be tracked on a function so that fmt_docval_args doesn't strip them out for key in extras.keys(): ret[key] = extras[key] return {'args': ret, 'future_warnings': future_warnings, 'type_errors': type_errors, 'value_errors': value_errors, @@ -414,95 +412,6 @@ def get_docval(func, *args): return tuple() -# def docval_wrap(func, is_method=True): -# if is_method: -# @docval(*get_docval(func)) -# def method(self, **kwargs): -# -# return call_docval_args(func, kwargs) -# return method -# else: -# @docval(*get_docval(func)) -# def static_method(**kwargs): -# return call_docval_args(func, kwargs) -# return method - - -def fmt_docval_args(func, kwargs): - ''' Separate positional and keyword arguments - - Useful for methods that wrap other methods - ''' - warnings.warn("fmt_docval_args will be deprecated in a future version of HDMF. Instead of using fmt_docval_args, " - "call the function directly with the kwargs. Please note that fmt_docval_args " - "removes all arguments not accepted by the function's docval, so if you are passing kwargs that " - "includes extra arguments and the function's docval does not allow extra arguments (allow_extra=True " - "is set), then you will need to pop the extra arguments out of kwargs before calling the function.", - PendingDeprecationWarning, stacklevel=2) - func_docval = getattr(func, docval_attr_name, None) - ret_args = list() - ret_kwargs = dict() - kwargs_copy = _copy.copy(kwargs) - if func_docval: - for arg in func_docval[__docval_args_loc]: - val = kwargs_copy.pop(arg['name'], None) - if 'default' in arg: - if val is not None: - ret_kwargs[arg['name']] = val - else: - ret_args.append(val) - if func_docval['allow_extra']: - ret_kwargs.update(kwargs_copy) - else: - raise ValueError('no docval found on %s' % str(func)) - return ret_args, ret_kwargs - - -# def _remove_extra_args(func, kwargs): -# """Return a dict of only the keyword arguments that are accepted by the function's docval. -# -# If the docval specifies allow_extra=True, then the original kwargs are returned. -# """ -# # NOTE: this has the same functionality as the to-be-deprecated fmt_docval_args except that -# # kwargs are kept as kwargs instead of parsed into args and kwargs -# func_docval = getattr(func, docval_attr_name, None) -# if func_docval: -# if func_docval['allow_extra']: -# # if extra args are allowed, return all args -# return kwargs -# else: -# # save only the arguments listed in the function's docval (skip any others present in kwargs) -# ret_kwargs = dict() -# for arg in func_docval[__docval_args_loc]: -# val = kwargs.get(arg['name'], None) -# if val is not None: # do not return arguments that are not present or have value None -# ret_kwargs[arg['name']] = val -# return ret_kwargs -# else: -# raise ValueError('No docval found on %s' % str(func)) - - -def call_docval_func(func, kwargs): - """Call the function with only the keyword arguments that are accepted by the function's docval. - - Extra keyword arguments are not passed to the function unless the function's docval has allow_extra=True. - """ - warnings.warn("call_docval_func will be deprecated in a future version of HDMF. Instead of using call_docval_func, " - "call the function directly with the kwargs. Please note that call_docval_func " - "removes all arguments not accepted by the function's docval, so if you are passing kwargs that " - "includes extra arguments and the function's docval does not allow extra arguments (allow_extra=True " - "is set), then you will need to pop the extra arguments out of kwargs before calling the function.", - PendingDeprecationWarning, stacklevel=2) - with warnings.catch_warnings(record=True): - # catch and ignore only PendingDeprecationWarnings from fmt_docval_args so that two - # PendingDeprecationWarnings saying the same thing are not raised - warnings.simplefilter("ignore", UserWarning) - warnings.simplefilter("always", PendingDeprecationWarning) - fargs, fkwargs = fmt_docval_args(func, kwargs) - - return func(*fargs, **fkwargs) - - def __resolve_type(t): if t is None: return t diff --git a/tests/unit/common/test_table.py b/tests/unit/common/test_table.py index 38175b230..15a0c9e91 100644 --- a/tests/unit/common/test_table.py +++ b/tests/unit/common/test_table.py @@ -429,9 +429,7 @@ def test_add_column_vectorindex(self): table.add_column(name='qux', description='qux column') ind = VectorIndex(name='quux', data=list(), target=table['qux']) - msg = ("Passing a VectorIndex in for index may lead to unexpected behavior. This functionality will be " - "deprecated in a future version of HDMF.") - with self.assertWarnsWith(FutureWarning, msg): + with self.assertRaises(ValueError): table.add_column(name='bad', description='bad column', index=ind) def test_add_column_multi_index(self): diff --git a/tests/unit/test_container.py b/tests/unit/test_container.py index c12247de7..2abe6349b 100644 --- a/tests/unit/test_container.py +++ b/tests/unit/test_container.py @@ -213,18 +213,6 @@ def test_all_children(self): obj = species.all_objects self.assertEqual(sorted(list(obj.keys())), sorted([species.object_id, species.id.object_id, col1.object_id])) - def test_add_child(self): - """Test that add child creates deprecation warning and also properly sets child's parent and modified - """ - parent_obj = Container('obj1') - child_obj = Container('obj2') - parent_obj.set_modified(False) - with self.assertWarnsWith(DeprecationWarning, 'add_child is deprecated. Set the parent attribute instead.'): - parent_obj.add_child(child_obj) - self.assertIs(child_obj.parent, parent_obj) - self.assertTrue(parent_obj.modified) - self.assertIs(parent_obj.children[0], child_obj) - def test_parent_set_link_warning(self): col1 = VectorData( name='col1', diff --git a/tests/unit/test_query.py b/tests/unit/test_query.py deleted file mode 100644 index b2ff267a7..000000000 --- a/tests/unit/test_query.py +++ /dev/null @@ -1,161 +0,0 @@ -import os -from abc import ABCMeta, abstractmethod - -import numpy as np -from h5py import File -from hdmf.array import SortedArray, LinSpace -from hdmf.query import HDMFDataset, Query -from hdmf.testing import TestCase - - -class AbstractQueryMixin(metaclass=ABCMeta): - - @abstractmethod - def getDataset(self): - raise NotImplementedError('Cannot run test unless getDataset is implemented') - - def setUp(self): - self.dset = self.getDataset() - self.wrapper = HDMFDataset(self.dset) - - def test_get_dataset(self): - array = self.wrapper.dataset - self.assertIsInstance(array, SortedArray) - - def test___gt__(self): - ''' - Test wrapper greater than magic method - ''' - q = self.wrapper > 5 - self.assertIsInstance(q, Query) - result = q.evaluate() - expected = [False, False, False, False, False, - False, True, True, True, True] - expected = slice(6, 10) - self.assertEqual(result, expected) - - def test___ge__(self): - ''' - Test wrapper greater than or equal magic method - ''' - q = self.wrapper >= 5 - self.assertIsInstance(q, Query) - result = q.evaluate() - expected = [False, False, False, False, False, - True, True, True, True, True] - expected = slice(5, 10) - self.assertEqual(result, expected) - - def test___lt__(self): - ''' - Test wrapper less than magic method - ''' - q = self.wrapper < 5 - self.assertIsInstance(q, Query) - result = q.evaluate() - expected = [True, True, True, True, True, - False, False, False, False, False] - expected = slice(0, 5) - self.assertEqual(result, expected) - - def test___le__(self): - ''' - Test wrapper less than or equal magic method - ''' - q = self.wrapper <= 5 - self.assertIsInstance(q, Query) - result = q.evaluate() - expected = [True, True, True, True, True, - True, False, False, False, False] - expected = slice(0, 6) - self.assertEqual(result, expected) - - def test___eq__(self): - ''' - Test wrapper equals magic method - ''' - q = self.wrapper == 5 - self.assertIsInstance(q, Query) - result = q.evaluate() - expected = [False, False, False, False, False, - True, False, False, False, False] - expected = 5 - self.assertTrue(np.array_equal(result, expected)) - - def test___ne__(self): - ''' - Test wrapper not equal magic method - ''' - q = self.wrapper != 5 - self.assertIsInstance(q, Query) - result = q.evaluate() - expected = [True, True, True, True, True, - False, True, True, True, True] - expected = [slice(0, 5), slice(6, 10)] - self.assertTrue(np.array_equal(result, expected)) - - def test___getitem__(self): - ''' - Test wrapper getitem using slice - ''' - result = self.wrapper[0:5] - expected = [0, 1, 2, 3, 4] - self.assertTrue(np.array_equal(result, expected)) - - def test___getitem__query(self): - ''' - Test wrapper getitem using query - ''' - q = self.wrapper < 5 - result = self.wrapper[q] - expected = [0, 1, 2, 3, 4] - self.assertTrue(np.array_equal(result, expected)) - - -class SortedQueryTest(AbstractQueryMixin, TestCase): - - path = 'SortedQueryTest.h5' - - def getDataset(self): - self.f = File(self.path, 'w') - self.input = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - self.d = self.f.create_dataset('dset', data=self.input) - return SortedArray(self.d) - - def tearDown(self): - self.f.close() - if os.path.exists(self.path): - os.remove(self.path) - - -class LinspaceQueryTest(AbstractQueryMixin, TestCase): - - path = 'LinspaceQueryTest.h5' - - def getDataset(self): - return LinSpace(0, 10, 1) - - -class CompoundQueryTest(TestCase): - - def getM(self): - return SortedArray(np.arange(10, 20, 1)) - - def getN(self): - return SortedArray(np.arange(10.0, 20.0, 0.5)) - - def setUp(self): - self.m = HDMFDataset(self.getM()) - self.n = HDMFDataset(self.getN()) - - # TODO: test not completed - # def test_map(self): - # q = self.m == (12, 16) # IN operation - # q.evaluate() # [2,3,4,5] - # q.evaluate(False) # RangeResult(2,6) - # r = self.m[q] # noqa: F841 - # r = self.m[q.evaluate()] # noqa: F841 - # r = self.m[q.evaluate(False)] # noqa: F841 - - def tearDown(self): - pass diff --git a/tests/unit/utils_test/test_core_DataIO.py b/tests/unit/utils_test/test_core_DataIO.py index 4c2ffac15..80518a316 100644 --- a/tests/unit/utils_test/test_core_DataIO.py +++ b/tests/unit/utils_test/test_core_DataIO.py @@ -1,10 +1,8 @@ from copy import copy, deepcopy import numpy as np -from hdmf.container import Data from hdmf.data_utils import DataIO from hdmf.testing import TestCase -import warnings class DataIOTests(TestCase): @@ -30,34 +28,13 @@ def test_dataio_slice_delegation(self): dset = DataIO(indata) self.assertTrue(np.all(dset[1:3, 5:8] == indata[1:3, 5:8])) - def test_set_dataio(self): - """ - Test that Data.set_dataio works as intended - """ - dataio = DataIO() - data = np.arange(30).reshape(5, 2, 3) - container = Data('wrapped_data', data) - msg = "Data.set_dataio() is deprecated. Please use Data.set_data_io() instead." - with self.assertWarnsWith(DeprecationWarning, msg): - container.set_dataio(dataio) - self.assertIs(dataio.data, data) - self.assertIs(dataio, container.data) - - def test_set_dataio_data_already_set(self): + def test_set_data_io_data_already_set(self): """ Test that Data.set_dataio works as intended """ dataio = DataIO(data=np.arange(30).reshape(5, 2, 3)) - data = np.arange(30).reshape(5, 2, 3) - container = Data('wrapped_data', data) with self.assertRaisesWith(ValueError, "cannot overwrite 'data' on DataIO"): - with warnings.catch_warnings(record=True): - warnings.filterwarnings( - action='ignore', - category=DeprecationWarning, - message="Data.set_dataio() is deprecated. Please use Data.set_data_io() instead.", - ) - container.set_dataio(dataio) + dataio.data=[1,2,3,4] def test_dataio_options(self): """ diff --git a/tests/unit/utils_test/test_docval.py b/tests/unit/utils_test/test_docval.py index c766dcf46..bed5cd134 100644 --- a/tests/unit/utils_test/test_docval.py +++ b/tests/unit/utils_test/test_docval.py @@ -1,7 +1,7 @@ import numpy as np from hdmf.testing import TestCase -from hdmf.utils import (docval, fmt_docval_args, get_docval, getargs, popargs, AllowPositional, get_docval_macro, - docval_macro, popargs_to_dict, call_docval_func) +from hdmf.utils import (docval, get_docval, getargs, popargs, AllowPositional, get_docval_macro, + docval_macro, popargs_to_dict) class MyTestClass(object): @@ -137,80 +137,6 @@ def method1(self, **kwargs): with self.assertRaises(ValueError): method1(self, arg1=[[1, 1, 1]]) - fmt_docval_warning_msg = ( - "fmt_docval_args will be deprecated in a future version of HDMF. Instead of using fmt_docval_args, " - "call the function directly with the kwargs. Please note that fmt_docval_args " - "removes all arguments not accepted by the function's docval, so if you are passing kwargs that " - "includes extra arguments and the function's docval does not allow extra arguments (allow_extra=True " - "is set), then you will need to pop the extra arguments out of kwargs before calling the function." - ) - - def test_fmt_docval_args(self): - """ Test that fmt_docval_args parses the args and strips extra args """ - test_kwargs = { - 'arg1': 'a string', - 'arg2': 1, - 'arg3': True, - 'hello': 'abc', - 'list': ['abc', 1, 2, 3] - } - with self.assertWarnsWith(PendingDeprecationWarning, self.fmt_docval_warning_msg): - rec_args, rec_kwargs = fmt_docval_args(self.test_obj.basic_add2_kw, test_kwargs) - exp_args = ['a string', 1] - self.assertListEqual(rec_args, exp_args) - exp_kwargs = {'arg3': True} - self.assertDictEqual(rec_kwargs, exp_kwargs) - - def test_fmt_docval_args_no_docval(self): - """ Test that fmt_docval_args raises an error when run on function without docval """ - def method1(self, **kwargs): - pass - - with self.assertRaisesRegex(ValueError, r"no docval found on .*method1.*"): - with self.assertWarnsWith(PendingDeprecationWarning, self.fmt_docval_warning_msg): - fmt_docval_args(method1, {}) - - def test_fmt_docval_args_allow_extra(self): - """ Test that fmt_docval_args works """ - test_kwargs = { - 'arg1': 'a string', - 'arg2': 1, - 'arg3': True, - 'hello': 'abc', - 'list': ['abc', 1, 2, 3] - } - with self.assertWarnsWith(PendingDeprecationWarning, self.fmt_docval_warning_msg): - rec_args, rec_kwargs = fmt_docval_args(self.test_obj.basic_add2_kw_allow_extra, test_kwargs) - exp_args = ['a string', 1] - self.assertListEqual(rec_args, exp_args) - exp_kwargs = {'arg3': True, 'hello': 'abc', 'list': ['abc', 1, 2, 3]} - self.assertDictEqual(rec_kwargs, exp_kwargs) - - def test_call_docval_func(self): - """Test that call_docval_func strips extra args and calls the function.""" - test_kwargs = { - 'arg1': 'a string', - 'arg2': 1, - 'arg3': True, - 'hello': 'abc', - 'list': ['abc', 1, 2, 3] - } - msg = ( - "call_docval_func will be deprecated in a future version of HDMF. Instead of using call_docval_func, " - "call the function directly with the kwargs. Please note that call_docval_func " - "removes all arguments not accepted by the function's docval, so if you are passing kwargs that " - "includes extra arguments and the function's docval does not allow extra arguments (allow_extra=True " - "is set), then you will need to pop the extra arguments out of kwargs before calling the function." - ) - with self.assertWarnsWith(PendingDeprecationWarning, msg): - ret_kwargs = call_docval_func(self.test_obj.basic_add2_kw, test_kwargs) - exp_kwargs = { - 'arg1': 'a string', - 'arg2': 1, - 'arg3': True - } - self.assertDictEqual(ret_kwargs, exp_kwargs) - def test_docval_add(self): """Test that docval works with a single positional argument From 26d584cc0a3e0985d0e053ab9eeaf5bf8849cd31 Mon Sep 17 00:00:00 2001 From: Matthew Avaylon Date: Thu, 14 Nov 2024 18:25:06 -0800 Subject: [PATCH 63/82] Remove Python 3.8 Support and Add 3.13 Support (#1209) --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/workflows/run_all_tests.yml | 57 +++++++++++++-------------- .github/workflows/run_coverage.yml | 2 +- .github/workflows/run_tests.yml | 30 +++++++------- .readthedocs.yaml | 4 +- CHANGELOG.md | 1 + docs/source/install_users.rst | 2 +- environment-ros3.yml | 10 ++--- pyproject.toml | 24 +++++------ requirements-min.txt | 17 +++----- requirements-opt.txt | 6 +-- requirements.txt | 10 ++--- src/hdmf/common/__init__.py | 6 +-- tox.ini | 16 ++++---- 14 files changed, 88 insertions(+), 99 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index ef3daed9c..f1d04aa69 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -60,11 +60,11 @@ body: attributes: label: Python Version options: + - "3.13" - "3.12" - "3.11" - "3.10" - "3.9" - - "3.8" validations: required: true - type: textarea diff --git a/.github/workflows/run_all_tests.yml b/.github/workflows/run_all_tests.yml index b713f4763..961500194 100644 --- a/.github/workflows/run_all_tests.yml +++ b/.github/workflows/run_all_tests.yml @@ -25,30 +25,30 @@ jobs: fail-fast: false matrix: include: - - { name: linux-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } - - { name: linux-python3.9 , test-tox-env: pytest-py39-pinned , python-ver: "3.9" , os: ubuntu-latest } + - { name: linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - { name: linux-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: ubuntu-latest } - { name: linux-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: ubuntu-latest } - { name: linux-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: ubuntu-latest } - { name: linux-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: ubuntu-latest } - - { name: linux-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } - - { name: linux-python3.12-prerelease , test-tox-env: pytest-py312-prerelease , python-ver: "3.12", os: ubuntu-latest } - - { name: windows-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: windows-latest } - - { name: windows-python3.9 , test-tox-env: pytest-py39-pinned , python-ver: "3.9" , os: windows-latest } + - { name: linux-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: ubuntu-latest } + - { name: linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } + - { name: linux-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: ubuntu-latest } + - { name: windows-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: windows-latest } - { name: windows-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: windows-latest } - { name: windows-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: windows-latest } - { name: windows-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: windows-latest } - { name: windows-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: windows-latest } - - { name: windows-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: windows-latest } - - { name: windows-python3.12-prerelease , test-tox-env: pytest-py312-prerelease , python-ver: "3.12", os: windows-latest } - - { name: macos-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: macos-13 } - - { name: macos-python3.9 , test-tox-env: pytest-py39-pinned , python-ver: "3.9" , os: macos-13 } + - { name: windows-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: windows-latest } + - { name: windows-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: windows-latest } + - { name: windows-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: windows-latest } + - { name: macos-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: macos-13 } - { name: macos-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: macos-latest } - { name: macos-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: macos-latest } - { name: macos-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: macos-latest } - { name: macos-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: macos-latest } - - { name: macos-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: macos-latest } - - { name: macos-python3.12-prerelease , test-tox-env: pytest-py312-prerelease , python-ver: "3.12", os: macos-latest } + - { name: macos-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: macos-latest } + - { name: macos-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: macos-latest } + - { name: macos-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -97,18 +97,18 @@ jobs: fail-fast: false matrix: include: - - { name: linux-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } + - { name: linux-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - { name: linux-gallery-python3.11-optional , test-tox-env: gallery-py311-optional-pinned , python-ver: "3.11", os: ubuntu-latest } - - { name: linux-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } - - { name: linux-gallery-python3.12-prerelease , test-tox-env: gallery-py312-prerelease , python-ver: "3.12", os: ubuntu-latest } - - { name: windows-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: windows-latest } + - { name: linux-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } + - { name: linux-gallery-python3.13-prerelease , test-tox-env: gallery-py313-prerelease , python-ver: "3.13", os: ubuntu-latest } + - { name: windows-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: windows-latest } - { name: windows-gallery-python3.11-optional , test-tox-env: gallery-py311-optional-pinned , python-ver: "3.11", os: windows-latest } - - { name: windows-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: windows-latest } - - { name: windows-gallery-python3.12-prerelease, test-tox-env: gallery-py312-prerelease , python-ver: "3.12", os: windows-latest } - - { name: macos-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: macos-13 } + - { name: windows-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: windows-latest } + - { name: windows-gallery-python3.13-prerelease, test-tox-env: gallery-py313-prerelease , python-ver: "3.13", os: windows-latest } + - { name: macos-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: macos-13 } - { name: macos-gallery-python3.11-optional , test-tox-env: gallery-py311-optional-pinned , python-ver: "3.11", os: macos-latest } - - { name: macos-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: macos-latest } - - { name: macos-gallery-python3.12-prerelease , test-tox-env: gallery-py312-prerelease , python-ver: "3.12", os: macos-latest } + - { name: macos-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: macos-latest } + - { name: macos-gallery-python3.13-prerelease , test-tox-env: gallery-py313-prerelease , python-ver: "3.13", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -144,14 +144,13 @@ jobs: fail-fast: false matrix: include: - - { name: conda-linux-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } - - { name: conda-linux-python3.9 , test-tox-env: pytest-py39-pinned , python-ver: "3.9" , os: ubuntu-latest } + - { name: conda-linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - { name: conda-linux-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: ubuntu-latest } - { name: conda-linux-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: ubuntu-latest } - { name: conda-linux-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: ubuntu-latest } - - { name: conda-linux-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: ubuntu-latest } - - { name: conda-linux-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } - - { name: conda-linux-python3.12-prerelease , test-tox-env: pytest-py312-prerelease , python-ver: "3.12", os: ubuntu-latest } + - { name: conda-linux-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: ubuntu-latest } + - { name: conda-linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } + - { name: conda-linux-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: ubuntu-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -209,9 +208,9 @@ jobs: fail-fast: false matrix: include: - - { name: linux-python3.12-ros3 , python-ver: "3.12", os: ubuntu-latest } - - { name: windows-python3.12-ros3 , python-ver: "3.12", os: windows-latest } - - { name: macos-python3.12-ros3 , python-ver: "3.12", os: macos-latest } + - { name: linux-python3.13-ros3 , python-ver: "3.13", os: ubuntu-latest } + - { name: windows-python3.13-ros3 , python-ver: "3.13", os: windows-latest } + - { name: macos-python3.13-ros3 , python-ver: "3.13", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 diff --git a/.github/workflows/run_coverage.yml b/.github/workflows/run_coverage.yml index 08b6c59ea..324b1e9d5 100644 --- a/.github/workflows/run_coverage.yml +++ b/.github/workflows/run_coverage.yml @@ -84,7 +84,7 @@ jobs: fail-fast: false matrix: include: - - { name: linux-python3.12-ros3 , python-ver: "3.12", os: ubuntu-latest } + - { name: linux-python3.13-ros3 , python-ver: "3.13", os: ubuntu-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 2e94bcb62..a3fa5a84c 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -23,13 +23,13 @@ jobs: matrix: include: # NOTE config below with "upload-wheels: true" specifies that wheels should be uploaded as an artifact - - { name: linux-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } - - { name: linux-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: ubuntu-latest } - - { name: linux-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: ubuntu-latest , upload-wheels: true } - - { name: windows-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: windows-latest } - - { name: windows-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: windows-latest } - - { name: macos-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: macos-13 } - - { name: macos-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: macos-latest } + - { name: linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } + - { name: linux-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: ubuntu-latest } + - { name: linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest , upload-wheels: true } + - { name: windows-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: windows-latest } + - { name: windows-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: windows-latest } + - { name: macos-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: macos-13 } + - { name: macos-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -85,10 +85,10 @@ jobs: fail-fast: false matrix: include: - - { name: linux-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } - - { name: linux-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } - - { name: windows-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: windows-latest } - - { name: windows-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: windows-latest } + - { name: linux-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } + - { name: linux-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } + - { name: windows-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: windows-latest } + - { name: windows-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: windows-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -124,8 +124,8 @@ jobs: fail-fast: false matrix: include: - - { name: conda-linux-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } - - { name: conda-linux-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } + - { name: conda-linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } + - { name: conda-linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -188,7 +188,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - name: Download wheel and source distributions from artifact uses: actions/download-artifact@v4 @@ -221,7 +221,7 @@ jobs: fail-fast: false matrix: include: - - { name: linux-python3.12-ros3 , python-ver: "3.12", os: ubuntu-latest } + - { name: linux-python3.13-ros3 , python-ver: "3.13", os: ubuntu-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 diff --git a/.readthedocs.yaml b/.readthedocs.yaml index a4f1ea037..19bcaee80 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -6,9 +6,9 @@ version: 2 build: - os: ubuntu-20.04 + os: ubuntu-22.04 tools: - python: '3.9' + python: '3.12' # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/CHANGELOG.md b/CHANGELOG.md index b0639630b..a8a97ff58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Deprecations - The following classes have been deprecated and removed: Array, AbstractSortedArray, SortedArray, LinSpace, Query, RegionSlicer, ListSlicer, H5RegionSlicer, DataRegion. The following methods have been deprecated and removed: fmt_docval_args, call_docval_func, get_container_cls, add_child, set_dataio (now refactored as set_data_io). We have also removed all early evelopment for region references. @mavaylon1 [#1998](https://github.com/hdmf-dev/hdmf/pull/1198) +- Python 3.8 has been deprecated. Python 3.9 is the new minimum with support for Python 3.13. @mavaylon1 [#1209](https://github.com/hdmf-dev/hdmf/pull/1209) ### Enhancements - Added support for expandable datasets of references for untyped and compound data types. @stephprince [#1188](https://github.com/hdmf-dev/hdmf/pull/1188) diff --git a/docs/source/install_users.rst b/docs/source/install_users.rst index 49fbe07b2..f4d701c07 100644 --- a/docs/source/install_users.rst +++ b/docs/source/install_users.rst @@ -4,7 +4,7 @@ Installing HDMF --------------- -HDMF requires having Python 3.8, 3.9, 3.10, 3.11, or 3.12 installed. If you don't have Python installed and want the simplest way to +HDMF requires having Python 3.9-3.13 installed. If you don't have Python installed and want the simplest way to get started, we recommend you install and use the `Anaconda Distribution`_. It includes Python, NumPy, and many other commonly used packages for scientific computing and data science. diff --git a/environment-ros3.yml b/environment-ros3.yml index 34c37cc01..be2b9d4f6 100644 --- a/environment-ros3.yml +++ b/environment-ros3.yml @@ -4,11 +4,11 @@ channels: - conda-forge - defaults dependencies: - - python==3.12 - - h5py==3.11.0 - - matplotlib==3.8.4 - - numpy==2.0.0 - - pandas==2.2.2 + - python==3.13 + - h5py==3.12.1 + - matplotlib==3.9.2 + - numpy==2.1.3 + - pandas==2.2.3 - python-dateutil==2.8.2 - pytest==8.1.2 # regression introduced in pytest 8.2.*, will be fixed in 8.3.0 - pytest-cov==5.0.0 diff --git a/pyproject.toml b/pyproject.toml index 86e52a137..86b9e18e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,15 +13,15 @@ authors = [ ] description = "A hierarchical data modeling framework for modern science data standards" readme = "README.rst" -requires-python = ">=3.8" +requires-python = ">=3.9" license = {text = "BSD-3-Clause"} classifiers = [ "Programming Language :: Python", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "License :: OSI Approved :: BSD License", "Development Status :: 5 - Production/Stable", "Operating System :: OS Independent", @@ -30,22 +30,22 @@ classifiers = [ "Topic :: Scientific/Engineering :: Medical Science Apps.", ] dependencies = [ - "h5py>=2.10", - "jsonschema>=2.6.0", - 'numpy>=1.18', - "pandas>=1.0.5", + "h5py>=3.1.0", + "jsonschema>=3.2.0", + 'numpy>=1.19.3', + "pandas>=1.2.0", "ruamel.yaml>=0.16", - "scipy>=1.4", - "importlib-resources; python_version < '3.9'", # TODO: remove when minimum python version is 3.9 + "scipy>=1.7", ] dynamic = ["version"] [project.optional-dependencies] tqdm = ["tqdm>=4.41.0"] -termset = ["linkml-runtime>=1.5.5; python_version >= '3.9'", - "schemasheets>=0.1.23; python_version >= '3.9'", - "oaklib>=0.5.12; python_version >= '3.9'", - "pyyaml>=6.0.1; python_version >= '3.9'"] +zarr = ["zarr>=2.12.0"] +termset = ["linkml-runtime>=1.5.5", + "schemasheets>=0.1.23", + "oaklib>=0.5.12", + "pyyaml>=6.0.1"] [project.urls] "Homepage" = "https://github.com/hdmf-dev/hdmf" diff --git a/requirements-min.txt b/requirements-min.txt index a437fc588..a9fbeb93e 100644 --- a/requirements-min.txt +++ b/requirements-min.txt @@ -1,15 +1,10 @@ # minimum versions of package dependencies for installing HDMF -h5py==2.10 # support for selection of datasets with list of indices added in 2.10 -importlib-resources==5.12.0; python_version < "3.9" # TODO: remove when when minimum python version is 3.9 +# NOTE: these should match the minimum bound for dependencies in pyproject.toml +h5py==3.1.0 jsonschema==3.2.0 -numpy==1.18 -pandas==1.0.5 # when this is changed to >=1.5.0, see TODO items referenced in #762 -ruamel.yaml==0.16 -scipy==1.4 -# this file is currently used to test only python~=3.8 so these dependencies are not needed -# linkml-runtime==1.5.5; python_version >= "3.9" -# schemasheets==0.1.23; python_version >= "3.9" -# oaklib==0.5.12; python_version >= "3.9" -# pyyaml==6.0.1; python_version >= "3.9" +numpy==1.19.3 +pandas==1.2.0 +ruamel.yaml==0.16.0 +scipy==1.7.0 tqdm==4.41.0 zarr==2.12.0 diff --git a/requirements-opt.txt b/requirements-opt.txt index 4831d1949..8811ff46e 100644 --- a/requirements-opt.txt +++ b/requirements-opt.txt @@ -1,6 +1,6 @@ # pinned dependencies that are optional. used to reproduce an entire development environment to use HDMF tqdm==4.66.4 zarr==2.18.2 -linkml-runtime==1.7.7; python_version >= "3.9" -schemasheets==0.2.1; python_version >= "3.9" -oaklib==0.6.10; python_version >= "3.9" +linkml-runtime==1.7.7 +schemasheets==0.2.1 +oaklib==0.6.10 diff --git a/requirements.txt b/requirements.txt index 30a596ada..b431b4e63 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,8 @@ # pinned dependencies to reproduce an entire development environment to use HDMF -h5py==3.11.0 -importlib-resources==6.1.0; python_version < "3.9" # TODO: remove when minimum python version is 3.9 +h5py==3.12.1 jsonschema==4.22.0 -numpy==1.26.4 # TODO: numpy 2.0.0 is supported by hdmf but incompatible with pandas and scipy -pandas==2.2.2; python_version >= "3.9" -pandas==2.1.2; python_version < "3.8" # TODO: remove when minimum python version is 3.9 +numpy==2.1.3 ruamel.yaml==0.18.2 -scipy==1.14.0; python_version >= "3.10" +pandas==2.2.3 +scipy==1.14.1; python_version >= "3.10" scipy==1.11.3; python_version < "3.10" diff --git a/src/hdmf/common/__init__.py b/src/hdmf/common/__init__.py index 5c9d9a3b7..6b36e29cd 100644 --- a/src/hdmf/common/__init__.py +++ b/src/hdmf/common/__init__.py @@ -108,11 +108,7 @@ def _dec(cls): def __get_resources(): - try: - from importlib.resources import files - except ImportError: - # TODO: Remove when python 3.9 becomes the new minimum - from importlib_resources import files + from importlib.resources import files __location_of_this_file = files(__name__) __core_ns_file_name = 'namespace.yaml' diff --git a/tox.ini b/tox.ini index 75b011aa0..42305ae25 100644 --- a/tox.ini +++ b/tox.ini @@ -42,17 +42,17 @@ commands = # list of pre-defined environments. (Technically environments not listed here # like build-py312 can also be used.) -[testenv:pytest-py312-upgraded] -[testenv:pytest-py312-prerelease] +[testenv:pytest-py313-upgraded] +[testenv:pytest-py313-prerelease] [testenv:pytest-py311-optional-pinned] # some optional reqs not compatible with py312 yet -[testenv:pytest-py{38,39,310,311,312}-pinned] -[testenv:pytest-py38-minimum] +[testenv:pytest-py{39,310,311,312,313}-pinned] +[testenv:pytest-py39-minimum] -[testenv:gallery-py312-upgraded] -[testenv:gallery-py312-prerelease] +[testenv:gallery-py313-upgraded] +[testenv:gallery-py313-prerelease] [testenv:gallery-py311-optional-pinned] -[testenv:gallery-py{38,39,310,311,312}-pinned] -[testenv:gallery-py38-minimum] +[testenv:gallery-py{39,310,311,312,313}-pinned] +[testenv:gallery-py39-minimum] [testenv:build] # using tox for this so that we can have a clean build environment [testenv:wheelinstall] # use with `--installpkg dist/*-none-any.whl` From a1af49d49c381aeb41b8abdee69689ac7ddae797 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 17:16:36 -0800 Subject: [PATCH 64/82] [pre-commit.ci] pre-commit autoupdate (#1216) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0cc5da870..b826f648f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.3 + rev: v0.7.4 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From 4fb554acb368280fede9beaf03245ea7540b3966 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Thu, 21 Nov 2024 11:35:31 -0800 Subject: [PATCH 65/82] Remove RegionBuilder and remaining references to region references (#1212) * Update builders.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update spec.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Remove more references to region references and RegionBuilder * Remove to-be-deprecated aliases to object reference * Remove reference to referencebuilder in docs * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- CHANGELOG.md | 2 +- docs/source/conf.py | 1 - .../source/overview_software_architecture.rst | 2 +- src/hdmf/backends/hdf5/h5tools.py | 4 +--- src/hdmf/build/__init__.py | 2 +- src/hdmf/build/builders.py | 21 +------------------ src/hdmf/build/objectmapper.py | 4 +--- src/hdmf/spec/spec.py | 10 +++------ src/hdmf/validate/validator.py | 5 +---- tests/unit/build_tests/test_builder.py | 11 +--------- tests/unit/spec_tests/test_ref_spec.py | 6 ------ 11 files changed, 11 insertions(+), 57 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8a97ff58..438a0c7df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## HDMF 4.0.0 (Upcoming) ### Deprecations -- The following classes have been deprecated and removed: Array, AbstractSortedArray, SortedArray, LinSpace, Query, RegionSlicer, ListSlicer, H5RegionSlicer, DataRegion. The following methods have been deprecated and removed: fmt_docval_args, call_docval_func, get_container_cls, add_child, set_dataio (now refactored as set_data_io). We have also removed all early evelopment for region references. @mavaylon1 [#1998](https://github.com/hdmf-dev/hdmf/pull/1198) +- The following classes have been deprecated and removed: Array, AbstractSortedArray, SortedArray, LinSpace, Query, RegionSlicer, ListSlicer, H5RegionSlicer, DataRegion, RegionBuilder. The following methods have been deprecated and removed: fmt_docval_args, call_docval_func, get_container_cls, add_child, set_dataio (now refactored as set_data_io). We have also removed all early development for region references. @mavaylon1, @rly [#1998](https://github.com/hdmf-dev/hdmf/pull/1198), [#1212](https://github.com/hdmf-dev/hdmf/pull/1212) - Python 3.8 has been deprecated. Python 3.9 is the new minimum with support for Python 3.13. @mavaylon1 [#1209](https://github.com/hdmf-dev/hdmf/pull/1209) ### Enhancements diff --git a/docs/source/conf.py b/docs/source/conf.py index c20869e12..d385630d2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -87,7 +87,6 @@ nitpicky = True nitpick_ignore = [('py:class', 'Intracomm'), - ('py:class', 'h5py.RegionReference'), ('py:class', 'h5py._hl.dataset.Dataset'), ('py:class', 'function'), ('py:class', 'unittest.case.TestCase'), diff --git a/docs/source/overview_software_architecture.rst b/docs/source/overview_software_architecture.rst index 973a01b2f..d63c953fe 100644 --- a/docs/source/overview_software_architecture.rst +++ b/docs/source/overview_software_architecture.rst @@ -68,7 +68,7 @@ Builder * :py:class:`~hdmf.build.builders.GroupBuilder` - represents a collection of objects * :py:class:`~hdmf.build.builders.DatasetBuilder` - represents data * :py:class:`~hdmf.build.builders.LinkBuilder` - represents soft-links - * :py:class:`~hdmf.build.builders.RegionBuilder` - represents a slice into data (Subclass of :py:class:`~hdmf.build.builders.DatasetBuilder`) + * :py:class:`~hdmf.build.builders.ReferenceBuilder` - represents a reference to another group or dataset * **Main Module:** :py:class:`hdmf.build.builders` diff --git a/src/hdmf/backends/hdf5/h5tools.py b/src/hdmf/backends/hdf5/h5tools.py index bf8f86bb9..66605409b 100644 --- a/src/hdmf/backends/hdf5/h5tools.py +++ b/src/hdmf/backends/hdf5/h5tools.py @@ -908,8 +908,6 @@ def get_type(cls, data): "utf-8": H5_TEXT, "ascii": H5_BINARY, "bytes": H5_BINARY, - "ref": H5_REF, - "reference": H5_REF, "object": H5_REF, "isodatetime": H5_TEXT, "datetime": H5_TEXT, @@ -1492,7 +1490,7 @@ def __is_ref(self, dtype): if isinstance(dtype, dict): # may be dict from reading a compound dataset return self.__is_ref(dtype['dtype']) if isinstance(dtype, str): - return dtype == DatasetBuilder.OBJECT_REF_TYPE or dtype == DatasetBuilder.REGION_REF_TYPE + return dtype == DatasetBuilder.OBJECT_REF_TYPE return False def __queue_ref(self, func): diff --git a/src/hdmf/build/__init__.py b/src/hdmf/build/__init__.py index ea5d21152..87e0ac57e 100644 --- a/src/hdmf/build/__init__.py +++ b/src/hdmf/build/__init__.py @@ -1,4 +1,4 @@ -from .builders import Builder, DatasetBuilder, GroupBuilder, LinkBuilder, ReferenceBuilder, RegionBuilder +from .builders import Builder, DatasetBuilder, GroupBuilder, LinkBuilder, ReferenceBuilder from .classgenerator import CustomClassGenerator, MCIClassGenerator from .errors import (BuildError, OrphanContainerBuildError, ReferenceTargetNotBuiltError, ContainerConfigurationError, ConstructError) diff --git a/src/hdmf/build/builders.py b/src/hdmf/build/builders.py index cb658b6d4..2d90c24e3 100644 --- a/src/hdmf/build/builders.py +++ b/src/hdmf/build/builders.py @@ -6,7 +6,6 @@ from datetime import datetime, date import numpy as np -from h5py import RegionReference from ..utils import docval, getargs, get_docval @@ -320,11 +319,10 @@ def values(self): class DatasetBuilder(BaseBuilder): OBJECT_REF_TYPE = 'object' - REGION_REF_TYPE = 'region' @docval({'name': 'name', 'type': str, 'doc': 'The name of the dataset.'}, {'name': 'data', - 'type': ('array_data', 'scalar_data', 'data', 'DatasetBuilder', 'RegionBuilder', Iterable, datetime, date), + 'type': ('array_data', 'scalar_data', 'data', 'DatasetBuilder', Iterable, datetime, date), 'doc': 'The data in this dataset.', 'default': None}, {'name': 'dtype', 'type': (type, np.dtype, str, list), 'doc': 'The datatype of this dataset.', 'default': None}, @@ -429,20 +427,3 @@ def __init__(self, **kwargs): def builder(self): """The target builder object.""" return self['builder'] - - -class RegionBuilder(ReferenceBuilder): - - @docval({'name': 'region', 'type': (slice, tuple, list, RegionReference), - 'doc': 'The region, i.e. slice or indices, into the target dataset.'}, - {'name': 'builder', 'type': DatasetBuilder, 'doc': 'The dataset this region reference applies to.'}) - def __init__(self, **kwargs): - """Create a builder object for a region reference.""" - region, builder = getargs('region', 'builder', kwargs) - super().__init__(builder) - self['region'] = region - - @property - def region(self): - """The selected region of the target dataset.""" - return self['region'] diff --git a/src/hdmf/build/objectmapper.py b/src/hdmf/build/objectmapper.py index c2ef44b5f..176de322c 100644 --- a/src/hdmf/build/objectmapper.py +++ b/src/hdmf/build/objectmapper.py @@ -6,7 +6,7 @@ import numpy as np -from .builders import DatasetBuilder, GroupBuilder, LinkBuilder, Builder, ReferenceBuilder, RegionBuilder, BaseBuilder +from .builders import DatasetBuilder, GroupBuilder, LinkBuilder, Builder, ReferenceBuilder, BaseBuilder from .errors import (BuildError, OrphanContainerBuildError, ReferenceTargetNotBuiltError, ContainerConfigurationError, ConstructError) from .manager import Proxy, BuildManager @@ -1214,8 +1214,6 @@ def __get_subspec_values(self, builder, spec, manager): continue if isinstance(attr_val, (GroupBuilder, DatasetBuilder)): ret[attr_spec] = manager.construct(attr_val) - elif isinstance(attr_val, RegionBuilder): # pragma: no cover - raise ValueError("RegionReferences as attributes is not yet supported") elif isinstance(attr_val, ReferenceBuilder): ret[attr_spec] = manager.construct(attr_val.builder) else: diff --git a/src/hdmf/spec/spec.py b/src/hdmf/spec/spec.py index e10d5e43e..d5adf6b5e 100644 --- a/src/hdmf/spec/spec.py +++ b/src/hdmf/spec/spec.py @@ -38,7 +38,6 @@ class DtypeHelper: 'uint32': ["uint32", "uint"], 'uint64': ["uint64"], 'object': ['object'], - 'region': ['region'], 'numeric': ['numeric'], 'isodatetime': ["isodatetime", "datetime", "date"] } @@ -174,12 +173,13 @@ def path(self): _ref_args = [ {'name': _target_type_key, 'type': str, 'doc': 'the target type GroupSpec or DatasetSpec'}, - {'name': 'reftype', 'type': str, 'doc': 'the type of references this is i.e. region or object'}, + {'name': 'reftype', 'type': str, + 'doc': 'the type of reference this is. only "object" is supported currently.'}, ] class RefSpec(ConstructableDict): - __allowable_types = ('object', 'region') + __allowable_types = ('object', ) @docval(*_ref_args) def __init__(self, **kwargs): @@ -200,10 +200,6 @@ def reftype(self): '''The type of reference''' return self['reftype'] - @docval(rtype=bool, returns='True if this RefSpec specifies a region reference, False otherwise') - def is_region(self): - return self['reftype'] == 'region' - _attr_args = [ {'name': 'name', 'type': str, 'doc': 'The name of this attribute'}, diff --git a/src/hdmf/validate/validator.py b/src/hdmf/validate/validator.py index daa5adac4..d7ec78eaa 100644 --- a/src/hdmf/validate/validator.py +++ b/src/hdmf/validate/validator.py @@ -8,7 +8,7 @@ from .errors import Error, DtypeError, MissingError, MissingDataType, ShapeError, IllegalLinkError, IncorrectDataType from .errors import ExpectedArrayError, IncorrectQuantityError -from ..build import GroupBuilder, DatasetBuilder, LinkBuilder, ReferenceBuilder, RegionBuilder +from ..build import GroupBuilder, DatasetBuilder, LinkBuilder, ReferenceBuilder from ..build.builders import BaseBuilder from ..spec import Spec, AttributeSpec, GroupSpec, DatasetSpec, RefSpec, LinkSpec from ..spec import SpecNamespace @@ -124,9 +124,6 @@ def get_type(data, builder_dtype=None): # Bytes data elif isinstance(data, bytes): return 'ascii', get_string_format(data) - # RegionBuilder data - elif isinstance(data, RegionBuilder): - return 'region', None # ReferenceBuilder data elif isinstance(data, ReferenceBuilder): return 'object', None diff --git a/tests/unit/build_tests/test_builder.py b/tests/unit/build_tests/test_builder.py index a35dc64ac..62ebd0675 100644 --- a/tests/unit/build_tests/test_builder.py +++ b/tests/unit/build_tests/test_builder.py @@ -1,4 +1,4 @@ -from hdmf.build import GroupBuilder, DatasetBuilder, LinkBuilder, ReferenceBuilder, RegionBuilder +from hdmf.build import GroupBuilder, DatasetBuilder, LinkBuilder, ReferenceBuilder from hdmf.testing import TestCase @@ -392,12 +392,3 @@ def test_constructor(self): db = DatasetBuilder('db1', [1, 2, 3]) rb = ReferenceBuilder(db) self.assertIs(rb.builder, db) - - -class TestRegionBuilder(TestCase): - - def test_constructor(self): - db = DatasetBuilder('db1', [1, 2, 3]) - rb = RegionBuilder(slice(1, 3), db) - self.assertEqual(rb.region, slice(1, 3)) - self.assertIs(rb.builder, db) diff --git a/tests/unit/spec_tests/test_ref_spec.py b/tests/unit/spec_tests/test_ref_spec.py index bb1c0efb8..3277673d1 100644 --- a/tests/unit/spec_tests/test_ref_spec.py +++ b/tests/unit/spec_tests/test_ref_spec.py @@ -15,9 +15,3 @@ def test_constructor(self): def test_wrong_reference_type(self): with self.assertRaises(ValueError): RefSpec('TimeSeries', 'unknownreftype') - - def test_isregion(self): - spec = RefSpec('TimeSeries', 'object') - self.assertFalse(spec.is_region()) - spec = RefSpec('Data', 'region') - self.assertTrue(spec.is_region()) From 1d3421e7bcd4e02ae82b3d3853829d97745bba8e Mon Sep 17 00:00:00 2001 From: Heberto Mayorquin Date: Sat, 23 Nov 2024 00:29:47 -0600 Subject: [PATCH 66/82] Route array representation for HTML (#1206) * small patch to html repr * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * re-organize * comment request * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * improve docstrings * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Apply suggestions from code review Co-authored-by: Oliver Ruebel --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steph Prince <40640337+stephprince@users.noreply.github.com> Co-authored-by: Ryan Ly Co-authored-by: Oliver Ruebel --- src/hdmf/backends/hdf5/h5tools.py | 11 +++++++---- src/hdmf/container.py | 32 ++++++++++++++++++++++++------- src/hdmf/utils.py | 20 +++++++++++++------ 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/hdmf/backends/hdf5/h5tools.py b/src/hdmf/backends/hdf5/h5tools.py index 66605409b..d30cef06c 100644 --- a/src/hdmf/backends/hdf5/h5tools.py +++ b/src/hdmf/backends/hdf5/h5tools.py @@ -1539,7 +1539,7 @@ def generate_dataset_html(dataset): array_info_dict = get_basic_array_info(dataset) if isinstance(dataset, h5py.Dataset): - + dataset_type = "HDF5 dataset" # get info from hdf5 dataset compressed_size = dataset.id.get_storage_size() if hasattr(dataset, "nbytes"): # TODO: Remove this after h5py minimal version is larger than 3.0 @@ -1554,10 +1554,13 @@ def generate_dataset_html(dataset): "Compression opts": dataset.compression_opts, "Compression ratio": compression_ratio, } - array_info_dict.update(hdf5_info_dict) - # generate html repr - repr_html = generate_array_html_repr(array_info_dict, dataset, "HDF5 dataset") + elif isinstance(dataset, np.ndarray): + dataset_type = "NumPy array" + else: + dataset_type = dataset.__class__.__name__ + + repr_html = generate_array_html_repr(array_info_dict, dataset, dataset_type) return repr_html diff --git a/src/hdmf/container.py b/src/hdmf/container.py index 864b34ee9..ce4e8b821 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -707,8 +707,11 @@ def _generate_field_html(self, key, value, level, access_code): return f'
    {key}: {value}
    ' - is_array_data = isinstance(value, (np.ndarray, h5py.Dataset, DataIO)) or \ - (hasattr(value, "store") and hasattr(value, "shape")) # Duck typing for zarr array + # Detects array-like objects that conform to the Array Interface specification + # (e.g., NumPy arrays, HDF5 datasets, DataIO objects). Objects must have both + # 'shape' and 'dtype' attributes. Iterators are excluded as they lack 'shape'. + # This approach keeps the implementation generic without coupling to specific backends methods + is_array_data = hasattr(value, "shape") and hasattr(value, "dtype") if is_array_data: html_content = self._generate_array_html(value, level + 1) @@ -735,14 +738,29 @@ def _generate_field_html(self, key, value, level, access_code): def _generate_array_html(self, array, level): - """Generates HTML for array data""" + """Generates HTML for array data (e.g., NumPy arrays, HDF5 datasets, Zarr datasets and DataIO objects).""" - read_io = self.get_read_io() # if the Container was read from file, get IO object - if read_io is not None: # Note that sometimes numpy array have a read_io attribute - repr_html = read_io.generate_dataset_html(array) - else: + is_numpy_array = isinstance(array, np.ndarray) + read_io = self.get_read_io() + it_was_read_with_io = read_io is not None + is_data_io = isinstance(array, DataIO) + + if is_numpy_array: array_info_dict = get_basic_array_info(array) repr_html = generate_array_html_repr(array_info_dict, array, "NumPy array") + elif is_data_io: + array_info_dict = get_basic_array_info(array.data) + repr_html = generate_array_html_repr(array_info_dict, array.data, "DataIO") + elif it_was_read_with_io: + # The backend handles the representation here. Two special cases worth noting: + # 1. Array-type attributes (e.g., start_frame in ImageSeries) remain NumPy arrays + # even when their parent container has an IO + # 2. Data may have been modified after being read from storage + repr_html = read_io.generate_dataset_html(array) + else: # Not sure which object could get here + object_class = array.__class__.__name__ + array_info_dict = get_basic_array_info(array.data) + repr_html = generate_array_html_repr(array_info_dict, array.data, object_class) return f'
    {repr_html}
    ' diff --git a/src/hdmf/utils.py b/src/hdmf/utils.py index 6b5900384..c21382a2a 100644 --- a/src/hdmf/utils.py +++ b/src/hdmf/utils.py @@ -894,7 +894,7 @@ def convert_bytes_to_str(bytes_size): return basic_array_info_dict -def generate_array_html_repr(backend_info_dict, array, dataset_type=None): +def generate_array_html_repr(array_info_dict, array, dataset_type=None): def html_table(item_dicts) -> str: """ Generates an html table from a dictionary @@ -912,14 +912,22 @@ def html_table(item_dicts) -> str: report += "" return report - array_info_html = html_table(backend_info_dict) + array_info_html = html_table(array_info_dict) repr_html = dataset_type + "
    " + array_info_html if dataset_type is not None else array_info_html - if hasattr(array, "nbytes"): # TODO: Remove this after h5py minimal version is larger than 3.0 - array_size = array.nbytes + # Array like might lack nbytes (h5py < 3.0) or size (DataIO object) + if hasattr(array, "nbytes"): + array_size_bytes = array.nbytes else: - array_size = array.size * array.dtype.itemsize - array_is_small = array_size < 1024 * 0.1 # 10 % a kilobyte to display the array + if hasattr(array, "size"): + array_size = array.size + else: + import math + array_size = math.prod(array.shape) + array_size_bytes = array_size * array.dtype.itemsize + + # Heuristic for displaying data + array_is_small = array_size_bytes < 1024 * 0.1 # 10 % a kilobyte to display the array if array_is_small: repr_html += "
    " + str(np.asarray(array)) From f50dc845b27a8cd952e0037f34c3fa9d92b939bf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:05:23 -0800 Subject: [PATCH 67/82] [pre-commit.ci] pre-commit autoupdate (#1217) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b826f648f..9f5814605 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.4 + rev: v0.8.0 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From d1d1b5a654a4d0887929770f0d48493ab99621da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 00:37:59 +0000 Subject: [PATCH 68/82] Bump codecov/codecov-action from 4 to 5 (#1215) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ryan Ly --- .github/workflows/run_coverage.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run_coverage.yml b/.github/workflows/run_coverage.yml index 324b1e9d5..bea8df734 100644 --- a/.github/workflows/run_coverage.yml +++ b/.github/workflows/run_coverage.yml @@ -64,10 +64,10 @@ jobs: pytest --cov --cov-report=xml --cov-report=term # codecov uploader requires xml format - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: fail_ci_if_error: true - file: ./coverage.xml + files: ./coverage.xml env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} @@ -118,9 +118,9 @@ jobs: pytest --cov --cov-report=xml --cov-report=term tests/unit/test_io_hdf5_streaming.py - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: fail_ci_if_error: true - file: ./coverage.xml + files: ./coverage.xml env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} From 08656979908f159ca25510d1dde24a956d501899 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:54:17 -0800 Subject: [PATCH 69/82] [pre-commit.ci] pre-commit autoupdate (#1218) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.0 → v0.8.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.0...v0.8.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9f5814605..f638f1416 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.0 + rev: v0.8.3 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From f6dc806ead716fb42cf84d67e8844edff234bb13 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Wed, 18 Dec 2024 12:07:10 -0800 Subject: [PATCH 70/82] Delete SortedQueryTest.h5 (#1220) --- SortedQueryTest.h5 | Bin 2128 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 SortedQueryTest.h5 diff --git a/SortedQueryTest.h5 b/SortedQueryTest.h5 deleted file mode 100644 index 67f3d7d6cbd59ee6452fdaebcd21834eb191f551..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2128 zcmeHIF%H5o47Af0LM5cKAts){6F_QFC#Zmdu>-sS9swKA;3@nHlK3nk#!i(;D)lao z<0jVGz1QV>oX>Ld!&*wI2vlEhjK+#Z=epj4Fz^O^8X-*nA)3NILHu98!>+2xd1`z` zY@GteDVI!{bpPGiq<-uC;d5FJW0$z%s{sc aNH=r+3EQ=-@!NG>P{sf_1Zp1`Vg3Opl`aJU From b779d08c8c782ef178f4ff04e6d8e447f92dab65 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Thu, 19 Dec 2024 15:45:35 -0800 Subject: [PATCH 71/82] Fix hdmf-zarr workflow (#1222) * Fix hdmf-zarr workflow * Update CHANGELOG.md --- .github/workflows/run_hdmf_zarr_tests.yml | 9 ++++----- CHANGELOG.md | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run_hdmf_zarr_tests.yml b/.github/workflows/run_hdmf_zarr_tests.yml index 5e76711af..6eb5546ab 100644 --- a/.github/workflows/run_hdmf_zarr_tests.yml +++ b/.github/workflows/run_hdmf_zarr_tests.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.10' # use 3.10 until hdmf-zarr updates versioneer.py which breaks on newer python + python-version: '3.13' - name: Update pip run: python -m pip install --upgrade pip @@ -29,10 +29,9 @@ jobs: - name: Clone HDMF-Zarr and install dev branch of HDMF run: | python -m pip list - git clone https://github.com/hdmf-dev/hdmf-zarr.git --recurse-submodules + git clone https://github.com/hdmf-dev/hdmf-zarr.git cd hdmf-zarr - python -m pip install -r requirements-dev.txt # do not install the pinned install requirements - python -m pip install . # this will install a different version of hdmf from the current one + python -m pip install .[test] # this will install a different version of hdmf from the current one cd .. python -m pip uninstall -y hdmf # uninstall the other version of hdmf python -m pip install . # reinstall current branch of hdmf @@ -41,4 +40,4 @@ jobs: - name: Run HDMF-Zarr tests on HDMF-Zarr dev branch run: | cd hdmf-zarr - pytest + pytest -v diff --git a/CHANGELOG.md b/CHANGELOG.md index 438a0c7df..5c965f9dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Added support for expandable datasets of references for untyped and compound data types. @stephprince [#1188](https://github.com/hdmf-dev/hdmf/pull/1188) - Improved html representation of data in `Container` objects. @h-mayorquin [#1100](https://github.com/hdmf-dev/hdmf/pull/1100) - Added error when using colon for `Container` name. A colon cannot be used as a group name when writing to Zarr on Windows. @stephprince [#1202](https://github.com/hdmf-dev/hdmf/pull/1202) +- Adjusted testing for hdmf-zarr. @rly [#1222](https://github.com/hdmf-dev/hdmf/pull/1222) ### Bug fixes - Fixed inaccurate error message when validating reference data types. @stephprince [#1199](https://github.com/hdmf-dev/hdmf/pull/1199) From 8194a8a97e995dd939a6e6e915d16f066007c65c Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Thu, 19 Dec 2024 16:00:03 -0800 Subject: [PATCH 72/82] Fix numpy 2 deprecation warning (#1223) --- tests/unit/test_io_hdf5_h5tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/test_io_hdf5_h5tools.py b/tests/unit/test_io_hdf5_h5tools.py index 79d7e1e6b..6c679bb49 100644 --- a/tests/unit/test_io_hdf5_h5tools.py +++ b/tests/unit/test_io_hdf5_h5tools.py @@ -1847,7 +1847,7 @@ def test_link(self): self.assertTrue(self.foo2.my_data.valid) # test valid self.assertEqual(len(self.foo2.my_data), 5) # test len self.assertEqual(self.foo2.my_data.shape, (5,)) # test getattr with shape - self.assertTrue(np.array_equal(np.array(self.foo2.my_data), [1, 2, 3, 4, 5])) # test array conversion + np.testing.assert_array_equal(self.foo2.my_data, [1, 2, 3, 4, 5]) # test array conversion # test loop through iterable match = [1, 2, 3, 4, 5] From 6396a81854e04aace39be309a8141fc4e6095fc7 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Thu, 19 Dec 2024 16:07:06 -0800 Subject: [PATCH 73/82] Delete src/hdmf/build/map.py (#1221) * Delete src/hdmf/build/map.py * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/hdmf/build/map.py | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 src/hdmf/build/map.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c965f9dc..281469ffe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Deprecations - The following classes have been deprecated and removed: Array, AbstractSortedArray, SortedArray, LinSpace, Query, RegionSlicer, ListSlicer, H5RegionSlicer, DataRegion, RegionBuilder. The following methods have been deprecated and removed: fmt_docval_args, call_docval_func, get_container_cls, add_child, set_dataio (now refactored as set_data_io). We have also removed all early development for region references. @mavaylon1, @rly [#1998](https://github.com/hdmf-dev/hdmf/pull/1198), [#1212](https://github.com/hdmf-dev/hdmf/pull/1212) +- Importing from hdmf.build.map is no longer supported. Import from hdmf.build instead. @rly [#1221](https://github.com/hdmf-dev/hdmf/pull/1221) - Python 3.8 has been deprecated. Python 3.9 is the new minimum with support for Python 3.13. @mavaylon1 [#1209](https://github.com/hdmf-dev/hdmf/pull/1209) ### Enhancements diff --git a/src/hdmf/build/map.py b/src/hdmf/build/map.py deleted file mode 100644 index 5267609f5..000000000 --- a/src/hdmf/build/map.py +++ /dev/null @@ -1,7 +0,0 @@ -# this prevents breaking of code that imports these classes directly from map.py -from .manager import Proxy, BuildManager, TypeSource, TypeMap # noqa: F401 -from .objectmapper import ObjectMapper # noqa: F401 - -import warnings -warnings.warn('Classes in map.py should be imported from hdmf.build. Importing from hdmf.build.map will be removed ' - 'in HDMF 3.0.', DeprecationWarning, stacklevel=2) From 035d3ec2411e2d1b19e3ace49b2b9208fcfab8ca Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Fri, 20 Dec 2024 10:18:35 -0800 Subject: [PATCH 74/82] Temporarily revert breaking changes for bugfix release (#1224) --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/workflows/run_all_tests.yml | 57 ++--- .github/workflows/run_coverage.yml | 2 +- .github/workflows/run_tests.yml | 30 +-- .readthedocs.yaml | 4 +- CHANGELOG.md | 7 +- docs/source/conf.py | 1 + docs/source/install_users.rst | 2 +- .../source/overview_software_architecture.rst | 2 +- environment-ros3.yml | 10 +- pyproject.toml | 24 +-- requirements-min.txt | 17 +- requirements-opt.txt | 6 +- requirements.txt | 10 +- src/hdmf/__init__.py | 26 ++- src/hdmf/array.py | 197 ++++++++++++++++++ src/hdmf/backends/hdf5/__init__.py | 2 +- src/hdmf/backends/hdf5/h5_utils.py | 78 ++++++- src/hdmf/backends/hdf5/h5tools.py | 124 ++++++++--- src/hdmf/build/__init__.py | 2 +- src/hdmf/build/builders.py | 21 +- src/hdmf/build/classgenerator.py | 4 +- src/hdmf/build/manager.py | 16 +- src/hdmf/build/map.py | 7 + src/hdmf/build/objectmapper.py | 54 +++-- src/hdmf/common/__init__.py | 6 +- src/hdmf/common/table.py | 4 +- src/hdmf/container.py | 49 +++++ src/hdmf/query.py | 121 ++++++++++- src/hdmf/region.py | 91 ++++++++ src/hdmf/spec/spec.py | 10 +- src/hdmf/utils.py | 91 ++++++++ src/hdmf/validate/validator.py | 5 +- tests/unit/build_tests/test_builder.py | 11 +- tests/unit/common/test_table.py | 4 +- tests/unit/spec_tests/test_ref_spec.py | 6 + tests/unit/test_container.py | 12 ++ tests/unit/test_query.py | 161 ++++++++++++++ tests/unit/utils_test/test_core_DataIO.py | 27 ++- tests/unit/utils_test/test_docval.py | 78 ++++++- tox.ini | 16 +- 41 files changed, 1235 insertions(+), 162 deletions(-) create mode 100644 src/hdmf/array.py create mode 100644 src/hdmf/build/map.py create mode 100644 src/hdmf/region.py create mode 100644 tests/unit/test_query.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f1d04aa69..ef3daed9c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -60,11 +60,11 @@ body: attributes: label: Python Version options: - - "3.13" - "3.12" - "3.11" - "3.10" - "3.9" + - "3.8" validations: required: true - type: textarea diff --git a/.github/workflows/run_all_tests.yml b/.github/workflows/run_all_tests.yml index 961500194..b713f4763 100644 --- a/.github/workflows/run_all_tests.yml +++ b/.github/workflows/run_all_tests.yml @@ -25,30 +25,30 @@ jobs: fail-fast: false matrix: include: - - { name: linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } + - { name: linux-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } + - { name: linux-python3.9 , test-tox-env: pytest-py39-pinned , python-ver: "3.9" , os: ubuntu-latest } - { name: linux-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: ubuntu-latest } - { name: linux-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: ubuntu-latest } - { name: linux-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: ubuntu-latest } - { name: linux-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: ubuntu-latest } - - { name: linux-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: ubuntu-latest } - - { name: linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } - - { name: linux-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: ubuntu-latest } - - { name: windows-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: windows-latest } + - { name: linux-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } + - { name: linux-python3.12-prerelease , test-tox-env: pytest-py312-prerelease , python-ver: "3.12", os: ubuntu-latest } + - { name: windows-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: windows-latest } + - { name: windows-python3.9 , test-tox-env: pytest-py39-pinned , python-ver: "3.9" , os: windows-latest } - { name: windows-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: windows-latest } - { name: windows-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: windows-latest } - { name: windows-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: windows-latest } - { name: windows-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: windows-latest } - - { name: windows-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: windows-latest } - - { name: windows-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: windows-latest } - - { name: windows-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: windows-latest } - - { name: macos-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: macos-13 } + - { name: windows-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: windows-latest } + - { name: windows-python3.12-prerelease , test-tox-env: pytest-py312-prerelease , python-ver: "3.12", os: windows-latest } + - { name: macos-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: macos-13 } + - { name: macos-python3.9 , test-tox-env: pytest-py39-pinned , python-ver: "3.9" , os: macos-13 } - { name: macos-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: macos-latest } - { name: macos-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: macos-latest } - { name: macos-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: macos-latest } - { name: macos-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: macos-latest } - - { name: macos-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: macos-latest } - - { name: macos-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: macos-latest } - - { name: macos-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: macos-latest } + - { name: macos-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: macos-latest } + - { name: macos-python3.12-prerelease , test-tox-env: pytest-py312-prerelease , python-ver: "3.12", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -97,18 +97,18 @@ jobs: fail-fast: false matrix: include: - - { name: linux-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } + - { name: linux-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } - { name: linux-gallery-python3.11-optional , test-tox-env: gallery-py311-optional-pinned , python-ver: "3.11", os: ubuntu-latest } - - { name: linux-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } - - { name: linux-gallery-python3.13-prerelease , test-tox-env: gallery-py313-prerelease , python-ver: "3.13", os: ubuntu-latest } - - { name: windows-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: windows-latest } + - { name: linux-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } + - { name: linux-gallery-python3.12-prerelease , test-tox-env: gallery-py312-prerelease , python-ver: "3.12", os: ubuntu-latest } + - { name: windows-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: windows-latest } - { name: windows-gallery-python3.11-optional , test-tox-env: gallery-py311-optional-pinned , python-ver: "3.11", os: windows-latest } - - { name: windows-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: windows-latest } - - { name: windows-gallery-python3.13-prerelease, test-tox-env: gallery-py313-prerelease , python-ver: "3.13", os: windows-latest } - - { name: macos-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: macos-13 } + - { name: windows-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: windows-latest } + - { name: windows-gallery-python3.12-prerelease, test-tox-env: gallery-py312-prerelease , python-ver: "3.12", os: windows-latest } + - { name: macos-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: macos-13 } - { name: macos-gallery-python3.11-optional , test-tox-env: gallery-py311-optional-pinned , python-ver: "3.11", os: macos-latest } - - { name: macos-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: macos-latest } - - { name: macos-gallery-python3.13-prerelease , test-tox-env: gallery-py313-prerelease , python-ver: "3.13", os: macos-latest } + - { name: macos-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: macos-latest } + - { name: macos-gallery-python3.12-prerelease , test-tox-env: gallery-py312-prerelease , python-ver: "3.12", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -144,13 +144,14 @@ jobs: fail-fast: false matrix: include: - - { name: conda-linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } + - { name: conda-linux-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } + - { name: conda-linux-python3.9 , test-tox-env: pytest-py39-pinned , python-ver: "3.9" , os: ubuntu-latest } - { name: conda-linux-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: ubuntu-latest } - { name: conda-linux-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: ubuntu-latest } - { name: conda-linux-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: ubuntu-latest } - - { name: conda-linux-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: ubuntu-latest } - - { name: conda-linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } - - { name: conda-linux-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: ubuntu-latest } + - { name: conda-linux-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: ubuntu-latest } + - { name: conda-linux-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } + - { name: conda-linux-python3.12-prerelease , test-tox-env: pytest-py312-prerelease , python-ver: "3.12", os: ubuntu-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -208,9 +209,9 @@ jobs: fail-fast: false matrix: include: - - { name: linux-python3.13-ros3 , python-ver: "3.13", os: ubuntu-latest } - - { name: windows-python3.13-ros3 , python-ver: "3.13", os: windows-latest } - - { name: macos-python3.13-ros3 , python-ver: "3.13", os: macos-latest } + - { name: linux-python3.12-ros3 , python-ver: "3.12", os: ubuntu-latest } + - { name: windows-python3.12-ros3 , python-ver: "3.12", os: windows-latest } + - { name: macos-python3.12-ros3 , python-ver: "3.12", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 diff --git a/.github/workflows/run_coverage.yml b/.github/workflows/run_coverage.yml index bea8df734..5e6057dbe 100644 --- a/.github/workflows/run_coverage.yml +++ b/.github/workflows/run_coverage.yml @@ -84,7 +84,7 @@ jobs: fail-fast: false matrix: include: - - { name: linux-python3.13-ros3 , python-ver: "3.13", os: ubuntu-latest } + - { name: linux-python3.12-ros3 , python-ver: "3.12", os: ubuntu-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index a3fa5a84c..2e94bcb62 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -23,13 +23,13 @@ jobs: matrix: include: # NOTE config below with "upload-wheels: true" specifies that wheels should be uploaded as an artifact - - { name: linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - - { name: linux-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: ubuntu-latest } - - { name: linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest , upload-wheels: true } - - { name: windows-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: windows-latest } - - { name: windows-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: windows-latest } - - { name: macos-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: macos-13 } - - { name: macos-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: macos-latest } + - { name: linux-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } + - { name: linux-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: ubuntu-latest } + - { name: linux-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: ubuntu-latest , upload-wheels: true } + - { name: windows-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: windows-latest } + - { name: windows-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: windows-latest } + - { name: macos-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: macos-13 } + - { name: macos-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -85,10 +85,10 @@ jobs: fail-fast: false matrix: include: - - { name: linux-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - - { name: linux-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } - - { name: windows-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: windows-latest } - - { name: windows-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: windows-latest } + - { name: linux-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } + - { name: linux-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } + - { name: windows-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: windows-latest } + - { name: windows-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: windows-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -124,8 +124,8 @@ jobs: fail-fast: false matrix: include: - - { name: conda-linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - - { name: conda-linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } + - { name: conda-linux-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } + - { name: conda-linux-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -188,7 +188,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.13' + python-version: '3.12' - name: Download wheel and source distributions from artifact uses: actions/download-artifact@v4 @@ -221,7 +221,7 @@ jobs: fail-fast: false matrix: include: - - { name: linux-python3.13-ros3 , python-ver: "3.13", os: ubuntu-latest } + - { name: linux-python3.12-ros3 , python-ver: "3.12", os: ubuntu-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 19bcaee80..a4f1ea037 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -6,9 +6,9 @@ version: 2 build: - os: ubuntu-22.04 + os: ubuntu-20.04 tools: - python: '3.12' + python: '3.9' # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/CHANGELOG.md b/CHANGELOG.md index 281469ffe..028e745c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,6 @@ # HDMF Changelog -## HDMF 4.0.0 (Upcoming) - -### Deprecations -- The following classes have been deprecated and removed: Array, AbstractSortedArray, SortedArray, LinSpace, Query, RegionSlicer, ListSlicer, H5RegionSlicer, DataRegion, RegionBuilder. The following methods have been deprecated and removed: fmt_docval_args, call_docval_func, get_container_cls, add_child, set_dataio (now refactored as set_data_io). We have also removed all early development for region references. @mavaylon1, @rly [#1998](https://github.com/hdmf-dev/hdmf/pull/1198), [#1212](https://github.com/hdmf-dev/hdmf/pull/1212) -- Importing from hdmf.build.map is no longer supported. Import from hdmf.build instead. @rly [#1221](https://github.com/hdmf-dev/hdmf/pull/1221) -- Python 3.8 has been deprecated. Python 3.9 is the new minimum with support for Python 3.13. @mavaylon1 [#1209](https://github.com/hdmf-dev/hdmf/pull/1209) +## HDMF 3.14.6 (Upcoming) ### Enhancements - Added support for expandable datasets of references for untyped and compound data types. @stephprince [#1188](https://github.com/hdmf-dev/hdmf/pull/1188) diff --git a/docs/source/conf.py b/docs/source/conf.py index d385630d2..c20869e12 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -87,6 +87,7 @@ nitpicky = True nitpick_ignore = [('py:class', 'Intracomm'), + ('py:class', 'h5py.RegionReference'), ('py:class', 'h5py._hl.dataset.Dataset'), ('py:class', 'function'), ('py:class', 'unittest.case.TestCase'), diff --git a/docs/source/install_users.rst b/docs/source/install_users.rst index f4d701c07..49fbe07b2 100644 --- a/docs/source/install_users.rst +++ b/docs/source/install_users.rst @@ -4,7 +4,7 @@ Installing HDMF --------------- -HDMF requires having Python 3.9-3.13 installed. If you don't have Python installed and want the simplest way to +HDMF requires having Python 3.8, 3.9, 3.10, 3.11, or 3.12 installed. If you don't have Python installed and want the simplest way to get started, we recommend you install and use the `Anaconda Distribution`_. It includes Python, NumPy, and many other commonly used packages for scientific computing and data science. diff --git a/docs/source/overview_software_architecture.rst b/docs/source/overview_software_architecture.rst index d63c953fe..973a01b2f 100644 --- a/docs/source/overview_software_architecture.rst +++ b/docs/source/overview_software_architecture.rst @@ -68,7 +68,7 @@ Builder * :py:class:`~hdmf.build.builders.GroupBuilder` - represents a collection of objects * :py:class:`~hdmf.build.builders.DatasetBuilder` - represents data * :py:class:`~hdmf.build.builders.LinkBuilder` - represents soft-links - * :py:class:`~hdmf.build.builders.ReferenceBuilder` - represents a reference to another group or dataset + * :py:class:`~hdmf.build.builders.RegionBuilder` - represents a slice into data (Subclass of :py:class:`~hdmf.build.builders.DatasetBuilder`) * **Main Module:** :py:class:`hdmf.build.builders` diff --git a/environment-ros3.yml b/environment-ros3.yml index be2b9d4f6..34c37cc01 100644 --- a/environment-ros3.yml +++ b/environment-ros3.yml @@ -4,11 +4,11 @@ channels: - conda-forge - defaults dependencies: - - python==3.13 - - h5py==3.12.1 - - matplotlib==3.9.2 - - numpy==2.1.3 - - pandas==2.2.3 + - python==3.12 + - h5py==3.11.0 + - matplotlib==3.8.4 + - numpy==2.0.0 + - pandas==2.2.2 - python-dateutil==2.8.2 - pytest==8.1.2 # regression introduced in pytest 8.2.*, will be fixed in 8.3.0 - pytest-cov==5.0.0 diff --git a/pyproject.toml b/pyproject.toml index 86b9e18e1..86e52a137 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,15 +13,15 @@ authors = [ ] description = "A hierarchical data modeling framework for modern science data standards" readme = "README.rst" -requires-python = ">=3.9" +requires-python = ">=3.8" license = {text = "BSD-3-Clause"} classifiers = [ "Programming Language :: Python", + "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", "License :: OSI Approved :: BSD License", "Development Status :: 5 - Production/Stable", "Operating System :: OS Independent", @@ -30,22 +30,22 @@ classifiers = [ "Topic :: Scientific/Engineering :: Medical Science Apps.", ] dependencies = [ - "h5py>=3.1.0", - "jsonschema>=3.2.0", - 'numpy>=1.19.3', - "pandas>=1.2.0", + "h5py>=2.10", + "jsonschema>=2.6.0", + 'numpy>=1.18', + "pandas>=1.0.5", "ruamel.yaml>=0.16", - "scipy>=1.7", + "scipy>=1.4", + "importlib-resources; python_version < '3.9'", # TODO: remove when minimum python version is 3.9 ] dynamic = ["version"] [project.optional-dependencies] tqdm = ["tqdm>=4.41.0"] -zarr = ["zarr>=2.12.0"] -termset = ["linkml-runtime>=1.5.5", - "schemasheets>=0.1.23", - "oaklib>=0.5.12", - "pyyaml>=6.0.1"] +termset = ["linkml-runtime>=1.5.5; python_version >= '3.9'", + "schemasheets>=0.1.23; python_version >= '3.9'", + "oaklib>=0.5.12; python_version >= '3.9'", + "pyyaml>=6.0.1; python_version >= '3.9'"] [project.urls] "Homepage" = "https://github.com/hdmf-dev/hdmf" diff --git a/requirements-min.txt b/requirements-min.txt index a9fbeb93e..a437fc588 100644 --- a/requirements-min.txt +++ b/requirements-min.txt @@ -1,10 +1,15 @@ # minimum versions of package dependencies for installing HDMF -# NOTE: these should match the minimum bound for dependencies in pyproject.toml -h5py==3.1.0 +h5py==2.10 # support for selection of datasets with list of indices added in 2.10 +importlib-resources==5.12.0; python_version < "3.9" # TODO: remove when when minimum python version is 3.9 jsonschema==3.2.0 -numpy==1.19.3 -pandas==1.2.0 -ruamel.yaml==0.16.0 -scipy==1.7.0 +numpy==1.18 +pandas==1.0.5 # when this is changed to >=1.5.0, see TODO items referenced in #762 +ruamel.yaml==0.16 +scipy==1.4 +# this file is currently used to test only python~=3.8 so these dependencies are not needed +# linkml-runtime==1.5.5; python_version >= "3.9" +# schemasheets==0.1.23; python_version >= "3.9" +# oaklib==0.5.12; python_version >= "3.9" +# pyyaml==6.0.1; python_version >= "3.9" tqdm==4.41.0 zarr==2.12.0 diff --git a/requirements-opt.txt b/requirements-opt.txt index 8811ff46e..4831d1949 100644 --- a/requirements-opt.txt +++ b/requirements-opt.txt @@ -1,6 +1,6 @@ # pinned dependencies that are optional. used to reproduce an entire development environment to use HDMF tqdm==4.66.4 zarr==2.18.2 -linkml-runtime==1.7.7 -schemasheets==0.2.1 -oaklib==0.6.10 +linkml-runtime==1.7.7; python_version >= "3.9" +schemasheets==0.2.1; python_version >= "3.9" +oaklib==0.6.10; python_version >= "3.9" diff --git a/requirements.txt b/requirements.txt index b431b4e63..30a596ada 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,10 @@ # pinned dependencies to reproduce an entire development environment to use HDMF -h5py==3.12.1 +h5py==3.11.0 +importlib-resources==6.1.0; python_version < "3.9" # TODO: remove when minimum python version is 3.9 jsonschema==4.22.0 -numpy==2.1.3 +numpy==1.26.4 # TODO: numpy 2.0.0 is supported by hdmf but incompatible with pandas and scipy +pandas==2.2.2; python_version >= "3.9" +pandas==2.1.2; python_version < "3.8" # TODO: remove when minimum python version is 3.9 ruamel.yaml==0.18.2 -pandas==2.2.3 -scipy==1.14.1; python_version >= "3.10" +scipy==1.14.0; python_version >= "3.10" scipy==1.11.3; python_version < "3.10" diff --git a/src/hdmf/__init__.py b/src/hdmf/__init__.py index 10305d37b..6fc72a117 100644 --- a/src/hdmf/__init__.py +++ b/src/hdmf/__init__.py @@ -1,10 +1,32 @@ from . import query -from .backends.hdf5.h5_utils import H5Dataset -from .container import Container, Data, HERDManager +from .backends.hdf5.h5_utils import H5Dataset, H5RegionSlicer +from .container import Container, Data, DataRegion, HERDManager +from .region import ListSlicer from .utils import docval, getargs from .term_set import TermSet, TermSetWrapper, TypeConfigurator +@docval( + {"name": "dataset", "type": None, "doc": "the HDF5 dataset to slice"}, + {"name": "region", "type": None, "doc": "the region reference to use to slice"}, + is_method=False, +) +def get_region_slicer(**kwargs): + import warnings # noqa: E402 + + warnings.warn( + "get_region_slicer is deprecated and will be removed in HDMF 3.0.", + DeprecationWarning, + ) + + dataset, region = getargs("dataset", "region", kwargs) + if isinstance(dataset, (list, tuple, Data)): + return ListSlicer(dataset, region) + elif isinstance(dataset, H5Dataset): + return H5RegionSlicer(dataset, region) + return None + + try: # see https://effigies.gitlab.io/posts/python-packaging-2023/ from ._version import __version__ diff --git a/src/hdmf/array.py b/src/hdmf/array.py new file mode 100644 index 000000000..a684572e4 --- /dev/null +++ b/src/hdmf/array.py @@ -0,0 +1,197 @@ +from abc import abstractmethod, ABCMeta + +import numpy as np + + +class Array: + + def __init__(self, data): + self.__data = data + if hasattr(data, 'dtype'): + self.dtype = data.dtype + else: + tmp = data + while isinstance(tmp, (list, tuple)): + tmp = tmp[0] + self.dtype = type(tmp) + + @property + def data(self): + return self.__data + + def __len__(self): + return len(self.__data) + + def get_data(self): + return self.__data + + def __getidx__(self, arg): + return self.__data[arg] + + def __sliceiter(self, arg): + return (x for x in range(*arg.indices(len(self)))) + + def __getitem__(self, arg): + if isinstance(arg, list): + idx = list() + for i in arg: + if isinstance(i, slice): + idx.extend(x for x in self.__sliceiter(i)) + else: + idx.append(i) + return np.fromiter((self.__getidx__(x) for x in idx), dtype=self.dtype) + elif isinstance(arg, slice): + return np.fromiter((self.__getidx__(x) for x in self.__sliceiter(arg)), dtype=self.dtype) + elif isinstance(arg, tuple): + return (self.__getidx__(arg[0]), self.__getidx__(arg[1])) + else: + return self.__getidx__(arg) + + +class AbstractSortedArray(Array, metaclass=ABCMeta): + ''' + An abstract class for representing sorted array + ''' + + @abstractmethod + def find_point(self, val): + pass + + def get_data(self): + return self + + def __lower(self, other): + ins = self.find_point(other) + return ins + + def __upper(self, other): + ins = self.__lower(other) + while self[ins] == other: + ins += 1 + return ins + + def __lt__(self, other): + ins = self.__lower(other) + return slice(0, ins) + + def __le__(self, other): + ins = self.__upper(other) + return slice(0, ins) + + def __gt__(self, other): + ins = self.__upper(other) + return slice(ins, len(self)) + + def __ge__(self, other): + ins = self.__lower(other) + return slice(ins, len(self)) + + @staticmethod + def __sort(a): + if isinstance(a, tuple): + return a[0] + else: + return a + + def __eq__(self, other): + if isinstance(other, list): + ret = list() + for i in other: + eq = self == i + ret.append(eq) + ret = sorted(ret, key=self.__sort) + tmp = list() + for i in range(1, len(ret)): + a, b = ret[i - 1], ret[i] + if isinstance(a, tuple): + if isinstance(b, tuple): + if a[1] >= b[0]: + b[0] = a[0] + else: + tmp.append(slice(*a)) + else: + if b > a[1]: + tmp.append(slice(*a)) + elif b == a[1]: + a[1] == b + 1 + else: + ret[i] = a + else: + if isinstance(b, tuple): + if a < b[0]: + tmp.append(a) + else: + if b - a == 1: + ret[i] = (a, b) + else: + tmp.append(a) + if isinstance(ret[-1], tuple): + tmp.append(slice(*ret[-1])) + else: + tmp.append(ret[-1]) + ret = tmp + return ret + elif isinstance(other, tuple): + ge = self >= other[0] + ge = ge.start + lt = self < other[1] + lt = lt.stop + if ge == lt: + return ge + else: + return slice(ge, lt) + else: + lower = self.__lower(other) + upper = self.__upper(other) + d = upper - lower + if d == 1: + return lower + elif d == 0: + return None + else: + return slice(lower, upper) + + def __ne__(self, other): + eq = self == other + if isinstance(eq, tuple): + return [slice(0, eq[0]), slice(eq[1], len(self))] + else: + return [slice(0, eq), slice(eq + 1, len(self))] + + +class SortedArray(AbstractSortedArray): + ''' + A class for wrapping sorted arrays. This class overrides + <,>,<=,>=,==, and != to leverage the sorted content for + efficiency. + ''' + + def __init__(self, array): + super().__init__(array) + + def find_point(self, val): + return np.searchsorted(self.data, val) + + +class LinSpace(SortedArray): + + def __init__(self, start, stop, step): + self.start = start + self.stop = stop + self.step = step + self.dtype = float if any(isinstance(s, float) for s in (start, stop, step)) else int + self.__len = int((stop - start) / step) + + def __len__(self): + return self.__len + + def find_point(self, val): + nsteps = (val - self.start) / self.step + fl = int(nsteps) + if fl == nsteps: + return int(fl) + else: + return int(fl + 1) + + def __getidx__(self, arg): + return self.start + self.step * arg diff --git a/src/hdmf/backends/hdf5/__init__.py b/src/hdmf/backends/hdf5/__init__.py index 8f76d7bcc..6abfc8c85 100644 --- a/src/hdmf/backends/hdf5/__init__.py +++ b/src/hdmf/backends/hdf5/__init__.py @@ -1,3 +1,3 @@ from . import h5_utils, h5tools -from .h5_utils import H5DataIO +from .h5_utils import H5RegionSlicer, H5DataIO from .h5tools import HDF5IO, H5SpecWriter, H5SpecReader diff --git a/src/hdmf/backends/hdf5/h5_utils.py b/src/hdmf/backends/hdf5/h5_utils.py index 878ebf089..2d7187721 100644 --- a/src/hdmf/backends/hdf5/h5_utils.py +++ b/src/hdmf/backends/hdf5/h5_utils.py @@ -8,7 +8,7 @@ from collections.abc import Iterable from copy import copy -from h5py import Group, Dataset, Reference, special_dtype +from h5py import Group, Dataset, RegionReference, Reference, special_dtype from h5py import filters as h5py_filters import json import numpy as np @@ -16,8 +16,10 @@ import os import logging +from ...array import Array from ...data_utils import DataIO, AbstractDataChunkIterator, append_data from ...query import HDMFDataset, ReferenceResolver, ContainerResolver, BuilderResolver +from ...region import RegionSlicer from ...spec import SpecWriter, SpecReader from ...utils import docval, getargs, popargs, get_docval, get_data_shape @@ -83,7 +85,7 @@ def append(self, dataset, data): class H5Dataset(HDMFDataset): - @docval({'name': 'dataset', 'type': Dataset, 'doc': 'the HDF5 file lazily evaluate'}, + @docval({'name': 'dataset', 'type': (Dataset, Array), 'doc': 'the HDF5 file lazily evaluate'}, {'name': 'io', 'type': 'hdmf.backends.hdf5.h5tools.HDF5IO', 'doc': 'the IO object that was used to read the underlying dataset'}) def __init__(self, **kwargs): @@ -94,6 +96,10 @@ def __init__(self, **kwargs): def io(self): return self.__io + @property + def regionref(self): + return self.dataset.regionref + @property def ref(self): return self.dataset.ref @@ -183,7 +189,7 @@ def get_object(self, h5obj): class AbstractH5TableDataset(DatasetOfReferences): - @docval({'name': 'dataset', 'type': Dataset, 'doc': 'the HDF5 file lazily evaluate'}, + @docval({'name': 'dataset', 'type': (Dataset, Array), 'doc': 'the HDF5 file lazily evaluate'}, {'name': 'io', 'type': 'hdmf.backends.hdf5.h5tools.HDF5IO', 'doc': 'the IO object that was used to read the underlying dataset'}, {'name': 'types', 'type': (list, tuple), @@ -193,7 +199,9 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.__refgetters = dict() for i, t in enumerate(types): - if t is Reference: + if t is RegionReference: + self.__refgetters[i] = self.__get_regref + elif t is Reference: self.__refgetters[i] = self._get_ref elif t is str: # we need this for when we read compound data types @@ -215,6 +223,8 @@ def __init__(self, **kwargs): t = sub.metadata['ref'] if t is Reference: tmp.append('object') + elif t is RegionReference: + tmp.append('region') else: tmp.append(sub.type.__name__) self.__dtype = tmp @@ -247,6 +257,10 @@ def _get_utf(self, string): """ return string.decode('utf-8') if isinstance(string, bytes) else string + def __get_regref(self, ref): + obj = self._get_ref(ref) + return obj[ref] + def resolve(self, manager): return self[0:len(self)] @@ -269,6 +283,18 @@ def dtype(self): return 'object' +class AbstractH5RegionDataset(AbstractH5ReferenceDataset): + + def __getitem__(self, arg): + obj = super().__getitem__(arg) + ref = self.dataset[arg] + return obj[ref] + + @property + def dtype(self): + return 'region' + + class ContainerH5TableDataset(ContainerResolverMixin, AbstractH5TableDataset): """ A reference-resolving dataset for resolving references inside tables @@ -313,6 +339,28 @@ def get_inverse_class(cls): return ContainerH5ReferenceDataset +class ContainerH5RegionDataset(ContainerResolverMixin, AbstractH5RegionDataset): + """ + A reference-resolving dataset for resolving region references that returns + resolved references as Containers + """ + + @classmethod + def get_inverse_class(cls): + return BuilderH5RegionDataset + + +class BuilderH5RegionDataset(BuilderResolverMixin, AbstractH5RegionDataset): + """ + A reference-resolving dataset for resolving region references that returns + resolved references as Builders + """ + + @classmethod + def get_inverse_class(cls): + return ContainerH5RegionDataset + + class H5SpecWriter(SpecWriter): __str_type = special_dtype(vlen=str) @@ -372,6 +420,28 @@ def read_namespace(self, ns_path): return ret +class H5RegionSlicer(RegionSlicer): + + @docval({'name': 'dataset', 'type': (Dataset, H5Dataset), 'doc': 'the HDF5 dataset to slice'}, + {'name': 'region', 'type': RegionReference, 'doc': 'the region reference to use to slice'}) + def __init__(self, **kwargs): + self.__dataset = getargs('dataset', kwargs) + self.__regref = getargs('region', kwargs) + self.__len = self.__dataset.regionref.selection(self.__regref)[0] + self.__region = None + + def __read_region(self): + if self.__region is None: + self.__region = self.__dataset[self.__regref] + + def __getitem__(self, idx): + self.__read_region() + return self.__region[idx] + + def __len__(self): + return self.__len + + class H5DataIO(DataIO): """ Wrap data arrays for write via HDF5IO to customize I/O behavior, such as compression and chunking diff --git a/src/hdmf/backends/hdf5/h5tools.py b/src/hdmf/backends/hdf5/h5tools.py index d30cef06c..4fc9c258f 100644 --- a/src/hdmf/backends/hdf5/h5tools.py +++ b/src/hdmf/backends/hdf5/h5tools.py @@ -7,14 +7,14 @@ import numpy as np import h5py -from h5py import File, Group, Dataset, special_dtype, SoftLink, ExternalLink, Reference, check_dtype +from h5py import File, Group, Dataset, special_dtype, SoftLink, ExternalLink, Reference, RegionReference, check_dtype -from .h5_utils import (BuilderH5ReferenceDataset, BuilderH5TableDataset, H5DataIO, +from .h5_utils import (BuilderH5ReferenceDataset, BuilderH5RegionDataset, BuilderH5TableDataset, H5DataIO, H5SpecReader, H5SpecWriter, HDF5IODataChunkIteratorQueue) from ..io import HDMFIO from ..errors import UnsupportedOperation from ..warnings import BrokenLinkWarning -from ...build import (Builder, GroupBuilder, DatasetBuilder, LinkBuilder, BuildManager, +from ...build import (Builder, GroupBuilder, DatasetBuilder, LinkBuilder, BuildManager, RegionBuilder, ReferenceBuilder, TypeMap, ObjectMapper) from ...container import Container from ...data_utils import AbstractDataChunkIterator @@ -28,6 +28,7 @@ H5_TEXT = special_dtype(vlen=str) H5_BINARY = special_dtype(vlen=bytes) H5_REF = special_dtype(ref=Reference) +H5_REGREF = special_dtype(ref=RegionReference) RDCC_NBYTES = 32*2**20 # set raw data chunk cache size = 32 MiB @@ -692,7 +693,10 @@ def __read_dataset(self, h5obj, name=None): target = h5obj.file[scalar] target_builder = self.__read_dataset(target) self.__set_built(target.file.filename, target.id, target_builder) - d = ReferenceBuilder(target_builder) + if isinstance(scalar, RegionReference): + d = RegionBuilder(scalar, target_builder) + else: + d = ReferenceBuilder(target_builder) kwargs['data'] = d kwargs['dtype'] = d.dtype elif h5obj.dtype.kind == 'V': # scalar compound data type @@ -709,6 +713,9 @@ def __read_dataset(self, h5obj, name=None): elem1 = h5obj[tuple([0] * (h5obj.ndim - 1) + [0])] if isinstance(elem1, (str, bytes)): d = self._check_str_dtype(h5obj) + elif isinstance(elem1, RegionReference): # read list of references + d = BuilderH5RegionDataset(h5obj, self) + kwargs['dtype'] = d.dtype elif isinstance(elem1, Reference): d = BuilderH5ReferenceDataset(h5obj, self) kwargs['dtype'] = d.dtype @@ -744,7 +751,9 @@ def __read_attrs(self, h5obj): for k, v in h5obj.attrs.items(): if k == SPEC_LOC_ATTR: # ignore cached spec continue - if isinstance(v, Reference): + if isinstance(v, RegionReference): + raise ValueError("cannot read region reference attributes yet") + elif isinstance(v, Reference): ret[k] = self.__read_ref(h5obj.file[v]) else: ret[k] = v @@ -908,7 +917,10 @@ def get_type(cls, data): "utf-8": H5_TEXT, "ascii": H5_BINARY, "bytes": H5_BINARY, + "ref": H5_REF, + "reference": H5_REF, "object": H5_REF, + "region": H5_REGREF, "isodatetime": H5_TEXT, "datetime": H5_TEXT, } @@ -1226,12 +1238,29 @@ def _filler(): dset = self.__scalar_fill__(parent, name, data, options) else: dset = self.__list_fill__(parent, name, data, options) - # Write a dataset containing references, i.e., object reference. + # Write a dataset containing references, i.e., a region or object reference. # NOTE: we can ignore options['io_settings'] for scalar data elif self.__is_ref(options['dtype']): _dtype = self.__dtypes.get(options['dtype']) + # Write a scalar data region reference dataset + if isinstance(data, RegionBuilder): + dset = parent.require_dataset(name, shape=(), dtype=_dtype) + self.__set_written(builder) + self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing a " + "region reference. attributes: %s" + % (name, list(attributes.keys()))) + + @self.__queue_ref + def _filler(): + self.logger.debug("Resolving region reference and setting attribute on dataset '%s' " + "containing attributes: %s" + % (name, list(attributes.keys()))) + ref = self.__get_ref(data.builder, data.region) + dset = parent[name] + dset[()] = ref + self.set_attributes(dset, attributes) # Write a scalar object reference dataset - if isinstance(data, ReferenceBuilder): + elif isinstance(data, ReferenceBuilder): dset = parent.require_dataset(name, dtype=_dtype, shape=()) self.__set_written(builder) self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing an " @@ -1249,24 +1278,44 @@ def _filler(): self.set_attributes(dset, attributes) # Write an array dataset of references else: - # Write array of object references - dset = parent.require_dataset(name, shape=(len(data),), dtype=_dtype, **options['io_settings']) - self.__set_written(builder) - self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing " - "object references. attributes: %s" - % (name, list(attributes.keys()))) + # Write a array of region references + if options['dtype'] == 'region': + dset = parent.require_dataset(name, dtype=_dtype, shape=(len(data),), **options['io_settings']) + self.__set_written(builder) + self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing " + "region references. attributes: %s" + % (name, list(attributes.keys()))) - @self.__queue_ref - def _filler(): - self.logger.debug("Resolving object references and setting attribute on dataset '%s' " - "containing attributes: %s" + @self.__queue_ref + def _filler(): + self.logger.debug("Resolving region references and setting attribute on dataset '%s' " + "containing attributes: %s" + % (name, list(attributes.keys()))) + refs = list() + for item in data: + refs.append(self.__get_ref(item.builder, item.region)) + dset = parent[name] + dset[()] = refs + self.set_attributes(dset, attributes) + # Write array of object references + else: + dset = parent.require_dataset(name, shape=(len(data),), dtype=_dtype, **options['io_settings']) + self.__set_written(builder) + self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing " + "object references. attributes: %s" % (name, list(attributes.keys()))) - refs = list() - for item in data: - refs.append(self.__get_ref(item)) - dset = parent[name] - dset[()] = refs - self.set_attributes(dset, attributes) + + @self.__queue_ref + def _filler(): + self.logger.debug("Resolving object references and setting attribute on dataset '%s' " + "containing attributes: %s" + % (name, list(attributes.keys()))) + refs = list() + for item in data: + refs.append(self.__get_ref(item)) + dset = parent[name] + dset[()] = refs + self.set_attributes(dset, attributes) return # write a "regular" dataset else: @@ -1454,9 +1503,11 @@ def __list_fill__(cls, parent, name, data, options=None): @docval({'name': 'container', 'type': (Builder, Container, ReferenceBuilder), 'doc': 'the object to reference', 'default': None}, + {'name': 'region', 'type': (slice, list, tuple), 'doc': 'the region reference indexing object', + 'default': None}, returns='the reference', rtype=Reference) def __get_ref(self, **kwargs): - container = getargs('container', kwargs) + container, region = getargs('container', 'region', kwargs) if container is None: return None if isinstance(container, Builder): @@ -1474,10 +1525,20 @@ def __get_ref(self, **kwargs): path = self.__get_path(builder) self.logger.debug("Getting reference at path '%s'" % path) - return self.__file[path].ref + if isinstance(container, RegionBuilder): + region = container.region + if region is not None: + dset = self.__file[path] + if not isinstance(dset, Dataset): + raise ValueError('cannot create region reference without Dataset') + return self.__file[path].regionref[region] + else: + return self.__file[path].ref @docval({'name': 'container', 'type': (Builder, Container, ReferenceBuilder), 'doc': 'the object to reference', 'default': None}, + {'name': 'region', 'type': (slice, list, tuple), 'doc': 'the region reference indexing object', + 'default': None}, returns='the reference', rtype=Reference) def _create_ref(self, **kwargs): return self.__get_ref(**kwargs) @@ -1490,7 +1551,7 @@ def __is_ref(self, dtype): if isinstance(dtype, dict): # may be dict from reading a compound dataset return self.__is_ref(dtype['dtype']) if isinstance(dtype, str): - return dtype == DatasetBuilder.OBJECT_REF_TYPE + return dtype == DatasetBuilder.OBJECT_REF_TYPE or dtype == DatasetBuilder.REGION_REF_TYPE return False def __queue_ref(self, func): @@ -1509,6 +1570,17 @@ def __queue_ref(self, func): # dependency self.__ref_queue.append(func) + def __rec_get_ref(self, ref_list): + ret = list() + for elem in ref_list: + if isinstance(elem, (list, tuple)): + ret.append(self.__rec_get_ref(elem)) + elif isinstance(elem, (Builder, Container)): + ret.append(self.__get_ref(elem)) + else: + ret.append(elem) + return ret + @property def mode(self): """ diff --git a/src/hdmf/build/__init__.py b/src/hdmf/build/__init__.py index 87e0ac57e..ea5d21152 100644 --- a/src/hdmf/build/__init__.py +++ b/src/hdmf/build/__init__.py @@ -1,4 +1,4 @@ -from .builders import Builder, DatasetBuilder, GroupBuilder, LinkBuilder, ReferenceBuilder +from .builders import Builder, DatasetBuilder, GroupBuilder, LinkBuilder, ReferenceBuilder, RegionBuilder from .classgenerator import CustomClassGenerator, MCIClassGenerator from .errors import (BuildError, OrphanContainerBuildError, ReferenceTargetNotBuiltError, ContainerConfigurationError, ConstructError) diff --git a/src/hdmf/build/builders.py b/src/hdmf/build/builders.py index 2d90c24e3..cb658b6d4 100644 --- a/src/hdmf/build/builders.py +++ b/src/hdmf/build/builders.py @@ -6,6 +6,7 @@ from datetime import datetime, date import numpy as np +from h5py import RegionReference from ..utils import docval, getargs, get_docval @@ -319,10 +320,11 @@ def values(self): class DatasetBuilder(BaseBuilder): OBJECT_REF_TYPE = 'object' + REGION_REF_TYPE = 'region' @docval({'name': 'name', 'type': str, 'doc': 'The name of the dataset.'}, {'name': 'data', - 'type': ('array_data', 'scalar_data', 'data', 'DatasetBuilder', Iterable, datetime, date), + 'type': ('array_data', 'scalar_data', 'data', 'DatasetBuilder', 'RegionBuilder', Iterable, datetime, date), 'doc': 'The data in this dataset.', 'default': None}, {'name': 'dtype', 'type': (type, np.dtype, str, list), 'doc': 'The datatype of this dataset.', 'default': None}, @@ -427,3 +429,20 @@ def __init__(self, **kwargs): def builder(self): """The target builder object.""" return self['builder'] + + +class RegionBuilder(ReferenceBuilder): + + @docval({'name': 'region', 'type': (slice, tuple, list, RegionReference), + 'doc': 'The region, i.e. slice or indices, into the target dataset.'}, + {'name': 'builder', 'type': DatasetBuilder, 'doc': 'The dataset this region reference applies to.'}) + def __init__(self, **kwargs): + """Create a builder object for a region reference.""" + region, builder = getargs('region', 'builder', kwargs) + super().__init__(builder) + self['region'] = region + + @property + def region(self): + """The selected region of the target dataset.""" + return self['region'] diff --git a/src/hdmf/build/classgenerator.py b/src/hdmf/build/classgenerator.py index 3b7d7c96e..a3336b98e 100644 --- a/src/hdmf/build/classgenerator.py +++ b/src/hdmf/build/classgenerator.py @@ -4,7 +4,7 @@ import numpy as np -from ..container import Container, Data, MultiContainerInterface +from ..container import Container, Data, DataRegion, MultiContainerInterface from ..spec import AttributeSpec, LinkSpec, RefSpec, GroupSpec from ..spec.spec import BaseStorageSpec, ZERO_OR_MANY, ONE_OR_MANY from ..utils import docval, getargs, ExtenderMeta, get_docval, popargs, AllowPositional @@ -195,7 +195,7 @@ def _ischild(cls, dtype): if isinstance(dtype, tuple): for sub in dtype: ret = ret or cls._ischild(sub) - elif isinstance(dtype, type) and issubclass(dtype, (Container, Data)): + elif isinstance(dtype, type) and issubclass(dtype, (Container, Data, DataRegion)): ret = True return ret diff --git a/src/hdmf/build/manager.py b/src/hdmf/build/manager.py index bc586013c..967c34010 100644 --- a/src/hdmf/build/manager.py +++ b/src/hdmf/build/manager.py @@ -490,6 +490,20 @@ def load_namespaces(self, **kwargs): self.register_container_type(new_ns, dt, container_cls) return deps + @docval({"name": "namespace", "type": str, "doc": "the namespace containing the data_type"}, + {"name": "data_type", "type": str, "doc": "the data type to create a AbstractContainer class for"}, + {"name": "autogen", "type": bool, "doc": "autogenerate class if one does not exist", "default": True}, + returns='the class for the given namespace and data_type', rtype=type) + def get_container_cls(self, **kwargs): + """Get the container class from data type specification. + If no class has been associated with the ``data_type`` from ``namespace``, a class will be dynamically + created and returned. + """ + # NOTE: this internally used function get_container_cls will be removed in favor of get_dt_container_cls + # Deprecated: Will be removed by HDMF 4.0 + namespace, data_type, autogen = getargs('namespace', 'data_type', 'autogen', kwargs) + return self.get_dt_container_cls(data_type, namespace, autogen) + @docval({"name": "data_type", "type": str, "doc": "the data type to create a AbstractContainer class for"}, {"name": "namespace", "type": str, "doc": "the namespace containing the data_type", "default": None}, {'name': 'post_init_method', 'type': Callable, 'default': None, @@ -501,7 +515,7 @@ def get_dt_container_cls(self, **kwargs): If no class has been associated with the ``data_type`` from ``namespace``, a class will be dynamically created and returned. - Namespace is optional. If namespace is unknown, it will be looked up from + Replaces get_container_cls but namespace is optional. If namespace is unknown, it will be looked up from all namespaces. """ namespace, data_type, post_init_method, autogen = getargs('namespace', 'data_type', diff --git a/src/hdmf/build/map.py b/src/hdmf/build/map.py new file mode 100644 index 000000000..5267609f5 --- /dev/null +++ b/src/hdmf/build/map.py @@ -0,0 +1,7 @@ +# this prevents breaking of code that imports these classes directly from map.py +from .manager import Proxy, BuildManager, TypeSource, TypeMap # noqa: F401 +from .objectmapper import ObjectMapper # noqa: F401 + +import warnings +warnings.warn('Classes in map.py should be imported from hdmf.build. Importing from hdmf.build.map will be removed ' + 'in HDMF 3.0.', DeprecationWarning, stacklevel=2) diff --git a/src/hdmf/build/objectmapper.py b/src/hdmf/build/objectmapper.py index 176de322c..3394ebb91 100644 --- a/src/hdmf/build/objectmapper.py +++ b/src/hdmf/build/objectmapper.py @@ -6,7 +6,7 @@ import numpy as np -from .builders import DatasetBuilder, GroupBuilder, LinkBuilder, Builder, ReferenceBuilder, BaseBuilder +from .builders import DatasetBuilder, GroupBuilder, LinkBuilder, Builder, ReferenceBuilder, RegionBuilder, BaseBuilder from .errors import (BuildError, OrphanContainerBuildError, ReferenceTargetNotBuiltError, ContainerConfigurationError, ConstructError) from .manager import Proxy, BuildManager @@ -15,7 +15,7 @@ IncorrectDatasetShapeBuildWarning) from hdmf.backends.hdf5.h5_utils import H5DataIO -from ..container import AbstractContainer, Data +from ..container import AbstractContainer, Data, DataRegion from ..term_set import TermSetWrapper from ..data_utils import DataIO, AbstractDataChunkIterator from ..query import ReferenceResolver @@ -966,23 +966,41 @@ def _filler(): return _filler def __get_ref_builder(self, builder, dtype, shape, container, build_manager): - self.logger.debug("Setting object reference dataset on %s '%s' data" - % (builder.__class__.__name__, builder.name)) - if isinstance(container, Data): - self.logger.debug("Setting %s '%s' data to list of reference builders" - % (builder.__class__.__name__, builder.name)) - bldr_data = list() - for d in container.data: - target_builder = self.__get_target_builder(d, build_manager, builder) - bldr_data.append(ReferenceBuilder(target_builder)) - if isinstance(container.data, H5DataIO): - # This is here to support appending a dataset of references. - bldr_data = H5DataIO(bldr_data, **container.data.get_io_params()) + bldr_data = None + if dtype.is_region(): + if shape is None: + if not isinstance(container, DataRegion): + msg = "'container' must be of type DataRegion if spec represents region reference" + raise ValueError(msg) + self.logger.debug("Setting %s '%s' data to region reference builder" + % (builder.__class__.__name__, builder.name)) + target_builder = self.__get_target_builder(container.data, build_manager, builder) + bldr_data = RegionBuilder(container.region, target_builder) + else: + self.logger.debug("Setting %s '%s' data to list of region reference builders" + % (builder.__class__.__name__, builder.name)) + bldr_data = list() + for d in container.data: + target_builder = self.__get_target_builder(d.target, build_manager, builder) + bldr_data.append(RegionBuilder(d.slice, target_builder)) else: - self.logger.debug("Setting %s '%s' data to reference builder" + self.logger.debug("Setting object reference dataset on %s '%s' data" % (builder.__class__.__name__, builder.name)) - target_builder = self.__get_target_builder(container, build_manager, builder) - bldr_data = ReferenceBuilder(target_builder) + if isinstance(container, Data): + self.logger.debug("Setting %s '%s' data to list of reference builders" + % (builder.__class__.__name__, builder.name)) + bldr_data = list() + for d in container.data: + target_builder = self.__get_target_builder(d, build_manager, builder) + bldr_data.append(ReferenceBuilder(target_builder)) + if isinstance(container.data, H5DataIO): + # This is here to support appending a dataset of references. + bldr_data = H5DataIO(bldr_data, **container.data.get_io_params()) + else: + self.logger.debug("Setting %s '%s' data to reference builder" + % (builder.__class__.__name__, builder.name)) + target_builder = self.__get_target_builder(container, build_manager, builder) + bldr_data = ReferenceBuilder(target_builder) return bldr_data def __get_target_builder(self, container, build_manager, builder): @@ -1214,6 +1232,8 @@ def __get_subspec_values(self, builder, spec, manager): continue if isinstance(attr_val, (GroupBuilder, DatasetBuilder)): ret[attr_spec] = manager.construct(attr_val) + elif isinstance(attr_val, RegionBuilder): # pragma: no cover + raise ValueError("RegionReferences as attributes is not yet supported") elif isinstance(attr_val, ReferenceBuilder): ret[attr_spec] = manager.construct(attr_val.builder) else: diff --git a/src/hdmf/common/__init__.py b/src/hdmf/common/__init__.py index 6b36e29cd..5c9d9a3b7 100644 --- a/src/hdmf/common/__init__.py +++ b/src/hdmf/common/__init__.py @@ -108,7 +108,11 @@ def _dec(cls): def __get_resources(): - from importlib.resources import files + try: + from importlib.resources import files + except ImportError: + # TODO: Remove when python 3.9 becomes the new minimum + from importlib_resources import files __location_of_this_file = files(__name__) __core_ns_file_name = 'namespace.yaml' diff --git a/src/hdmf/common/table.py b/src/hdmf/common/table.py index 84ac4da3b..b4530c7b7 100644 --- a/src/hdmf/common/table.py +++ b/src/hdmf/common/table.py @@ -775,8 +775,8 @@ def add_column(self, **kwargs): # noqa: C901 index, table, enum, col_cls, check_ragged = popargs('index', 'table', 'enum', 'col_cls', 'check_ragged', kwargs) if isinstance(index, VectorIndex): - msg = "Passing a VectorIndex may lead to unexpected behavior. This functionality is not supported." - raise ValueError(msg) + warn("Passing a VectorIndex in for index may lead to unexpected behavior. This functionality will be " + "deprecated in a future version of HDMF.", category=FutureWarning, stacklevel=3) if name in self.__colids: # column has already been added msg = "column '%s' already exists in %s '%s'" % (name, self.__class__.__name__, self.name) diff --git a/src/hdmf/container.py b/src/hdmf/container.py index ce4e8b821..f2dba6e8d 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -1,4 +1,5 @@ import types +from abc import abstractmethod from collections import OrderedDict from copy import deepcopy from typing import Type, Optional @@ -466,6 +467,21 @@ def set_modified(self, **kwargs): def children(self): return tuple(self.__children) + @docval({'name': 'child', 'type': 'Container', + 'doc': 'the child Container for this Container', 'default': None}) + def add_child(self, **kwargs): + warn(DeprecationWarning('add_child is deprecated. Set the parent attribute instead.')) + child = getargs('child', kwargs) + if child is not None: + # if child.parent is a Container, then the mismatch between child.parent and parent + # is used to make a soft/external link from the parent to a child elsewhere + # if child.parent is not a Container, it is either None or a Proxy and should be set to self + if not isinstance(child.parent, AbstractContainer): + # actually add the child to the parent in parent setter + child.parent = self + else: + warn('Cannot add None as child to a container %s' % self.name) + @classmethod def type_hierarchy(cls): return cls.__mro__ @@ -915,6 +931,20 @@ def shape(self): """ return get_data_shape(self.__data) + @docval({'name': 'dataio', 'type': DataIO, 'doc': 'the DataIO to apply to the data held by this Data'}) + def set_dataio(self, **kwargs): + """ + Apply DataIO object to the data held by this Data object + """ + warn( + "Data.set_dataio() is deprecated. Please use Data.set_data_io() instead.", + DeprecationWarning, + stacklevel=3, + ) + dataio = getargs('dataio', kwargs) + dataio.data = self.__data + self.__data = dataio + def set_data_io( self, data_io_class: Type[DataIO], @@ -1010,6 +1040,25 @@ def _validate_new_data_element(self, arg): pass +class DataRegion(Data): + + @property + @abstractmethod + def data(self): + ''' + The target data that this region applies to + ''' + pass + + @property + @abstractmethod + def region(self): + ''' + The region that indexes into data e.g. slice or list of indices + ''' + pass + + class MultiContainerInterface(Container): """Class that dynamically defines methods to support a Container holding multiple Containers of the same type. diff --git a/src/hdmf/query.py b/src/hdmf/query.py index abe2a93a7..9693b0b1c 100644 --- a/src/hdmf/query.py +++ b/src/hdmf/query.py @@ -2,24 +2,143 @@ import numpy as np +from .array import Array from .utils import ExtenderMeta, docval_macro, docval, getargs +class Query(metaclass=ExtenderMeta): + __operations__ = ( + '__lt__', + '__gt__', + '__le__', + '__ge__', + '__eq__', + '__ne__', + ) + + @classmethod + def __build_operation(cls, op): + def __func(self, arg): + return cls(self, op, arg) + + @ExtenderMeta.pre_init + def __make_operators(cls, name, bases, classdict): + if not isinstance(cls.__operations__, tuple): + raise TypeError("'__operations__' must be of type tuple") + # add any new operations + if len(bases) and 'Query' in globals() and issubclass(bases[-1], Query) \ + and bases[-1].__operations__ is not cls.__operations__: + new_operations = list(cls.__operations__) + new_operations[0:0] = bases[-1].__operations__ + cls.__operations__ = tuple(new_operations) + for op in cls.__operations__: + if not hasattr(cls, op): + setattr(cls, op, cls.__build_operation(op)) + + def __init__(self, obj, op, arg): + self.obj = obj + self.op = op + self.arg = arg + self.collapsed = None + self.expanded = None + + @docval({'name': 'expand', 'type': bool, 'help': 'whether or not to expand result', 'default': True}) + def evaluate(self, **kwargs): + expand = getargs('expand', kwargs) + if expand: + if self.expanded is None: + self.expanded = self.__evalhelper() + return self.expanded + else: + if self.collapsed is None: + self.collapsed = self.__collapse(self.__evalhelper()) + return self.collapsed + + def __evalhelper(self): + obj = self.obj + arg = self.arg + if isinstance(obj, Query): + obj = obj.evaluate() + elif isinstance(obj, HDMFDataset): + obj = obj.dataset + if isinstance(arg, Query): + arg = self.arg.evaluate() + return getattr(obj, self.op)(self.arg) + + def __collapse(self, result): + if isinstance(result, slice): + return (result.start, result.stop) + elif isinstance(result, list): + ret = list() + for idx in result: + if isinstance(idx, slice) and (idx.step is None or idx.step == 1): + ret.append((idx.start, idx.stop)) + else: + ret.append(idx) + return ret + else: + return result + + def __and__(self, other): + return NotImplemented + + def __or__(self, other): + return NotImplemented + + def __xor__(self, other): + return NotImplemented + + def __contains__(self, other): + return NotImplemented + + @docval_macro('array_data') class HDMFDataset(metaclass=ExtenderMeta): + __operations__ = ( + '__lt__', + '__gt__', + '__le__', + '__ge__', + '__eq__', + '__ne__', + ) + + @classmethod + def __build_operation(cls, op): + def __func(self, arg): + return Query(self, op, arg) + + setattr(__func, '__name__', op) + return __func + + @ExtenderMeta.pre_init + def __make_operators(cls, name, bases, classdict): + if not isinstance(cls.__operations__, tuple): + raise TypeError("'__operations__' must be of type tuple") + # add any new operations + if len(bases) and 'Query' in globals() and issubclass(bases[-1], Query) \ + and bases[-1].__operations__ is not cls.__operations__: + new_operations = list(cls.__operations__) + new_operations[0:0] = bases[-1].__operations__ + cls.__operations__ = tuple(new_operations) + for op in cls.__operations__: + setattr(cls, op, cls.__build_operation(op)) + def __evaluate_key(self, key): if isinstance(key, tuple) and len(key) == 0: return key if isinstance(key, (tuple, list, np.ndarray)): return list(map(self.__evaluate_key, key)) else: + if isinstance(key, Query): + return key.evaluate() return key def __getitem__(self, key): idx = self.__evaluate_key(key) return self.dataset[idx] - @docval({'name': 'dataset', 'type': 'array_data', 'doc': 'the HDF5 file lazily evaluate'}) + @docval({'name': 'dataset', 'type': ('array_data', Array), 'doc': 'the HDF5 file lazily evaluate'}) def __init__(self, **kwargs): super().__init__() self.__dataset = getargs('dataset', kwargs) diff --git a/src/hdmf/region.py b/src/hdmf/region.py new file mode 100644 index 000000000..9feeba401 --- /dev/null +++ b/src/hdmf/region.py @@ -0,0 +1,91 @@ +from abc import ABCMeta, abstractmethod +from operator import itemgetter + +from .container import Data, DataRegion +from .utils import docval, getargs + + +class RegionSlicer(DataRegion, metaclass=ABCMeta): + ''' + A abstract base class to control getting using a region + + Subclasses must implement `__getitem__` and `__len__` + ''' + + @docval({'name': 'target', 'type': None, 'doc': 'the target to slice'}, + {'name': 'slice', 'type': None, 'doc': 'the region to slice'}) + def __init__(self, **kwargs): + self.__target = getargs('target', kwargs) + self.__slice = getargs('slice', kwargs) + + @property + def data(self): + """The target data. Same as self.target""" + return self.target + + @property + def region(self): + """The selected region. Same as self.slice""" + return self.slice + + @property + def target(self): + """The target data""" + return self.__target + + @property + def slice(self): + """The selected slice""" + return self.__slice + + @property + @abstractmethod + def __getitem__(self, idx): + """Must be implemented by subclasses""" + pass + + @property + @abstractmethod + def __len__(self): + """Must be implemented by subclasses""" + pass + + +class ListSlicer(RegionSlicer): + """Implementation of RegionSlicer for slicing Lists and Data""" + + @docval({'name': 'dataset', 'type': (list, tuple, Data), 'doc': 'the dataset to slice'}, + {'name': 'region', 'type': (list, tuple, slice), 'doc': 'the region reference to use to slice'}) + def __init__(self, **kwargs): + self.__dataset, self.__region = getargs('dataset', 'region', kwargs) + super().__init__(self.__dataset, self.__region) + if isinstance(self.__region, slice): + self.__getter = itemgetter(self.__region) + self.__len = len(range(*self.__region.indices(len(self.__dataset)))) + else: + self.__getter = itemgetter(*self.__region) + self.__len = len(self.__region) + + def __read_region(self): + """ + Internal helper function used to define self._read + """ + if not hasattr(self, '_read'): + self._read = self.__getter(self.__dataset) + del self.__getter + + def __getitem__(self, idx): + """ + Get data values from selected data + """ + self.__read_region() + getter = None + if isinstance(idx, (list, tuple)): + getter = itemgetter(*idx) + else: + getter = itemgetter(idx) + return getter(self._read) + + def __len__(self): + """Number of values in the slice/region""" + return self.__len diff --git a/src/hdmf/spec/spec.py b/src/hdmf/spec/spec.py index d5adf6b5e..e10d5e43e 100644 --- a/src/hdmf/spec/spec.py +++ b/src/hdmf/spec/spec.py @@ -38,6 +38,7 @@ class DtypeHelper: 'uint32': ["uint32", "uint"], 'uint64': ["uint64"], 'object': ['object'], + 'region': ['region'], 'numeric': ['numeric'], 'isodatetime': ["isodatetime", "datetime", "date"] } @@ -173,13 +174,12 @@ def path(self): _ref_args = [ {'name': _target_type_key, 'type': str, 'doc': 'the target type GroupSpec or DatasetSpec'}, - {'name': 'reftype', 'type': str, - 'doc': 'the type of reference this is. only "object" is supported currently.'}, + {'name': 'reftype', 'type': str, 'doc': 'the type of references this is i.e. region or object'}, ] class RefSpec(ConstructableDict): - __allowable_types = ('object', ) + __allowable_types = ('object', 'region') @docval(*_ref_args) def __init__(self, **kwargs): @@ -200,6 +200,10 @@ def reftype(self): '''The type of reference''' return self['reftype'] + @docval(rtype=bool, returns='True if this RefSpec specifies a region reference, False otherwise') + def is_region(self): + return self['reftype'] == 'region' + _attr_args = [ {'name': 'name', 'type': str, 'doc': 'The name of this attribute'}, diff --git a/src/hdmf/utils.py b/src/hdmf/utils.py index c21382a2a..d05b52d93 100644 --- a/src/hdmf/utils.py +++ b/src/hdmf/utils.py @@ -382,6 +382,8 @@ def __parse_args(validator, args, kwargs, enforce_type=True, enforce_shape=True, for key in extras.keys(): type_errors.append("unrecognized argument: '%s'" % key) else: + # TODO: Extras get stripped out if function arguments are composed with fmt_docval_args. + # allow_extra needs to be tracked on a function so that fmt_docval_args doesn't strip them out for key in extras.keys(): ret[key] = extras[key] return {'args': ret, 'future_warnings': future_warnings, 'type_errors': type_errors, 'value_errors': value_errors, @@ -412,6 +414,95 @@ def get_docval(func, *args): return tuple() +# def docval_wrap(func, is_method=True): +# if is_method: +# @docval(*get_docval(func)) +# def method(self, **kwargs): +# +# return call_docval_args(func, kwargs) +# return method +# else: +# @docval(*get_docval(func)) +# def static_method(**kwargs): +# return call_docval_args(func, kwargs) +# return method + + +def fmt_docval_args(func, kwargs): + ''' Separate positional and keyword arguments + + Useful for methods that wrap other methods + ''' + warnings.warn("fmt_docval_args will be deprecated in a future version of HDMF. Instead of using fmt_docval_args, " + "call the function directly with the kwargs. Please note that fmt_docval_args " + "removes all arguments not accepted by the function's docval, so if you are passing kwargs that " + "includes extra arguments and the function's docval does not allow extra arguments (allow_extra=True " + "is set), then you will need to pop the extra arguments out of kwargs before calling the function.", + PendingDeprecationWarning, stacklevel=2) + func_docval = getattr(func, docval_attr_name, None) + ret_args = list() + ret_kwargs = dict() + kwargs_copy = _copy.copy(kwargs) + if func_docval: + for arg in func_docval[__docval_args_loc]: + val = kwargs_copy.pop(arg['name'], None) + if 'default' in arg: + if val is not None: + ret_kwargs[arg['name']] = val + else: + ret_args.append(val) + if func_docval['allow_extra']: + ret_kwargs.update(kwargs_copy) + else: + raise ValueError('no docval found on %s' % str(func)) + return ret_args, ret_kwargs + + +# def _remove_extra_args(func, kwargs): +# """Return a dict of only the keyword arguments that are accepted by the function's docval. +# +# If the docval specifies allow_extra=True, then the original kwargs are returned. +# """ +# # NOTE: this has the same functionality as the to-be-deprecated fmt_docval_args except that +# # kwargs are kept as kwargs instead of parsed into args and kwargs +# func_docval = getattr(func, docval_attr_name, None) +# if func_docval: +# if func_docval['allow_extra']: +# # if extra args are allowed, return all args +# return kwargs +# else: +# # save only the arguments listed in the function's docval (skip any others present in kwargs) +# ret_kwargs = dict() +# for arg in func_docval[__docval_args_loc]: +# val = kwargs.get(arg['name'], None) +# if val is not None: # do not return arguments that are not present or have value None +# ret_kwargs[arg['name']] = val +# return ret_kwargs +# else: +# raise ValueError('No docval found on %s' % str(func)) + + +def call_docval_func(func, kwargs): + """Call the function with only the keyword arguments that are accepted by the function's docval. + + Extra keyword arguments are not passed to the function unless the function's docval has allow_extra=True. + """ + warnings.warn("call_docval_func will be deprecated in a future version of HDMF. Instead of using call_docval_func, " + "call the function directly with the kwargs. Please note that call_docval_func " + "removes all arguments not accepted by the function's docval, so if you are passing kwargs that " + "includes extra arguments and the function's docval does not allow extra arguments (allow_extra=True " + "is set), then you will need to pop the extra arguments out of kwargs before calling the function.", + PendingDeprecationWarning, stacklevel=2) + with warnings.catch_warnings(record=True): + # catch and ignore only PendingDeprecationWarnings from fmt_docval_args so that two + # PendingDeprecationWarnings saying the same thing are not raised + warnings.simplefilter("ignore", UserWarning) + warnings.simplefilter("always", PendingDeprecationWarning) + fargs, fkwargs = fmt_docval_args(func, kwargs) + + return func(*fargs, **fkwargs) + + def __resolve_type(t): if t is None: return t diff --git a/src/hdmf/validate/validator.py b/src/hdmf/validate/validator.py index d7ec78eaa..daa5adac4 100644 --- a/src/hdmf/validate/validator.py +++ b/src/hdmf/validate/validator.py @@ -8,7 +8,7 @@ from .errors import Error, DtypeError, MissingError, MissingDataType, ShapeError, IllegalLinkError, IncorrectDataType from .errors import ExpectedArrayError, IncorrectQuantityError -from ..build import GroupBuilder, DatasetBuilder, LinkBuilder, ReferenceBuilder +from ..build import GroupBuilder, DatasetBuilder, LinkBuilder, ReferenceBuilder, RegionBuilder from ..build.builders import BaseBuilder from ..spec import Spec, AttributeSpec, GroupSpec, DatasetSpec, RefSpec, LinkSpec from ..spec import SpecNamespace @@ -124,6 +124,9 @@ def get_type(data, builder_dtype=None): # Bytes data elif isinstance(data, bytes): return 'ascii', get_string_format(data) + # RegionBuilder data + elif isinstance(data, RegionBuilder): + return 'region', None # ReferenceBuilder data elif isinstance(data, ReferenceBuilder): return 'object', None diff --git a/tests/unit/build_tests/test_builder.py b/tests/unit/build_tests/test_builder.py index 62ebd0675..a35dc64ac 100644 --- a/tests/unit/build_tests/test_builder.py +++ b/tests/unit/build_tests/test_builder.py @@ -1,4 +1,4 @@ -from hdmf.build import GroupBuilder, DatasetBuilder, LinkBuilder, ReferenceBuilder +from hdmf.build import GroupBuilder, DatasetBuilder, LinkBuilder, ReferenceBuilder, RegionBuilder from hdmf.testing import TestCase @@ -392,3 +392,12 @@ def test_constructor(self): db = DatasetBuilder('db1', [1, 2, 3]) rb = ReferenceBuilder(db) self.assertIs(rb.builder, db) + + +class TestRegionBuilder(TestCase): + + def test_constructor(self): + db = DatasetBuilder('db1', [1, 2, 3]) + rb = RegionBuilder(slice(1, 3), db) + self.assertEqual(rb.region, slice(1, 3)) + self.assertIs(rb.builder, db) diff --git a/tests/unit/common/test_table.py b/tests/unit/common/test_table.py index 15a0c9e91..38175b230 100644 --- a/tests/unit/common/test_table.py +++ b/tests/unit/common/test_table.py @@ -429,7 +429,9 @@ def test_add_column_vectorindex(self): table.add_column(name='qux', description='qux column') ind = VectorIndex(name='quux', data=list(), target=table['qux']) - with self.assertRaises(ValueError): + msg = ("Passing a VectorIndex in for index may lead to unexpected behavior. This functionality will be " + "deprecated in a future version of HDMF.") + with self.assertWarnsWith(FutureWarning, msg): table.add_column(name='bad', description='bad column', index=ind) def test_add_column_multi_index(self): diff --git a/tests/unit/spec_tests/test_ref_spec.py b/tests/unit/spec_tests/test_ref_spec.py index 3277673d1..bb1c0efb8 100644 --- a/tests/unit/spec_tests/test_ref_spec.py +++ b/tests/unit/spec_tests/test_ref_spec.py @@ -15,3 +15,9 @@ def test_constructor(self): def test_wrong_reference_type(self): with self.assertRaises(ValueError): RefSpec('TimeSeries', 'unknownreftype') + + def test_isregion(self): + spec = RefSpec('TimeSeries', 'object') + self.assertFalse(spec.is_region()) + spec = RefSpec('Data', 'region') + self.assertTrue(spec.is_region()) diff --git a/tests/unit/test_container.py b/tests/unit/test_container.py index 2abe6349b..c12247de7 100644 --- a/tests/unit/test_container.py +++ b/tests/unit/test_container.py @@ -213,6 +213,18 @@ def test_all_children(self): obj = species.all_objects self.assertEqual(sorted(list(obj.keys())), sorted([species.object_id, species.id.object_id, col1.object_id])) + def test_add_child(self): + """Test that add child creates deprecation warning and also properly sets child's parent and modified + """ + parent_obj = Container('obj1') + child_obj = Container('obj2') + parent_obj.set_modified(False) + with self.assertWarnsWith(DeprecationWarning, 'add_child is deprecated. Set the parent attribute instead.'): + parent_obj.add_child(child_obj) + self.assertIs(child_obj.parent, parent_obj) + self.assertTrue(parent_obj.modified) + self.assertIs(parent_obj.children[0], child_obj) + def test_parent_set_link_warning(self): col1 = VectorData( name='col1', diff --git a/tests/unit/test_query.py b/tests/unit/test_query.py new file mode 100644 index 000000000..b2ff267a7 --- /dev/null +++ b/tests/unit/test_query.py @@ -0,0 +1,161 @@ +import os +from abc import ABCMeta, abstractmethod + +import numpy as np +from h5py import File +from hdmf.array import SortedArray, LinSpace +from hdmf.query import HDMFDataset, Query +from hdmf.testing import TestCase + + +class AbstractQueryMixin(metaclass=ABCMeta): + + @abstractmethod + def getDataset(self): + raise NotImplementedError('Cannot run test unless getDataset is implemented') + + def setUp(self): + self.dset = self.getDataset() + self.wrapper = HDMFDataset(self.dset) + + def test_get_dataset(self): + array = self.wrapper.dataset + self.assertIsInstance(array, SortedArray) + + def test___gt__(self): + ''' + Test wrapper greater than magic method + ''' + q = self.wrapper > 5 + self.assertIsInstance(q, Query) + result = q.evaluate() + expected = [False, False, False, False, False, + False, True, True, True, True] + expected = slice(6, 10) + self.assertEqual(result, expected) + + def test___ge__(self): + ''' + Test wrapper greater than or equal magic method + ''' + q = self.wrapper >= 5 + self.assertIsInstance(q, Query) + result = q.evaluate() + expected = [False, False, False, False, False, + True, True, True, True, True] + expected = slice(5, 10) + self.assertEqual(result, expected) + + def test___lt__(self): + ''' + Test wrapper less than magic method + ''' + q = self.wrapper < 5 + self.assertIsInstance(q, Query) + result = q.evaluate() + expected = [True, True, True, True, True, + False, False, False, False, False] + expected = slice(0, 5) + self.assertEqual(result, expected) + + def test___le__(self): + ''' + Test wrapper less than or equal magic method + ''' + q = self.wrapper <= 5 + self.assertIsInstance(q, Query) + result = q.evaluate() + expected = [True, True, True, True, True, + True, False, False, False, False] + expected = slice(0, 6) + self.assertEqual(result, expected) + + def test___eq__(self): + ''' + Test wrapper equals magic method + ''' + q = self.wrapper == 5 + self.assertIsInstance(q, Query) + result = q.evaluate() + expected = [False, False, False, False, False, + True, False, False, False, False] + expected = 5 + self.assertTrue(np.array_equal(result, expected)) + + def test___ne__(self): + ''' + Test wrapper not equal magic method + ''' + q = self.wrapper != 5 + self.assertIsInstance(q, Query) + result = q.evaluate() + expected = [True, True, True, True, True, + False, True, True, True, True] + expected = [slice(0, 5), slice(6, 10)] + self.assertTrue(np.array_equal(result, expected)) + + def test___getitem__(self): + ''' + Test wrapper getitem using slice + ''' + result = self.wrapper[0:5] + expected = [0, 1, 2, 3, 4] + self.assertTrue(np.array_equal(result, expected)) + + def test___getitem__query(self): + ''' + Test wrapper getitem using query + ''' + q = self.wrapper < 5 + result = self.wrapper[q] + expected = [0, 1, 2, 3, 4] + self.assertTrue(np.array_equal(result, expected)) + + +class SortedQueryTest(AbstractQueryMixin, TestCase): + + path = 'SortedQueryTest.h5' + + def getDataset(self): + self.f = File(self.path, 'w') + self.input = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + self.d = self.f.create_dataset('dset', data=self.input) + return SortedArray(self.d) + + def tearDown(self): + self.f.close() + if os.path.exists(self.path): + os.remove(self.path) + + +class LinspaceQueryTest(AbstractQueryMixin, TestCase): + + path = 'LinspaceQueryTest.h5' + + def getDataset(self): + return LinSpace(0, 10, 1) + + +class CompoundQueryTest(TestCase): + + def getM(self): + return SortedArray(np.arange(10, 20, 1)) + + def getN(self): + return SortedArray(np.arange(10.0, 20.0, 0.5)) + + def setUp(self): + self.m = HDMFDataset(self.getM()) + self.n = HDMFDataset(self.getN()) + + # TODO: test not completed + # def test_map(self): + # q = self.m == (12, 16) # IN operation + # q.evaluate() # [2,3,4,5] + # q.evaluate(False) # RangeResult(2,6) + # r = self.m[q] # noqa: F841 + # r = self.m[q.evaluate()] # noqa: F841 + # r = self.m[q.evaluate(False)] # noqa: F841 + + def tearDown(self): + pass diff --git a/tests/unit/utils_test/test_core_DataIO.py b/tests/unit/utils_test/test_core_DataIO.py index 80518a316..4c2ffac15 100644 --- a/tests/unit/utils_test/test_core_DataIO.py +++ b/tests/unit/utils_test/test_core_DataIO.py @@ -1,8 +1,10 @@ from copy import copy, deepcopy import numpy as np +from hdmf.container import Data from hdmf.data_utils import DataIO from hdmf.testing import TestCase +import warnings class DataIOTests(TestCase): @@ -28,13 +30,34 @@ def test_dataio_slice_delegation(self): dset = DataIO(indata) self.assertTrue(np.all(dset[1:3, 5:8] == indata[1:3, 5:8])) - def test_set_data_io_data_already_set(self): + def test_set_dataio(self): + """ + Test that Data.set_dataio works as intended + """ + dataio = DataIO() + data = np.arange(30).reshape(5, 2, 3) + container = Data('wrapped_data', data) + msg = "Data.set_dataio() is deprecated. Please use Data.set_data_io() instead." + with self.assertWarnsWith(DeprecationWarning, msg): + container.set_dataio(dataio) + self.assertIs(dataio.data, data) + self.assertIs(dataio, container.data) + + def test_set_dataio_data_already_set(self): """ Test that Data.set_dataio works as intended """ dataio = DataIO(data=np.arange(30).reshape(5, 2, 3)) + data = np.arange(30).reshape(5, 2, 3) + container = Data('wrapped_data', data) with self.assertRaisesWith(ValueError, "cannot overwrite 'data' on DataIO"): - dataio.data=[1,2,3,4] + with warnings.catch_warnings(record=True): + warnings.filterwarnings( + action='ignore', + category=DeprecationWarning, + message="Data.set_dataio() is deprecated. Please use Data.set_data_io() instead.", + ) + container.set_dataio(dataio) def test_dataio_options(self): """ diff --git a/tests/unit/utils_test/test_docval.py b/tests/unit/utils_test/test_docval.py index bed5cd134..c766dcf46 100644 --- a/tests/unit/utils_test/test_docval.py +++ b/tests/unit/utils_test/test_docval.py @@ -1,7 +1,7 @@ import numpy as np from hdmf.testing import TestCase -from hdmf.utils import (docval, get_docval, getargs, popargs, AllowPositional, get_docval_macro, - docval_macro, popargs_to_dict) +from hdmf.utils import (docval, fmt_docval_args, get_docval, getargs, popargs, AllowPositional, get_docval_macro, + docval_macro, popargs_to_dict, call_docval_func) class MyTestClass(object): @@ -137,6 +137,80 @@ def method1(self, **kwargs): with self.assertRaises(ValueError): method1(self, arg1=[[1, 1, 1]]) + fmt_docval_warning_msg = ( + "fmt_docval_args will be deprecated in a future version of HDMF. Instead of using fmt_docval_args, " + "call the function directly with the kwargs. Please note that fmt_docval_args " + "removes all arguments not accepted by the function's docval, so if you are passing kwargs that " + "includes extra arguments and the function's docval does not allow extra arguments (allow_extra=True " + "is set), then you will need to pop the extra arguments out of kwargs before calling the function." + ) + + def test_fmt_docval_args(self): + """ Test that fmt_docval_args parses the args and strips extra args """ + test_kwargs = { + 'arg1': 'a string', + 'arg2': 1, + 'arg3': True, + 'hello': 'abc', + 'list': ['abc', 1, 2, 3] + } + with self.assertWarnsWith(PendingDeprecationWarning, self.fmt_docval_warning_msg): + rec_args, rec_kwargs = fmt_docval_args(self.test_obj.basic_add2_kw, test_kwargs) + exp_args = ['a string', 1] + self.assertListEqual(rec_args, exp_args) + exp_kwargs = {'arg3': True} + self.assertDictEqual(rec_kwargs, exp_kwargs) + + def test_fmt_docval_args_no_docval(self): + """ Test that fmt_docval_args raises an error when run on function without docval """ + def method1(self, **kwargs): + pass + + with self.assertRaisesRegex(ValueError, r"no docval found on .*method1.*"): + with self.assertWarnsWith(PendingDeprecationWarning, self.fmt_docval_warning_msg): + fmt_docval_args(method1, {}) + + def test_fmt_docval_args_allow_extra(self): + """ Test that fmt_docval_args works """ + test_kwargs = { + 'arg1': 'a string', + 'arg2': 1, + 'arg3': True, + 'hello': 'abc', + 'list': ['abc', 1, 2, 3] + } + with self.assertWarnsWith(PendingDeprecationWarning, self.fmt_docval_warning_msg): + rec_args, rec_kwargs = fmt_docval_args(self.test_obj.basic_add2_kw_allow_extra, test_kwargs) + exp_args = ['a string', 1] + self.assertListEqual(rec_args, exp_args) + exp_kwargs = {'arg3': True, 'hello': 'abc', 'list': ['abc', 1, 2, 3]} + self.assertDictEqual(rec_kwargs, exp_kwargs) + + def test_call_docval_func(self): + """Test that call_docval_func strips extra args and calls the function.""" + test_kwargs = { + 'arg1': 'a string', + 'arg2': 1, + 'arg3': True, + 'hello': 'abc', + 'list': ['abc', 1, 2, 3] + } + msg = ( + "call_docval_func will be deprecated in a future version of HDMF. Instead of using call_docval_func, " + "call the function directly with the kwargs. Please note that call_docval_func " + "removes all arguments not accepted by the function's docval, so if you are passing kwargs that " + "includes extra arguments and the function's docval does not allow extra arguments (allow_extra=True " + "is set), then you will need to pop the extra arguments out of kwargs before calling the function." + ) + with self.assertWarnsWith(PendingDeprecationWarning, msg): + ret_kwargs = call_docval_func(self.test_obj.basic_add2_kw, test_kwargs) + exp_kwargs = { + 'arg1': 'a string', + 'arg2': 1, + 'arg3': True + } + self.assertDictEqual(ret_kwargs, exp_kwargs) + def test_docval_add(self): """Test that docval works with a single positional argument diff --git a/tox.ini b/tox.ini index 42305ae25..75b011aa0 100644 --- a/tox.ini +++ b/tox.ini @@ -42,17 +42,17 @@ commands = # list of pre-defined environments. (Technically environments not listed here # like build-py312 can also be used.) -[testenv:pytest-py313-upgraded] -[testenv:pytest-py313-prerelease] +[testenv:pytest-py312-upgraded] +[testenv:pytest-py312-prerelease] [testenv:pytest-py311-optional-pinned] # some optional reqs not compatible with py312 yet -[testenv:pytest-py{39,310,311,312,313}-pinned] -[testenv:pytest-py39-minimum] +[testenv:pytest-py{38,39,310,311,312}-pinned] +[testenv:pytest-py38-minimum] -[testenv:gallery-py313-upgraded] -[testenv:gallery-py313-prerelease] +[testenv:gallery-py312-upgraded] +[testenv:gallery-py312-prerelease] [testenv:gallery-py311-optional-pinned] -[testenv:gallery-py{39,310,311,312,313}-pinned] -[testenv:gallery-py39-minimum] +[testenv:gallery-py{38,39,310,311,312}-pinned] +[testenv:gallery-py38-minimum] [testenv:build] # using tox for this so that we can have a clean build environment [testenv:wheelinstall] # use with `--installpkg dist/*-none-any.whl` From 8ac00714e9500adb64c6fdb53214b9ef4b49b9a7 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Fri, 20 Dec 2024 12:04:50 -0800 Subject: [PATCH 75/82] Prepare release 3.14.6 (#1225) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 028e745c3..9a1dd6701 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # HDMF Changelog -## HDMF 3.14.6 (Upcoming) +## HDMF 3.14.6 (December 20, 2024) ### Enhancements - Added support for expandable datasets of references for untyped and compound data types. @stephprince [#1188](https://github.com/hdmf-dev/hdmf/pull/1188) From 63b862761660800ef3dbe2993f84ae251446f8f0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 14:10:26 -0800 Subject: [PATCH 76/82] [pre-commit.ci] pre-commit autoupdate (#1226) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f638f1416..340c50361 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.3 + rev: v0.8.4 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From a3c1998c56bdcb876124900c16d288bef422c4c0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 20:26:19 -0800 Subject: [PATCH 77/82] [pre-commit.ci] pre-commit autoupdate (#1228) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 340c50361..05fbd3e04 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.4 + rev: v0.8.6 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate From d63e5880265490dacd4362705fecd9ecbb2377ab Mon Sep 17 00:00:00 2001 From: Ben Dichter Date: Wed, 8 Jan 2025 22:22:27 -0500 Subject: [PATCH 78/82] Forbid slashes in names of Groups and Datasets (#1219) * * add checks to ensure that name and default name args do not contain slashes. * include tests * ruff * Update changelog --------- Co-authored-by: Ryan Ly --- CHANGELOG.md | 5 +++++ src/hdmf/spec/spec.py | 6 ++++++ tests/unit/spec_tests/test_dataset_spec.py | 14 ++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a1dd6701..4a38f0279 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # HDMF Changelog +## HDMF 4.0.0 (Upcoming) + +### Bug fixes +- Added checks to ensure that group and dataset spec names and default names do not contain slashes. @bendichter [#1219](https://github.com/hdmf-dev/hdmf/pull/1219) + ## HDMF 3.14.6 (December 20, 2024) ### Enhancements diff --git a/src/hdmf/spec/spec.py b/src/hdmf/spec/spec.py index e10d5e43e..9f02b262f 100644 --- a/src/hdmf/spec/spec.py +++ b/src/hdmf/spec/spec.py @@ -314,12 +314,18 @@ class BaseStorageSpec(Spec): def __init__(self, **kwargs): name, doc, quantity, attributes, linkable, data_type_def, data_type_inc = \ getargs('name', 'doc', 'quantity', 'attributes', 'linkable', 'data_type_def', 'data_type_inc', kwargs) + if name is not None and "/" in name: + raise ValueError(f"Name '{name}' is invalid. Names of Groups and Datasets cannot contain '/'") if name is None and data_type_def is None and data_type_inc is None: raise ValueError("Cannot create Group or Dataset spec with no name " "without specifying '%s' and/or '%s'." % (self.def_key(), self.inc_key())) super().__init__(doc, name=name) default_name = getargs('default_name', kwargs) if default_name: + if "/" in default_name: + raise ValueError( + f"Default name '{default_name}' is invalid. Names of Groups and Datasets cannot contain '/'" + ) if name is not None: warn("found 'default_name' with 'name' - ignoring 'default_name'") else: diff --git a/tests/unit/spec_tests/test_dataset_spec.py b/tests/unit/spec_tests/test_dataset_spec.py index c9db14635..60025fd7e 100644 --- a/tests/unit/spec_tests/test_dataset_spec.py +++ b/tests/unit/spec_tests/test_dataset_spec.py @@ -261,3 +261,17 @@ def test_build_warn_extra_args(self): "'dtype': 'int', 'required': True}") with self.assertWarnsWith(UserWarning, msg): DatasetSpec.build_spec(spec_dict) + + def test_constructor_validates_name(self): + with self.assertRaisesWith( + ValueError, + "Name 'one/two' is invalid. Names of Groups and Datasets cannot contain '/'", + ): + DatasetSpec(doc='my first dataset', dtype='int', name='one/two') + + def test_constructor_validates_default_name(self): + with self.assertRaisesWith( + ValueError, + "Default name 'one/two' is invalid. Names of Groups and Datasets cannot contain '/'", + ): + DatasetSpec(doc='my first dataset', dtype='int', default_name='one/two', data_type_def='test') From 85c9af816932e86d35ec08a08e258588e1bdec7d Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Thu, 9 Jan 2025 14:53:20 -0800 Subject: [PATCH 79/82] Revert "Temporarily revert breaking changes for bugfix release (#1224)" (#1227) --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/workflows/run_all_tests.yml | 57 +++-- .github/workflows/run_coverage.yml | 2 +- .github/workflows/run_tests.yml | 30 +-- .readthedocs.yaml | 4 +- CHANGELOG.md | 5 + docs/source/conf.py | 1 - docs/source/install_users.rst | 2 +- .../source/overview_software_architecture.rst | 2 +- environment-ros3.yml | 10 +- pyproject.toml | 24 +-- requirements-min.txt | 17 +- requirements-opt.txt | 6 +- requirements.txt | 10 +- src/hdmf/__init__.py | 26 +-- src/hdmf/array.py | 197 ------------------ src/hdmf/backends/hdf5/__init__.py | 2 +- src/hdmf/backends/hdf5/h5_utils.py | 78 +------ src/hdmf/backends/hdf5/h5tools.py | 124 +++-------- src/hdmf/build/__init__.py | 2 +- src/hdmf/build/builders.py | 21 +- src/hdmf/build/classgenerator.py | 4 +- src/hdmf/build/manager.py | 16 +- src/hdmf/build/map.py | 7 - src/hdmf/build/objectmapper.py | 54 ++--- src/hdmf/common/__init__.py | 6 +- src/hdmf/common/table.py | 4 +- src/hdmf/container.py | 49 ----- src/hdmf/query.py | 121 +---------- src/hdmf/region.py | 91 -------- src/hdmf/spec/spec.py | 10 +- src/hdmf/utils.py | 91 -------- src/hdmf/validate/validator.py | 5 +- tests/unit/build_tests/test_builder.py | 11 +- tests/unit/common/test_table.py | 4 +- tests/unit/spec_tests/test_ref_spec.py | 6 - tests/unit/test_container.py | 12 -- tests/unit/test_query.py | 161 -------------- tests/unit/utils_test/test_core_DataIO.py | 27 +-- tests/unit/utils_test/test_docval.py | 78 +------ tox.ini | 16 +- 41 files changed, 161 insertions(+), 1234 deletions(-) delete mode 100644 src/hdmf/array.py delete mode 100644 src/hdmf/build/map.py delete mode 100644 src/hdmf/region.py delete mode 100644 tests/unit/test_query.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index ef3daed9c..f1d04aa69 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -60,11 +60,11 @@ body: attributes: label: Python Version options: + - "3.13" - "3.12" - "3.11" - "3.10" - "3.9" - - "3.8" validations: required: true - type: textarea diff --git a/.github/workflows/run_all_tests.yml b/.github/workflows/run_all_tests.yml index b713f4763..961500194 100644 --- a/.github/workflows/run_all_tests.yml +++ b/.github/workflows/run_all_tests.yml @@ -25,30 +25,30 @@ jobs: fail-fast: false matrix: include: - - { name: linux-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } - - { name: linux-python3.9 , test-tox-env: pytest-py39-pinned , python-ver: "3.9" , os: ubuntu-latest } + - { name: linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - { name: linux-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: ubuntu-latest } - { name: linux-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: ubuntu-latest } - { name: linux-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: ubuntu-latest } - { name: linux-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: ubuntu-latest } - - { name: linux-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } - - { name: linux-python3.12-prerelease , test-tox-env: pytest-py312-prerelease , python-ver: "3.12", os: ubuntu-latest } - - { name: windows-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: windows-latest } - - { name: windows-python3.9 , test-tox-env: pytest-py39-pinned , python-ver: "3.9" , os: windows-latest } + - { name: linux-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: ubuntu-latest } + - { name: linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } + - { name: linux-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: ubuntu-latest } + - { name: windows-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: windows-latest } - { name: windows-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: windows-latest } - { name: windows-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: windows-latest } - { name: windows-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: windows-latest } - { name: windows-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: windows-latest } - - { name: windows-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: windows-latest } - - { name: windows-python3.12-prerelease , test-tox-env: pytest-py312-prerelease , python-ver: "3.12", os: windows-latest } - - { name: macos-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: macos-13 } - - { name: macos-python3.9 , test-tox-env: pytest-py39-pinned , python-ver: "3.9" , os: macos-13 } + - { name: windows-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: windows-latest } + - { name: windows-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: windows-latest } + - { name: windows-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: windows-latest } + - { name: macos-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: macos-13 } - { name: macos-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: macos-latest } - { name: macos-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: macos-latest } - { name: macos-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: macos-latest } - { name: macos-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: macos-latest } - - { name: macos-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: macos-latest } - - { name: macos-python3.12-prerelease , test-tox-env: pytest-py312-prerelease , python-ver: "3.12", os: macos-latest } + - { name: macos-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: macos-latest } + - { name: macos-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: macos-latest } + - { name: macos-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -97,18 +97,18 @@ jobs: fail-fast: false matrix: include: - - { name: linux-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } + - { name: linux-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - { name: linux-gallery-python3.11-optional , test-tox-env: gallery-py311-optional-pinned , python-ver: "3.11", os: ubuntu-latest } - - { name: linux-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } - - { name: linux-gallery-python3.12-prerelease , test-tox-env: gallery-py312-prerelease , python-ver: "3.12", os: ubuntu-latest } - - { name: windows-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: windows-latest } + - { name: linux-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } + - { name: linux-gallery-python3.13-prerelease , test-tox-env: gallery-py313-prerelease , python-ver: "3.13", os: ubuntu-latest } + - { name: windows-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: windows-latest } - { name: windows-gallery-python3.11-optional , test-tox-env: gallery-py311-optional-pinned , python-ver: "3.11", os: windows-latest } - - { name: windows-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: windows-latest } - - { name: windows-gallery-python3.12-prerelease, test-tox-env: gallery-py312-prerelease , python-ver: "3.12", os: windows-latest } - - { name: macos-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: macos-13 } + - { name: windows-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: windows-latest } + - { name: windows-gallery-python3.13-prerelease, test-tox-env: gallery-py313-prerelease , python-ver: "3.13", os: windows-latest } + - { name: macos-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: macos-13 } - { name: macos-gallery-python3.11-optional , test-tox-env: gallery-py311-optional-pinned , python-ver: "3.11", os: macos-latest } - - { name: macos-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: macos-latest } - - { name: macos-gallery-python3.12-prerelease , test-tox-env: gallery-py312-prerelease , python-ver: "3.12", os: macos-latest } + - { name: macos-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: macos-latest } + - { name: macos-gallery-python3.13-prerelease , test-tox-env: gallery-py313-prerelease , python-ver: "3.13", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -144,14 +144,13 @@ jobs: fail-fast: false matrix: include: - - { name: conda-linux-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } - - { name: conda-linux-python3.9 , test-tox-env: pytest-py39-pinned , python-ver: "3.9" , os: ubuntu-latest } + - { name: conda-linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - { name: conda-linux-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: ubuntu-latest } - { name: conda-linux-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: ubuntu-latest } - { name: conda-linux-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: ubuntu-latest } - - { name: conda-linux-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: ubuntu-latest } - - { name: conda-linux-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } - - { name: conda-linux-python3.12-prerelease , test-tox-env: pytest-py312-prerelease , python-ver: "3.12", os: ubuntu-latest } + - { name: conda-linux-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: ubuntu-latest } + - { name: conda-linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } + - { name: conda-linux-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: ubuntu-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -209,9 +208,9 @@ jobs: fail-fast: false matrix: include: - - { name: linux-python3.12-ros3 , python-ver: "3.12", os: ubuntu-latest } - - { name: windows-python3.12-ros3 , python-ver: "3.12", os: windows-latest } - - { name: macos-python3.12-ros3 , python-ver: "3.12", os: macos-latest } + - { name: linux-python3.13-ros3 , python-ver: "3.13", os: ubuntu-latest } + - { name: windows-python3.13-ros3 , python-ver: "3.13", os: windows-latest } + - { name: macos-python3.13-ros3 , python-ver: "3.13", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 diff --git a/.github/workflows/run_coverage.yml b/.github/workflows/run_coverage.yml index 5e6057dbe..bea8df734 100644 --- a/.github/workflows/run_coverage.yml +++ b/.github/workflows/run_coverage.yml @@ -84,7 +84,7 @@ jobs: fail-fast: false matrix: include: - - { name: linux-python3.12-ros3 , python-ver: "3.12", os: ubuntu-latest } + - { name: linux-python3.13-ros3 , python-ver: "3.13", os: ubuntu-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 2e94bcb62..a3fa5a84c 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -23,13 +23,13 @@ jobs: matrix: include: # NOTE config below with "upload-wheels: true" specifies that wheels should be uploaded as an artifact - - { name: linux-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } - - { name: linux-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: ubuntu-latest } - - { name: linux-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: ubuntu-latest , upload-wheels: true } - - { name: windows-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: windows-latest } - - { name: windows-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: windows-latest } - - { name: macos-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: macos-13 } - - { name: macos-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: macos-latest } + - { name: linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } + - { name: linux-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: ubuntu-latest } + - { name: linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest , upload-wheels: true } + - { name: windows-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: windows-latest } + - { name: windows-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: windows-latest } + - { name: macos-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: macos-13 } + - { name: macos-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -85,10 +85,10 @@ jobs: fail-fast: false matrix: include: - - { name: linux-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } - - { name: linux-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } - - { name: windows-gallery-python3.8-minimum , test-tox-env: gallery-py38-minimum , python-ver: "3.8" , os: windows-latest } - - { name: windows-gallery-python3.12-upgraded , test-tox-env: gallery-py312-upgraded , python-ver: "3.12", os: windows-latest } + - { name: linux-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } + - { name: linux-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } + - { name: windows-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: windows-latest } + - { name: windows-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: windows-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -124,8 +124,8 @@ jobs: fail-fast: false matrix: include: - - { name: conda-linux-python3.8-minimum , test-tox-env: pytest-py38-minimum , python-ver: "3.8" , os: ubuntu-latest } - - { name: conda-linux-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } + - { name: conda-linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } + - { name: conda-linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -188,7 +188,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - name: Download wheel and source distributions from artifact uses: actions/download-artifact@v4 @@ -221,7 +221,7 @@ jobs: fail-fast: false matrix: include: - - { name: linux-python3.12-ros3 , python-ver: "3.12", os: ubuntu-latest } + - { name: linux-python3.13-ros3 , python-ver: "3.13", os: ubuntu-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 diff --git a/.readthedocs.yaml b/.readthedocs.yaml index a4f1ea037..19bcaee80 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -6,9 +6,9 @@ version: 2 build: - os: ubuntu-20.04 + os: ubuntu-22.04 tools: - python: '3.9' + python: '3.12' # Build documentation in the docs/ directory with Sphinx sphinx: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a38f0279..ce3fb48a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## HDMF 4.0.0 (Upcoming) +### Breaking changes +- The following classes have been deprecated and removed: Array, AbstractSortedArray, SortedArray, LinSpace, Query, RegionSlicer, ListSlicer, H5RegionSlicer, DataRegion, RegionBuilder. The following methods have been deprecated and removed: fmt_docval_args, call_docval_func, get_container_cls, add_child, set_dataio (now refactored as set_data_io). We have also removed all early development for region references. @mavaylon1, @rly [#1998](https://github.com/hdmf-dev/hdmf/pull/1198), [#1212](https://github.com/hdmf-dev/hdmf/pull/1212) +- Importing from hdmf.build.map is no longer supported. Import from hdmf.build instead. @rly [#1221](https://github.com/hdmf-dev/hdmf/pull/1221) +- Python 3.8 has reached end of life. Drop support for Python 3.8 and add support for Python 3.13. @mavaylon1 [#1209](https://github.com/hdmf-dev/hdmf/pull/1209) + ### Bug fixes - Added checks to ensure that group and dataset spec names and default names do not contain slashes. @bendichter [#1219](https://github.com/hdmf-dev/hdmf/pull/1219) diff --git a/docs/source/conf.py b/docs/source/conf.py index c20869e12..d385630d2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -87,7 +87,6 @@ nitpicky = True nitpick_ignore = [('py:class', 'Intracomm'), - ('py:class', 'h5py.RegionReference'), ('py:class', 'h5py._hl.dataset.Dataset'), ('py:class', 'function'), ('py:class', 'unittest.case.TestCase'), diff --git a/docs/source/install_users.rst b/docs/source/install_users.rst index 49fbe07b2..f4d701c07 100644 --- a/docs/source/install_users.rst +++ b/docs/source/install_users.rst @@ -4,7 +4,7 @@ Installing HDMF --------------- -HDMF requires having Python 3.8, 3.9, 3.10, 3.11, or 3.12 installed. If you don't have Python installed and want the simplest way to +HDMF requires having Python 3.9-3.13 installed. If you don't have Python installed and want the simplest way to get started, we recommend you install and use the `Anaconda Distribution`_. It includes Python, NumPy, and many other commonly used packages for scientific computing and data science. diff --git a/docs/source/overview_software_architecture.rst b/docs/source/overview_software_architecture.rst index 973a01b2f..d63c953fe 100644 --- a/docs/source/overview_software_architecture.rst +++ b/docs/source/overview_software_architecture.rst @@ -68,7 +68,7 @@ Builder * :py:class:`~hdmf.build.builders.GroupBuilder` - represents a collection of objects * :py:class:`~hdmf.build.builders.DatasetBuilder` - represents data * :py:class:`~hdmf.build.builders.LinkBuilder` - represents soft-links - * :py:class:`~hdmf.build.builders.RegionBuilder` - represents a slice into data (Subclass of :py:class:`~hdmf.build.builders.DatasetBuilder`) + * :py:class:`~hdmf.build.builders.ReferenceBuilder` - represents a reference to another group or dataset * **Main Module:** :py:class:`hdmf.build.builders` diff --git a/environment-ros3.yml b/environment-ros3.yml index 34c37cc01..be2b9d4f6 100644 --- a/environment-ros3.yml +++ b/environment-ros3.yml @@ -4,11 +4,11 @@ channels: - conda-forge - defaults dependencies: - - python==3.12 - - h5py==3.11.0 - - matplotlib==3.8.4 - - numpy==2.0.0 - - pandas==2.2.2 + - python==3.13 + - h5py==3.12.1 + - matplotlib==3.9.2 + - numpy==2.1.3 + - pandas==2.2.3 - python-dateutil==2.8.2 - pytest==8.1.2 # regression introduced in pytest 8.2.*, will be fixed in 8.3.0 - pytest-cov==5.0.0 diff --git a/pyproject.toml b/pyproject.toml index 86e52a137..86b9e18e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,15 +13,15 @@ authors = [ ] description = "A hierarchical data modeling framework for modern science data standards" readme = "README.rst" -requires-python = ">=3.8" +requires-python = ">=3.9" license = {text = "BSD-3-Clause"} classifiers = [ "Programming Language :: Python", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "License :: OSI Approved :: BSD License", "Development Status :: 5 - Production/Stable", "Operating System :: OS Independent", @@ -30,22 +30,22 @@ classifiers = [ "Topic :: Scientific/Engineering :: Medical Science Apps.", ] dependencies = [ - "h5py>=2.10", - "jsonschema>=2.6.0", - 'numpy>=1.18', - "pandas>=1.0.5", + "h5py>=3.1.0", + "jsonschema>=3.2.0", + 'numpy>=1.19.3', + "pandas>=1.2.0", "ruamel.yaml>=0.16", - "scipy>=1.4", - "importlib-resources; python_version < '3.9'", # TODO: remove when minimum python version is 3.9 + "scipy>=1.7", ] dynamic = ["version"] [project.optional-dependencies] tqdm = ["tqdm>=4.41.0"] -termset = ["linkml-runtime>=1.5.5; python_version >= '3.9'", - "schemasheets>=0.1.23; python_version >= '3.9'", - "oaklib>=0.5.12; python_version >= '3.9'", - "pyyaml>=6.0.1; python_version >= '3.9'"] +zarr = ["zarr>=2.12.0"] +termset = ["linkml-runtime>=1.5.5", + "schemasheets>=0.1.23", + "oaklib>=0.5.12", + "pyyaml>=6.0.1"] [project.urls] "Homepage" = "https://github.com/hdmf-dev/hdmf" diff --git a/requirements-min.txt b/requirements-min.txt index a437fc588..a9fbeb93e 100644 --- a/requirements-min.txt +++ b/requirements-min.txt @@ -1,15 +1,10 @@ # minimum versions of package dependencies for installing HDMF -h5py==2.10 # support for selection of datasets with list of indices added in 2.10 -importlib-resources==5.12.0; python_version < "3.9" # TODO: remove when when minimum python version is 3.9 +# NOTE: these should match the minimum bound for dependencies in pyproject.toml +h5py==3.1.0 jsonschema==3.2.0 -numpy==1.18 -pandas==1.0.5 # when this is changed to >=1.5.0, see TODO items referenced in #762 -ruamel.yaml==0.16 -scipy==1.4 -# this file is currently used to test only python~=3.8 so these dependencies are not needed -# linkml-runtime==1.5.5; python_version >= "3.9" -# schemasheets==0.1.23; python_version >= "3.9" -# oaklib==0.5.12; python_version >= "3.9" -# pyyaml==6.0.1; python_version >= "3.9" +numpy==1.19.3 +pandas==1.2.0 +ruamel.yaml==0.16.0 +scipy==1.7.0 tqdm==4.41.0 zarr==2.12.0 diff --git a/requirements-opt.txt b/requirements-opt.txt index 4831d1949..8811ff46e 100644 --- a/requirements-opt.txt +++ b/requirements-opt.txt @@ -1,6 +1,6 @@ # pinned dependencies that are optional. used to reproduce an entire development environment to use HDMF tqdm==4.66.4 zarr==2.18.2 -linkml-runtime==1.7.7; python_version >= "3.9" -schemasheets==0.2.1; python_version >= "3.9" -oaklib==0.6.10; python_version >= "3.9" +linkml-runtime==1.7.7 +schemasheets==0.2.1 +oaklib==0.6.10 diff --git a/requirements.txt b/requirements.txt index 30a596ada..b431b4e63 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,8 @@ # pinned dependencies to reproduce an entire development environment to use HDMF -h5py==3.11.0 -importlib-resources==6.1.0; python_version < "3.9" # TODO: remove when minimum python version is 3.9 +h5py==3.12.1 jsonschema==4.22.0 -numpy==1.26.4 # TODO: numpy 2.0.0 is supported by hdmf but incompatible with pandas and scipy -pandas==2.2.2; python_version >= "3.9" -pandas==2.1.2; python_version < "3.8" # TODO: remove when minimum python version is 3.9 +numpy==2.1.3 ruamel.yaml==0.18.2 -scipy==1.14.0; python_version >= "3.10" +pandas==2.2.3 +scipy==1.14.1; python_version >= "3.10" scipy==1.11.3; python_version < "3.10" diff --git a/src/hdmf/__init__.py b/src/hdmf/__init__.py index 6fc72a117..10305d37b 100644 --- a/src/hdmf/__init__.py +++ b/src/hdmf/__init__.py @@ -1,32 +1,10 @@ from . import query -from .backends.hdf5.h5_utils import H5Dataset, H5RegionSlicer -from .container import Container, Data, DataRegion, HERDManager -from .region import ListSlicer +from .backends.hdf5.h5_utils import H5Dataset +from .container import Container, Data, HERDManager from .utils import docval, getargs from .term_set import TermSet, TermSetWrapper, TypeConfigurator -@docval( - {"name": "dataset", "type": None, "doc": "the HDF5 dataset to slice"}, - {"name": "region", "type": None, "doc": "the region reference to use to slice"}, - is_method=False, -) -def get_region_slicer(**kwargs): - import warnings # noqa: E402 - - warnings.warn( - "get_region_slicer is deprecated and will be removed in HDMF 3.0.", - DeprecationWarning, - ) - - dataset, region = getargs("dataset", "region", kwargs) - if isinstance(dataset, (list, tuple, Data)): - return ListSlicer(dataset, region) - elif isinstance(dataset, H5Dataset): - return H5RegionSlicer(dataset, region) - return None - - try: # see https://effigies.gitlab.io/posts/python-packaging-2023/ from ._version import __version__ diff --git a/src/hdmf/array.py b/src/hdmf/array.py deleted file mode 100644 index a684572e4..000000000 --- a/src/hdmf/array.py +++ /dev/null @@ -1,197 +0,0 @@ -from abc import abstractmethod, ABCMeta - -import numpy as np - - -class Array: - - def __init__(self, data): - self.__data = data - if hasattr(data, 'dtype'): - self.dtype = data.dtype - else: - tmp = data - while isinstance(tmp, (list, tuple)): - tmp = tmp[0] - self.dtype = type(tmp) - - @property - def data(self): - return self.__data - - def __len__(self): - return len(self.__data) - - def get_data(self): - return self.__data - - def __getidx__(self, arg): - return self.__data[arg] - - def __sliceiter(self, arg): - return (x for x in range(*arg.indices(len(self)))) - - def __getitem__(self, arg): - if isinstance(arg, list): - idx = list() - for i in arg: - if isinstance(i, slice): - idx.extend(x for x in self.__sliceiter(i)) - else: - idx.append(i) - return np.fromiter((self.__getidx__(x) for x in idx), dtype=self.dtype) - elif isinstance(arg, slice): - return np.fromiter((self.__getidx__(x) for x in self.__sliceiter(arg)), dtype=self.dtype) - elif isinstance(arg, tuple): - return (self.__getidx__(arg[0]), self.__getidx__(arg[1])) - else: - return self.__getidx__(arg) - - -class AbstractSortedArray(Array, metaclass=ABCMeta): - ''' - An abstract class for representing sorted array - ''' - - @abstractmethod - def find_point(self, val): - pass - - def get_data(self): - return self - - def __lower(self, other): - ins = self.find_point(other) - return ins - - def __upper(self, other): - ins = self.__lower(other) - while self[ins] == other: - ins += 1 - return ins - - def __lt__(self, other): - ins = self.__lower(other) - return slice(0, ins) - - def __le__(self, other): - ins = self.__upper(other) - return slice(0, ins) - - def __gt__(self, other): - ins = self.__upper(other) - return slice(ins, len(self)) - - def __ge__(self, other): - ins = self.__lower(other) - return slice(ins, len(self)) - - @staticmethod - def __sort(a): - if isinstance(a, tuple): - return a[0] - else: - return a - - def __eq__(self, other): - if isinstance(other, list): - ret = list() - for i in other: - eq = self == i - ret.append(eq) - ret = sorted(ret, key=self.__sort) - tmp = list() - for i in range(1, len(ret)): - a, b = ret[i - 1], ret[i] - if isinstance(a, tuple): - if isinstance(b, tuple): - if a[1] >= b[0]: - b[0] = a[0] - else: - tmp.append(slice(*a)) - else: - if b > a[1]: - tmp.append(slice(*a)) - elif b == a[1]: - a[1] == b + 1 - else: - ret[i] = a - else: - if isinstance(b, tuple): - if a < b[0]: - tmp.append(a) - else: - if b - a == 1: - ret[i] = (a, b) - else: - tmp.append(a) - if isinstance(ret[-1], tuple): - tmp.append(slice(*ret[-1])) - else: - tmp.append(ret[-1]) - ret = tmp - return ret - elif isinstance(other, tuple): - ge = self >= other[0] - ge = ge.start - lt = self < other[1] - lt = lt.stop - if ge == lt: - return ge - else: - return slice(ge, lt) - else: - lower = self.__lower(other) - upper = self.__upper(other) - d = upper - lower - if d == 1: - return lower - elif d == 0: - return None - else: - return slice(lower, upper) - - def __ne__(self, other): - eq = self == other - if isinstance(eq, tuple): - return [slice(0, eq[0]), slice(eq[1], len(self))] - else: - return [slice(0, eq), slice(eq + 1, len(self))] - - -class SortedArray(AbstractSortedArray): - ''' - A class for wrapping sorted arrays. This class overrides - <,>,<=,>=,==, and != to leverage the sorted content for - efficiency. - ''' - - def __init__(self, array): - super().__init__(array) - - def find_point(self, val): - return np.searchsorted(self.data, val) - - -class LinSpace(SortedArray): - - def __init__(self, start, stop, step): - self.start = start - self.stop = stop - self.step = step - self.dtype = float if any(isinstance(s, float) for s in (start, stop, step)) else int - self.__len = int((stop - start) / step) - - def __len__(self): - return self.__len - - def find_point(self, val): - nsteps = (val - self.start) / self.step - fl = int(nsteps) - if fl == nsteps: - return int(fl) - else: - return int(fl + 1) - - def __getidx__(self, arg): - return self.start + self.step * arg diff --git a/src/hdmf/backends/hdf5/__init__.py b/src/hdmf/backends/hdf5/__init__.py index 6abfc8c85..8f76d7bcc 100644 --- a/src/hdmf/backends/hdf5/__init__.py +++ b/src/hdmf/backends/hdf5/__init__.py @@ -1,3 +1,3 @@ from . import h5_utils, h5tools -from .h5_utils import H5RegionSlicer, H5DataIO +from .h5_utils import H5DataIO from .h5tools import HDF5IO, H5SpecWriter, H5SpecReader diff --git a/src/hdmf/backends/hdf5/h5_utils.py b/src/hdmf/backends/hdf5/h5_utils.py index 2d7187721..878ebf089 100644 --- a/src/hdmf/backends/hdf5/h5_utils.py +++ b/src/hdmf/backends/hdf5/h5_utils.py @@ -8,7 +8,7 @@ from collections.abc import Iterable from copy import copy -from h5py import Group, Dataset, RegionReference, Reference, special_dtype +from h5py import Group, Dataset, Reference, special_dtype from h5py import filters as h5py_filters import json import numpy as np @@ -16,10 +16,8 @@ import os import logging -from ...array import Array from ...data_utils import DataIO, AbstractDataChunkIterator, append_data from ...query import HDMFDataset, ReferenceResolver, ContainerResolver, BuilderResolver -from ...region import RegionSlicer from ...spec import SpecWriter, SpecReader from ...utils import docval, getargs, popargs, get_docval, get_data_shape @@ -85,7 +83,7 @@ def append(self, dataset, data): class H5Dataset(HDMFDataset): - @docval({'name': 'dataset', 'type': (Dataset, Array), 'doc': 'the HDF5 file lazily evaluate'}, + @docval({'name': 'dataset', 'type': Dataset, 'doc': 'the HDF5 file lazily evaluate'}, {'name': 'io', 'type': 'hdmf.backends.hdf5.h5tools.HDF5IO', 'doc': 'the IO object that was used to read the underlying dataset'}) def __init__(self, **kwargs): @@ -96,10 +94,6 @@ def __init__(self, **kwargs): def io(self): return self.__io - @property - def regionref(self): - return self.dataset.regionref - @property def ref(self): return self.dataset.ref @@ -189,7 +183,7 @@ def get_object(self, h5obj): class AbstractH5TableDataset(DatasetOfReferences): - @docval({'name': 'dataset', 'type': (Dataset, Array), 'doc': 'the HDF5 file lazily evaluate'}, + @docval({'name': 'dataset', 'type': Dataset, 'doc': 'the HDF5 file lazily evaluate'}, {'name': 'io', 'type': 'hdmf.backends.hdf5.h5tools.HDF5IO', 'doc': 'the IO object that was used to read the underlying dataset'}, {'name': 'types', 'type': (list, tuple), @@ -199,9 +193,7 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.__refgetters = dict() for i, t in enumerate(types): - if t is RegionReference: - self.__refgetters[i] = self.__get_regref - elif t is Reference: + if t is Reference: self.__refgetters[i] = self._get_ref elif t is str: # we need this for when we read compound data types @@ -223,8 +215,6 @@ def __init__(self, **kwargs): t = sub.metadata['ref'] if t is Reference: tmp.append('object') - elif t is RegionReference: - tmp.append('region') else: tmp.append(sub.type.__name__) self.__dtype = tmp @@ -257,10 +247,6 @@ def _get_utf(self, string): """ return string.decode('utf-8') if isinstance(string, bytes) else string - def __get_regref(self, ref): - obj = self._get_ref(ref) - return obj[ref] - def resolve(self, manager): return self[0:len(self)] @@ -283,18 +269,6 @@ def dtype(self): return 'object' -class AbstractH5RegionDataset(AbstractH5ReferenceDataset): - - def __getitem__(self, arg): - obj = super().__getitem__(arg) - ref = self.dataset[arg] - return obj[ref] - - @property - def dtype(self): - return 'region' - - class ContainerH5TableDataset(ContainerResolverMixin, AbstractH5TableDataset): """ A reference-resolving dataset for resolving references inside tables @@ -339,28 +313,6 @@ def get_inverse_class(cls): return ContainerH5ReferenceDataset -class ContainerH5RegionDataset(ContainerResolverMixin, AbstractH5RegionDataset): - """ - A reference-resolving dataset for resolving region references that returns - resolved references as Containers - """ - - @classmethod - def get_inverse_class(cls): - return BuilderH5RegionDataset - - -class BuilderH5RegionDataset(BuilderResolverMixin, AbstractH5RegionDataset): - """ - A reference-resolving dataset for resolving region references that returns - resolved references as Builders - """ - - @classmethod - def get_inverse_class(cls): - return ContainerH5RegionDataset - - class H5SpecWriter(SpecWriter): __str_type = special_dtype(vlen=str) @@ -420,28 +372,6 @@ def read_namespace(self, ns_path): return ret -class H5RegionSlicer(RegionSlicer): - - @docval({'name': 'dataset', 'type': (Dataset, H5Dataset), 'doc': 'the HDF5 dataset to slice'}, - {'name': 'region', 'type': RegionReference, 'doc': 'the region reference to use to slice'}) - def __init__(self, **kwargs): - self.__dataset = getargs('dataset', kwargs) - self.__regref = getargs('region', kwargs) - self.__len = self.__dataset.regionref.selection(self.__regref)[0] - self.__region = None - - def __read_region(self): - if self.__region is None: - self.__region = self.__dataset[self.__regref] - - def __getitem__(self, idx): - self.__read_region() - return self.__region[idx] - - def __len__(self): - return self.__len - - class H5DataIO(DataIO): """ Wrap data arrays for write via HDF5IO to customize I/O behavior, such as compression and chunking diff --git a/src/hdmf/backends/hdf5/h5tools.py b/src/hdmf/backends/hdf5/h5tools.py index 4fc9c258f..d30cef06c 100644 --- a/src/hdmf/backends/hdf5/h5tools.py +++ b/src/hdmf/backends/hdf5/h5tools.py @@ -7,14 +7,14 @@ import numpy as np import h5py -from h5py import File, Group, Dataset, special_dtype, SoftLink, ExternalLink, Reference, RegionReference, check_dtype +from h5py import File, Group, Dataset, special_dtype, SoftLink, ExternalLink, Reference, check_dtype -from .h5_utils import (BuilderH5ReferenceDataset, BuilderH5RegionDataset, BuilderH5TableDataset, H5DataIO, +from .h5_utils import (BuilderH5ReferenceDataset, BuilderH5TableDataset, H5DataIO, H5SpecReader, H5SpecWriter, HDF5IODataChunkIteratorQueue) from ..io import HDMFIO from ..errors import UnsupportedOperation from ..warnings import BrokenLinkWarning -from ...build import (Builder, GroupBuilder, DatasetBuilder, LinkBuilder, BuildManager, RegionBuilder, +from ...build import (Builder, GroupBuilder, DatasetBuilder, LinkBuilder, BuildManager, ReferenceBuilder, TypeMap, ObjectMapper) from ...container import Container from ...data_utils import AbstractDataChunkIterator @@ -28,7 +28,6 @@ H5_TEXT = special_dtype(vlen=str) H5_BINARY = special_dtype(vlen=bytes) H5_REF = special_dtype(ref=Reference) -H5_REGREF = special_dtype(ref=RegionReference) RDCC_NBYTES = 32*2**20 # set raw data chunk cache size = 32 MiB @@ -693,10 +692,7 @@ def __read_dataset(self, h5obj, name=None): target = h5obj.file[scalar] target_builder = self.__read_dataset(target) self.__set_built(target.file.filename, target.id, target_builder) - if isinstance(scalar, RegionReference): - d = RegionBuilder(scalar, target_builder) - else: - d = ReferenceBuilder(target_builder) + d = ReferenceBuilder(target_builder) kwargs['data'] = d kwargs['dtype'] = d.dtype elif h5obj.dtype.kind == 'V': # scalar compound data type @@ -713,9 +709,6 @@ def __read_dataset(self, h5obj, name=None): elem1 = h5obj[tuple([0] * (h5obj.ndim - 1) + [0])] if isinstance(elem1, (str, bytes)): d = self._check_str_dtype(h5obj) - elif isinstance(elem1, RegionReference): # read list of references - d = BuilderH5RegionDataset(h5obj, self) - kwargs['dtype'] = d.dtype elif isinstance(elem1, Reference): d = BuilderH5ReferenceDataset(h5obj, self) kwargs['dtype'] = d.dtype @@ -751,9 +744,7 @@ def __read_attrs(self, h5obj): for k, v in h5obj.attrs.items(): if k == SPEC_LOC_ATTR: # ignore cached spec continue - if isinstance(v, RegionReference): - raise ValueError("cannot read region reference attributes yet") - elif isinstance(v, Reference): + if isinstance(v, Reference): ret[k] = self.__read_ref(h5obj.file[v]) else: ret[k] = v @@ -917,10 +908,7 @@ def get_type(cls, data): "utf-8": H5_TEXT, "ascii": H5_BINARY, "bytes": H5_BINARY, - "ref": H5_REF, - "reference": H5_REF, "object": H5_REF, - "region": H5_REGREF, "isodatetime": H5_TEXT, "datetime": H5_TEXT, } @@ -1238,29 +1226,12 @@ def _filler(): dset = self.__scalar_fill__(parent, name, data, options) else: dset = self.__list_fill__(parent, name, data, options) - # Write a dataset containing references, i.e., a region or object reference. + # Write a dataset containing references, i.e., object reference. # NOTE: we can ignore options['io_settings'] for scalar data elif self.__is_ref(options['dtype']): _dtype = self.__dtypes.get(options['dtype']) - # Write a scalar data region reference dataset - if isinstance(data, RegionBuilder): - dset = parent.require_dataset(name, shape=(), dtype=_dtype) - self.__set_written(builder) - self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing a " - "region reference. attributes: %s" - % (name, list(attributes.keys()))) - - @self.__queue_ref - def _filler(): - self.logger.debug("Resolving region reference and setting attribute on dataset '%s' " - "containing attributes: %s" - % (name, list(attributes.keys()))) - ref = self.__get_ref(data.builder, data.region) - dset = parent[name] - dset[()] = ref - self.set_attributes(dset, attributes) # Write a scalar object reference dataset - elif isinstance(data, ReferenceBuilder): + if isinstance(data, ReferenceBuilder): dset = parent.require_dataset(name, dtype=_dtype, shape=()) self.__set_written(builder) self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing an " @@ -1278,44 +1249,24 @@ def _filler(): self.set_attributes(dset, attributes) # Write an array dataset of references else: - # Write a array of region references - if options['dtype'] == 'region': - dset = parent.require_dataset(name, dtype=_dtype, shape=(len(data),), **options['io_settings']) - self.__set_written(builder) - self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing " - "region references. attributes: %s" - % (name, list(attributes.keys()))) - - @self.__queue_ref - def _filler(): - self.logger.debug("Resolving region references and setting attribute on dataset '%s' " - "containing attributes: %s" - % (name, list(attributes.keys()))) - refs = list() - for item in data: - refs.append(self.__get_ref(item.builder, item.region)) - dset = parent[name] - dset[()] = refs - self.set_attributes(dset, attributes) # Write array of object references - else: - dset = parent.require_dataset(name, shape=(len(data),), dtype=_dtype, **options['io_settings']) - self.__set_written(builder) - self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing " - "object references. attributes: %s" - % (name, list(attributes.keys()))) + dset = parent.require_dataset(name, shape=(len(data),), dtype=_dtype, **options['io_settings']) + self.__set_written(builder) + self.logger.debug("Queueing reference resolution and set attribute on dataset '%s' containing " + "object references. attributes: %s" + % (name, list(attributes.keys()))) - @self.__queue_ref - def _filler(): - self.logger.debug("Resolving object references and setting attribute on dataset '%s' " - "containing attributes: %s" - % (name, list(attributes.keys()))) - refs = list() - for item in data: - refs.append(self.__get_ref(item)) - dset = parent[name] - dset[()] = refs - self.set_attributes(dset, attributes) + @self.__queue_ref + def _filler(): + self.logger.debug("Resolving object references and setting attribute on dataset '%s' " + "containing attributes: %s" + % (name, list(attributes.keys()))) + refs = list() + for item in data: + refs.append(self.__get_ref(item)) + dset = parent[name] + dset[()] = refs + self.set_attributes(dset, attributes) return # write a "regular" dataset else: @@ -1503,11 +1454,9 @@ def __list_fill__(cls, parent, name, data, options=None): @docval({'name': 'container', 'type': (Builder, Container, ReferenceBuilder), 'doc': 'the object to reference', 'default': None}, - {'name': 'region', 'type': (slice, list, tuple), 'doc': 'the region reference indexing object', - 'default': None}, returns='the reference', rtype=Reference) def __get_ref(self, **kwargs): - container, region = getargs('container', 'region', kwargs) + container = getargs('container', kwargs) if container is None: return None if isinstance(container, Builder): @@ -1525,20 +1474,10 @@ def __get_ref(self, **kwargs): path = self.__get_path(builder) self.logger.debug("Getting reference at path '%s'" % path) - if isinstance(container, RegionBuilder): - region = container.region - if region is not None: - dset = self.__file[path] - if not isinstance(dset, Dataset): - raise ValueError('cannot create region reference without Dataset') - return self.__file[path].regionref[region] - else: - return self.__file[path].ref + return self.__file[path].ref @docval({'name': 'container', 'type': (Builder, Container, ReferenceBuilder), 'doc': 'the object to reference', 'default': None}, - {'name': 'region', 'type': (slice, list, tuple), 'doc': 'the region reference indexing object', - 'default': None}, returns='the reference', rtype=Reference) def _create_ref(self, **kwargs): return self.__get_ref(**kwargs) @@ -1551,7 +1490,7 @@ def __is_ref(self, dtype): if isinstance(dtype, dict): # may be dict from reading a compound dataset return self.__is_ref(dtype['dtype']) if isinstance(dtype, str): - return dtype == DatasetBuilder.OBJECT_REF_TYPE or dtype == DatasetBuilder.REGION_REF_TYPE + return dtype == DatasetBuilder.OBJECT_REF_TYPE return False def __queue_ref(self, func): @@ -1570,17 +1509,6 @@ def __queue_ref(self, func): # dependency self.__ref_queue.append(func) - def __rec_get_ref(self, ref_list): - ret = list() - for elem in ref_list: - if isinstance(elem, (list, tuple)): - ret.append(self.__rec_get_ref(elem)) - elif isinstance(elem, (Builder, Container)): - ret.append(self.__get_ref(elem)) - else: - ret.append(elem) - return ret - @property def mode(self): """ diff --git a/src/hdmf/build/__init__.py b/src/hdmf/build/__init__.py index ea5d21152..87e0ac57e 100644 --- a/src/hdmf/build/__init__.py +++ b/src/hdmf/build/__init__.py @@ -1,4 +1,4 @@ -from .builders import Builder, DatasetBuilder, GroupBuilder, LinkBuilder, ReferenceBuilder, RegionBuilder +from .builders import Builder, DatasetBuilder, GroupBuilder, LinkBuilder, ReferenceBuilder from .classgenerator import CustomClassGenerator, MCIClassGenerator from .errors import (BuildError, OrphanContainerBuildError, ReferenceTargetNotBuiltError, ContainerConfigurationError, ConstructError) diff --git a/src/hdmf/build/builders.py b/src/hdmf/build/builders.py index cb658b6d4..2d90c24e3 100644 --- a/src/hdmf/build/builders.py +++ b/src/hdmf/build/builders.py @@ -6,7 +6,6 @@ from datetime import datetime, date import numpy as np -from h5py import RegionReference from ..utils import docval, getargs, get_docval @@ -320,11 +319,10 @@ def values(self): class DatasetBuilder(BaseBuilder): OBJECT_REF_TYPE = 'object' - REGION_REF_TYPE = 'region' @docval({'name': 'name', 'type': str, 'doc': 'The name of the dataset.'}, {'name': 'data', - 'type': ('array_data', 'scalar_data', 'data', 'DatasetBuilder', 'RegionBuilder', Iterable, datetime, date), + 'type': ('array_data', 'scalar_data', 'data', 'DatasetBuilder', Iterable, datetime, date), 'doc': 'The data in this dataset.', 'default': None}, {'name': 'dtype', 'type': (type, np.dtype, str, list), 'doc': 'The datatype of this dataset.', 'default': None}, @@ -429,20 +427,3 @@ def __init__(self, **kwargs): def builder(self): """The target builder object.""" return self['builder'] - - -class RegionBuilder(ReferenceBuilder): - - @docval({'name': 'region', 'type': (slice, tuple, list, RegionReference), - 'doc': 'The region, i.e. slice or indices, into the target dataset.'}, - {'name': 'builder', 'type': DatasetBuilder, 'doc': 'The dataset this region reference applies to.'}) - def __init__(self, **kwargs): - """Create a builder object for a region reference.""" - region, builder = getargs('region', 'builder', kwargs) - super().__init__(builder) - self['region'] = region - - @property - def region(self): - """The selected region of the target dataset.""" - return self['region'] diff --git a/src/hdmf/build/classgenerator.py b/src/hdmf/build/classgenerator.py index a3336b98e..3b7d7c96e 100644 --- a/src/hdmf/build/classgenerator.py +++ b/src/hdmf/build/classgenerator.py @@ -4,7 +4,7 @@ import numpy as np -from ..container import Container, Data, DataRegion, MultiContainerInterface +from ..container import Container, Data, MultiContainerInterface from ..spec import AttributeSpec, LinkSpec, RefSpec, GroupSpec from ..spec.spec import BaseStorageSpec, ZERO_OR_MANY, ONE_OR_MANY from ..utils import docval, getargs, ExtenderMeta, get_docval, popargs, AllowPositional @@ -195,7 +195,7 @@ def _ischild(cls, dtype): if isinstance(dtype, tuple): for sub in dtype: ret = ret or cls._ischild(sub) - elif isinstance(dtype, type) and issubclass(dtype, (Container, Data, DataRegion)): + elif isinstance(dtype, type) and issubclass(dtype, (Container, Data)): ret = True return ret diff --git a/src/hdmf/build/manager.py b/src/hdmf/build/manager.py index 967c34010..bc586013c 100644 --- a/src/hdmf/build/manager.py +++ b/src/hdmf/build/manager.py @@ -490,20 +490,6 @@ def load_namespaces(self, **kwargs): self.register_container_type(new_ns, dt, container_cls) return deps - @docval({"name": "namespace", "type": str, "doc": "the namespace containing the data_type"}, - {"name": "data_type", "type": str, "doc": "the data type to create a AbstractContainer class for"}, - {"name": "autogen", "type": bool, "doc": "autogenerate class if one does not exist", "default": True}, - returns='the class for the given namespace and data_type', rtype=type) - def get_container_cls(self, **kwargs): - """Get the container class from data type specification. - If no class has been associated with the ``data_type`` from ``namespace``, a class will be dynamically - created and returned. - """ - # NOTE: this internally used function get_container_cls will be removed in favor of get_dt_container_cls - # Deprecated: Will be removed by HDMF 4.0 - namespace, data_type, autogen = getargs('namespace', 'data_type', 'autogen', kwargs) - return self.get_dt_container_cls(data_type, namespace, autogen) - @docval({"name": "data_type", "type": str, "doc": "the data type to create a AbstractContainer class for"}, {"name": "namespace", "type": str, "doc": "the namespace containing the data_type", "default": None}, {'name': 'post_init_method', 'type': Callable, 'default': None, @@ -515,7 +501,7 @@ def get_dt_container_cls(self, **kwargs): If no class has been associated with the ``data_type`` from ``namespace``, a class will be dynamically created and returned. - Replaces get_container_cls but namespace is optional. If namespace is unknown, it will be looked up from + Namespace is optional. If namespace is unknown, it will be looked up from all namespaces. """ namespace, data_type, post_init_method, autogen = getargs('namespace', 'data_type', diff --git a/src/hdmf/build/map.py b/src/hdmf/build/map.py deleted file mode 100644 index 5267609f5..000000000 --- a/src/hdmf/build/map.py +++ /dev/null @@ -1,7 +0,0 @@ -# this prevents breaking of code that imports these classes directly from map.py -from .manager import Proxy, BuildManager, TypeSource, TypeMap # noqa: F401 -from .objectmapper import ObjectMapper # noqa: F401 - -import warnings -warnings.warn('Classes in map.py should be imported from hdmf.build. Importing from hdmf.build.map will be removed ' - 'in HDMF 3.0.', DeprecationWarning, stacklevel=2) diff --git a/src/hdmf/build/objectmapper.py b/src/hdmf/build/objectmapper.py index 3394ebb91..176de322c 100644 --- a/src/hdmf/build/objectmapper.py +++ b/src/hdmf/build/objectmapper.py @@ -6,7 +6,7 @@ import numpy as np -from .builders import DatasetBuilder, GroupBuilder, LinkBuilder, Builder, ReferenceBuilder, RegionBuilder, BaseBuilder +from .builders import DatasetBuilder, GroupBuilder, LinkBuilder, Builder, ReferenceBuilder, BaseBuilder from .errors import (BuildError, OrphanContainerBuildError, ReferenceTargetNotBuiltError, ContainerConfigurationError, ConstructError) from .manager import Proxy, BuildManager @@ -15,7 +15,7 @@ IncorrectDatasetShapeBuildWarning) from hdmf.backends.hdf5.h5_utils import H5DataIO -from ..container import AbstractContainer, Data, DataRegion +from ..container import AbstractContainer, Data from ..term_set import TermSetWrapper from ..data_utils import DataIO, AbstractDataChunkIterator from ..query import ReferenceResolver @@ -966,41 +966,23 @@ def _filler(): return _filler def __get_ref_builder(self, builder, dtype, shape, container, build_manager): - bldr_data = None - if dtype.is_region(): - if shape is None: - if not isinstance(container, DataRegion): - msg = "'container' must be of type DataRegion if spec represents region reference" - raise ValueError(msg) - self.logger.debug("Setting %s '%s' data to region reference builder" - % (builder.__class__.__name__, builder.name)) - target_builder = self.__get_target_builder(container.data, build_manager, builder) - bldr_data = RegionBuilder(container.region, target_builder) - else: - self.logger.debug("Setting %s '%s' data to list of region reference builders" - % (builder.__class__.__name__, builder.name)) - bldr_data = list() - for d in container.data: - target_builder = self.__get_target_builder(d.target, build_manager, builder) - bldr_data.append(RegionBuilder(d.slice, target_builder)) + self.logger.debug("Setting object reference dataset on %s '%s' data" + % (builder.__class__.__name__, builder.name)) + if isinstance(container, Data): + self.logger.debug("Setting %s '%s' data to list of reference builders" + % (builder.__class__.__name__, builder.name)) + bldr_data = list() + for d in container.data: + target_builder = self.__get_target_builder(d, build_manager, builder) + bldr_data.append(ReferenceBuilder(target_builder)) + if isinstance(container.data, H5DataIO): + # This is here to support appending a dataset of references. + bldr_data = H5DataIO(bldr_data, **container.data.get_io_params()) else: - self.logger.debug("Setting object reference dataset on %s '%s' data" + self.logger.debug("Setting %s '%s' data to reference builder" % (builder.__class__.__name__, builder.name)) - if isinstance(container, Data): - self.logger.debug("Setting %s '%s' data to list of reference builders" - % (builder.__class__.__name__, builder.name)) - bldr_data = list() - for d in container.data: - target_builder = self.__get_target_builder(d, build_manager, builder) - bldr_data.append(ReferenceBuilder(target_builder)) - if isinstance(container.data, H5DataIO): - # This is here to support appending a dataset of references. - bldr_data = H5DataIO(bldr_data, **container.data.get_io_params()) - else: - self.logger.debug("Setting %s '%s' data to reference builder" - % (builder.__class__.__name__, builder.name)) - target_builder = self.__get_target_builder(container, build_manager, builder) - bldr_data = ReferenceBuilder(target_builder) + target_builder = self.__get_target_builder(container, build_manager, builder) + bldr_data = ReferenceBuilder(target_builder) return bldr_data def __get_target_builder(self, container, build_manager, builder): @@ -1232,8 +1214,6 @@ def __get_subspec_values(self, builder, spec, manager): continue if isinstance(attr_val, (GroupBuilder, DatasetBuilder)): ret[attr_spec] = manager.construct(attr_val) - elif isinstance(attr_val, RegionBuilder): # pragma: no cover - raise ValueError("RegionReferences as attributes is not yet supported") elif isinstance(attr_val, ReferenceBuilder): ret[attr_spec] = manager.construct(attr_val.builder) else: diff --git a/src/hdmf/common/__init__.py b/src/hdmf/common/__init__.py index 5c9d9a3b7..6b36e29cd 100644 --- a/src/hdmf/common/__init__.py +++ b/src/hdmf/common/__init__.py @@ -108,11 +108,7 @@ def _dec(cls): def __get_resources(): - try: - from importlib.resources import files - except ImportError: - # TODO: Remove when python 3.9 becomes the new minimum - from importlib_resources import files + from importlib.resources import files __location_of_this_file = files(__name__) __core_ns_file_name = 'namespace.yaml' diff --git a/src/hdmf/common/table.py b/src/hdmf/common/table.py index b4530c7b7..84ac4da3b 100644 --- a/src/hdmf/common/table.py +++ b/src/hdmf/common/table.py @@ -775,8 +775,8 @@ def add_column(self, **kwargs): # noqa: C901 index, table, enum, col_cls, check_ragged = popargs('index', 'table', 'enum', 'col_cls', 'check_ragged', kwargs) if isinstance(index, VectorIndex): - warn("Passing a VectorIndex in for index may lead to unexpected behavior. This functionality will be " - "deprecated in a future version of HDMF.", category=FutureWarning, stacklevel=3) + msg = "Passing a VectorIndex may lead to unexpected behavior. This functionality is not supported." + raise ValueError(msg) if name in self.__colids: # column has already been added msg = "column '%s' already exists in %s '%s'" % (name, self.__class__.__name__, self.name) diff --git a/src/hdmf/container.py b/src/hdmf/container.py index f2dba6e8d..ce4e8b821 100644 --- a/src/hdmf/container.py +++ b/src/hdmf/container.py @@ -1,5 +1,4 @@ import types -from abc import abstractmethod from collections import OrderedDict from copy import deepcopy from typing import Type, Optional @@ -467,21 +466,6 @@ def set_modified(self, **kwargs): def children(self): return tuple(self.__children) - @docval({'name': 'child', 'type': 'Container', - 'doc': 'the child Container for this Container', 'default': None}) - def add_child(self, **kwargs): - warn(DeprecationWarning('add_child is deprecated. Set the parent attribute instead.')) - child = getargs('child', kwargs) - if child is not None: - # if child.parent is a Container, then the mismatch between child.parent and parent - # is used to make a soft/external link from the parent to a child elsewhere - # if child.parent is not a Container, it is either None or a Proxy and should be set to self - if not isinstance(child.parent, AbstractContainer): - # actually add the child to the parent in parent setter - child.parent = self - else: - warn('Cannot add None as child to a container %s' % self.name) - @classmethod def type_hierarchy(cls): return cls.__mro__ @@ -931,20 +915,6 @@ def shape(self): """ return get_data_shape(self.__data) - @docval({'name': 'dataio', 'type': DataIO, 'doc': 'the DataIO to apply to the data held by this Data'}) - def set_dataio(self, **kwargs): - """ - Apply DataIO object to the data held by this Data object - """ - warn( - "Data.set_dataio() is deprecated. Please use Data.set_data_io() instead.", - DeprecationWarning, - stacklevel=3, - ) - dataio = getargs('dataio', kwargs) - dataio.data = self.__data - self.__data = dataio - def set_data_io( self, data_io_class: Type[DataIO], @@ -1040,25 +1010,6 @@ def _validate_new_data_element(self, arg): pass -class DataRegion(Data): - - @property - @abstractmethod - def data(self): - ''' - The target data that this region applies to - ''' - pass - - @property - @abstractmethod - def region(self): - ''' - The region that indexes into data e.g. slice or list of indices - ''' - pass - - class MultiContainerInterface(Container): """Class that dynamically defines methods to support a Container holding multiple Containers of the same type. diff --git a/src/hdmf/query.py b/src/hdmf/query.py index 9693b0b1c..abe2a93a7 100644 --- a/src/hdmf/query.py +++ b/src/hdmf/query.py @@ -2,143 +2,24 @@ import numpy as np -from .array import Array from .utils import ExtenderMeta, docval_macro, docval, getargs -class Query(metaclass=ExtenderMeta): - __operations__ = ( - '__lt__', - '__gt__', - '__le__', - '__ge__', - '__eq__', - '__ne__', - ) - - @classmethod - def __build_operation(cls, op): - def __func(self, arg): - return cls(self, op, arg) - - @ExtenderMeta.pre_init - def __make_operators(cls, name, bases, classdict): - if not isinstance(cls.__operations__, tuple): - raise TypeError("'__operations__' must be of type tuple") - # add any new operations - if len(bases) and 'Query' in globals() and issubclass(bases[-1], Query) \ - and bases[-1].__operations__ is not cls.__operations__: - new_operations = list(cls.__operations__) - new_operations[0:0] = bases[-1].__operations__ - cls.__operations__ = tuple(new_operations) - for op in cls.__operations__: - if not hasattr(cls, op): - setattr(cls, op, cls.__build_operation(op)) - - def __init__(self, obj, op, arg): - self.obj = obj - self.op = op - self.arg = arg - self.collapsed = None - self.expanded = None - - @docval({'name': 'expand', 'type': bool, 'help': 'whether or not to expand result', 'default': True}) - def evaluate(self, **kwargs): - expand = getargs('expand', kwargs) - if expand: - if self.expanded is None: - self.expanded = self.__evalhelper() - return self.expanded - else: - if self.collapsed is None: - self.collapsed = self.__collapse(self.__evalhelper()) - return self.collapsed - - def __evalhelper(self): - obj = self.obj - arg = self.arg - if isinstance(obj, Query): - obj = obj.evaluate() - elif isinstance(obj, HDMFDataset): - obj = obj.dataset - if isinstance(arg, Query): - arg = self.arg.evaluate() - return getattr(obj, self.op)(self.arg) - - def __collapse(self, result): - if isinstance(result, slice): - return (result.start, result.stop) - elif isinstance(result, list): - ret = list() - for idx in result: - if isinstance(idx, slice) and (idx.step is None or idx.step == 1): - ret.append((idx.start, idx.stop)) - else: - ret.append(idx) - return ret - else: - return result - - def __and__(self, other): - return NotImplemented - - def __or__(self, other): - return NotImplemented - - def __xor__(self, other): - return NotImplemented - - def __contains__(self, other): - return NotImplemented - - @docval_macro('array_data') class HDMFDataset(metaclass=ExtenderMeta): - __operations__ = ( - '__lt__', - '__gt__', - '__le__', - '__ge__', - '__eq__', - '__ne__', - ) - - @classmethod - def __build_operation(cls, op): - def __func(self, arg): - return Query(self, op, arg) - - setattr(__func, '__name__', op) - return __func - - @ExtenderMeta.pre_init - def __make_operators(cls, name, bases, classdict): - if not isinstance(cls.__operations__, tuple): - raise TypeError("'__operations__' must be of type tuple") - # add any new operations - if len(bases) and 'Query' in globals() and issubclass(bases[-1], Query) \ - and bases[-1].__operations__ is not cls.__operations__: - new_operations = list(cls.__operations__) - new_operations[0:0] = bases[-1].__operations__ - cls.__operations__ = tuple(new_operations) - for op in cls.__operations__: - setattr(cls, op, cls.__build_operation(op)) - def __evaluate_key(self, key): if isinstance(key, tuple) and len(key) == 0: return key if isinstance(key, (tuple, list, np.ndarray)): return list(map(self.__evaluate_key, key)) else: - if isinstance(key, Query): - return key.evaluate() return key def __getitem__(self, key): idx = self.__evaluate_key(key) return self.dataset[idx] - @docval({'name': 'dataset', 'type': ('array_data', Array), 'doc': 'the HDF5 file lazily evaluate'}) + @docval({'name': 'dataset', 'type': 'array_data', 'doc': 'the HDF5 file lazily evaluate'}) def __init__(self, **kwargs): super().__init__() self.__dataset = getargs('dataset', kwargs) diff --git a/src/hdmf/region.py b/src/hdmf/region.py deleted file mode 100644 index 9feeba401..000000000 --- a/src/hdmf/region.py +++ /dev/null @@ -1,91 +0,0 @@ -from abc import ABCMeta, abstractmethod -from operator import itemgetter - -from .container import Data, DataRegion -from .utils import docval, getargs - - -class RegionSlicer(DataRegion, metaclass=ABCMeta): - ''' - A abstract base class to control getting using a region - - Subclasses must implement `__getitem__` and `__len__` - ''' - - @docval({'name': 'target', 'type': None, 'doc': 'the target to slice'}, - {'name': 'slice', 'type': None, 'doc': 'the region to slice'}) - def __init__(self, **kwargs): - self.__target = getargs('target', kwargs) - self.__slice = getargs('slice', kwargs) - - @property - def data(self): - """The target data. Same as self.target""" - return self.target - - @property - def region(self): - """The selected region. Same as self.slice""" - return self.slice - - @property - def target(self): - """The target data""" - return self.__target - - @property - def slice(self): - """The selected slice""" - return self.__slice - - @property - @abstractmethod - def __getitem__(self, idx): - """Must be implemented by subclasses""" - pass - - @property - @abstractmethod - def __len__(self): - """Must be implemented by subclasses""" - pass - - -class ListSlicer(RegionSlicer): - """Implementation of RegionSlicer for slicing Lists and Data""" - - @docval({'name': 'dataset', 'type': (list, tuple, Data), 'doc': 'the dataset to slice'}, - {'name': 'region', 'type': (list, tuple, slice), 'doc': 'the region reference to use to slice'}) - def __init__(self, **kwargs): - self.__dataset, self.__region = getargs('dataset', 'region', kwargs) - super().__init__(self.__dataset, self.__region) - if isinstance(self.__region, slice): - self.__getter = itemgetter(self.__region) - self.__len = len(range(*self.__region.indices(len(self.__dataset)))) - else: - self.__getter = itemgetter(*self.__region) - self.__len = len(self.__region) - - def __read_region(self): - """ - Internal helper function used to define self._read - """ - if not hasattr(self, '_read'): - self._read = self.__getter(self.__dataset) - del self.__getter - - def __getitem__(self, idx): - """ - Get data values from selected data - """ - self.__read_region() - getter = None - if isinstance(idx, (list, tuple)): - getter = itemgetter(*idx) - else: - getter = itemgetter(idx) - return getter(self._read) - - def __len__(self): - """Number of values in the slice/region""" - return self.__len diff --git a/src/hdmf/spec/spec.py b/src/hdmf/spec/spec.py index 9f02b262f..bbd97b592 100644 --- a/src/hdmf/spec/spec.py +++ b/src/hdmf/spec/spec.py @@ -38,7 +38,6 @@ class DtypeHelper: 'uint32': ["uint32", "uint"], 'uint64': ["uint64"], 'object': ['object'], - 'region': ['region'], 'numeric': ['numeric'], 'isodatetime': ["isodatetime", "datetime", "date"] } @@ -174,12 +173,13 @@ def path(self): _ref_args = [ {'name': _target_type_key, 'type': str, 'doc': 'the target type GroupSpec or DatasetSpec'}, - {'name': 'reftype', 'type': str, 'doc': 'the type of references this is i.e. region or object'}, + {'name': 'reftype', 'type': str, + 'doc': 'the type of reference this is. only "object" is supported currently.'}, ] class RefSpec(ConstructableDict): - __allowable_types = ('object', 'region') + __allowable_types = ('object', ) @docval(*_ref_args) def __init__(self, **kwargs): @@ -200,10 +200,6 @@ def reftype(self): '''The type of reference''' return self['reftype'] - @docval(rtype=bool, returns='True if this RefSpec specifies a region reference, False otherwise') - def is_region(self): - return self['reftype'] == 'region' - _attr_args = [ {'name': 'name', 'type': str, 'doc': 'The name of this attribute'}, diff --git a/src/hdmf/utils.py b/src/hdmf/utils.py index d05b52d93..c21382a2a 100644 --- a/src/hdmf/utils.py +++ b/src/hdmf/utils.py @@ -382,8 +382,6 @@ def __parse_args(validator, args, kwargs, enforce_type=True, enforce_shape=True, for key in extras.keys(): type_errors.append("unrecognized argument: '%s'" % key) else: - # TODO: Extras get stripped out if function arguments are composed with fmt_docval_args. - # allow_extra needs to be tracked on a function so that fmt_docval_args doesn't strip them out for key in extras.keys(): ret[key] = extras[key] return {'args': ret, 'future_warnings': future_warnings, 'type_errors': type_errors, 'value_errors': value_errors, @@ -414,95 +412,6 @@ def get_docval(func, *args): return tuple() -# def docval_wrap(func, is_method=True): -# if is_method: -# @docval(*get_docval(func)) -# def method(self, **kwargs): -# -# return call_docval_args(func, kwargs) -# return method -# else: -# @docval(*get_docval(func)) -# def static_method(**kwargs): -# return call_docval_args(func, kwargs) -# return method - - -def fmt_docval_args(func, kwargs): - ''' Separate positional and keyword arguments - - Useful for methods that wrap other methods - ''' - warnings.warn("fmt_docval_args will be deprecated in a future version of HDMF. Instead of using fmt_docval_args, " - "call the function directly with the kwargs. Please note that fmt_docval_args " - "removes all arguments not accepted by the function's docval, so if you are passing kwargs that " - "includes extra arguments and the function's docval does not allow extra arguments (allow_extra=True " - "is set), then you will need to pop the extra arguments out of kwargs before calling the function.", - PendingDeprecationWarning, stacklevel=2) - func_docval = getattr(func, docval_attr_name, None) - ret_args = list() - ret_kwargs = dict() - kwargs_copy = _copy.copy(kwargs) - if func_docval: - for arg in func_docval[__docval_args_loc]: - val = kwargs_copy.pop(arg['name'], None) - if 'default' in arg: - if val is not None: - ret_kwargs[arg['name']] = val - else: - ret_args.append(val) - if func_docval['allow_extra']: - ret_kwargs.update(kwargs_copy) - else: - raise ValueError('no docval found on %s' % str(func)) - return ret_args, ret_kwargs - - -# def _remove_extra_args(func, kwargs): -# """Return a dict of only the keyword arguments that are accepted by the function's docval. -# -# If the docval specifies allow_extra=True, then the original kwargs are returned. -# """ -# # NOTE: this has the same functionality as the to-be-deprecated fmt_docval_args except that -# # kwargs are kept as kwargs instead of parsed into args and kwargs -# func_docval = getattr(func, docval_attr_name, None) -# if func_docval: -# if func_docval['allow_extra']: -# # if extra args are allowed, return all args -# return kwargs -# else: -# # save only the arguments listed in the function's docval (skip any others present in kwargs) -# ret_kwargs = dict() -# for arg in func_docval[__docval_args_loc]: -# val = kwargs.get(arg['name'], None) -# if val is not None: # do not return arguments that are not present or have value None -# ret_kwargs[arg['name']] = val -# return ret_kwargs -# else: -# raise ValueError('No docval found on %s' % str(func)) - - -def call_docval_func(func, kwargs): - """Call the function with only the keyword arguments that are accepted by the function's docval. - - Extra keyword arguments are not passed to the function unless the function's docval has allow_extra=True. - """ - warnings.warn("call_docval_func will be deprecated in a future version of HDMF. Instead of using call_docval_func, " - "call the function directly with the kwargs. Please note that call_docval_func " - "removes all arguments not accepted by the function's docval, so if you are passing kwargs that " - "includes extra arguments and the function's docval does not allow extra arguments (allow_extra=True " - "is set), then you will need to pop the extra arguments out of kwargs before calling the function.", - PendingDeprecationWarning, stacklevel=2) - with warnings.catch_warnings(record=True): - # catch and ignore only PendingDeprecationWarnings from fmt_docval_args so that two - # PendingDeprecationWarnings saying the same thing are not raised - warnings.simplefilter("ignore", UserWarning) - warnings.simplefilter("always", PendingDeprecationWarning) - fargs, fkwargs = fmt_docval_args(func, kwargs) - - return func(*fargs, **fkwargs) - - def __resolve_type(t): if t is None: return t diff --git a/src/hdmf/validate/validator.py b/src/hdmf/validate/validator.py index daa5adac4..d7ec78eaa 100644 --- a/src/hdmf/validate/validator.py +++ b/src/hdmf/validate/validator.py @@ -8,7 +8,7 @@ from .errors import Error, DtypeError, MissingError, MissingDataType, ShapeError, IllegalLinkError, IncorrectDataType from .errors import ExpectedArrayError, IncorrectQuantityError -from ..build import GroupBuilder, DatasetBuilder, LinkBuilder, ReferenceBuilder, RegionBuilder +from ..build import GroupBuilder, DatasetBuilder, LinkBuilder, ReferenceBuilder from ..build.builders import BaseBuilder from ..spec import Spec, AttributeSpec, GroupSpec, DatasetSpec, RefSpec, LinkSpec from ..spec import SpecNamespace @@ -124,9 +124,6 @@ def get_type(data, builder_dtype=None): # Bytes data elif isinstance(data, bytes): return 'ascii', get_string_format(data) - # RegionBuilder data - elif isinstance(data, RegionBuilder): - return 'region', None # ReferenceBuilder data elif isinstance(data, ReferenceBuilder): return 'object', None diff --git a/tests/unit/build_tests/test_builder.py b/tests/unit/build_tests/test_builder.py index a35dc64ac..62ebd0675 100644 --- a/tests/unit/build_tests/test_builder.py +++ b/tests/unit/build_tests/test_builder.py @@ -1,4 +1,4 @@ -from hdmf.build import GroupBuilder, DatasetBuilder, LinkBuilder, ReferenceBuilder, RegionBuilder +from hdmf.build import GroupBuilder, DatasetBuilder, LinkBuilder, ReferenceBuilder from hdmf.testing import TestCase @@ -392,12 +392,3 @@ def test_constructor(self): db = DatasetBuilder('db1', [1, 2, 3]) rb = ReferenceBuilder(db) self.assertIs(rb.builder, db) - - -class TestRegionBuilder(TestCase): - - def test_constructor(self): - db = DatasetBuilder('db1', [1, 2, 3]) - rb = RegionBuilder(slice(1, 3), db) - self.assertEqual(rb.region, slice(1, 3)) - self.assertIs(rb.builder, db) diff --git a/tests/unit/common/test_table.py b/tests/unit/common/test_table.py index 38175b230..15a0c9e91 100644 --- a/tests/unit/common/test_table.py +++ b/tests/unit/common/test_table.py @@ -429,9 +429,7 @@ def test_add_column_vectorindex(self): table.add_column(name='qux', description='qux column') ind = VectorIndex(name='quux', data=list(), target=table['qux']) - msg = ("Passing a VectorIndex in for index may lead to unexpected behavior. This functionality will be " - "deprecated in a future version of HDMF.") - with self.assertWarnsWith(FutureWarning, msg): + with self.assertRaises(ValueError): table.add_column(name='bad', description='bad column', index=ind) def test_add_column_multi_index(self): diff --git a/tests/unit/spec_tests/test_ref_spec.py b/tests/unit/spec_tests/test_ref_spec.py index bb1c0efb8..3277673d1 100644 --- a/tests/unit/spec_tests/test_ref_spec.py +++ b/tests/unit/spec_tests/test_ref_spec.py @@ -15,9 +15,3 @@ def test_constructor(self): def test_wrong_reference_type(self): with self.assertRaises(ValueError): RefSpec('TimeSeries', 'unknownreftype') - - def test_isregion(self): - spec = RefSpec('TimeSeries', 'object') - self.assertFalse(spec.is_region()) - spec = RefSpec('Data', 'region') - self.assertTrue(spec.is_region()) diff --git a/tests/unit/test_container.py b/tests/unit/test_container.py index c12247de7..2abe6349b 100644 --- a/tests/unit/test_container.py +++ b/tests/unit/test_container.py @@ -213,18 +213,6 @@ def test_all_children(self): obj = species.all_objects self.assertEqual(sorted(list(obj.keys())), sorted([species.object_id, species.id.object_id, col1.object_id])) - def test_add_child(self): - """Test that add child creates deprecation warning and also properly sets child's parent and modified - """ - parent_obj = Container('obj1') - child_obj = Container('obj2') - parent_obj.set_modified(False) - with self.assertWarnsWith(DeprecationWarning, 'add_child is deprecated. Set the parent attribute instead.'): - parent_obj.add_child(child_obj) - self.assertIs(child_obj.parent, parent_obj) - self.assertTrue(parent_obj.modified) - self.assertIs(parent_obj.children[0], child_obj) - def test_parent_set_link_warning(self): col1 = VectorData( name='col1', diff --git a/tests/unit/test_query.py b/tests/unit/test_query.py deleted file mode 100644 index b2ff267a7..000000000 --- a/tests/unit/test_query.py +++ /dev/null @@ -1,161 +0,0 @@ -import os -from abc import ABCMeta, abstractmethod - -import numpy as np -from h5py import File -from hdmf.array import SortedArray, LinSpace -from hdmf.query import HDMFDataset, Query -from hdmf.testing import TestCase - - -class AbstractQueryMixin(metaclass=ABCMeta): - - @abstractmethod - def getDataset(self): - raise NotImplementedError('Cannot run test unless getDataset is implemented') - - def setUp(self): - self.dset = self.getDataset() - self.wrapper = HDMFDataset(self.dset) - - def test_get_dataset(self): - array = self.wrapper.dataset - self.assertIsInstance(array, SortedArray) - - def test___gt__(self): - ''' - Test wrapper greater than magic method - ''' - q = self.wrapper > 5 - self.assertIsInstance(q, Query) - result = q.evaluate() - expected = [False, False, False, False, False, - False, True, True, True, True] - expected = slice(6, 10) - self.assertEqual(result, expected) - - def test___ge__(self): - ''' - Test wrapper greater than or equal magic method - ''' - q = self.wrapper >= 5 - self.assertIsInstance(q, Query) - result = q.evaluate() - expected = [False, False, False, False, False, - True, True, True, True, True] - expected = slice(5, 10) - self.assertEqual(result, expected) - - def test___lt__(self): - ''' - Test wrapper less than magic method - ''' - q = self.wrapper < 5 - self.assertIsInstance(q, Query) - result = q.evaluate() - expected = [True, True, True, True, True, - False, False, False, False, False] - expected = slice(0, 5) - self.assertEqual(result, expected) - - def test___le__(self): - ''' - Test wrapper less than or equal magic method - ''' - q = self.wrapper <= 5 - self.assertIsInstance(q, Query) - result = q.evaluate() - expected = [True, True, True, True, True, - True, False, False, False, False] - expected = slice(0, 6) - self.assertEqual(result, expected) - - def test___eq__(self): - ''' - Test wrapper equals magic method - ''' - q = self.wrapper == 5 - self.assertIsInstance(q, Query) - result = q.evaluate() - expected = [False, False, False, False, False, - True, False, False, False, False] - expected = 5 - self.assertTrue(np.array_equal(result, expected)) - - def test___ne__(self): - ''' - Test wrapper not equal magic method - ''' - q = self.wrapper != 5 - self.assertIsInstance(q, Query) - result = q.evaluate() - expected = [True, True, True, True, True, - False, True, True, True, True] - expected = [slice(0, 5), slice(6, 10)] - self.assertTrue(np.array_equal(result, expected)) - - def test___getitem__(self): - ''' - Test wrapper getitem using slice - ''' - result = self.wrapper[0:5] - expected = [0, 1, 2, 3, 4] - self.assertTrue(np.array_equal(result, expected)) - - def test___getitem__query(self): - ''' - Test wrapper getitem using query - ''' - q = self.wrapper < 5 - result = self.wrapper[q] - expected = [0, 1, 2, 3, 4] - self.assertTrue(np.array_equal(result, expected)) - - -class SortedQueryTest(AbstractQueryMixin, TestCase): - - path = 'SortedQueryTest.h5' - - def getDataset(self): - self.f = File(self.path, 'w') - self.input = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - self.d = self.f.create_dataset('dset', data=self.input) - return SortedArray(self.d) - - def tearDown(self): - self.f.close() - if os.path.exists(self.path): - os.remove(self.path) - - -class LinspaceQueryTest(AbstractQueryMixin, TestCase): - - path = 'LinspaceQueryTest.h5' - - def getDataset(self): - return LinSpace(0, 10, 1) - - -class CompoundQueryTest(TestCase): - - def getM(self): - return SortedArray(np.arange(10, 20, 1)) - - def getN(self): - return SortedArray(np.arange(10.0, 20.0, 0.5)) - - def setUp(self): - self.m = HDMFDataset(self.getM()) - self.n = HDMFDataset(self.getN()) - - # TODO: test not completed - # def test_map(self): - # q = self.m == (12, 16) # IN operation - # q.evaluate() # [2,3,4,5] - # q.evaluate(False) # RangeResult(2,6) - # r = self.m[q] # noqa: F841 - # r = self.m[q.evaluate()] # noqa: F841 - # r = self.m[q.evaluate(False)] # noqa: F841 - - def tearDown(self): - pass diff --git a/tests/unit/utils_test/test_core_DataIO.py b/tests/unit/utils_test/test_core_DataIO.py index 4c2ffac15..80518a316 100644 --- a/tests/unit/utils_test/test_core_DataIO.py +++ b/tests/unit/utils_test/test_core_DataIO.py @@ -1,10 +1,8 @@ from copy import copy, deepcopy import numpy as np -from hdmf.container import Data from hdmf.data_utils import DataIO from hdmf.testing import TestCase -import warnings class DataIOTests(TestCase): @@ -30,34 +28,13 @@ def test_dataio_slice_delegation(self): dset = DataIO(indata) self.assertTrue(np.all(dset[1:3, 5:8] == indata[1:3, 5:8])) - def test_set_dataio(self): - """ - Test that Data.set_dataio works as intended - """ - dataio = DataIO() - data = np.arange(30).reshape(5, 2, 3) - container = Data('wrapped_data', data) - msg = "Data.set_dataio() is deprecated. Please use Data.set_data_io() instead." - with self.assertWarnsWith(DeprecationWarning, msg): - container.set_dataio(dataio) - self.assertIs(dataio.data, data) - self.assertIs(dataio, container.data) - - def test_set_dataio_data_already_set(self): + def test_set_data_io_data_already_set(self): """ Test that Data.set_dataio works as intended """ dataio = DataIO(data=np.arange(30).reshape(5, 2, 3)) - data = np.arange(30).reshape(5, 2, 3) - container = Data('wrapped_data', data) with self.assertRaisesWith(ValueError, "cannot overwrite 'data' on DataIO"): - with warnings.catch_warnings(record=True): - warnings.filterwarnings( - action='ignore', - category=DeprecationWarning, - message="Data.set_dataio() is deprecated. Please use Data.set_data_io() instead.", - ) - container.set_dataio(dataio) + dataio.data=[1,2,3,4] def test_dataio_options(self): """ diff --git a/tests/unit/utils_test/test_docval.py b/tests/unit/utils_test/test_docval.py index c766dcf46..bed5cd134 100644 --- a/tests/unit/utils_test/test_docval.py +++ b/tests/unit/utils_test/test_docval.py @@ -1,7 +1,7 @@ import numpy as np from hdmf.testing import TestCase -from hdmf.utils import (docval, fmt_docval_args, get_docval, getargs, popargs, AllowPositional, get_docval_macro, - docval_macro, popargs_to_dict, call_docval_func) +from hdmf.utils import (docval, get_docval, getargs, popargs, AllowPositional, get_docval_macro, + docval_macro, popargs_to_dict) class MyTestClass(object): @@ -137,80 +137,6 @@ def method1(self, **kwargs): with self.assertRaises(ValueError): method1(self, arg1=[[1, 1, 1]]) - fmt_docval_warning_msg = ( - "fmt_docval_args will be deprecated in a future version of HDMF. Instead of using fmt_docval_args, " - "call the function directly with the kwargs. Please note that fmt_docval_args " - "removes all arguments not accepted by the function's docval, so if you are passing kwargs that " - "includes extra arguments and the function's docval does not allow extra arguments (allow_extra=True " - "is set), then you will need to pop the extra arguments out of kwargs before calling the function." - ) - - def test_fmt_docval_args(self): - """ Test that fmt_docval_args parses the args and strips extra args """ - test_kwargs = { - 'arg1': 'a string', - 'arg2': 1, - 'arg3': True, - 'hello': 'abc', - 'list': ['abc', 1, 2, 3] - } - with self.assertWarnsWith(PendingDeprecationWarning, self.fmt_docval_warning_msg): - rec_args, rec_kwargs = fmt_docval_args(self.test_obj.basic_add2_kw, test_kwargs) - exp_args = ['a string', 1] - self.assertListEqual(rec_args, exp_args) - exp_kwargs = {'arg3': True} - self.assertDictEqual(rec_kwargs, exp_kwargs) - - def test_fmt_docval_args_no_docval(self): - """ Test that fmt_docval_args raises an error when run on function without docval """ - def method1(self, **kwargs): - pass - - with self.assertRaisesRegex(ValueError, r"no docval found on .*method1.*"): - with self.assertWarnsWith(PendingDeprecationWarning, self.fmt_docval_warning_msg): - fmt_docval_args(method1, {}) - - def test_fmt_docval_args_allow_extra(self): - """ Test that fmt_docval_args works """ - test_kwargs = { - 'arg1': 'a string', - 'arg2': 1, - 'arg3': True, - 'hello': 'abc', - 'list': ['abc', 1, 2, 3] - } - with self.assertWarnsWith(PendingDeprecationWarning, self.fmt_docval_warning_msg): - rec_args, rec_kwargs = fmt_docval_args(self.test_obj.basic_add2_kw_allow_extra, test_kwargs) - exp_args = ['a string', 1] - self.assertListEqual(rec_args, exp_args) - exp_kwargs = {'arg3': True, 'hello': 'abc', 'list': ['abc', 1, 2, 3]} - self.assertDictEqual(rec_kwargs, exp_kwargs) - - def test_call_docval_func(self): - """Test that call_docval_func strips extra args and calls the function.""" - test_kwargs = { - 'arg1': 'a string', - 'arg2': 1, - 'arg3': True, - 'hello': 'abc', - 'list': ['abc', 1, 2, 3] - } - msg = ( - "call_docval_func will be deprecated in a future version of HDMF. Instead of using call_docval_func, " - "call the function directly with the kwargs. Please note that call_docval_func " - "removes all arguments not accepted by the function's docval, so if you are passing kwargs that " - "includes extra arguments and the function's docval does not allow extra arguments (allow_extra=True " - "is set), then you will need to pop the extra arguments out of kwargs before calling the function." - ) - with self.assertWarnsWith(PendingDeprecationWarning, msg): - ret_kwargs = call_docval_func(self.test_obj.basic_add2_kw, test_kwargs) - exp_kwargs = { - 'arg1': 'a string', - 'arg2': 1, - 'arg3': True - } - self.assertDictEqual(ret_kwargs, exp_kwargs) - def test_docval_add(self): """Test that docval works with a single positional argument diff --git a/tox.ini b/tox.ini index 75b011aa0..42305ae25 100644 --- a/tox.ini +++ b/tox.ini @@ -42,17 +42,17 @@ commands = # list of pre-defined environments. (Technically environments not listed here # like build-py312 can also be used.) -[testenv:pytest-py312-upgraded] -[testenv:pytest-py312-prerelease] +[testenv:pytest-py313-upgraded] +[testenv:pytest-py313-prerelease] [testenv:pytest-py311-optional-pinned] # some optional reqs not compatible with py312 yet -[testenv:pytest-py{38,39,310,311,312}-pinned] -[testenv:pytest-py38-minimum] +[testenv:pytest-py{39,310,311,312,313}-pinned] +[testenv:pytest-py39-minimum] -[testenv:gallery-py312-upgraded] -[testenv:gallery-py312-prerelease] +[testenv:gallery-py313-upgraded] +[testenv:gallery-py313-prerelease] [testenv:gallery-py311-optional-pinned] -[testenv:gallery-py{38,39,310,311,312}-pinned] -[testenv:gallery-py38-minimum] +[testenv:gallery-py{39,310,311,312,313}-pinned] +[testenv:gallery-py39-minimum] [testenv:build] # using tox for this so that we can have a clean build environment [testenv:wheelinstall] # use with `--installpkg dist/*-none-any.whl` From a7a5507e02ec622e8664fe87b6114cc23098620d Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Fri, 10 Jan 2025 10:51:45 -0800 Subject: [PATCH 80/82] Fix broken zarr intersphinx, limit zarr to < 3 (#1229) --- CHANGELOG.md | 1 + docs/source/conf.py | 2 +- pyproject.toml | 12 +++++++----- requirements-opt.txt | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce3fb48a3..ec7480aad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - The following classes have been deprecated and removed: Array, AbstractSortedArray, SortedArray, LinSpace, Query, RegionSlicer, ListSlicer, H5RegionSlicer, DataRegion, RegionBuilder. The following methods have been deprecated and removed: fmt_docval_args, call_docval_func, get_container_cls, add_child, set_dataio (now refactored as set_data_io). We have also removed all early development for region references. @mavaylon1, @rly [#1998](https://github.com/hdmf-dev/hdmf/pull/1198), [#1212](https://github.com/hdmf-dev/hdmf/pull/1212) - Importing from hdmf.build.map is no longer supported. Import from hdmf.build instead. @rly [#1221](https://github.com/hdmf-dev/hdmf/pull/1221) - Python 3.8 has reached end of life. Drop support for Python 3.8 and add support for Python 3.13. @mavaylon1 [#1209](https://github.com/hdmf-dev/hdmf/pull/1209) +- Support for Zarr is limited to versions < 3. @rly [#1229](https://github.com/hdmf-dev/hdmf/pull/1229) ### Bug fixes - Added checks to ensure that group and dataset spec names and default names do not contain slashes. @bendichter [#1219](https://github.com/hdmf-dev/hdmf/pull/1219) diff --git a/docs/source/conf.py b/docs/source/conf.py index d385630d2..208ac325a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -76,7 +76,7 @@ "matplotlib": ("https://matplotlib.org/stable/", None), "h5py": ("https://docs.h5py.org/en/latest/", None), "pandas": ("https://pandas.pydata.org/pandas-docs/stable/", None), - "zarr": ("https://zarr.readthedocs.io/en/stable/", None), + "zarr": ("https://zarr.readthedocs.io/en/v2.18.4/", None), # TODO - update when hdmf-zarr supports Zarr 3.0 } # these links cannot be checked in github actions diff --git a/pyproject.toml b/pyproject.toml index 86b9e18e1..9b13aec54 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,11 +41,13 @@ dynamic = ["version"] [project.optional-dependencies] tqdm = ["tqdm>=4.41.0"] -zarr = ["zarr>=2.12.0"] -termset = ["linkml-runtime>=1.5.5", - "schemasheets>=0.1.23", - "oaklib>=0.5.12", - "pyyaml>=6.0.1"] +zarr = ["zarr>=2.12.0,<3"] +termset = [ + "linkml-runtime>=1.5.5", + "schemasheets>=0.1.23", + "oaklib>=0.5.12", + "pyyaml>=6.0.1", +] [project.urls] "Homepage" = "https://github.com/hdmf-dev/hdmf" diff --git a/requirements-opt.txt b/requirements-opt.txt index 8811ff46e..b2bba6814 100644 --- a/requirements-opt.txt +++ b/requirements-opt.txt @@ -1,6 +1,6 @@ # pinned dependencies that are optional. used to reproduce an entire development environment to use HDMF tqdm==4.66.4 -zarr==2.18.2 +zarr==2.18.4 linkml-runtime==1.7.7 schemasheets==0.2.1 oaklib==0.6.10 From dc51890c65400d0b17bcf7b200ed7abb9f38f193 Mon Sep 17 00:00:00 2001 From: Ryan Ly Date: Fri, 17 Jan 2025 07:28:46 -0800 Subject: [PATCH 81/82] Update dates and how optional deps are specified and tested (#1230) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/bug_report.yml | 10 +- .github/PULL_REQUEST_TEMPLATE/release.md | 11 +- .github/workflows/check_sphinx_links.yml | 5 +- .github/workflows/deploy_release.yml | 4 +- .github/workflows/run_all_tests.yml | 131 ++++---------- .github/workflows/run_coverage.yml | 22 +-- .github/workflows/run_hdmf_zarr_tests.yml | 2 +- .github/workflows/run_pynwb_tests.yml | 2 +- .github/workflows/run_tests.yml | 84 ++------- .gitignore | 1 + .readthedocs.yaml | 9 +- CHANGELOG.md | 16 +- Legal.txt | 2 +- README.rst | 4 +- docs/gallery/plot_external_resources.py | 5 + docs/source/conf.py | 2 +- docs/source/index.rst | 1 - docs/source/install_developers.rst | 7 +- docs/source/make_a_release.rst | 7 +- docs/source/software_process.rst | 42 ++--- docs/source/update_requirements.rst | 78 -------- environment-ros3.yml | 11 +- license.txt | 2 +- pyproject.toml | 24 +++ requirements-dev.txt | 14 -- requirements-doc.txt | 6 - requirements-opt.txt | 6 - requirements.txt | 8 - scripts/check_py_support.py | 205 ++++++++++++++++++++++ test_gallery.py | 5 +- tox.ini | 46 ++--- 31 files changed, 378 insertions(+), 394 deletions(-) delete mode 100644 docs/source/update_requirements.rst delete mode 100644 requirements-dev.txt delete mode 100644 requirements-doc.txt delete mode 100644 requirements-opt.txt delete mode 100644 requirements.txt create mode 100644 scripts/check_py_support.py diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f1d04aa69..c96a78551 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -46,15 +46,6 @@ body: - Linux validations: required: true - - type: dropdown - id: executable - attributes: - label: Python Executable - options: - - Conda - - Python - validations: - required: true - type: dropdown id: python_version attributes: @@ -65,6 +56,7 @@ body: - "3.11" - "3.10" - "3.9" + - "newer" validations: required: true - type: textarea diff --git a/.github/PULL_REQUEST_TEMPLATE/release.md b/.github/PULL_REQUEST_TEMPLATE/release.md index 86a7ad57d..82f987164 100644 --- a/.github/PULL_REQUEST_TEMPLATE/release.md +++ b/.github/PULL_REQUEST_TEMPLATE/release.md @@ -2,11 +2,8 @@ Prepare for release of HDMF [version] ### Before merging: - [ ] Make sure all PRs to be included in this release have been merged to `dev`. -- [ ] Major and minor releases: Update package versions in `requirements.txt`, `requirements-dev.txt`, - `requirements-doc.txt`, `requirements-opt.txt`, and `environment-ros3.yml` to the latest versions, - and update dependency ranges in `pyproject.toml` and minimums in `requirements-min.txt` as needed. - Run `pip install pur && pur -r requirements-dev.txt -r requirements.txt -r requirements-opt.txt` - and manually update `environment-ros3.yml`. +- [ ] Major and minor releases: Update dependency ranges in `pyproject.toml` and minimums in + `requirements-min.txt` as needed. - [ ] Check legal file dates and information in `Legal.txt`, `license.txt`, `README.rst`, `docs/source/conf.py`, and any other locations as needed - [ ] Update `pyproject.toml` as needed @@ -34,5 +31,5 @@ Prepare for release of HDMF [version] 4. Either monitor [conda-forge/hdmf-feedstock](https://github.com/conda-forge/hdmf-feedstock) for the regro-cf-autotick-bot bot to create a PR updating the version of HDMF to the latest PyPI release, usually within 24 hours of release, or manually create a PR updating `recipe/meta.yaml` with the latest version number - and SHA256 retrieved from PyPI > HDMF > Download Files > View hashes for the `.tar.gz` file. Re-render and update - dependencies as needed. + and SHA256 retrieved from PyPI > HDMF > Download Files > View hashes for the `.tar.gz` file. Re-render and + update the dependencies as needed. diff --git a/.github/workflows/check_sphinx_links.yml b/.github/workflows/check_sphinx_links.yml index 15fc61e30..24422c47c 100644 --- a/.github/workflows/check_sphinx_links.yml +++ b/.github/workflows/check_sphinx_links.yml @@ -21,13 +21,12 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.11' # TODO update to 3.12 when optional reqs (e.g., oaklib) support 3.12 + python-version: '3.12' # TODO: Update to 3.13 when linkml and its deps support 3.13 - name: Install Sphinx dependencies and package run: | python -m pip install --upgrade pip - python -m pip install -r requirements-doc.txt -r requirements-opt.txt - python -m pip install . + python -m pip install ".[all]" - name: Check Sphinx internal and external links run: sphinx-build -W -b linkcheck ./docs/source ./test_build diff --git a/.github/workflows/deploy_release.yml b/.github/workflows/deploy_release.yml index 5861ab136..ab0db960a 100644 --- a/.github/workflows/deploy_release.yml +++ b/.github/workflows/deploy_release.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - name: Install build dependencies run: | @@ -28,7 +28,7 @@ jobs: - name: Run tox tests run: | - tox -e py312-upgraded + tox -e py313-upgraded - name: Build wheel and source distribution run: | diff --git a/.github/workflows/run_all_tests.yml b/.github/workflows/run_all_tests.yml index 961500194..b1d2ddc59 100644 --- a/.github/workflows/run_all_tests.yml +++ b/.github/workflows/run_all_tests.yml @@ -25,30 +25,27 @@ jobs: fail-fast: false matrix: include: - - { name: linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - - { name: linux-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: ubuntu-latest } - - { name: linux-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: ubuntu-latest } - - { name: linux-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: ubuntu-latest } - - { name: linux-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: ubuntu-latest } - - { name: linux-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: ubuntu-latest } - - { name: linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } - - { name: linux-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: ubuntu-latest } - - { name: windows-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: windows-latest } - - { name: windows-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: windows-latest } - - { name: windows-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: windows-latest } - - { name: windows-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: windows-latest } - - { name: windows-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: windows-latest } - - { name: windows-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: windows-latest } - - { name: windows-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: windows-latest } - - { name: windows-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: windows-latest } - - { name: macos-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: macos-13 } - - { name: macos-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: macos-latest } - - { name: macos-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: macos-latest } - - { name: macos-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: macos-latest } - - { name: macos-python3.12 , test-tox-env: pytest-py312-pinned , python-ver: "3.12", os: macos-latest } - - { name: macos-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: macos-latest } - - { name: macos-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: macos-latest } - - { name: macos-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: macos-latest } + - { name: linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } + - { name: linux-python3.10-upgraded , test-tox-env: pytest-py310-upgraded , python-ver: "3.10", os: ubuntu-latest } + - { name: linux-python3.11-upgraded , test-tox-env: pytest-py311-upgraded , python-ver: "3.11", os: ubuntu-latest } + - { name: linux-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: ubuntu-latest } + - { name: linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } + - { name: linux-python3.13-upgraded-optional , test-tox-env: pytest-py313-upgraded-optional , python-ver: "3.13", os: ubuntu-latest } + - { name: linux-python3.13-prerelease-optional , test-tox-env: pytest-py313-prerelease-optional , python-ver: "3.13", os: ubuntu-latest } + - { name: windows-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: windows-latest } + - { name: windows-python3.10-upgraded , test-tox-env: pytest-py310-upgraded , python-ver: "3.10", os: windows-latest } + - { name: windows-python3.11-upgraded , test-tox-env: pytest-py311-upgraded , python-ver: "3.11", os: windows-latest } + - { name: windows-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: windows-latest } + - { name: windows-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: windows-latest } + - { name: windows-python3.13-upgraded-optional , test-tox-env: pytest-py313-upgraded-optional , python-ver: "3.13", os: windows-latest } + - { name: windows-python3.13-prerelease-optional , test-tox-env: pytest-py313-prerelease-optional , python-ver: "3.13", os: windows-latest } + - { name: macos-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: macos-13 } + - { name: macos-python3.10-upgraded , test-tox-env: pytest-py310-upgraded , python-ver: "3.10", os: macos-latest } + - { name: macos-python3.11-upgraded , test-tox-env: pytest-py311-upgraded , python-ver: "3.11", os: macos-latest } + - { name: macos-python3.12-upgraded , test-tox-env: pytest-py312-upgraded , python-ver: "3.12", os: macos-latest } + - { name: macos-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: macos-latest } + - { name: macos-python3.13-upgraded-optional , test-tox-env: pytest-py313-upgraded-optional , python-ver: "3.13", os: macos-latest } + - { name: macos-python3.13-prerelease-optional , test-tox-env: pytest-py313-prerelease-optional , python-ver: "3.13", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -97,18 +94,16 @@ jobs: fail-fast: false matrix: include: - - { name: linux-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - - { name: linux-gallery-python3.11-optional , test-tox-env: gallery-py311-optional-pinned , python-ver: "3.11", os: ubuntu-latest } - - { name: linux-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } - - { name: linux-gallery-python3.13-prerelease , test-tox-env: gallery-py313-prerelease , python-ver: "3.13", os: ubuntu-latest } - - { name: windows-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: windows-latest } - - { name: windows-gallery-python3.11-optional , test-tox-env: gallery-py311-optional-pinned , python-ver: "3.11", os: windows-latest } - - { name: windows-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: windows-latest } - - { name: windows-gallery-python3.13-prerelease, test-tox-env: gallery-py313-prerelease , python-ver: "3.13", os: windows-latest } - - { name: macos-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: macos-13 } - - { name: macos-gallery-python3.11-optional , test-tox-env: gallery-py311-optional-pinned , python-ver: "3.11", os: macos-latest } - - { name: macos-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: macos-latest } - - { name: macos-gallery-python3.13-prerelease , test-tox-env: gallery-py313-prerelease , python-ver: "3.13", os: macos-latest } + # TODO: Update to 3.13 when linkml and its deps support 3.13 + - { name: linux-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } + - { name: linux-gallery-python3.12-upgraded-optional , test-tox-env: gallery-py312-upgraded-optional , python-ver: "3.12", os: ubuntu-latest } + - { name: linux-gallery-python3.12-prerelease-optional , test-tox-env: gallery-py312-prerelease-optional , python-ver: "3.12", os: ubuntu-latest } + - { name: windows-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: windows-latest } + - { name: windows-gallery-python3.12-upgraded-optional , test-tox-env: gallery-py312-upgraded-optional , python-ver: "3.12", os: windows-latest } + - { name: windows-gallery-python3.12-prerelease-optional , test-tox-env: gallery-py312-prerelease-optional , python-ver: "3.12", os: windows-latest } + - { name: macos-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: macos-13 } + - { name: macos-gallery-python3.12-upgraded-optional , test-tox-env: gallery-py312-upgraded-optional , python-ver: "3.12", os: macos-latest } + - { name: macos-gallery-python3.12-prerelease-optional , test-tox-env: gallery-py312-prerelease-optional , python-ver: "3.12", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -131,70 +126,6 @@ jobs: run: | tox -e ${{ matrix.test-tox-env }} - run-all-tests-on-conda: - name: ${{ matrix.name }} - runs-on: ubuntu-latest - defaults: - run: - shell: bash -l {0} # needed for conda environment to work - concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.name }} - cancel-in-progress: true - strategy: - fail-fast: false - matrix: - include: - - { name: conda-linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - - { name: conda-linux-python3.10 , test-tox-env: pytest-py310-pinned , python-ver: "3.10", os: ubuntu-latest } - - { name: conda-linux-python3.11 , test-tox-env: pytest-py311-pinned , python-ver: "3.11", os: ubuntu-latest } - - { name: conda-linux-python3.11-optional , test-tox-env: pytest-py311-optional-pinned , python-ver: "3.11", os: ubuntu-latest } - - { name: conda-linux-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: ubuntu-latest } - - { name: conda-linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } - - { name: conda-linux-python3.13-prerelease , test-tox-env: pytest-py313-prerelease , python-ver: "3.13", os: ubuntu-latest } - steps: - - name: Checkout repo with submodules - uses: actions/checkout@v4 - with: - submodules: 'recursive' - fetch-depth: 0 # tags are required to determine the version - - - name: Set up Conda - uses: conda-incubator/setup-miniconda@v3 - with: - auto-update-conda: true - python-version: ${{ matrix.python-ver }} - channels: conda-forge - - - name: Install build dependencies - run: | - conda config --set always_yes yes --set changeps1 no - conda info - conda install -c conda-forge "tox>=4" - - - name: Conda reporting - run: | - conda info - conda config --show-sources - conda list --show-channel-urls - - # NOTE tox installs packages from PyPI not conda-forge... - - name: Run tox tests - run: | - tox -e ${{ matrix.test-tox-env }} - - - name: Build wheel and source distribution - run: | - tox -e build - ls -1 dist - - - name: Test installation from a wheel - run: | - tox -e wheelinstall --installpkg dist/*-none-any.whl - - - name: Test installation from a source distribution - run: | - tox -e wheelinstall --installpkg dist/*.tar.gz - run-ros3-tests: name: ${{ matrix.name }} runs-on: ${{ matrix.os }} diff --git a/.github/workflows/run_coverage.yml b/.github/workflows/run_coverage.yml index bea8df734..ee1f9ff91 100644 --- a/.github/workflows/run_coverage.yml +++ b/.github/workflows/run_coverage.yml @@ -31,7 +31,7 @@ jobs: - { os: macos-latest , opt_req: false } env: # used by codecov-action OS: ${{ matrix.os }} - PYTHON: '3.11' # TODO update to 3.12 when optional reqs (e.g., oaklib) support 3.12 + PYTHON: '3.12' # TODO: Update to 3.13 when linkml and its deps support 3.13 steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -44,24 +44,26 @@ jobs: with: python-version: ${{ env.PYTHON }} - - name: Install dependencies + - name: Upgrade pip run: | python -m pip install --upgrade pip - python -m pip install -r requirements-dev.txt -r requirements.txt - - - name: Install optional dependencies - if: ${{ matrix.opt_req }} - run: python -m pip install -r requirements-opt.txt - name: Install package + if: ${{ ! matrix.opt_req }} run: | - python -m pip install . - python -m pip list + python -m pip install ".[test]" + + - name: Install package with optional dependencies + if: ${{ matrix.opt_req }} + run: | + python -m pip install ".[test,tqdm,zarr,termset]" - name: Run tests and generate coverage report run: | # coverage is configured in pyproject.toml - pytest --cov --cov-report=xml --cov-report=term # codecov uploader requires xml format + # codecov uploader requires xml format + python -m pip list + pytest --cov --cov-report=xml --cov-report=term - name: Upload coverage to Codecov uses: codecov/codecov-action@v5 diff --git a/.github/workflows/run_hdmf_zarr_tests.yml b/.github/workflows/run_hdmf_zarr_tests.yml index 6eb5546ab..51a01977a 100644 --- a/.github/workflows/run_hdmf_zarr_tests.yml +++ b/.github/workflows/run_hdmf_zarr_tests.yml @@ -31,7 +31,7 @@ jobs: python -m pip list git clone https://github.com/hdmf-dev/hdmf-zarr.git cd hdmf-zarr - python -m pip install .[test] # this will install a different version of hdmf from the current one + python -m pip install ".[test]" # this will install a different version of hdmf from the current one cd .. python -m pip uninstall -y hdmf # uninstall the other version of hdmf python -m pip install . # reinstall current branch of hdmf diff --git a/.github/workflows/run_pynwb_tests.yml b/.github/workflows/run_pynwb_tests.yml index 1a714ed9f..a159380cd 100644 --- a/.github/workflows/run_pynwb_tests.yml +++ b/.github/workflows/run_pynwb_tests.yml @@ -21,7 +21,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: '3.13' - name: Update pip run: python -m pip install --upgrade pip diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index a3fa5a84c..2ff759029 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -23,13 +23,13 @@ jobs: matrix: include: # NOTE config below with "upload-wheels: true" specifies that wheels should be uploaded as an artifact - - { name: linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - - { name: linux-python3.13 , test-tox-env: pytest-py313-pinned , python-ver: "3.13", os: ubuntu-latest } - - { name: linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest , upload-wheels: true } - - { name: windows-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: windows-latest } - - { name: windows-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: windows-latest } - - { name: macos-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: macos-13 } - - { name: macos-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: macos-latest } + - { name: linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } + - { name: linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } + - { name: linux-python3.13-upgraded-optional , test-tox-env: pytest-py313-upgraded-optional , python-ver: "3.13", os: ubuntu-latest , upload-wheels: true } + - { name: windows-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: windows-latest } + - { name: windows-python3.13-upgraded-optional , test-tox-env: pytest-py313-upgraded-optional , python-ver: "3.13", os: windows-latest } + - { name: macos-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: macos-13 } + - { name: macos-python3.13-upgraded-optional , test-tox-env: pytest-py313-upgraded-optional , python-ver: "3.13", os: macos-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -85,10 +85,11 @@ jobs: fail-fast: false matrix: include: - - { name: linux-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - - { name: linux-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } - - { name: windows-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: windows-latest } - - { name: windows-gallery-python3.13-upgraded , test-tox-env: gallery-py313-upgraded , python-ver: "3.13", os: windows-latest } + # TODO: Update to 3.13 when linkml and its deps support 3.13 + - { name: linux-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } + - { name: linux-gallery-python3.12-upgraded-optional , test-tox-env: gallery-py312-upgraded-optional , python-ver: "3.12", os: ubuntu-latest } + - { name: windows-gallery-python3.9-minimum , test-tox-env: gallery-py39-minimum , python-ver: "3.9" , os: windows-latest } + - { name: windows-gallery-python3.12-upgraded-optional , test-tox-env: gallery-py312-upgraded-optional , python-ver: "3.12", os: windows-latest } steps: - name: Checkout repo with submodules uses: actions/checkout@v4 @@ -111,68 +112,9 @@ jobs: run: | tox -e ${{ matrix.test-tox-env }} - run-tests-on-conda: - name: ${{ matrix.name }} - runs-on: ubuntu-latest - defaults: - run: - shell: bash -l {0} - concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.name }} - cancel-in-progress: true - strategy: - fail-fast: false - matrix: - include: - - { name: conda-linux-python3.9-minimum , test-tox-env: pytest-py39-minimum , python-ver: "3.9" , os: ubuntu-latest } - - { name: conda-linux-python3.13-upgraded , test-tox-env: pytest-py313-upgraded , python-ver: "3.13", os: ubuntu-latest } - steps: - - name: Checkout repo with submodules - uses: actions/checkout@v4 - with: - submodules: 'recursive' - fetch-depth: 0 # tags are required to determine the version - - - name: Set up Conda - uses: conda-incubator/setup-miniconda@v3 - with: - auto-update-conda: true - python-version: ${{ matrix.python-ver }} - channels: conda-forge - - - name: Install build dependencies - run: | - conda config --set always_yes yes --set changeps1 no - conda info - conda install -c conda-forge "tox>=4" - - - name: Conda reporting - run: | - conda info - conda config --show-sources - conda list --show-channel-urls - - # NOTE tox installs packages from PyPI not conda-forge... - - name: Run tox tests - run: | - tox -e ${{ matrix.test-tox-env }} - - - name: Build wheel and source distribution - run: | - tox -e build - ls -1 dist - - - name: Test installation from a wheel - run: | - tox -e wheelinstall --installpkg dist/*-none-any.whl - - - name: Test installation from a source distribution - run: | - tox -e wheelinstall --installpkg dist/*.tar.gz - deploy-dev: name: Deploy pre-release from dev - needs: [run-tests, run-gallery-tests, run-tests-on-conda] + needs: [run-tests, run-gallery-tests] if: ${{ github.event_name == 'push' }} runs-on: ubuntu-latest concurrency: diff --git a/.gitignore b/.gitignore index d75abc985..e202b3526 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ # Auto-generated apidocs RST files /docs/source/gen_modules/ /docs/source/hdmf*.rst +/docs/source/sg_execution_times.rst /docs/gallery/*.hdf5 /docs/gallery/*.sqlite /docs/gallery/expanded_example_dynamic_term_set.yaml diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 19bcaee80..b752396f4 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -6,9 +6,9 @@ version: 2 build: - os: ubuntu-22.04 + os: ubuntu-24.04 tools: - python: '3.12' + python: '3.12' # TODO: Update to 3.13 when linkml and its deps support 3.13 # Build documentation in the docs/ directory with Sphinx sphinx: @@ -24,10 +24,7 @@ formats: all # Optionally set the version of Python and requirements required to build your docs python: install: - - requirements: requirements-doc.txt - - requirements: requirements-opt.txt - - requirements: requirements.txt - - path: . + - path: .[docs,tqdm,zarr,termset] # path to the package relative to the root # Optionally include all submodules submodules: diff --git a/CHANGELOG.md b/CHANGELOG.md index ec7480aad..32ab56940 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,23 @@ # HDMF Changelog -## HDMF 4.0.0 (Upcoming) +## [Unreleased] ### Breaking changes - The following classes have been deprecated and removed: Array, AbstractSortedArray, SortedArray, LinSpace, Query, RegionSlicer, ListSlicer, H5RegionSlicer, DataRegion, RegionBuilder. The following methods have been deprecated and removed: fmt_docval_args, call_docval_func, get_container_cls, add_child, set_dataio (now refactored as set_data_io). We have also removed all early development for region references. @mavaylon1, @rly [#1998](https://github.com/hdmf-dev/hdmf/pull/1198), [#1212](https://github.com/hdmf-dev/hdmf/pull/1212) -- Importing from hdmf.build.map is no longer supported. Import from hdmf.build instead. @rly [#1221](https://github.com/hdmf-dev/hdmf/pull/1221) -- Python 3.8 has reached end of life. Drop support for Python 3.8 and add support for Python 3.13. @mavaylon1 [#1209](https://github.com/hdmf-dev/hdmf/pull/1209) +- Importing from `hdmf.build.map` is no longer supported. Import from `hdmf.build` instead. @rly [#1221](https://github.com/hdmf-dev/hdmf/pull/1221) +- Python 3.8 has reached end of life. Dropped support for Python 3.8 and add support for Python 3.13. @mavaylon1 [#1209](https://github.com/hdmf-dev/hdmf/pull/1209) - Support for Zarr is limited to versions < 3. @rly [#1229](https://github.com/hdmf-dev/hdmf/pull/1229) -### Bug fixes +### Changed - Added checks to ensure that group and dataset spec names and default names do not contain slashes. @bendichter [#1219](https://github.com/hdmf-dev/hdmf/pull/1219) +- Updated copyright dates. @rly [#1230](https://github.com/hdmf-dev/hdmf/pull/1230) +- Created optional dependency groups in `pyproject.toml` and update GitHub Actions workflows to use those instead of requirements files. @rly [#1230](https://github.com/hdmf-dev/hdmf/pull/1230) +- Stopped using pinned dependencies in the docs and testing. These are not necessary for library testing, confuse new users and developers, and add maintenance burden. Current dependencies are stable enough that they need not be pinned and users can report the libraries they use. @rly [#1230](https://github.com/hdmf-dev/hdmf/pull/1230) +- Stopped redundant testing using a conda environment. @rly [#1230](https://github.com/hdmf-dev/hdmf/pull/1230) +- Adopted changelog format conventions: https://keepachangelog.com/en/1.1.0/ . @rly [#1230](https://github.com/hdmf-dev/hdmf/pull/1230) + +### Added +- Added script to check Python version support for HDMF dependencies. @rly [#1230](https://github.com/hdmf-dev/hdmf/pull/1230) ## HDMF 3.14.6 (December 20, 2024) diff --git a/Legal.txt b/Legal.txt index db343a634..e54bb27ac 100644 --- a/Legal.txt +++ b/Legal.txt @@ -1,4 +1,4 @@ -“hdmf” Copyright (c) 2017-2024, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. +“hdmf” Copyright (c) 2017-2025, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. If you have questions about your rights to use or distribute this software, please contact Berkeley Lab's Innovation & Partnerships Office at IPO@lbl.gov. diff --git a/README.rst b/README.rst index b56f7efd2..c35f45ccd 100644 --- a/README.rst +++ b/README.rst @@ -94,7 +94,7 @@ Citing HDMF LICENSE ======= -"hdmf" Copyright (c) 2017-2024, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. +"hdmf" Copyright (c) 2017-2025, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: (1) Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. @@ -110,7 +110,7 @@ You are under no obligation whatsoever to provide any bug fixes, patches, or upg COPYRIGHT ========= -"hdmf" Copyright (c) 2017-2024, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. +"hdmf" Copyright (c) 2017-2025, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. If you have questions about your rights to use or distribute this software, please contact Berkeley Lab's Innovation & Partnerships Office at IPO@lbl.gov. NOTICE. This Software was developed under funding from the U.S. Department of Energy and the U.S. Government consequently retains certain rights. As such, the U.S. Government has been granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license in the Software to reproduce, distribute copies to the public, prepare derivative works, and perform publicly and display publicly, and to permit other to do so. diff --git a/docs/gallery/plot_external_resources.py b/docs/gallery/plot_external_resources.py index 36e84b357..c8090f30f 100644 --- a/docs/gallery/plot_external_resources.py +++ b/docs/gallery/plot_external_resources.py @@ -100,6 +100,11 @@ import warnings warnings.filterwarnings("ignore", category=UserWarning, message="HERD is experimental*") +try: + import linkml_runtime # noqa: F401 +except ImportError as e: + raise ImportError("Please install linkml-runtime to run this example: pip install linkml-runtime") from e + try: dir_path = os.path.dirname(os.path.abspath(__file__)) yaml_file = os.path.join(dir_path, 'example_term_set.yaml') diff --git a/docs/source/conf.py b/docs/source/conf.py index 208ac325a..4898074d2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -109,7 +109,7 @@ # General information about the project. project = "HDMF" -copyright = "2017-2024, Hierarchical Data Modeling Framework" +copyright = "2017-2025, Hierarchical Data Modeling Framework" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/docs/source/index.rst b/docs/source/index.rst index 2fcd4778a..842bacc98 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -62,7 +62,6 @@ If you use HDMF in your research, please use the following citation: :caption: For Maintainers make_a_release - update_requirements .. toctree:: :hidden: diff --git a/docs/source/install_developers.rst b/docs/source/install_developers.rst index 04e351c41..72da40332 100644 --- a/docs/source/install_developers.rst +++ b/docs/source/install_developers.rst @@ -52,11 +52,11 @@ Option 2: Using conda The `conda package and environment management system`_ is an alternate way of managing virtual environments. First, install Anaconda_ to install the ``conda`` tool. Then create and -activate a new virtual environment called ``"hdmf-env"`` with Python 3.12 installed. +activate a new virtual environment called ``"hdmf-env"`` with Python 3.13 installed. .. code:: bash - conda create --name hdmf-env python=3.12 + conda create --name hdmf-env python=3.13 conda activate hdmf-env Similar to a virtual environment created with ``venv``, a conda environment @@ -88,8 +88,7 @@ package requirements using the pip_ Python package manager, and install HDMF in git clone --recurse-submodules https://github.com/hdmf-dev/hdmf.git cd hdmf - pip install -r requirements.txt -r requirements-dev.txt -r requirements-doc.txt -r requirements-opt.txt - pip install -e . + pip install -e ".[all]" .. note:: diff --git a/docs/source/make_a_release.rst b/docs/source/make_a_release.rst index d2da593bd..57dd26a2e 100644 --- a/docs/source/make_a_release.rst +++ b/docs/source/make_a_release.rst @@ -20,8 +20,7 @@ Prerequisites * You have a `GPG signing key`_. -* Dependency versions in ``requirements.txt``, ``requirements-dev.txt``, ``requirements-opt.txt``, - ``requirements-doc.txt``, and ``requirements-min.txt`` are up-to-date. +* Dependency versions are up-to-date. * Legal information and copyright dates in ``Legal.txt``, ``license.txt``, ``README.rst``, ``docs/source/conf.py``, and any other files are up-to-date. @@ -177,7 +176,7 @@ Publish release on conda-forge: Step-by-step Conda-forge maintains a bot called "regro-cf-autotick-bot" that regularly monitors PyPI for new releases of packages that are also on conda-forge. When a new release is detected, usually within 24 hours of publishing on PyPI, the bot will create a Pull Request with the correct modifications to the version and sha256 values - in ``meta.yaml``. If the requirements in ``setup.py`` have been changed, then you need to modify the + in ``meta.yaml``. If the requirements in ``pyproject.toml`` have been changed, then you need to modify the requirements/run section in ``meta.yaml`` manually to reflect these changes. Once tests pass, merge the PR, and a new release will be published on Anaconda cloud. This is the easiest way to update the package version on conda-forge. @@ -242,7 +241,7 @@ In order to release a new version on conda-forge manually, follow the steps belo $ sha=$(openssl sha256 /tmp/hdmf-$release.tar.gz | awk '{print $2}') $ sed -i -- "3s/.*/{$ set sha256 = \"$sha\" %}/" recipe/meta.yaml - If the requirements in ``setup.py`` have been changed, then modify the requirements/run list in + If the requirements in ``pyproject.toml`` have been changed, then modify the requirements/run list in the ``meta.yaml`` file to reflect these changes. diff --git a/docs/source/software_process.rst b/docs/source/software_process.rst index 30501769e..f3a6c7457 100644 --- a/docs/source/software_process.rst +++ b/docs/source/software_process.rst @@ -45,48 +45,44 @@ pyproject.toml_ contains a list of package dependencies and their version ranges running HDMF. As a library, upper bound version constraints create more harm than good in the long term (see this `blog post`_) so we avoid setting upper bounds on requirements. -If some of the packages are outdated, see :ref:`update_requirements_files`. +When setting lower bounds, make sure to specify the lower bounds in both pyproject.toml_ and +requirements-min.txt_. The latter is used in automated testing to ensure that the package runs +correctly using the minimum versions of dependencies. + +Minimum requirements should be updated manually if a new feature or bug fix is added in a dependency that is required +for proper running of HDMF. Minimum requirements should also be updated if a user requests that HDMF be installable +with an older version of a dependency, all tests pass using the older version, and there is no valid reason for the +minimum version to be as high as it is. .. _pyproject.toml: https://github.com/hdmf-dev/hdmf/blob/dev/pyproject.toml .. _blog post: https://iscinumpy.dev/post/bound-version-constraints/ +.. _requirements-min.txt: https://github.com/hdmf-dev/hdmf/blob/dev/requirements-min.txt -------------------- Testing Requirements -------------------- -There are several kinds of requirements files used for testing PyNWB. - -The first one is requirements-min.txt_, which lists the package dependencies and their minimum versions for -installing HDMF. +pyproject.toml_ contains the optional dependency group "test" with testing requirements. -The second one is requirements.txt_, which lists the pinned (concrete) dependencies to reproduce -an entire development environment to use HDMF. +See tox.ini_ and the GitHub Actions workflows for how different testing environments are +defined using the optional dependency groups. -The third one is requirements-dev.txt_, which list the pinned (concrete) dependencies to reproduce -an entire development environment to use HDMF, run HDMF tests, check code style, compute coverage, and create test -environments. +environment-ros3.yml_ lists the dependencies used to test ROS3 streaming in HDMF which +can only be done in a Conda environment. -The fourth one is requirements-opt.txt_, which lists the pinned (concrete) optional dependencies to use all -available features in HDMF. - -The final one is environment-ros3.yml_, which lists the dependencies used to -test ROS3 streaming in HDMF. - -.. _requirements-min.txt: https://github.com/hdmf-dev/hdmf/blob/dev/requirements-min.txt -.. _requirements.txt: https://github.com/hdmf-dev/hdmf/blob/dev/requirements.txt -.. _requirements-dev.txt: https://github.com/hdmf-dev/hdmf/blob/dev/requirements-dev.txt -.. _requirements-opt.txt: https://github.com/hdmf-dev/hdmf/blob/dev/requirements-opt.txt +.. _tox.ini: https://github.com/hdmf-dev/hdmf/blob/dev/tox.ini .. _environment-ros3.yml: https://github.com/hdmf-dev/hdmf/blob/dev/environment-ros3.yml -------------------------- Documentation Requirements -------------------------- -requirements-doc.txt_ lists the dependencies to generate the documentation for HDMF. -Both this file and `requirements.txt` are used by ReadTheDocs_ to initialize the local environment for Sphinx to run. +pyproject.toml_ contains the optional dependency group "docs" with documentation requirements. +This dependency group is used by ReadTheDocs_ to initialize the local environment for Sphinx to run +(see .readthedocs.yaml_). -.. _requirements-doc.txt: https://github.com/hdmf-dev/hdmf/blob/dev/requirements-doc.txt .. _ReadTheDocs: https://readthedocs.org/projects/hdmf/ +.. _.readthedocs.yaml: https://github.com/hdmf-dev/hdmf/blob/dev/.readthedocs.yaml ------------------------- Versioning and Releasing diff --git a/docs/source/update_requirements.rst b/docs/source/update_requirements.rst deleted file mode 100644 index 65b4b99d4..000000000 --- a/docs/source/update_requirements.rst +++ /dev/null @@ -1,78 +0,0 @@ - -.. _update_requirements_files: - -================================ -How to Update Requirements Files -================================ - -The different requirements files introduced in :ref:`software_process` section are the following: - -* requirements.txt_ -* requirements-dev.txt_ -* requirements-doc.txt_ -* requirements-min.txt_ -* requirements-opt.txt_ - -.. _requirements.txt: https://github.com/hdmf-dev/hdmf/blob/dev/requirements.txt -.. _requirements-dev.txt: https://github.com/hdmf-dev/hdmf/blob/dev/requirements-dev.txt -.. _requirements-doc.txt: https://github.com/hdmf-dev/hdmf/blob/dev/requirements-doc.txt -.. _requirements-min.txt: https://github.com/hdmf-dev/hdmf/blob/dev/requirements-min.txt -.. _requirements-opt.txt: https://github.com/hdmf-dev/hdmf/blob/dev/requirements-opt.txt - -requirements.txt -================ - -`requirements.txt` of the project can be created or updated and then captured using -the following script: - -.. code:: - - mkvirtualenv hdmf-requirements - - cd hdmf - pip install . - pip check # check for package conflicts - pip freeze > requirements.txt - - deactivate - rmvirtualenv hdmf-requirements - - -requirements-(dev|doc|opt).txt -============================== - -Any of these requirements files can be updated using -the following scripts: - -.. code:: - - cd hdmf - - # Set the requirements file to update - target_requirements=requirements-dev.txt - - mkvirtualenv hdmf-requirements - - # Install updated requirements - pip install -U -r $target_requirements - - # If relevant, you could pip install new requirements now - # pip install -U - - # Check for any conflicts in installed packages - pip check - - # Update list of pinned requirements - pip freeze > $target_requirements - - deactivate - rmvirtualenv hdmf-requirements - - -requirements-min.txt -==================== - -Minimum requirements should be updated manually if a new feature or bug fix is added in a dependency that is required -for proper running of HDMF. Minimum requirements should also be updated if a user requests that HDMF be installable -with an older version of a dependency, all tests pass using the older version, and there is no valid reason for the -minimum version to be as high as it is. diff --git a/environment-ros3.yml b/environment-ros3.yml index be2b9d4f6..6b4f6c472 100644 --- a/environment-ros3.yml +++ b/environment-ros3.yml @@ -1,4 +1,4 @@ -# pinned dependencies to reproduce an entire development environment to use PyNWB with ROS3 support +# environment file used to test HDMF with ROS3 support name: ros3 channels: - conda-forge @@ -7,9 +7,8 @@ dependencies: - python==3.13 - h5py==3.12.1 - matplotlib==3.9.2 - - numpy==2.1.3 + - numpy==2.2.1 - pandas==2.2.3 - - python-dateutil==2.8.2 - - pytest==8.1.2 # regression introduced in pytest 8.2.*, will be fixed in 8.3.0 - - pytest-cov==5.0.0 - - setuptools + - python-dateutil==2.9.0.post0 + - pytest==8.3.4 + - pytest-cov==6.0.0 diff --git a/license.txt b/license.txt index f7964f329..c43f1f876 100644 --- a/license.txt +++ b/license.txt @@ -1,4 +1,4 @@ -“hdmf” Copyright (c) 2017-2024, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. +“hdmf” Copyright (c) 2017-2025, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/pyproject.toml b/pyproject.toml index 9b13aec54..5308543d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,29 @@ termset = [ "pyyaml>=6.0.1", ] +# development dependencies +test = [ + "codespell", + "pre-commit", + "pytest", + "pytest-cov", + "python-dateutil", + "ruff", + "tox", +] + +# documentation dependencies +docs = [ + "matplotlib", + "sphinx>=4", # improved support for docutils>=0.17 + "sphinx_rtd_theme>=1", # <1 does not work with docutils>=0.17 + "sphinx-gallery", + "sphinx-copybutton", +] + +# all possible dependencies +all = ["hdmf[tqdm,zarr,termset,test,docs]"] + [project.urls] "Homepage" = "https://github.com/hdmf-dev/hdmf" "Bug Tracker" = "https://github.com/hdmf-dev/hdmf/issues" @@ -130,6 +153,7 @@ exclude = [ "src/hdmf/_due.py", "docs/source/tutorials/", "docs/_build/", + "scripts/" ] line-length = 120 diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 95cf0797e..000000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,14 +0,0 @@ -# pinned dependencies to reproduce an entire development environment to use HDMF, run HDMF tests, check code style, -# compute coverage, and create test environments. note that depending on the version of python installed, different -# versions of requirements may be installed due to package incompatibilities. -# -black==24.4.2 -codespell==2.3.0 -coverage==7.5.4 -pre-commit==3.7.1; python_version >= "3.9" -pre-commit==3.5.0; python_version < "3.9" -pytest==8.1.2 # regression introduced in pytest 8.2.*, will be fixed in 8.3.0 -pytest-cov==5.0.0 -python-dateutil==2.8.2 -ruff==0.5.0 -tox==4.15.1 diff --git a/requirements-doc.txt b/requirements-doc.txt deleted file mode 100644 index 32a790cf8..000000000 --- a/requirements-doc.txt +++ /dev/null @@ -1,6 +0,0 @@ -# dependencies to generate the documentation for HDMF -matplotlib -sphinx>=4 # improved support for docutils>=0.17 -sphinx_rtd_theme>=1 # <1 does not work with docutils>=0.17 -sphinx-gallery -sphinx-copybutton diff --git a/requirements-opt.txt b/requirements-opt.txt deleted file mode 100644 index b2bba6814..000000000 --- a/requirements-opt.txt +++ /dev/null @@ -1,6 +0,0 @@ -# pinned dependencies that are optional. used to reproduce an entire development environment to use HDMF -tqdm==4.66.4 -zarr==2.18.4 -linkml-runtime==1.7.7 -schemasheets==0.2.1 -oaklib==0.6.10 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index b431b4e63..000000000 --- a/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -# pinned dependencies to reproduce an entire development environment to use HDMF -h5py==3.12.1 -jsonschema==4.22.0 -numpy==2.1.3 -ruamel.yaml==0.18.2 -pandas==2.2.3 -scipy==1.14.1; python_version >= "3.10" -scipy==1.11.3; python_version < "3.10" diff --git a/scripts/check_py_support.py b/scripts/check_py_support.py new file mode 100644 index 000000000..5c48dac3c --- /dev/null +++ b/scripts/check_py_support.py @@ -0,0 +1,205 @@ +""" +Python Version Support Checker + +This script analyzes Python package dependencies listed in pyproject.toml to check their +compatibility with a specified Python version (default: 3.13). It examines both regular +and optional dependencies, checking their trove classifiers for explicit version support. + +The script provides: +- Grouped output of supported and unsupported packages +- Latest supported Python version for packages without explicit support +- Error reporting for packages that cannot be checked +- Summary statistics of compatibility status + +Usage: + # Run this command from the root of the repo + python scripts/check_py_support.py + +Requirements: + - Python 3.11+ + - packaging + - colorama + +Input: + - pyproject.toml file in the current directory + +Output format: + - Supported packages (green) with their versions + - Unsupported packages (red) with their versions and latest supported Python version + - Packages with errors (yellow) + - Summary statistics + +Note: + The absence of explicit version support in trove classifiers doesn't necessarily + indicate incompatibility, just that the package hasn't declared support. +""" + +import tomllib +import importlib.metadata +from pathlib import Path +from packaging.requirements import Requirement +from colorama import init, Fore, Style +from typing import NamedTuple +import re + +# Initialize colorama +init() + +# Global configuration +PYTHON_VERSION = "3.13" + +class PackageSupport(NamedTuple): + name: str + spec: str + version: str | None + latest_python: str | None + error: str | None + +def parse_dependencies(pyproject_path: Path) -> list[str]: + """Parse dependencies from pyproject.toml, including optional dependencies.""" + with pyproject_path.open("rb") as f: + pyproject = tomllib.load(f) + + # Get main dependencies + dependencies = pyproject.get("project", {}).get("dependencies", []) + + # Get optional dependencies and flatten them + optional_deps = pyproject.get("project", {}).get("optional-dependencies", {}) + for group_deps in optional_deps.values(): + dependencies.extend(group_deps) + + return dependencies + +def get_package_name(dependency_spec: str) -> str: + """Extract package name from dependency specification.""" + return Requirement(dependency_spec).name + +def get_latest_python_version(classifiers: list[str]) -> str | None: + """Extract the latest supported Python version from classifiers.""" + python_versions = [] + pattern = r"Programming Language :: Python :: (\d+\.\d+)" + + for classifier in classifiers: + match = re.match(pattern, classifier) + if match: + version = match.group(1) + try: + major, minor = map(int, version.split('.')) + python_versions.append((major, minor)) + except ValueError: + continue + + if not python_versions: + return None + + # Sort by major and minor version + latest = sorted(python_versions, key=lambda x: (x[0], x[1]), reverse=True)[0] + return f"{latest[0]}.{latest[1]}" + +def check_python_version_support(package_name: str) -> dict[str, str | bool | None]: + """Check if installed package supports Python 3.13.""" + try: + dist = importlib.metadata.distribution(package_name) + classifiers = dist.metadata.get_all('Classifier') + version_classifier = f"Programming Language :: Python :: {PYTHON_VERSION}" + + return { + 'installed_version': dist.version, + 'has_support': version_classifier in classifiers, + 'latest_python': get_latest_python_version(classifiers), + 'error': None + } + except importlib.metadata.PackageNotFoundError: + return { + 'installed_version': None, + 'has_support': False, + 'latest_python': None, + 'error': 'Package not installed' + } + except Exception as e: + return { + 'installed_version': None, + 'has_support': False, + 'latest_python': None, + 'error': str(e) + } + +def print_section_header(title: str, count: int) -> None: + """Print a formatted section header with count.""" + print(f"\n{Fore.CYAN}{title} ({count} packages){Style.RESET_ALL}") + print(f"{Fore.BLUE}{'-' * 100}{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{'Package':<25} {'Specification':<30} {'Version':<20} {'Latest Python'}{Style.RESET_ALL}") + print(f"{Fore.BLUE}{'-' * 100}{Style.RESET_ALL}") + +def main() -> None: + pyproject_path = Path("pyproject.toml") + + if not pyproject_path.exists(): + print(f"{Fore.RED}Error: pyproject.toml not found{Style.RESET_ALL}") + return + + try: + dependencies = parse_dependencies(pyproject_path) + except Exception as e: + print(f"{Fore.RED}Error parsing pyproject.toml: {e}{Style.RESET_ALL}") + return + + # Check each dependency + supported: list[PackageSupport] = [] + unsupported: list[PackageSupport] = [] + errors: list[PackageSupport] = [] + + for dep in dependencies: + package_name = get_package_name(dep) + result = check_python_version_support(package_name) + + package_info = PackageSupport( + name=package_name, + spec=dep, + version=result['installed_version'], + latest_python=result['latest_python'], + error=result['error'] + ) + + if result['error']: + errors.append(package_info) + elif result['has_support']: + supported.append(package_info) + else: + unsupported.append(package_info) + + # Print results + print(f"\n{Fore.CYAN}Python {PYTHON_VERSION} Explicit Support Check Results{Style.RESET_ALL}") + print(f"{Fore.BLUE}{'=' * 100}{Style.RESET_ALL}") + + # Print supported packages + if supported: + print_section_header("Supported Packages", len(supported)) + for pkg in supported: + print(f"{Fore.GREEN}{pkg.name:<25} {pkg.spec:<30} {pkg.version:<20} {PYTHON_VERSION}{Style.RESET_ALL}") + + # Print unsupported packages + if unsupported: + print_section_header("Unsupported Packages", len(unsupported)) + for pkg in unsupported: + latest = f"→ {pkg.latest_python}" if pkg.latest_python else "unknown" + print(f"{Fore.RED}{pkg.name:<25} {pkg.spec:<30} {pkg.version:<20} {latest}{Style.RESET_ALL}") + + # Print packages with errors + if errors: + print_section_header("Packages with Errors", len(errors)) + for pkg in errors: + print(f"{Fore.YELLOW}{pkg.name:<25} {pkg.spec:<30} {pkg.error:<20} N/A{Style.RESET_ALL}") + + # Print summary + print(f"\n{Fore.CYAN}Summary:{Style.RESET_ALL}") + print(f"{Fore.BLUE}{'-' * 100}{Style.RESET_ALL}") + total = len(supported) + len(unsupported) + len(errors) + print(f"{Fore.GREEN}Supported: {len(supported):3d} ({len(supported)/total*100:.1f}%){Style.RESET_ALL}") + print(f"{Fore.RED}Unsupported: {len(unsupported):3d} ({len(unsupported)/total*100:.1f}%){Style.RESET_ALL}") + if errors: + print(f"{Fore.YELLOW}Errors: {len(errors):3d} ({len(errors)/total*100:.1f}%){Style.RESET_ALL}") + print(f"{Fore.CYAN}Total: {total:3d}{Style.RESET_ALL}") + +if __name__ == "__main__": + main() diff --git a/test_gallery.py b/test_gallery.py index c3128b8fd..b2f0a9047 100644 --- a/test_gallery.py +++ b/test_gallery.py @@ -67,8 +67,9 @@ def run_gallery_tests(): ) _import_from_file(script) except (ImportError, ValueError) as e: - if "linkml" in str(e): - pass # this is OK because linkml is not always installed + if "Please install linkml-runtime to run this example" in str(e): + # this is OK because linkml is not always installed + print(f"Skipping {script} because linkml-runtime is not installed") else: raise e except Exception: diff --git a/tox.ini b/tox.ini index 42305ae25..775cb7592 100644 --- a/tox.ini +++ b/tox.ini @@ -4,54 +4,54 @@ # and then run "tox -e [envname]" from this directory. [tox] -requires = pip >= 22.0 +requires = pip >= 24.3.1 [testenv] download = True setenv = PYTHONDONTWRITEBYTECODE = 1 - VIRTUALENV_PIP = 23.3.1 recreate = - pinned, minimum, upgraded, prerelease: False + minimum, upgraded, prerelease: False build, wheelinstall: True # good practice to recreate the environment skip_install = - pinned, minimum, upgraded, prerelease, wheelinstall: False + minimum, upgraded, prerelease, wheelinstall: False build: True # no need to install anything when building install_command = # when using [testenv:wheelinstall] and --installpkg, the wheel and its dependencies # are installed, instead of the package in the current directory - pinned, minimum, wheelinstall: python -I -m pip install {opts} {packages} - upgraded: python -I -m pip install -U {opts} {packages} - prerelease: python -I -m pip install -U --pre {opts} {packages} + minimum, wheelinstall: python -I -m pip install {opts} {packages} + upgraded: python -I -m pip install -U {opts} {packages} + prerelease: python -I -m pip install -U --pre {opts} {packages} deps = - # use pinned, minimum, or neither (use dependencies in pyproject.toml) - pytest, gallery: -rrequirements-dev.txt - gallery: -rrequirements-doc.txt - optional: -rrequirements-opt.txt - pinned: -rrequirements.txt - minimum: -rrequirements-min.txt + # which requirements files to use (default: none) + minimum: -r requirements-min.txt +extras = + # which optional dependency set(s) to use (default: none) + pytest: test + gallery: doc + optional: tqdm,zarr,termset commands = + # commands to run for every environment python --version # print python version for debugging python -m pip check # check for conflicting packages python -m pip list # list installed packages for debugging + + # commands to run for select environments pytest: pytest -v gallery: python test_gallery.py build: python -m pip install -U build build: python -m build wheelinstall: python -c "import hdmf; import hdmf.common" -# list of pre-defined environments. (Technically environments not listed here -# like build-py312 can also be used.) -[testenv:pytest-py313-upgraded] -[testenv:pytest-py313-prerelease] -[testenv:pytest-py311-optional-pinned] # some optional reqs not compatible with py312 yet -[testenv:pytest-py{39,310,311,312,313}-pinned] +# list of pre-defined environments +[testenv:pytest-py{39,310,311,312,313}-upgraded] +[testenv:pytest-py313-upgraded-optional] +[testenv:pytest-py313-prerelease-optional] [testenv:pytest-py39-minimum] -[testenv:gallery-py313-upgraded] -[testenv:gallery-py313-prerelease] -[testenv:gallery-py311-optional-pinned] -[testenv:gallery-py{39,310,311,312,313}-pinned] +# TODO: Update to 3.13 when linkml and its deps support 3.13 +[testenv:gallery-py312-upgraded-optional] +[testenv:gallery-py312-prerelease-optional] [testenv:gallery-py39-minimum] [testenv:build] # using tox for this so that we can have a clean build environment From b115ca335f8b326b0de7a202f1df8e1e7d5c7711 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jan 2025 08:34:38 -0800 Subject: [PATCH 82/82] [pre-commit.ci] pre-commit autoupdate (#1231) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 05fbd3e04..03680eb2f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,7 +18,7 @@ repos: # hooks: # - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.8.6 + rev: v0.9.1 hooks: - id: ruff # - repo: https://github.com/econchick/interrogate