pax_global_header00006660000000000000000000000064146233123670014521gustar00rootroot0000000000000052 comment=02a92741e09db1299fd7ab00627fa8f3d54a14f2 pyvo-1.5.2/000077500000000000000000000000001462331236700125235ustar00rootroot00000000000000pyvo-1.5.2/.github/000077500000000000000000000000001462331236700140635ustar00rootroot00000000000000pyvo-1.5.2/.github/workflows/000077500000000000000000000000001462331236700161205ustar00rootroot00000000000000pyvo-1.5.2/.github/workflows/changelog.yml000066400000000000000000000010111462331236700205630ustar00rootroot00000000000000name: Changelog check on: pull_request: types: [labeled, unlabeled, opened, synchronize, reopened] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: changelog: name: Check changelog entry runs-on: ubuntu-latest steps: - name: Check change log entry uses: scientific-python/action-check-changelogfile@865ff8154dd94f008f08de6bb8d8c1f661113658 env: CHANGELOG_FILENAME: CHANGES.rst GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} pyvo-1.5.2/.github/workflows/ci_devtests.yml000066400000000000000000000015501462331236700211600ustar00rootroot00000000000000# This test job is separated out into its own workflow to be able to trigger separately name: CI-devtest on: push: branches: - main - 'v*' tags: - '*' pull_request: schedule: - cron: "0 3 * * 6" workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: devdeps: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python 3.12 uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install tox run: python -m pip install --upgrade tox - name: Run tests against dev dependencies run: tox -e py312-test-devdeps-alldeps-cov - name: Upload coverage to codecov uses: codecov/codecov-action@v3 with: file: ./coverage.xml verbose: true pyvo-1.5.2/.github/workflows/ci_tests.yml000066400000000000000000000042211462331236700204570ustar00rootroot00000000000000# Developer version testing is in separate workflow name: CI on: push: branches: - main - 'v*' tags: - '*' pull_request: schedule: - cron: "0 3 * * 6" workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: tests: runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - name: py38 oldest dependencies, Linux python-version: '3.8' tox_env: py38-test-oldestdeps-alldeps - name: py39 mandatory dependencies only, Linux python-version: '3.9' tox_env: py39-test - name: py311 with online tests, Linux python-version: '3.11' tox_env: py311-test-alldeps-online - name: linkcheck python-version: '3.10' tox_env: linkcheck steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install tox run: python -m pip install --upgrade tox - name: Install library dependencies and run Tests run: tox -e ${{ matrix.tox_env }} mac_windows: runs-on: ${{ matrix.os }} name: ${{ matrix.os }} py310 strategy: fail-fast: false matrix: os: [macos-latest, windows-latest] steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.10' - name: Install tox run: python -m pip install --upgrade tox - name: Python 3.10 with latest astropy run: tox -e py310-test-alldeps stylecheck: runs-on: ubuntu-latest strategy: fail-fast: false steps: - uses: actions/checkout@v4 - name: Set up Python 3.8 uses: actions/setup-python@v5 with: python-version: 3.8 - name: Install tox run: python -m pip install --upgrade tox - name: Check codestyle run: tox -e codestyle pyvo-1.5.2/.gitignore000066400000000000000000000013231462331236700145120ustar00rootroot00000000000000# Compiled files *.py[cod] *.a *.o *.so __pycache__ # Ignore .c files by default to avoid including generated code. If you want to # add a non-generated .c extension, use `git add -f filename.c`. *.c # Other generated files */version.py */cython_version.py htmlcov .coverage MANIFEST .ipynb_checkpoints .hypothesis # Sphinx docs/api docs/_build # Eclipse editor project files .project .pydevproject .settings # Pycharm editor project files .idea # Packages/installer info *.egg *.eggs *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg distribute-*.tar.gz pip-wheel-metadata # Other .cache .tox .*.sw[op] *~ .project .pydevproject .settings # Mac OSX .DS_Store # ipython .ipynb_checkpoints pyvo-1.5.2/.mailmap000066400000000000000000000020471462331236700141470ustar00rootroot00000000000000Adam Ginsburg Adrian Damian Adrian Damian Brigitta Sipőcz Brigitta Sipőcz Christine Banek Chuanming Mao Chuanming Mao <113032306+ChuanmingMao@users.noreply.github.com> Dustin Jenkins Hugo van Kemenade Markus Demleitner Pey Lian Lim <2090236+pllim@users.noreply.github.com> Ray Plante Ray Plante Stefan Becker Tess Jaffe Theresa Dower Tim Jenness Tom Donaldson pyvo-1.5.2/.readthedocs.yml000066400000000000000000000010241462331236700156060ustar00rootroot00000000000000version: 2 build: image: latest sphinx: builder: html configuration: docs/conf.py fail_on_warning: true # Install regular dependencies. # Then, install special pinning for RTD. build: os: ubuntu-22.04 tools: python: "3.10" apt_packages: - graphviz jobs: post_checkout: - git fetch --unshallow || true python: install: - requirements: docs/requirements.txt - method: pip path: . extra_requirements: - docs - all # Don't build any extra formats formats: [] pyvo-1.5.2/CHANGES.rst000066400000000000000000000225441462331236700143340ustar00rootroot000000000000001.5.2 (2024-05-22) ================== Bug Fixes --------- - Avoid Astropy Time error for SIAResult.dateobs when VOX:Image_MJDateObs or ssa:DataID.Date is nan. [#550] - More robust handling of SIA1 FORMAT [#545] 1.5.1 (2024-02-21) ================== Bug Fixes --------- - Fix ``pyvo.registry.Author`` to allow registry searches with author constraints. [#515] - Backing out of having alt_identifier in RegistryResource throughout. Use get_alt_identifier() instead [#523] - Fix ``maxrec=0`` special case for SIA2 queries. [#520] 1.5 (2023-12-19) ================ Enhancements and Fixes ---------------------- - ``registry.search`` now allows programmatic selection of the registry TAP service endpoint with the ``choose_RegTAP_service`` function. [#386] - ``registry.search`` now introspects the TAP service's capabilities and only offers extended functionality or optimisations if the required features are present [#386] - Registry search now finds SIA v2 services. [#422, #428] - Made SIA2Service accept access urls without finding them in the service capabilities. [#500] - Fix session inheritance in SIA2. [#490] - Add intersect modes for the spatial constraint in the registry module ``pyvo.registry.Spatial``. [#495] - Added ``alt_identifier``, ``created``, ``updated`` and ``rights`` to the attributes of ``pyvo.registry.regtap.RegistryResource`` [#492] - Added the ``source_value`` and ``alt_identifier`` information to the verbose output of ``describe()`` in ``regtap``. [#492] - Added convenience method DALResults.to_qtable() that returns an astropy.table.QTable object. [#384] - TAP examples now support the continuation property. [#483] - Fix poor polling behavior when running an async query against a TAP v1.1 service with unsupported WAIT parameter. [#440] - Adding python version to User-Agent. [#452] - Output of ``repr`` for DALResults instance now clearly shows it is a DALResultsTable and not a generic astropy Table. [#478] - Adding support for the VODataService 1.2 nrows attribute on table elements. [#503] Deprecations and Removals ------------------------- - Classes ``SIAService``, ``SIAQuery``, ``SIAResults`` for SIA v2 have been renamed to ``SIA2Service``, ``SIA2Query``, ``SIA2Results`` respectively as well as the variable ``SIA_PARAMETERS_DESC`` to ``SIA2_PARAMETERS_DESC``. The old names now issue an ``AstropyDeprecationWarning``. [#419] - Class ``pyvo.vosi.vodataservice.Table`` has been renamed to ``VODataServiceTable`` to avoid sharing the name with a more generic ``astropy.table.Table`` while having different API. [#484] - Deprecate VOSI ``AvailabilityMixin``, this mean the deprecation of the inherited ``availability``, ``available``, and ``up_since`` properties of DAL service classes, too. [#413] - Deprecating ``ivoid2service`` because it is ill-defined. [#439] 1.4.2 (2023-08-16) ================== - Fixed TapResults to inherit session. [#447] - Fix handling of nan values for Time properties in SIA2 records. [#463] - Fix SIA2 search to accept SkyCoord position inputs. [#459] - Favouring ``VOX:Image_AccessReference`` for data url for SIA1 queries. [#445] 1.4.1 (2023-03-07) ================== - ``pyvo.registry.search`` now accepts an optional ``maxrec`` argument rather than automatically passing the service's hard limit. [#375] - Fixed the RegTAP fragment for the discovery of EPN-TAP tables. [#395] - Removed defaults for optional SIAv1 and SSA query parameters to avoid unnecessarily overriding the server-side defaults. [#367] - Error messages from uws jobs are now in job.errorsummary.message rather than job.message (where one wouldn't expect them given the UWS schema). [#432] - Avoid raising ``AttributeError`` for None responses. [#392] 1.4 (2022-09-26) ================ - Added the TAP Table Manipulation prototype (cadc-tb-upload). [#274] - More explicit exception messages where the payload is sometimes considered if it can be presented properly (simple body text or job error message). [#355] - we now ignore namespaces in xsi-type attributes; this is a lame fix for services like ESO's and MAST's TAP, which do not use canonical prefixes while astropy.utils.xml ignores namespaces. [#323] - Overhaul of the registry.regsearch as discussed in https://blog.g-vo.org/towards-data-discovery-in-pyvo.html. This should be backwards-compatible. [#289] - Versions of astropy <4.1 are no longer supported. [#289] - pyvo.dal warns on results with overflow status. [#329] - Allow session to be passed through in SSA, SCR, and DataLink. [#327] - pyvo.dal.tap.AsyncTAPJob treats parameter names as case-insensitive when retrieving the query from the job record. [#357] - Adding support for prototype features via the ``prototype_feature`` decorator . [#309] - No longer formatting microseconds into SSA time literals. [#351] - Adding operating system to User-Agent. [#344] 1.3 (2022-02-19) ================== - pyvo deals with non-core terms in datalink.bysemantics again. [#299] - Versions of Python <3.8 are no longer supported. [#290] 1.2.1 (2022-01-12) ================== - Get wraps decorator from functools instead of astropy. [#283] 1.2 (2021-12-17) ================ - Make .bysemantics expand its terms to the entire branch by default [#241] - Added optional includeaux flag for regTAP search() [#258] - Added VOResource 1.1 mirrorurl and testquerystring to vosi.Interface [#269] - Versions of Python <3.7 are no longer supported. [#255] 1.1 (2020-06-26) ================ - Added TAP examples function. [#220] - Add default for UWS version. [#199] - Handle description of None when describing a TAP service's tables. [#197] - Properly handle single string keywords value for regsearch(). [#201] - Add support for SIA2. [#206] - Add kwargs to sia2. [#222] - Fix handling relative result URLs. [#192] 1.0 (2019-09-20) ================ - Fix pedantic table parsing not throwing exception. [#140] - Drop support for legacy Python 2.7. [#153] - Sphinx 1.7 or higher is needed to build the documentation. [#160] - Add support for authenticated requests. [#157] - Add a get_job_list method to the TAPService class. [#169] - Replace example's usage of pyvo.object2pos() with SkyCoord.from_name() [#171] - Stop installing files from scripts to /usr/local/bin. Move them to examples/images instead. [#166] - Update ex_casA_image_cat example. [#172] - Fix waveband option in registry.regsearch [#175] - Fix to regtap.ivoid2service(), few decode()'s, para_format_desc was moved to utils. [#177] - Fix default result id for fetch_results of async TAP. [#148] 0.9.3 (2019-05-30) ================== - Fix parsing of SecurityMethod in capabilities. [#114] - Keep up to date with upstream astropy changes. - Move into astropy GitHub organization and README updates. [#133] - Replace mimetype functions with library-based ones. 0.9.2 (2018-10-05) ================== - Fix typo fornat -> format. [#106] 0.9.1 (2018-10-02) ================== - Don't use OR's in RegTAP queries. - Add a timeout to job wait. 0.9 (2018-09-18) ================ - Add a describe method to services to print a human-readable description. - Use a customized user agent in http requests. - Fix some python2/3 issues. - Add general datalink processing method. [#103] 0.8.1 (2018-06-27) ================== - Pass use_names_over_ids=True to astropy's to_table. 0.8 (2018-06-07) ================ - Make XML handling more generic. 0.7rc1 (2018-02-18) =================== - Rework VOSI parsing using astropy xml handling. [#88] - Describe service object bases on vosi capabilities. - Add SODA functionallity. - Fixes and Improvements. 0.6.1 (2017-06-29) ================== - Add Datalink interface. - Put some common functionallity in Mixins. - Minor fixes and improvements. 0.6 (2017-04-17) ================ - Using RegTAP as the only registry interface. - Added a datamodel keyword to registry search. - Using the six libray to address Python 2/3 compatibility issues. - AsyncTAPJob is now context aware. - Improvement upload handling; it is no longer necessary to specifiy the type of upload. - Allow astropy's SkyCoord and Quantity as input parameters. 0.5.2 (2017-02-09) ================== - Remove trailing ? from query urls. [#78] - VOTable fieldnames are now gathered from names only instead of ID and name. 0.5.1 (2017-02-02) ================== - Fix content decoding related error in async result handling. 0.5 (2017-01-13) ================ - Added a RegTAP interface. [#73] - Removed urllib in favor of the requests library. [#74] - Deprecated vao registry interface. - Minor improvements and fixes. 0.4.1 (2016-12-02) ================== - Fix a bug where maxrec wasn't send to the server. 0.4 (2016-12-02) ================ - Use astropy tables for table metadata. [#71] - Fix another content encoding error. [#72] 0.3.2 (2016-12-02) ================== - Adding table property to DALResults. This is a shortcut to access the astropy table. - Improved Error Handling. - Adding ``upload_methods`` to TAPService. [#69] 0.3.1 (2016-12-02) ================== - Fix an error where the content wasn't decoded properly. [#67] - Fix a bug where POST parameters are submitted as GET parameters. 0.3 (2016-12-02) ================ - Adding TAP API. [#58, #66] 0.1 (2016-12-02) ================ - This is the last release that supports Python 2.6. [#62] - This release only contains bug fixes beyond 0.0beta2. pyvo-1.5.2/CONTRIBUTORS.rst000066400000000000000000000016641462331236700152210ustar00rootroot00000000000000PyVO Contributors ----------------- PyVO is an open source project developed through GitHub at https://github.com/pyvirtobs; community contributions are welcome. This project began in 2012 as a product of the US Virtual Astronomical Observatory, funded through a cooperative agreement with the US National Science Foundation. PyVO was developed with contributions from the following developers: Contributor's Name | Affiliations | GitHub identity ---------------------|----------------|------------------- Stefan Becker | Heidelberg Univ. | funbaker Thomas Boch | CDS | tboch Carlos Brandt | Universita di Roma La Sapienza | chbrandt Markus Demleitner | Heidelberg Univ. | msdemlei Christoph Deil | MPI for Nuclear Physics | cdeil Mike Fitzpatrick | NOAO | Matthew Graham | Caltech | doccosmos Gus Muench | Harvard/CfA, AAS | augustfly Ray Plante | NCSA/UIUC, NIST | RayPlante Brigitta Sipocz | Cambridge, UK | bsipocz Doug Tody | NRAO | pyvo-1.5.2/LICENSE.rst000066400000000000000000000027741462331236700143510ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2020, Astropy-pyvo Developers 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. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pyvo-1.5.2/MANIFEST.in000066400000000000000000000003771462331236700142700ustar00rootroot00000000000000include README.rst include CHANGES.rst include LICENSE.rst include setup.cfg include pyproject.toml recursive-include pyvo *py recursive-include docs * recursive-include licenses * prune build prune docs/_build prune docs/api global-exclude *.pyc *.o pyvo-1.5.2/README.rst000066400000000000000000000067751462331236700142310ustar00rootroot00000000000000PyVO =================================== .. image:: http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat :target: https://www.astropy.org :alt: Powered by Astropy Badge .. image:: https://github.com/astropy/pyvo/workflows/CI/badge.svg?branch=main :target: https://github.com/astropy/pyvo/workflows/CI/badge.svg?branch=main :alt: CI Status .. image:: https://codecov.io/gh/astropy/pyvo/branch/main/graph/badge.svg?token=Mynyo9xoPZ :target: https://codecov.io/gh/astropy/pyvo :alt: Coverage Status .. image:: https://zenodo.org/badge/10865450.svg :target: https://zenodo.org/badge/latestdoi/10865450 PyVO is a package providing access to remote data and services of the Virtual observatory (VO) using Python. Its development was launched by the NSF/NASA-funded Virtual Astronomical Observatory (VAO, www.usvao.org) project (formerly under the name VAOpy) as part of its initiative to bring VO capabilities to desktop. Its goal is to allow astronomers and tool developers to access data and services from remote archives and other web resources. It takes advantage of VO standards to give access to thousands of catalogs, data archives, information services, and analysis tools. It also takes advantage of the general capabilities of Astopy (and numpy), and so a secondary goal is to provide a development platform for migrating more VO capabilities into Astropy. Source code can be found `on GitHub `_ Installation and Requirements ----------------------------- Releases of PyVO are available from `PyPI `_ thus, it and its prerequisites can be most easily installed using ``pip``: pip install pyvo Releases are also conda packaged and available on the ``conda-forge`` channel. PyVO requires Python 3.8 or later. The following packages are required for PyVO: * `astropy `__ (>=4.1) * `requests `_ The following packages are optional dependencies and are required for the full functionality: * pillow For running the tests, and building the documentation, the following infrastructure packages are required: * `pytest-astropy `__ * requests-mock * `sphinx-astropy `__ To install from source use ``pip``: pip install .[all] Using the developer version of PyVO in testing ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ We recommend and encourage testing against the development version of PyVO in CI, both for dependent libraries and notebook providers. As PyVO is a pure Python library, this can be done as easily as pip installing the developer version from GitHub: pip install git+https://github.com/astropy/pyvo.git#egg=pyvo An example for setting up development version testing for a library as a GitHub Actions Workflow can be found in `astroquery `__. Examples -------- Many instructive examples can be found in the `PyVO Documentation `_. Additional examples can be found in the examples directory. Unit Tests ---------- PyVO uses the Astropy framework for unit tests which is built into the setup script. To run the tests, type: pip install .[test] pytest This will run all unit tests that do not require a network connection. To run all tests, including those that access the network, add the --remote-data option: pytest --remote-data pyvo-1.5.2/RELEASE.rst000066400000000000000000000015211462331236700143340ustar00rootroot00000000000000Release Procedure for pyvo ========================== These steps are intended to help guide a developer into making a new release. For these instructions, version is to be replaced by the version number of the release. 1. Edit setup.cfg and remove .dev from the version number 2. Edit CHANGES.rst to change unreleased to the date of the release. 3. Commit and push 4. git tag -a version -m "releasing new version version" (this makes a release tag) 5. git push origin version 6. python setup.py sdist (this makes a .tar.gz of the package in dist) 7. twine upload sdist/* (this uploads the output of the previous step to pypi) 8. Edit setup.cfg and set the version to the next release number and add .dev after the version number. Add a new section at the top for the next release number 9. Commit and push. This begins the new release pyvo-1.5.2/conftest.py000066400000000000000000000041041462331236700147210ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst # This file is the main file used when running tests with pytest directly, # in particular if running e.g. ``pytest docs/``. import os import tempfile try: from pytest_astropy_header.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS ASTROPY_HEADER = True except ImportError: ASTROPY_HEADER = False # Make sure we use temporary directories for the config and cache # so that the tests are insensitive to local configuration. os.environ['XDG_CONFIG_HOME'] = tempfile.mkdtemp('astropy_config') os.environ['XDG_CACHE_HOME'] = tempfile.mkdtemp('astropy_cache') os.mkdir(os.path.join(os.environ['XDG_CONFIG_HOME'], 'astropy')) os.mkdir(os.path.join(os.environ['XDG_CACHE_HOME'], 'astropy')) # Note that we don't need to change the environment variables back or remove # them after testing, because they are only changed for the duration of the # Python process, and this configuration only matters if running pytest # directly, not from e.g. an IPython session. try: from pyvo import __version__ as version except ImportError: version = 'unknown' # Disable IERS auto download for testing (to support the local, non-remote-data scenario), # revisit this config when minimum supported astropy is 5.1. from astropy.utils.iers import conf as iers_conf iers_conf.auto_download = False def pytest_configure(config): """Configure Pytest with Astropy. Parameters ---------- config : pytest configuration """ if ASTROPY_HEADER: config.option.astropy_header = True # Customize the following lines to add/remove entries from the list of # packages for which version numbers are displayed when running the tests. PYTEST_HEADER_MODULES['Astropy'] = 'astropy' # noqa PYTEST_HEADER_MODULES['requests'] = 'requests' # noqa PYTEST_HEADER_MODULES.pop('Pandas', None) PYTEST_HEADER_MODULES.pop('h5py', None) PYTEST_HEADER_MODULES.pop('Scipy', None) PYTEST_HEADER_MODULES.pop('Matplotlib', None) TESTED_VERSIONS['pyvo'] = version pyvo-1.5.2/docs/000077500000000000000000000000001462331236700134535ustar00rootroot00000000000000pyvo-1.5.2/docs/Makefile000066400000000000000000000111641462331236700151160ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest #This is needed with git because git doesn't create a dir if it's empty $(shell [ -d "_static" ] || mkdir -p _static) help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR) -rm -rf api html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Astropy.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Astropy.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Astropy" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Astropy" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." pyvo-1.5.2/docs/_templates/000077500000000000000000000000001462331236700156105ustar00rootroot00000000000000pyvo-1.5.2/docs/_templates/autosummary/000077500000000000000000000000001462331236700201765ustar00rootroot00000000000000pyvo-1.5.2/docs/_templates/autosummary/base.rst000066400000000000000000000003721462331236700216440ustar00rootroot00000000000000{% extends "autosummary_core/base.rst" %} {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #}pyvo-1.5.2/docs/_templates/autosummary/class.rst000066400000000000000000000003731462331236700220400ustar00rootroot00000000000000{% extends "autosummary_core/class.rst" %} {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #}pyvo-1.5.2/docs/_templates/autosummary/module.rst000066400000000000000000000003741462331236700222210ustar00rootroot00000000000000{% extends "autosummary_core/module.rst" %} {# The template this is inherited from is in astropy/sphinx/ext/templates/autosummary_core. If you want to modify this template, it is strongly recommended that you still inherit from the astropy template. #}pyvo-1.5.2/docs/auth/000077500000000000000000000000001462331236700144145ustar00rootroot00000000000000pyvo-1.5.2/docs/auth/index.rst000066400000000000000000000004141462331236700162540ustar00rootroot00000000000000.. _pyvo-auth: ****************** Auth (`pyvo.auth`) ****************** This module contains submodules which help handle auth when communicating with virtual observatory services. Reference/API ============= .. automodapi:: pyvo.auth :no-inheritance-diagram: pyvo-1.5.2/docs/conf.py000066400000000000000000000151061462331236700147550ustar00rootroot00000000000000# -*- coding: utf-8 -*- # Licensed under a 3-clause BSD style license - see LICENSE.rst # # Astropy documentation build configuration file. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this file. # # All configuration values have a default. Some values are defined in # the global Astropy configuration which is loaded here before anything else. # See astropy.sphinx.conf for which values are set there. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('..')) # IMPORTANT: the above commented section was generated by sphinx-quickstart, but # is *NOT* appropriate for astropy or Astropy affiliated packages. It is left # commented out with this explanation to make it clear why this should not be # done. If the sys.path entry above is added, when the astropy.sphinx.conf # import occurs, it will import the *source* version of astropy instead of the # version installed (if invoked as "make html" or directly with sphinx), or the # version in the build directory (if "python setup.py build_sphinx" is used). # Thus, any C-extensions that are needed to build the documentation will *not* # be accessible, and the documentation will not build correctly. import datetime import os import sys sys.path.insert(0, os.path.abspath('..')) try: from sphinx_astropy.conf.v1 import * # noqa except ImportError: print('ERROR: the documentation requires the sphinx-astropy package to be installed') sys.exit(1) # Get configuration information from setup.cfg from configparser import ConfigParser conf = ConfigParser() conf.read([os.path.join(os.path.dirname(__file__), '..', 'setup.cfg')]) setup_cfg = dict(conf.items('metadata')) # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = '1.7' # To perform a Sphinx version check that needs to be more specific than # major.minor, call `check_sphinx_version("x.y.z")` here. # check_sphinx_version("1.2.1") # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns.append('_templates') # Add any paths that contain templates here, relative to this directory. if 'templates_path' not in locals(): # in case parent conf.py defines it templates_path = [] templates_path.append('_templates') # This is added to the end of RST files - a good place to put substitutions to # be used globally. rst_epilog += """ """ # -- Project information ------------------------------------------------------ # This does not *have* to match the package name, but typically does project = setup_cfg['name'] author = setup_cfg['author'] copyright = '{}, {}'.format( datetime.datetime.now(tz=datetime.timezone.utc).year, setup_cfg['author']) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. __import__(project) package = sys.modules[project] # The short X.Y version. version = package.__version__.split('-', 1)[0] # The full version, including alpha/beta/rc tags. release = package.__version__ # -- Options for HTML output --------------------------------------------------- # A NOTE ON HTML THEMES # The global astropy configuration uses a custom theme, 'bootstrap-astropy', # which is installed along with astropy. A different theme can be used or # the options for this theme can be modified by overriding some of the # variables set in the global configuration. The variables set in the # global configuration are listed below, commented out. # Add any paths that contain custom themes here, relative to this directory. # To use a different custom theme, add the directory containing the theme. #html_theme_path = [] # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. To override the custom theme, set this to the # name of a builtin theme or the name of a custom theme in html_theme_path. #html_theme = None # Customized theme options html_theme_options = { 'logotext1': 'Py', # white, semi-bold 'logotext2': 'VO', # orange, light 'logotext3': '' # white, light } # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = '' # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '' # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". html_title = '{} v{}'.format(project, release) # Output file base name for HTML help builder. htmlhelp_basename = project + 'doc' # -- Options for LaTeX output -------------------------------------------------- # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [('index', project + '.tex', project + ' Documentation', author, 'manual')] # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [('index', project.lower(), project + ' Documentation', [author], 1)] # -- Options for the edit_on_github extension ---------------------------------------- if eval(setup_cfg.get('edit_on_github')): extensions += ['astropy.sphinx.ext.edit_on_github'] versionmod = __import__(project + '.version') edit_on_github_project = setup_cfg['github_project'] if versionmod.release: edit_on_github_branch = "v" + versionmod.version else: edit_on_github_branch = "main" edit_on_github_source_root = "" edit_on_github_doc_root = "docs" # -- Enable nitpicky mode - which ensures that all references in the docs resolve ---- nitpicky = True # See docs/nitpick-exceptions file for the actual listing. nitpick_ignore = [] for line in open("nitpick-exceptions"): if line.strip() == "" or line.startswith("#"): continue dtype, target = line.split(None, 1) nitpick_ignore.append((dtype, target.strip())) pyvo-1.5.2/docs/dal/000077500000000000000000000000001462331236700142135ustar00rootroot00000000000000pyvo-1.5.2/docs/dal/index.rst000066400000000000000000000754601462331236700160700ustar00rootroot00000000000000.. _pyvo-data-access: ************************ Data Access (`pyvo.dal`) ************************ This subpackage provides access to the various data services in the VO. Getting started =============== Service objects are created with the service url and provide service-specific metadata. .. doctest-remote-data:: >>> import pyvo as vo >>> service = vo.dal.SIAService("http://dc.zah.uni-heidelberg.de/lswscans/res/positions/siap/siap.xml") >>> print(service.description) Scans of plates kept at Landessternwarte Heidelberg-Königstuhl. They were obtained at location, at the German-Spanish Astronomical Center (Calar Alto Observatory), Spain, and at La Silla, Chile. The plates cover a time span between 1880 and 1999. Specifically, HDAP is essentially complete for the plates taken with the Bruce telescope, the Walz reflector, and Wolf's Doppelastrograph at both the original location in Heidelberg and its later home on Königstuhl. They provide a ``search`` method with varying standard parameters for submitting queries. .. doctest-skip:: >>> resultset = service.search(pos=pos, size=size) which returns a :ref:`resultset `. Individual services may define additional, custom parameters. You can pass these to the ``search`` method as (case-insensitive) keyword arguments. Call the method ``describe`` to print human-readable service metadata. You most likely want to use this in a notebook session or similar before actually querying the service. See :ref:`pyvo-services` for a explanation of the different interfaces. .. _pyvo-astro-params: Astrometric parameters ---------------------- Most services expose the astrometric parameters ``pos`` and ``size`` for which PyVO accept `~astropy.coordinates.SkyCoord` or `~astropy.units.Quantity` objects as well as any other sequence containing right ascension and declination in degrees, which are converted to the standard coordinate frame (in the VO, that usually is ICRS) in the standard units (always degrees in the VO) before they are submitted to the service. Also, `~astropy.coordinates.SkyCoord` can be used to lookup names of astronomical objects you are searching for. .. doctest-remote-data:: >>> import pyvo as vo >>> from astropy.coordinates import SkyCoord >>> from astropy.units import Quantity >>> >>> pos = SkyCoord.from_name('NGC 4993') >>> size = Quantity(0.5, unit="deg") See :ref:`astropy-coordinates` and :ref:`astropy-units` for details. The `~astropy.units.Quantity` object is also suitable for any other astrometric parameter, such as waveband ranges. Some services also accept `~astropy.time.Time` as ``time`` parameter. >>> from astropy.time import Time >>> time = Time(('2015-01-01T00:00:00', '2018-01-01T00:00:00'), ... format='isot', scale='utc') See :ref:`astropy-time` for explanation. .. _pyvo-verbosity: Verbosity --------- Several VO protocols have the notion of “verbosity”, where 1 means “minimal set of columns”, 2 means “columns most users can work with” and 3 ”everything including exotic items”. Query functions accept these values in the ``verbosity`` parameter. The exact semantics are service-specific. Capabilities ------------ VO services should offer some standard ”support” interfaces specified in VOSI. In pyVO, the information obtained from these endpoints can be obtained from some service attributes. Capabilities describe specific pieces of functionality (such as “this is a spectral search”) and further metadata (such as ”this service will never return more than 10000 rows”). This information is contained in the data structure :py:class:`~pyvo.io.vosi.endpoint.CapabilitiesFile` available through the ``pyvo.dal.vosi.CapabilityMixin.capabilities`` attribute. Exceptions ---------- See the ``pyvo.dal.exceptions`` module. .. _pyvo-services: Services ======== There are five types of services with different purposes but a mostly similar interface available. .. _pyvo_tap: Table Access Protocol --------------------- .. pull-quote:: This protocol defines a service protocol for accessing general table data, including astronomical catalogs as well as general database tables. Access is provided for both database and table metadata as well as for actual table data. This protocol supports the query language `Astronomical Data Query Language (ADQL) `_ within an integrated interface. It also includes support for both synchronous and asynchronous queries. Special support is provided for spatially indexed queries using the spatial extensions in ADQL. A multi-position query capability permits queries against an arbitrarily large list of astronomical targets, providing a simple spatial cross-matching capability. More sophisticated distributed cross-matching capabilities are possible by orchestrating a distributed query across multiple TAP services. -- `Table Access Protocol `_ Consider the following example for using TAP and ADQL, retrieving 5 objects from the GAIA DR3 database, showing their id, position and mean G-band magnitude between 19 - 20: .. doctest-remote-data:: >>> import pyvo as vo >>> tap_service = vo.dal.TAPService("http://dc.g-vo.org/tap") >>> ex_query = """ ... SELECT TOP 5 ... source_id, ra, dec, phot_g_mean_mag ... FROM gaia.dr3lite ... WHERE phot_g_mean_mag BETWEEN 19 AND 20 ... ORDER BY phot_g_mean_mag ... """ >>> result = tap_service.search(ex_query) >>> print(result) source_id ra dec phot_g_mean_mag deg deg mag int64 float64 float64 float32 ------------------- ------------------ ------------------ --------------- 2162809607452221440 315.96596187101636 45.945474015208106 19.0 2000273643933171456 337.1829026565382 50.7218533537033 19.0 2171530448339798784 323.9151025188806 51.27690705826792 19.0 2171810342771336704 323.25913736080776 51.94305655940998 19.0 2180349528028140800 310.5233961869657 50.3486391034819 19.0 To explore more query examples, you can try either the ``description`` attribute for some services. For other services like this one, try the ``examples`` attribute. .. doctest-remote-data:: >>> print(tap_service.examples[0]['QUERY']) SELECT TOP 50 l.id, l.pmra as lpmra, l.pmde as lpmde, g.source_id, g.pmra as gpmra, g.pmdec as gpmde FROM lspm.main as l JOIN gaia.dr3lite AS g ON (DISTANCE(g.ra, g.dec, l.raj2000, l.dej2000)<0.01) -- rough pre-selection WHERE DISTANCE( ivo_epoch_prop_pos( g.ra, g.dec, g.parallax, g.pmra, g.pmdec, g.radial_velocity, 2016, 2000), POINT(l.raj2000, l.dej2000) )<0.0002 -- fine selection with PMs Furthermore, one can find the names of the tables using: .. doctest-remote-data:: >>> print([tab_name for tab_name in tap_service.tables.keys()]) # doctest: +IGNORE_WARNINGS ['ivoa.obs_radio', 'ivoa.obscore', 'tap_schema.columns', 'tap_schema.tables',..., 'taptest.main', 'veronqsos.data', 'vlastripe82.stripe82'] And also the names of the columns from a known table, for instance the first three columns: .. doctest-remote-data:: >>> result.table.columns[:3] # doctest: +IGNORE_WARNINGS If you know a TAP service's access URL, you can directly pass it to :py:class:`~pyvo.dal.TAPService` to obtain a service object. Sometimes, such URLs are published in papers or passed around through other channels. Most commonly, you will discover them in the VO registry (cf. :ref:`pyvo.registry`). To perform a query using ADQL, the ``search()`` method is used. TAPService instances have several methods to inspect the metadata of the service - in particular, what tables with what columns are available - discussed below. To get an idea of how to write queries in ADQL, have a look at `GAVO's ADQL course`_; it is basically a standardised subset of SQL with some extensions to make it work better for astronomy. .. _GAVO's ADQL course: https://docs.g-vo.org/adql Synchronous vs. asynchronous query ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ In synchronous (“sync”) mode, the client keeps a connection for the entire runtime of the query, and query processing generally starts when the request is submitted. This is convenient but becomes brittle as queries have runtimes of the order of minutes, when you may encounter query timeouts. Also, many data providers impose rather strict limits on the runtime allotted to sync queries. In asynchronous (“async”) mode, on the other hand, the client just submits a query and receives a URL that let us inspect the execution status (and retrieve its result) later. This means that no connection needs to be held, which makes this mode a lot more robust of long-running queries. It also supports queuing queries, which allows service operators to be a lot more generous with resource limits. To specify the query mode, you can use either ``run_sync()`` for synchronous query or ``run_async()`` for asynchronous query. .. doctest-remote-data:: >>> job = tap_service.submit_job(ex_query) To learn more details from the asynchronous query, let's look at the ``submit_job()`` method. This submits an asynchronous query without starting it, it creates a new object :py:class:`~pyvo.dal.AsyncTAPJob`. .. doctest-remote-data:: >>> job.url 'http://dc.zah.uni-heidelberg.de/__system__/tap/run/async/...' The job URL mentioned before is available in the ``url`` attribute. Clicking on the URL leads you to the query itself, where you can check the status(phase) of the query and decide to run, modify or delete the job. You can also do it via various attributes: .. doctest-remote-data:: >>> job.phase 'PENDING' A newly created job is in the PENDING state. While it is pending, it can be configured, for instance, overriding the server's default time limit (after which the query will be canceled): .. doctest-remote-data:: >>> job.executionduration = 700 >>> job.executionduration 700 When you are ready, you can start the job: .. doctest-remote-data:: >>> job.run() This will put the job into the QUEUED state. Depending on how busy the server is, it will immediately go to the EXECUTING status: .. doctest-remote-data:: >>> job.phase # doctest: +IGNORE_OUTPUT 'EXECUTING' The job will eventually end up in one of the phases: * COMPLETED - if all went to plan, * ERROR - if the query failed for some reason; look at the error attribute of the job to find out details, * ABORTED - if you manually killed the query using the ``abort()`` method or the server killed your query, presumably because it hit the time limit. After the job ends up in COMPLETED, you can retrieve the result: .. doctest-remote-data:: >>> job.phase # doctest: +IGNORE_OUTPUT 'COMPLETED' >>> job.fetch_result() # doctest: +SKIP (result table as shown before) Eventually, it is friendly to clean up the job rather than relying on the server to clean it up once ``job.destruction`` (a datetime that you can change if you need to) is reached. .. doctest-remote-data:: >>> job.delete() For more attributes please read the description for the job object :py:class:`~pyvo.dal.AsyncTAPJob`. With ``run_async()`` you basically submit an asynchronous query and return its result. It is like running ``submit_job()`` first and then run the query manually. Query limit ^^^^^^^^^^^ As a sanity precaution, most services have some default limit of how many rows they will return before overflowing: .. doctest-remote-data:: >>> print(tap_service.maxrec) 20000 To retrieve more rows than that (often conservative) default limit, you must override maxrec in the call to ``search``. A warning can be expected if you reach the ``maxrec`` limit: .. doctest-remote-data:: >>> tap_results = tap_service.search("SELECT * FROM ivoa.obscore", maxrec=100000) # doctest: +SHOW_WARNINGS DALOverflowWarning: Partial result set. Potential causes MAXREC, async storage space, etc. Services will not let you raise maxrec beyond the hard match limit: .. doctest-remote-data:: >>> print(tap_service.hardlimit) 16000000 A list of the tables and the columns within them is available in the TAPService's :py:attr:`~pyvo.dal.TAPService.tables` attribute by using it as an iterator or calling it's ``describe()`` method for a human-readable summary. Uploads ^^^^^^^ Some TAP services allow you to upload your own tables to make them accessible in queries. For this the various query methods have a ``uploads`` keyword, which accepts a dictionary of table name and content. The mechanism behind this parameter is smart enough to distinct between various types of content, either a :py:class:`~str` pointing to a local file or a file-like object, a :py:class:`~astropy.table.Table` or :py:class:`~pyvo.dal.query.DALResults` for an inline upload, or a url :py:class:`~str` pointing to a remote resource. The uploaded tables will be available as ``TAP_UPLOAD.name``. .. note:: The supported upload methods are available under :py:meth:`~pyvo.dal.tap.TAPService.upload_methods`. .. _table manipulation: Table Manipulation ^^^^^^^^^^^^^^^^^^ .. note:: This is a prototype implementation and the interface might not be stable. More details about the feature at: :ref:`cadc-tb-upload` Some services allow users to create, modify and delete tables. Typically, these functionality is only available to authenticated (and authorized) users. .. Requires proper credentials and authorization .. doctest-skip:: >>> auth_session = vo.auth.AuthSession() >>> # authenticate. For ex: auth_session.credentials.set_client_certificate('') >>> tap_service = vo.dal.TAPService("https://ws-cadc.canfar.net/youcat", auth_session) >>> >>> table_definition = ''' ... ... my_table ... This is my very own table ... ... article ... some article ... char ... ... ... count ... how many ... long ... ... ''' >>> tap_service.create_table(name='test_schema.test_table', definition=StringIO(table_definition)) Table content can be loaded from a file or from memory. Supported data formats: tab-separated values (tsv), comma-separated values (cvs) or VOTable (VOTable): .. doctest-skip:: >>> tap_service.load_table(name='test_schema.test_table', ... source=StringIO('article,count\narticle1,10\narticle2,20\n'), format='csv') Users can also create indexes on single columns: .. doctest-skip:: >>> tap_service.create_index(table_name='test_schema.test_table', column_name='article', unique=True) Finally, tables and their content can be removed: .. doctest-skip:: >>> tap_service.remove_table(name='test_schema.test_table') For further information about the service's parameters, see :py:class:`~pyvo.dal.TAPService`. .. _pyvo-sia: Simple Image Access ------------------- .. pull-quote:: The Simple Image Access (SIA) protocol provides capabilities for the discovery, description, access, and retrieval of multi-dimensional image datasets, including 2-D images as well as datacubes of three or more dimensions. SIA data discovery is based on the `ObsCore Data Model `_, which primarily describes data products by the physical axes (spatial, spectral, time, and polarization). Image datasets with dimension greater than 2 are often referred to as datacubes, cube or image cube datasets and may be considered examples of hypercube or n-cube data. PyVO supports both versions of SIA. -- `Simple Image Access `_ Basic queries are done with the ``pos`` and ``size`` parameters described in :ref:`pyvo-astro-params`, with ``size`` being the rectangular region around ``pos``. .. doctest-remote-data:: >>> pos = SkyCoord.from_name('Eta Carina') >>> size = Quantity(0.5, unit="deg") >>> sia_service = vo.dal.SIAService("http://dc.zah.uni-heidelberg.de/hppunion/q/im/siap.xml") >>> sia_results = sia_service.search(pos=pos, size=size) The dataset format, 'all' by default, can be specified: .. doctest-remote-data:: >>> sia_results = sia_service.search(pos=pos, size=size, format='graphics') This would return all graphical image formats (png, jpeg, gif) available. Other possible values are image/* mimetypes, or ``metadata``, which returns no image at all but instead a declaration of the additional parameters supported by the given service. The ``intersect`` argument (defaulting to ``OVERLAPS``) lets a program specify the desired relationship between the region of interest and the coverage of the images (case-insensitively): .. doctest-remote-data:: >>> sia_results = sia_service.search(pos=pos, size=size, intersect='covers') Available values: ========= ====================================================== COVERS select images that completely cover the search region ENCLOSED select images that are complete enclosed by the region OVERLAPS select any image that overlaps with the search region CENTER select images whose center is within the search region ========= ====================================================== This service exposes the :ref:`verbosity ` parameter For further information about the service's parameters, see :py:class:`~pyvo.dal.SIAService`. .. _pyvo-ssa: Simple Spectrum Access ---------------------- .. pull-quote:: The Simple Spectral Access (SSA) Protocol (SSAP) defines a uniform interface to remotely discover and access one dimensional spectra. -- `Simple Spectral Access Protocol `_ Access to (one-dimensional) spectra resembles image access, with some subtile differences: The size parameter is called ``diameter`` here, and hence the search region is always circular with ``pos`` as center: .. doctest-remote-data:: >>> ssa_service = vo.dal.SSAService("https://irsa.ipac.caltech.edu/SSA") >>> ssa_results = ssa_service.search(pos=pos, diameter=size) SSA queries can be further constrained by the ``band`` and ``time`` parameters. .. doctest-remote-data:: >>> ssa_results = ssa_service.search( ... pos=pos, diameter=size, ... time=Time((53000, 54000), format='mjd'), band=Quantity((1e-13, 1e-12), unit="m")) For further information about the service's parameters, see :py:class:`~pyvo.dal.SSAService`. .. _pyvo-scs: Simple Cone Search ------------------ .. pull-quote:: The Simple Cone Search (SCS) API specification defines a simple query protocol for retrieving records from a catalog of astronomical sources. The query describes sky position and an angular distance, defining a cone on the sky. The response returns a list of astronomical sources from the catalog whose positions lie within the cone, formatted as a VOTable. -- `Simple Cone Search `_ The Simple Cone Search returns results – typically catalog entries – within a circular region on the sky defined by the parameters ``pos`` (again, ICRS) and ``radius``: .. doctest-remote-data:: >>> scs_srv = vo.dal.SCSService('http://dc.zah.uni-heidelberg.de/arihip/q/cone/scs.xml') >>> scs_results = scs_srv.search(pos=pos, radius=size) This service exposes the :ref:`verbosity ` parameter. For further information about the service's parameters, see :py:class:`~pyvo.dal.SCSService`. .. _pyvo-slap: Simple Line Access ------------------ .. pull-quote:: The Simple Line Access Protocol (SLAP) is an IVOA data access protocol which defines a protocol for retrieving spectral lines coming from various Spectral Line Data Collections through a uniform interface within the VO framework. -- `Simple Line Access Protocol `_ This service let you query for spectral lines in a certain ``wavelength`` range. The unit of the values is meters, but any unit may be specified using `~astropy.units.Quantity`. For further information about the service's parameters, see :py:class:`~pyvo.dal.SLAService`. Jobs ==== Some services, most notably TAP ones, allow asynchronous operation (i.e., you submit a job, receive a URL where to check for updates, and then can go away) using a VO standard called UWS. These have a ``submit_job`` method, which has the same parameters as their ``search`` but start a server-side job instead of waiting for the result to return. This is particularly useful for longer-running queries or when you want to run several queries in parallel from one script. .. note:: It is good practice to test the query with a maxrec constraint first. When you invoke ``submit_job`` you will get a job object. .. doctest-remote-data:: >>> async_srv = vo.dal.TAPService("http://dc.g-vo.org/tap") >>> job = async_srv.submit_job("SELECT * FROM ivoa.obscore") .. note:: Currently, only `pyvo.dal.tap.TAPService` supports server-side jobs. This job is not yet running yet. To start it invoke ``run`` .. doctest-remote-data:: >>> job.run() # doctest: +IGNORE_OUTPUT Get the current job phase: .. doctest-remote-data:: >>> print(job.phase) EXECUTING Maximum run time in seconds is available and can be changed with :py:attr:`~pyvo.dal.AsyncTAPJob.execution_duration` .. doctest-remote-data:: >>> print(job.execution_duration) 7200.0 >>> job.execution_duration = 3600 Obtaining the job url, which is needed to reconstruct the job at a later point: .. doctest-remote-data:: >>> job_url = job.url >>> job = vo.dal.tap.AsyncTAPJob(job_url) Besides ``run`` there are also several other job control methods: * :py:meth:`~pyvo.dal.AsyncTAPJob.abort` * :py:meth:`~pyvo.dal.AsyncTAPJob.delete` * :py:meth:`~pyvo.dal.AsyncTAPJob.wait` .. note:: Usually the service deletes the job after a certain time, but it is a good practice to delete it manually when done. The destruction time can be obtained and changed with :py:attr:`~pyvo.dal.AsyncTAPJob.destruction` Also, :py:class:`pyvo.dal.AsyncTAPJob` works as a context manager which takes care of this automatically: .. doctest-remote-data:: >>> with async_srv.submit_job("SELECT * FROM ivoa.obscore") as job1: ... job1.run() # doctest: +IGNORE_OUTPUT >>> print('job1 deleted!') job1 deleted! Check for errors in the job execution: .. doctest-remote-data:: >>> job.raise_if_error() If the execution was successful, the resultset can be obtained using :py:meth:`~pyvo.dal.AsyncTAPJob.fetch_result` The result url is available under :py:attr:`~pyvo.dal.AsyncTAPJob.result_uri` .. _pyvo-resultsets: Resultsets and Records ====================== Resultsets contain primarily tabular data and might also provide binary datasets and/or access to additional data services. To obtain the names of the columns in a service response, write: .. doctest-remote-data:: >>> tap_service = vo.dal.TAPService("http://dc.g-vo.org/tap") >>> resultset = tap_service.search("SELECT TOP 10 * FROM ivoa.obscore") >>> print(resultset.fieldnames) ('dataproduct_type', 'dataproduct_subtype', 'calib_level', 'obs_collection', 'obs_id', 'obs_title', 'obs_publisher_did', 'obs_creator_did', 'access_url', 'access_format', 'access_estsize', 'target_name', 'target_class', 's_ra', 's_dec', 's_fov', 's_region', 's_resolution', 't_min', 't_max', 't_exptime', 't_resolution', 'em_min', 'em_max', 'em_res_power', 'o_ucd', 'pol_states', 'facility_name', 'instrument_name', 's_xel1', 's_xel2', 't_xel', 'em_xel', 'pol_xel', 's_pixel_scale', 'em_ucd', 'preview', 'source_table') Rich metadata equivalent to what is found in VOTables (including unit, ucd, utype, and xtype) is available through resultset's :py:meth:`~pyvo.dal.query.DALResults.getdesc` method: .. doctest-remote-data:: >>> print(resultset.getdesc('s_fov').ucd) phys.angSize;instr.fov .. note:: Two convenience functions let you retrieve columns of a specific physics (by UCD) or with a particular legacy data model annotation (by utype), like this: .. doctest-remote-data:: >>> fieldname = resultset.fieldname_with_ucd('phys.angSize;instr.fov') >>> fieldname = resultset.fieldname_with_utype('obscore:access.reference') Iterating over a resultset gives the rows in the result: .. doctest-remote-data:: >>> for row in resultset: ... print(row['s_fov']) 0.05027778 0.05027778 0.05027778 0.05027778 0.05027778 0.05027778 0.06527778 0.06527778 0.06527778 0.06527778 The total number of rows in the answer is available as its ``len()``: .. doctest-remote-data:: >>> print(len(resultset)) 10 If the row contains datasets, they are exposed by several retrieval methods: .. remove skip once https://github.com/astropy/pyvo/issues/361 is fixed .. doctest-skip:: >>> url = row.getdataurl() >>> fileobj = row.getdataset() >>> obj = row.getdataobj() Returning the access url, the file-like object or the appropriate python object to further work on. As with general numpy arrays, accessing individual columns via names gives an array of all of their values: .. doctest-remote-data:: >>> column = resultset['obs_id'] whereas integers retrieve rows: .. doctest-remote-data:: >>> row = resultset[0] and both combined gives a single value: .. doctest-remote-data:: >>> value = resultset['obs_id', 0] Row objects may expose certain key columns as properties. See the corresponding API spec listed below for details. * :py:class:`pyvo.dal.sia.SIARecord` * :py:class:`pyvo.dal.ssa.SSARecord` * :py:class:`pyvo.dal.scs.SCSRecord` * :py:class:`pyvo.dal.sla.SLARecord` Convenience methods are available to transform the results into :py:class:`astropy.table.Table` or :py:class:`astropy.table.QTable` (values as quantities): .. doctest-remote-data:: >>> astropy_table = resultset.to_table() >>> astropy_qtable = resultset.to_qtable() Multiple datasets ----------------- PyVO supports multiple datasets exposed on record level through the datalink. To get an iterator yielding specific datasets, call :py:meth:`pyvo.dal.adhoc.DatalinkResults.bysemantics` with the identifier identifying the dataset you want it to return. .. remove skip once https://github.com/astropy/pyvo/issues/361 is fixed .. doctest-skip:: >>> preview = next(row.getdatalink().bysemantics('#preview')).getdataset() .. note:: Since the creation of datalink objects requires a network roundtrip, it is recommended to call ``getdatalink`` only once. Of course one can also build a datalink object from its url. .. doctest-remote-data:: >>> from pyvo.dal.adhoc import DatalinkResults >>> # In this example you know the URL from somewhere >>> url = 'https://ws.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/caom2ops/datalink?ID=ivo%3A%2F%2Fcadc.nrc.ca%2FHSTHLA%3Fhst_12477_28_acs_wfc_f606w_01%2Fhst_12477_28_acs_wfc_f606w_01_drz' >>> datalink = DatalinkResults.from_result_url(url) Server-side processing ---------------------- Some services support the server-side processing of record datasets. This includes spatial cutouts for 2d-images, reducing of spectra to a certain waveband range, and many more depending on the service. Datalink ^^^^^^^^ Generic access to processing services is provided through the datalink interface. .. remove skip once https://github.com/astropy/pyvo/issues/361 is fixed .. doctest-skip:: >>> datalink_proc = next(row.getdatalink().bysemantics('#proc')) .. note:: most times there is only one processing service per result, and thats all you need. The returned object lets you access the available input parameters which you can pass as keywords to the ``process`` method. .. remove skip once https://github.com/astropy/pyvo/issues/361 is fixed .. doctest-skip:: >>> datalink_proc = row.getdatalink().get_first_proc() >>> print(datalink_proc.input_params) For more details about this have a look at :py:class:`astropy.io.votable.tree.Param`. Calling the method will return a file-like object on success. .. remove skip once https://github.com/astropy/pyvo/issues/361 is fixed .. doctest-skip:: >>> print(datalink_proc) >>> fobj = datalink.process(circle=(1, 1, 1)) SODA ^^^^ SODA is a service with predefined parameters, available on row-level through :py:meth:`pyvo.dal.adhoc.SodaRecordMixin.processed` which exposes a set of parameters which are dependent on the type of service. - ``circle`` -- a sequence (degrees) or :py:class:`astropy.units.Quantity` of longitude, latitude and radius - ``range`` -- a sequence (degrees) or :py:class:`astropy.units.Quantity` of two longitude values and two latitude values describing a rectangle. - ``polygon`` -- multiple pairs of longitude and latitude points - ``band`` -- a sequence of two values (meters) or :py:class:`astropy.units.Quantity` with two bandwidth values. The right sort order will be ensured if converting from frequency to wavelength. Interoperabillity over SAMP --------------------------- Tables and datasets can be send to other astronomical applications, providing they have support for SAMP (Simple Application Messaging Protocol). You can either broadcast whole tables by calling ``broadcast_samp`` on the resultset or a single product (image, spectrum) by calling this method on the SIA or SSA record. .. note:: Don't forget to start the application and make sure there is a running SAMP Hub. Underlying data structures -------------------------- PyVO also allows access to underlying data structures. The astropy data classes :py:class:`astropy.table.Table` and :py:class:`astropy.table.QTable` are accessible with the method :py:meth:`pyvo.dal.DALResults.to_table` and :py:meth:`pyvo.dal.DALResults.to_qtable`, following astropy naming conventions. If you want to work with the XML data structures :py:class:`astropy.io.votable.tree.VOTableFile` or :py:class:`astropy.io.votable.tree.TableElement`, they are accessible by the attributes :py:attr:`pyvo.dal.DALResults.resultstable` and :py:attr:`pyvo.dal.DALResults.votable`, respectively. Reference/API ============= .. automodapi:: pyvo.dal .. automodapi:: pyvo.dal.adhoc pyvo-1.5.2/docs/index.rst000066400000000000000000000072411462331236700153200ustar00rootroot00000000000000PyVO ==== Introduction ------------ This is the documentation for PyVO, an affiliated package for the `astropy `__ package. PyVO lets you find and retrieve astronomical data available from archives that support standard `IVOA `__ virtual observatory service protocols. * :ref:`Table Access Protocol (TAP) ` -- accessing source catalogs using sql-ish queries. * :ref:`Simple Image Access (SIA) ` -- finding images in an archive. * :ref:`Simple Spectral Access (SSA) ` -- finding spectra in an archive. * :ref:`Simple Cone Search (SCS) ` -- for positional searching a source catalog or an observation log. * :ref:`Simple Line Access (SLAP) ` -- finding data about spectral lines, including their rest frequencies. .. note:: If you need to access data which is not available via the Virtual Observatory standards, try the astropy affiliated package `astroquery `__ (and, of course, ask the data providers to do the right thing and use the proper standards for their publication). Installation ------------ PyVO is installable via pip. .. code-block:: bash pip install pyvo Source Installation ^^^^^^^^^^^^^^^^^^^ .. code-block:: bash git clone http://github.com/pyvirtobs/pyvo cd pyvo python setup.py install Requirements ------------ * numpy * astropy * requests .. _getting-started: Getting started --------------- Data Access ^^^^^^^^^^^ Most of the interesting functionality of pyVO is through the various data access service interfaces (SCS for catalogs, SIA for images, SSAP for spectra, TAP for tables). All of these behave in a similar way. First, there is a class describing a specific type of service: .. doctest-remote-data:: >>> import pyvo as vo >>> service = vo.dal.TAPService("http://dc.g-vo.org/tap") Once you have a service object, you can run queries with parameters specific to the service type. In this example, a database query is enough: .. doctest-remote-data:: >>> resultset = service.search("SELECT TOP 1 * FROM ivoa.obscore") >>> resultset dataproduct_type dataproduct_subtype ... source_table ... object object ... object ---------------- ------------------- ... ------------ image ... ppakm31.maps What is returned by the search method is a to get a resultset object, which essentially works like a numpy record array. It can be processed either by columns: .. doctest-remote-data:: >>> row = resultset[0] >>> column = resultset["dataproduct_type"] or by rows. .. doctest-remote-data:: >>> for row in resultset: ... calib_level = row["calib_level"] For more details on how to use data access services see :ref:`pyvo-data-access` Registry search ^^^^^^^^^^^^^^^ PyVO also contains a component that lets your programs interrogate the IVOA Registry in a simple way. For instance, to iterate over all TAP services supporting the obscore data model (which lets people publish observational datasets through TAP tables), you can write: .. doctest-remote-data:: >>> for service in vo.regsearch(datamodel="obscore"): ... print(service['ivoid']) # doctest: +IGNORE_OUTPUT ivo://aip.gavo.org/tap ivo://archive.stsci.edu/caomtap ivo://astro.ucl.ac.uk/tap ivo://astron.nl/tap ivo://asu.cas.cz/tap ... ivo://xcatdb/3xmmdr7/tap ivo://xcatdb/4xmm/tap Using ``pyvo`` -------------- .. toctree:: :maxdepth: 1 dal/index registry/index io/index auth/index utils/index utils/prototypes pyvo-1.5.2/docs/io/000077500000000000000000000000001462331236700140625ustar00rootroot00000000000000pyvo-1.5.2/docs/io/index.rst000066400000000000000000000002771462331236700157310ustar00rootroot00000000000000**************** IO (``pyvo.io``) **************** This module contains submodules which handle datastructures related to the virtual observatory. .. toctree:: :maxdepth: 1 vosi uws pyvo-1.5.2/docs/io/uws.rst000066400000000000000000000002471462331236700154350ustar00rootroot00000000000000********************* UWS (``pyvo.io.uws``) ********************* Reference/API ============= .. automodapi:: pyvo.io.uws.endpoint .. automodapi:: pyvo.io.uws.tree pyvo-1.5.2/docs/io/vosi.rst000066400000000000000000000004641462331236700156000ustar00rootroot00000000000000*********************** VOSI (``pyvo.io.vosi``) *********************** Reference/API ============= .. automodapi:: pyvo.io.vosi.endpoint .. automodapi:: pyvo.io.vosi.voresource .. automodapi:: pyvo.io.vosi.vodataservice .. automodapi:: pyvo.io.vosi.tapregext .. automodapi:: pyvo.io.vosi.availability pyvo-1.5.2/docs/make.bat000066400000000000000000000106411462331236700150620ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Astropy.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Astropy.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end pyvo-1.5.2/docs/nitpick-exceptions000066400000000000000000000010131462331236700172110ustar00rootroot00000000000000# Deprecated API py:class pyvo.dal.vosi.AvailabilityMixin # Non-public API classes. We should probably remove references to them altogether py:obj pyvo.io.uws.tree.Jobs py:class pyvo.dal.vosi.CapabilityMixin py:class pyvo.dal.vosi.EndpointMixin py:class pyvo.dal.adhoc.AxisParamMixin py:class pyvo.dam.obscore.ObsCoreMetadata py:obj pyvo.registry.regtap.Interface # There is no public API docs for this, yet it's useful to leave the reference in py:obj pyvo.io.vosi.exceptions py:class pyvo.dal.exceptions.PyvoUserWarning pyvo-1.5.2/docs/registry/000077500000000000000000000000001462331236700153235ustar00rootroot00000000000000pyvo-1.5.2/docs/registry/index.rst000066400000000000000000000603701462331236700171720ustar00rootroot00000000000000.. _pyvo-registry: ************************** Registry (`pyvo.registry`) ************************** This is an interface to the Virtual Observatory Registry, a collection of metadata records of the VO's “resources” (“resource” is jargon for: a collection of datasets, usually with a service in front of it). For a wider background, see `2014A&C.....7..101D`_ for the general architecture and `2015A&C....10...88D`_ for the search interfaces. .. _2014A&C.....7..101D: https://ui.adsabs.harvard.edu/abs/2014A%26C.....7..101D/abstract .. _2015A&C....10...88D: https://ui.adsabs.harvard.edu/abs/2015A%26C....10...88D/abstract There are two fundamental modes of searching in the VO: (a) Data discovery: This is when you are looking for some sort of data collection based on its metadata; a classical example would be something like “I need redshifts of supernovae”. (b) Service discovery: This is what you need when you want to query all services of a certain kind (e.g., „all spectral services claiming to have infrared data“), which in turn is the basis of all-VO *dataset* discovery (“give me all infrared spectra of 3C273”) Both modes are supported by this module. .. _registry-basic-interface: Basic interface =============== The main interface for the module is :py:meth:`pyvo.registry.search`; the examples below assume:: >>> from pyvo import registry This function accepts one or more search constraints, which can be either specified using constraint objects as positional arguments or as keyword arguments. The following constraints are available: * :py:class:`~pyvo.registry.Freetext` (``keywords``): one or more freetext words, mached in the title, description or subject of the resource. * :py:class:`~pyvo.registry.Servicetype` (``servicetype``): constrain to one of tap, ssa, sia, conesearch (or full ivoids for other service types). This is the constraint you want to use for service discovery. * :py:class:`~pyvo.registry.UCD` (``ucd``): constrain by one or more UCD patterns; resources match when they serve columns having a matching UCD (e.g., ``phot.mag;em.ir.%`` for “any infrared magnitude”). * :py:class:`~pyvo.registry.Waveband` (``waveband``): one or more terms from the vocabulary at http://www.ivoa.net/rdf/messenger giving the rough spectral location of the resource. * :py:class:`~pyvo.registry.Author` (``author``): an author (“creator”). This is a single SQL pattern, and given the sloppy practices in the VO for how to write author names, you should probably generously use wildcards. * :py:class:`~pyvo.registry.Datamodel` (``datamodel``): one of obscore, epntap, or regtap: only return TAP services having tables of this kind. * :py:class:`~pyvo.registry.Ivoid` (``ivoid``): exactly match a single IVOA identifier (that is, in effect, the primary key in the VO). * :py:class:`~pyvo.registry.Spatial` (``spatial``): match resources covering, enclosed or overlapping a certain geometry (point, circle, polygon, or MOC). *RegTAP 1.2 Extension* * :py:class:`~pyvo.registry.Spectral` (``spectral``): match resources covering a certain part of the spectrum (usually, but not limited to, the electromagnetic spectrum). *RegTAP 1.2 Extension* * :py:class:`~pyvo.registry.Temporal` (``temporal``): match resources covering a some point or interval in time. *RegTAP 1.2 Extension* Multiple constraints are combined conjunctively (”AND”). Constraints marked with *RegTAP 1.2 Extension* are not available on all IVOA RegTAP services (they are on pyVO's default RegTAP endpoint, though). Also refer to the class documentation for further caveats on these. Hence, to look for for resources with UV data mentioning white dwarfs you could either run: .. doctest-remote-data:: >>> resources = registry.search(keywords="white dwarf", waveband="UV") or: .. doctest-remote-data:: >>> resources = registry.search(registry.Freetext("white dwarf"), ... registry.Waveband("UV")) or a mixture between the two. Constructing using explicit constraints is generally preferable with more complex queries. Where the constraints accept multiple arguments, you can pass in sequences to the keyword arguments; for instance: .. doctest-remote-data:: >>> resources = registry.search(registry.Waveband("Radio", "Millimeter")) is equivalent to: .. doctest-remote-data:: >>> resources = registry.search(waveband=["Radio", "Millimeter"]) There is also :py:meth:`~pyvo.registry.get_RegTAP_query`, accepting the same arguments as :py:meth:`pyvo.registry.search`. This function simply returns the ADQL query that search would execute. This is may be useful to construct custom RegTAP queries, which could then be executed on TAP services implementing the ``regtap`` data model. Data Discovery ============== In data discovery, you look for resources matching your constraints and then figure out in a second step how to query them. For instance, to look for resources giving redshifts in connection with supernovae, you would say: .. doctest-remote-data:: >>> resources = registry.search(registry.UCD("src.redshift"), ... registry.Freetext("supernova")) After that, ``resources`` is an instance of :py:class:`~pyvo.registry.regtap.RegistryResults`, which you can iterate over. In interactive data discovery, however, it is usually preferable to use the ``to_table`` method for an overview of the resources available: .. doctest-remote-data:: >>> resources.to_table() # doctest: +IGNORE_OUTPUT title ... interfaces str67 ... str24 --------------------------------------------------------------- ... ------------------------ Asiago Supernova Catalogue (Barbon et al., 1999-) ... conesearch, tap#aux, web Asiago Supernova Catalogue (Version 2008-Mar) ... conesearch, tap#aux, web Sloan Digital Sky Survey-II Supernova Survey (Sako+, 2018) ... conesearch, tap#aux, web ... And to look for tap resources *in* a specific cone, you would do .. doctest-remote-data:: >>> from astropy.coordinates import SkyCoord >>> registry.search(registry.Servicetype("tap"), ... registry.Spatial((SkyCoord("23d +3d"), 3), intersect="enclosed"), ... includeaux=True) # doctest: +IGNORE_OUTPUT ivoid res_type short_name res_title ... intf_types intf_roles alt_identifier ... object object object object ... object object object ------------------------------ ----------------- ------------- ------------------------------------------- ... ------------ ---------- -------------------------------- ivo://cds.vizier/j/apj/835/123 vs:catalogservice J/ApJ/835/123 Globular clusters in NGC 474 from CFHT obs. ... vs:paramhttp std doi:10.26093/cds/vizier.18350123 Where ``intersect`` can take the following values: * 'covers' is the default and returns resources that cover the geometry provided, * 'enclosed' is for services in the given region, * 'overlaps' returns services intersecting with the region. The idea is that in notebook-like interfaces you can pick resources by title, description, and perhaps the access mode (“interface”) offered. In the list of interfaces, you will sometimes spot an ``#aux`` after a standard id; this is a minor VO technicality that you can in practice ignore. For instance, you can simply construct :py:class:`~pyvo.dal.TAPService`-s from ``tap#aux`` interfaces. Once you have found a resource you would like to query, you can pick it by index; however, this will not be stable across multiple executions. Hence, RegistryResults also supports referencing results by short name, which is the style we recommend. Using full ivoids is possible, too, and safer because these are guaranteed to be unique (which short names are not), but it is rather clunky, and in the real VO short name collisions should be very rare. Use the ``get_service`` method of :py:class:`~pyvo.registry.regtap.RegistryResource` to obtain a DAL service object for a particular sort of interface. To query the fourth match using simple cone search, you would thus say: .. doctest-remote-data:: >>> resources["II/283"].get_service("conesearch").search(pos=(120, 73), sr=1) _RAJ2000 _DEJ2000 _r recno ... NED RAJ2000 DEJ2000 deg deg ... float64 float64 float64 int32 ... str3 str12 str12 ------------ ------------ -------- ----- ... ---- ------------ ------------ 117.98645833 73.00961111 0.588592 986 ... NED 07 51 56.750 +73 00 34.60 To operate TAP services, you need to know what tables make up a resource; you could construct a TAP service and access its ``tables`` attribute, but you can take a shortcut and call a RegistryResource's ``get_tables`` method for a rather similar result: .. doctest-remote-data:: >>> tables = resources["II/283"].get_tables() # doctest: +IGNORE_WARNINGS >>> list(tables.keys()) ['II/283/sncat'] >>> sorted(c.name for c in tables['II/283/sncat'].columns) ['band', 'bmag', 'deg', 'dej2000', 'disc', 'epmax', 'galaxy', 'hrv', 'i', 'logd25', 'maxmag', 'mtype', 'n_bmag', 'n_sn', 'n_x', 'n_y', 'ned', 'pa', 'rag', 'raj2000', 'recno', 'simbad', 'sn', 't', 'type', 'u_epmax', 'u_maxmag', 'u_sn', 'u_y', 'u_z', 'x', 'y', 'z'] In this case, this is a table with one of VizieR's somewhat funky names. To run a TAP query based on this metadata, do something like: .. doctest-remote-data:: >>> resources["II/283"].get_service("tap#aux").run_sync( ... 'SELECT sn, z FROM "J/A+A/437/789/table2" WHERE z>0.04') SN z object float64 ------ ------- 1992bh 0.045 1992bp 0.079 1993ag 0.049 1993O 0.051 A special sort of access mode is ``web``, which represents some facility related to the resource that works in a web browser. You can ask for a “service” for it, too; you will then receive an object that has a ``search`` method, and when you call it, a browser window should open with the query facility (this uses python's ``webbrowser`` module): .. doctest-skip:: >>> resources["II/283"].get_service("web").search() # doctest: +IGNORE_OUTPUT Note that for interactive data discovery in the VO Registry, you may also want to have a look at Aladin's discovery tree, TOPCAT's VO menu, or at services like DataScope_ or WIRR_ in your web browser. .. _DataScope: https://heasarc.gsfc.nasa.gov/cgi-bin/vo/datascope/init.pl .. _WIRR: https://dc.g-vo.org/WIRR Service Discovery ================= Service discovery is what you want typically in connection with a search for datasets, as in “Give me all infrared spectra of Bellatrix“. To do that, you want to run the same DAL query against all the services of a given sort. This means that you will have to include a ``servicetype`` constraint such that all resources in your registry results can be queried in the same way. When that is the case, you can use each RegistryResource's ``service`` attribute, which contains a DAL service instance. The opening example could be written like this: .. This one is too expensive to run as part of CI/testing .. doctest-skip:: >>> from astropy.coordinates import SkyCoord >>> my_obj = SkyCoord.from_name("Bellatrix") >>> for res in registry.search(waveband="infrared", servicetype="spectrum"): ... print(res.service.search(pos=my_obj, size=0.001)) ... In reality, you will have to add some error handling to this kind of all-VO queries: in a wide and distributed network, some service is always down. See `Appendix: Robust All-VO Queries`_ The central point is: With a ``servicetype`` constraint, each result has a well-defined ``service`` attribute that contains some subclass of dal.Service and that can be queried in a uniform fashion. TAP services may provide tables in well-defined data models, like EPN-TAP or obscore. These can be queried in similar loops, although in some cases you will have to adapt the queries to the resources found. In the obscore case, an all-VO query would look like this: .. Again, that's too expensive for CI/testing .. doctest-skip:: >>> for svc_rec in registry.search(datamodel="obscore"): ... print(svc_rec.service.run_sync( ... "SELECT DISTINCT dataproduct_type FROM ivoa.obscore")) Again, in production this needs explicit handling of failing services. For an example of how this might look like, see `GAVO's plate tutorial`_ .. _GAVO's plate tutorial: http://docs.g-vo.org/gavo_plates.pdf More examples ------------- Discover archives ^^^^^^^^^^^^^^^^^ You can use the registry ``search`` method (or the ``regsearch`` function) to discover archives that may have x-ray images and then query those archives to find what x-ray images that have of CasA. For the arguments you will enter ``'image'`` for the service type and ``'x-ray'`` for the waveband. The position is provided by the Astropy library. The query returns a :py:class:`~pyvo.registry.regtap.RegistryResults` object which is a container holding a table of matching services. In this example it returns 33 matching services. .. doctest-remote-data:: >>> import pyvo as vo >>> from astropy.coordinates import SkyCoord >>> >>> import warnings >>> warnings.filterwarnings('ignore', module="astropy.io.votable.*") >>> >>> archives = vo.regsearch(servicetype='image', waveband='x-ray') >>> pos = SkyCoord.from_name('Cas A') >>> len(archives) # doctest: +IGNORE_OUTPUT 33 There are also other type of services that you can choose via the ``servicetype`` parameter, for more details see :py:class:`~pyvo.registry.Servicetype`. You can learn more about the archives by printing their titles and access URL: .. doctest-remote-data:: >>> for service in archives: ... print(service.res_title, service.access_url) # doctest: +IGNORE_OUTPUT Chandra X-ray Observatory Data Archive https://cda.harvard.edu/cxcsiap/queryImages? Chandra Source Catalog http://cda.cfa.harvard.edu/cscsiap/queryImages? Chandra Source Catalog Release 1 http://cda.cfa.harvard.edu/csc1siap/queryImages? ... It is not necessary to keep track of the URL because you can search images directly from the registry record, for example using the Chandra X-ray Observatory (CDA) service and the ``search`` method, inserting the position and size for the desired object. .. doctest-remote-data:: >>> images = archives["CDA"].search(pos=pos, size=0.25) >>> len(images) # doctest: +IGNORE_OUTPUT 822 Sometimes you are looking for a type of object. For this purpose, the ``keywords`` parameter is useful here. For example, you want to find all catalogs related to blazars observed with Fermi: .. doctest-remote-data:: >>> cats = vo.regsearch(keywords=['blazar', 'Fermi']) >>> len(cats) # doctest: +IGNORE_OUTPUT 551 Or you already know the particular catalog but not the base URL for that service. For example, you want to get cutout images from the NRAO VLA Sky Survey (NVSS): .. doctest-remote-data:: >>> colls = vo.regsearch(keywords=['NVSS'], servicetype='sia') >>> for coll in colls: ... print(coll.res_title, coll.access_url) NRA) VLA Sky Survey https://skyview.gsfc.nasa.gov/cgi-bin/vo/sia.pl?survey=nvss& Sydney University Molonglo Sky Survey https://skyview.gsfc.nasa.gov/cgi-bin/vo/sia.pl?survey=sumss& Search results ============== What is coming back from registry.search is :py:class:`pyvo.registry.regtap.RegistryResults` which is rather similar to :ref:`pyvo-resultsets`; just remember that for interactive use there is the ``to_tables`` method discussed above. The individual items are instances of :py:class:`~pyvo.registry.regtap.RegistryResource`, which expose many pieces of metadata (e.g., title, description, creators, etc) in attributes named like their RegTAP counterparts (see the class documentation). Some attributes deserve a second look. .. doctest-remote-data:: >>> import pyvo as vo >>> colls = vo.regsearch(keywords=["NVSS"], servicetype='sia') >>> nvss = colls["NVSS"] >>> nvss.res_title 'NRA) VLA Sky Survey' If you are looking for a particular data collection or catalog, as we did above when we looked for the NVSS archive, often simply reviewing the titles is sufficient. Other times, particularly when you are not sure what you are looking for, it helps to look deeper. A selection of the resource metadata, including the title, shortname and description, can be printed out in a summary form with the ``describe`` function. .. doctest-remote-data:: >>> nvss.describe(verbose=True) NRA) VLA Sky Survey Short Name: NVSS IVOA Identifier: ivo://nasa.heasarc/skyview/nvss Access modes: sia Base URL: https://skyview.gsfc.nasa.gov/cgi-bin/vo/sia.pl?survey=nvss& ... The verbose option in ``describe`` will output more information about the content of the resource, if available. Possible added entries are the authors of the resource, an associated DOI, an url where more information is provided, or a reference to a related paper. The method ``service`` will, for resources that only have a single capability, return a DAL service object ready for querying using the respective protocol. You should only use that attribute when the original registry query constrained the service type, because otherwise there is no telling what kind of service you will get back. .. doctest-remote-data:: >>> nvss = colls["NVSS"].service # converts record to service object >>> nvss.search(pos=(350.85, 58.815),size=0.25,format="image/fits") Survey Ra ... LogicalName object float64 ... object ------ ------- ... ----------- nvss 350.85 ... 1 With this service object, we can either call its ``search`` function directly or create query objects to get cutouts for a whole list of sources. .. doctest-remote-data:: >>> cutouts1 = nvss.search(pos=(148.8888, 69.065), size=0.2) >>> nvssq = nvss.create_query(size=0.2) # or create a query object >>> nvssq.pos = (350.85, 58.815) >>> cutouts2 = nvssq.execute() Our discussion of service metadata offers an opportunity to highlight another important property, the service's *IVOA Identifier* (sometimes referred to as its *ivoid*). This is a globally-unique identifier that takes the form of a `URI `_: .. doctest-remote-data:: >>> colls = vo.regsearch(keywords=["NVSS"], servicetype='sia') >>> for coll in colls: ... print(coll.ivoid) ivo://nasa.heasarc/skyview/nvss ivo://nasa.heasarc/skyview/sumss This identifier can be used to retrieve a specific service from the registry. .. doctest-remote-data:: >>> nvss = vo.registry.search(ivoid='ivo://nasa.heasarc/skyview/nvss')[0].get_service('sia') >>> nvss.search(pos=(350.85, 58.815),size=0.25,format="image/fits") Survey Ra ... LogicalName object float64 ... object ------ ------- ... ----------- nvss 350.85 ... 1 When the registry query did not constrain the service type, you can use the ``access_modes`` method to see what capabilities are available. For instance with this identifier: .. doctest-remote-data:: >>> res = registry.search(ivoid="ivo://org.gavo.dc/flashheros/q/ssa")[0] >>> res.access_modes() # doctest: +IGNORE_OUTPUT {'ssa', 'datalink#links-1.0', 'tap#aux', 'web', 'soda#sync-1.0'} – this service can be accessed through SSA, TAP, a web interface, and two special capabilities that pyVO cannot produce services for (mainly because standalone service objects do not make much sense for them). To obtain a service for one of the access modes pyVO does support, use ``get_service(mode)``. For ``web``, this returns an object that opens a web browser window when its ``query`` method is called. RegistryResources also have a ``get_contact`` method. Use this if the service is down or seems to have bugs; you should in general get at least an e-Mail address: .. doctest-remote-data:: >>> res.get_contact() 'GAVO Data Center Team (++49 6221 54 1837) ' Finally, the registry has an idea of what kind of tables are published through a resource, much like the VOSI tables endpoint (as a matter of fact, the Registry should contain exactly what is there, as VOSI tables in effect just gives a part of the registry record). Not all publishers properly provide table metadata to the Registry, though, but most do these days, and then you can run: .. doctest-remote-data:: >>> res.get_tables() # doctest: +IGNORE_OUTPUT {'flashheros.data': ... 29 columns ..., 'ivoa.obscore': ... 0 columns ...} Alternative Registries ====================== There are several RegTAP services in the VO. PyVO by default uses the one at the TAP access URL http://reg.g-vo.org/tap. You can use alternative ones, for instance, because they are nearer to you or because the default endpoint is down. You can pre-select the URL by setting the ``IVOA_REGISTRY`` environment variable to the TAP access URL of the service you would like to use. In a bash-like shell, you would say:: export IVOA_REGISTRY="http://vao.stsci.edu/RegTAP/TapService.aspx" before starting python (or the notebook processor). Within a Python session, you can use the `pyvo.registry.choose_RegTAP_service` function, which also takes the TAP access URL. As long as you have on working registry endpoint, you can find the other RegTAP services using: .. We probably shouldn't test the result of the next code block; this will change every time someone registers a new RegTAP service... .. doctest-remote-data:: >>> res = registry.search(datamodel="regtap") >>> print("\n".join(sorted(r.get_interface("tap").access_url ... for r in res))) http://dc.zah.uni-heidelberg.de/tap http://gavo.aip.de/tap http://voparis-rr.obspm.fr/tap https://vao.stsci.edu/RegTAP/TapService.aspx Reference/API ============= .. automodapi:: pyvo.registry .. automodapi:: pyvo.registry.regtap .. automodapi:: pyvo.registry.rtcons Appendix: Robust All-VO Queries =============================== The VO contains many services, and even if all of them had 99.9% uptime (which not all do), at any time you would always see failures, some of them involving long timeouts. Hence, if you run all-VO queries, you should catch errors and, at least in interactive sessions, provide some way to interrupt overly long queries. Here is an example for how to query all obscore services; remove the ``break`` at the end of the loop to actually do the global query (it's there so that you don't blindly run all-VO queries without reading at least this sentence): .. doctest-remote-data:: >>> from astropy.table import vstack >>> from pyvo import registry >>> >>> QUERY = "SELECT TOP 1 s_ra, s_dec from ivoa.obscore" >>> >>> results = [] >>> for i, svc_rec in enumerate(registry.search(datamodel="obscore", servicetype="tap")): ... # print("Querying {}".format(svc_rec.res_title)) ... try: ... svc = svc_rec.get_service(service_type="tap", lax=True) ... results.append( ... svc.run_sync(QUERY).to_table()) ... except KeyboardInterrupt: ... # someone lost their patience with a service. Query next. ... pass ... except Exception as msg: ... # some service is broken; you *should* complain, but ... #print(" Broken: {} ({}). Complain to {}.\n".format( ... pass # svc_rec.ivoid, msg, svc_rec.get_contact())) ... if i == 5: ... break >>> total_result = vstack(results) # doctest: +IGNORE_WARNINGS >>> total_result # doctest: +IGNORE_OUTPUT
s_ra s_dec deg deg float64 float64 ------------------ ------------------- 350.4619 -9.76139 208.360833592735 52.3611106494996 148.204840298431 29.1690999975089 243.044008 -51.778222 321.63278049999997 -54.579285999999996 pyvo-1.5.2/docs/requirements.txt000066400000000000000000000002661462331236700167430ustar00rootroot00000000000000sphinx-astropy # 27/Oct/2021 - docutils pinned due to: # https://github.com/readthedocs/readthedocs.org/issues/8616 # https://github.com/sphinx-doc/sphinx/issues/9727 docutils<0.18 pyvo-1.5.2/docs/utils/000077500000000000000000000000001462331236700146135ustar00rootroot00000000000000pyvo-1.5.2/docs/utils/index.rst000066400000000000000000000004771462331236700164640ustar00rootroot00000000000000.. _pyvo-utils: *************************** PyVO utils (``pyvo.utils``) *************************** This module contains utilities and base classes intended for internal use within PyVO or other dependent libraries. Reference/API ============= .. automodapi:: pyvo.utils.xml.elements :no-inheritance-diagram: pyvo-1.5.2/docs/utils/prototypes.rst000066400000000000000000000102531462331236700175760ustar00rootroot00000000000000.. _pyvo-prototypes: ************************************************** Prototype Implementations (`pyvo.utils.prototype`) ************************************************** This subpackage provides support for prototype standard implementations. ``PyVO`` implements the IVOA standards. As part of the standard approval process, new features are proposed and need to be demonstrated before the standard may be approved. ``PyVO`` may implement features that are not yet part of an approved standard. Such features are unstable, as the standard may be subject to reviews and significant changes, until it's finally approved. The ``prototype`` package provides support for such prototypes by means of a decorator for implementations that are still unstable. The expectation is that they will eventually become standard at which time the decorator will be removed. Users of ``pyvo`` need to explicitly opt-in in order to use such features. If prototype implementations are accessed without the user explicitly opting in, an exception will be raised. .. _pyvo-prototypes-users: Activating Prototype Implementations ==================================== In order to activate a feature, users need to call the function:: activate_features('feature_one', 'feature_two') Where the arguments are names of prototype features. If a feature name does not exist, a `~pyvo.utils.prototype.PrototypeWarning` will be issued, but the call will not fail. If no arguments are provided, then all features are enabled. .. _pyvo-prototypes-developers: Marking Features as Experimental ================================ The design restricts the possible usage of the decorator, which needs to always be called with a single argument being the name of the corresponding feature. More arguments are allowed but will be ignored. If the decorator is not used with the correct ``@prototype_feature("feature-name")`` invocation, the code will error as soon as the class is imported. The decorator can be used to tag individual functions or methods:: @prototype_feature('a-feature') def i_am_a_prototype(*arg, **kwargs): pass In this case, a single function or method is tagged as part of the ``a-feature`` prototype feature. If the feature has a URL defined (see :ref:`pyvo-prototypes-registry` below). Alternatively, a class can be marked as belonging to a feature. All public methods will be marked as part of the prototype implementation. Protected, private, and *dunder* methods (i.e. any method starting with an underscore) will be ignored. The reason is that the class might be instantiated by some mediator before the user can call (and more importantly not call) a higher level facade:: @prototype_feature('a-feature') class SomeFeatureClass: def method(self): pass @staticmethod def static(): pass def __ignore__(self): pass Any number of classes and functions can belong to a single feature, and individual methods can be tagged in a class rather than the class itself. .. _pyvo-prototypes-registry: Feature Registry ================ The feature registry is a static ``features`` dictionary in the `~pyvo.utils.prototype` package. The key is the name of the feature and the value is an instance of the `~pyvo.utils.protofeature.Feature` class. This class is responsible for determining whether an instance should error or not, and to format an error message if it's not. While the current implementation of the ``Feature`` class is simple, future requirements might lead to other implementations with more complex logic or additional documentation elements. .. _pyvo-prototypes-api: Reference/API ============= .. automodapi:: pyvo.utils.prototype .. automodapi:: pyvo.utils.protofeature Existing Prototypes =================== .. _cadc-tb-upload: CADC Table Manipulation (cadc-tb-upload) ---------------------------------------- This is a proposed extension to the TAP protocol to allow users to manipulate tables (https://wiki.ivoa.net/twiki/bin/view/IVOA/TAP-1_1-Next). The `~pyvo.dal.tap.TAPService` has been extended with methods that allow for: * table creation * column index creation * table content upload * table removal More details at: :ref:`table manipulation` pyvo-1.5.2/licenses/000077500000000000000000000000001462331236700143305ustar00rootroot00000000000000pyvo-1.5.2/licenses/ASTROPY_LICENSE.rst000066400000000000000000000027301462331236700173670ustar00rootroot00000000000000Copyright (c) 2011-2013, Astropy Developers All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Astropy Team nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pyvo-1.5.2/licenses/LICENSE.rst000066400000000000000000000027511462331236700161510ustar00rootroot00000000000000Copyright (c) 2013, Virtual Astronomical Observatory, LLC (VAO) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the VAO nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pyvo-1.5.2/licenses/README.rst000066400000000000000000000002421462331236700160150ustar00rootroot00000000000000Licenses ======== This directory holds license and credit information for the affiliated package, works the affiliated package is derived from, and/or datasets. pyvo-1.5.2/pyproject.toml000066400000000000000000000002651462331236700154420ustar00rootroot00000000000000[build-system] requires = ["setuptools", "setuptools_scm", "wheel"] build-backend = 'setuptools.build_meta' [tool.black] force-exclude = ''' ( .* ) '''pyvo-1.5.2/pyvo/000077500000000000000000000000001462331236700135205ustar00rootroot00000000000000pyvo-1.5.2/pyvo/__init__.py000066400000000000000000000025571462331236700156420ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ PyVO is a package providing access to remote data and services of the Virtual observatory (VO) using Python. The pyvo module currently provides these main capabilities: * find archives that provide particular data of a particular type and/or relates to a particular topic * regsearch() * search an archive for datasets of a particular type * imagesearch(), spectrumsearch() * do simple searches on catalogs or databases * conesearch(), linesearch(), tablesearch() Submodules provide additional functions and classes for greater control over access to these services. This module also exposes the exception classes raised by the above functions, of which DALAccessError is the root parent exception. """ # Affiliated packages may add whatever they like to this file, but # should keep this content at the top. # ---------------------------------------------------------------------------- from ._astropy_init import * # ---------------------------------------------------------------------------- from . import registry from .dal import ssa, sia, sla, scs, tap from . import auth from .registry import search as regsearch from .dal import ( imagesearch, spectrumsearch, conesearch, linesearch, tablesearch, DALAccessError, DALProtocolError, DALFormatError, DALServiceError, DALQueryError) pyvo-1.5.2/pyvo/_astropy_init.py000066400000000000000000000005431462331236700167570ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst import os __all__ = ['__version__', 'test'] try: from .version import version as __version__ except ImportError: __version__ = '' # Create the test function for self test from astropy.tests.runner import TestRunner test = TestRunner.make_test_runner_in(os.path.dirname(__file__)) pyvo-1.5.2/pyvo/auth/000077500000000000000000000000001462331236700144615ustar00rootroot00000000000000pyvo-1.5.2/pyvo/auth/__init__.py000066400000000000000000000003541462331236700165740ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst __all__ = ["AuthSession", "AuthURLs", "CredentialStore"] from .authsession import AuthSession from .authurls import AuthURLs from .credentialstore import CredentialStore pyvo-1.5.2/pyvo/auth/authsession.py000066400000000000000000000071561462331236700174110ustar00rootroot00000000000000import logging from .authurls import AuthURLs from .credentialstore import CredentialStore __all__ = ["AuthSession"] class AuthSession: """ A requests-like session that pyvo can use to dispatch its network calls with authentication. The user adds their credentials to the credentials object, such as adding a cookie, certificate, or password. The network requests made by pyvo pass through here, and the URL of the request is matched against the capabilities of the service. Based on what credentials have been provided and the capabilities of the service, appropriate credentials are added to the request before it is sent. """ def __init__(self): super(AuthSession, self).__init__() self.credentials = CredentialStore() self._auth_urls = AuthURLs() def add_security_method_for_url(self, url, security_method, exact=False): """ Add a security method for a url. This is additive with update_from_capabilities. This can be useful to set additional security methods that aren't set in the capabilities for whatever reason. Parameters ---------- url : str URL to set a security method for security_method : str URI of the security method to set exact : bool If True, match only this URL. If false, match all URLs that match this as a base URL. """ self._auth_urls.add_security_method_for_url(url, security_method, exact=exact) def update_from_capabilities(self, capabilities): """ Update the URL to security method mapping using the capabilities provided. Parameters ---------- capabilities : object List of `~pyvo.io.vosi.voresource.Capability` """ self._auth_urls.update_from_capabilities(capabilities) def get(self, url, **kwargs): """ Wrapper to make a HTTP GET request with authentication. """ return self._request('GET', url, **kwargs) def post(self, url, **kwargs): """ Wrapper to make a HTTP POST request with authentication. """ return self._request('POST', url, **kwargs) def put(self, url, **kwargs): """ Wrapper to make a HTTP PUT request with authentication. """ return self._request('PUT', url, **kwargs) def delete(self, url, **kwargs): """ Wrapper to make a HTTP DELETE request with authentication. """ return self._request('DELETE', url, **kwargs) def _request(self, http_method, url, **kwargs): """ Make an HTTP request with authentication. This function looks at the url of the request, determines what credentials it should attach to the request to authenticate, and then dispatches the request to the underlying requests library using the session that has been configured with the credentials. Parameters ---------- http_method : str the HTTP verb of the request. url : str the URL to request """ auth_methods = self._auth_urls.allowed_auth_methods(url) logging.debug('Possible auth methods: %s', auth_methods) negotiated_method = self.credentials.negotiate_method(auth_methods) logging.debug('Using auth method: %s', negotiated_method) session = self.credentials.get(negotiated_method) return session.request(http_method, url, **kwargs) def __repr__(self): return '\n'.join([repr(self.credentials), repr(self._auth_urls)]) pyvo-1.5.2/pyvo/auth/authurls.py000066400000000000000000000075241462331236700167120ustar00rootroot00000000000000import collections import logging from . import securitymethods __all__ = ["AuthURLs"] class AuthURLs(): """ AuthURLs helps determine which security method should be used with a given URL. It learns the security methods through the VOSI capabilities, which are passed in via update_from_capabilities. """ def __init__(self): self.full_urls = collections.defaultdict(set) self.base_urls = collections.defaultdict(set) def update_from_capabilities(self, capabilities): """ Update the URL to security method mapping using the capabilities provided. Parameters ---------- capabilities : object List of `~pyvo.io.vosi.voresource.Capability` """ for c in capabilities: for i in c.interfaces: for u in i.accessurls: url = u.content exact = u.use == 'full' if not i.securitymethods: self.add_security_method_for_url(url, securitymethods.ANONYMOUS, exact) for sm in i.securitymethods: method = sm.standardid or securitymethods.ANONYMOUS self.add_security_method_for_url(url, method, exact) def add_security_method_for_url(self, url, security_method, exact=False): """ Add a security method for a url. This is additive with update_from_capabilities. This can be useful to set additional security methods that aren't set in the capabilities for whatever reason. Parameters ---------- url : str URL to set a security method for security_method : str URI of the security method to set exact : bool If True, match only this URL. If false, match all URLs that match this as a base URL. """ if exact: self.full_urls[url].add(security_method) else: self.base_urls[url].add(security_method) def allowed_auth_methods(self, url): """ Return the authentication methods allowed for a particular URL. The methods are returned as URIs that represent security methods. Parameters ---------- url : str the URL to determine authentication methods """ logging.debug('Determining auth method for %s', url) if url in self.full_urls: methods = self.full_urls[url] logging.debug('Matching full url %s, methods %s', url, methods) return methods for base_url, methods in self._iterate_base_urls(): if url.startswith(base_url): logging.debug('Matching base url %s, methods %s', base_url, methods) return methods logging.debug('No match, using anonymous auth') return {securitymethods.ANONYMOUS} def _iterate_base_urls(self): """ A generator to sort the base URLs in the correct way to determine the most specific base_url. This is done by returning them longest to shortest. """ def sort_by_len(x): return len(x[0]) # Sort the base path matching URLs, so that # the longest URLs (the most specific ones, if # there is a tie) are used to determine the # auth method. for url, method in sorted(self.base_urls.items(), key=sort_by_len, reverse=True): yield url, method def __repr__(self): urls = [] for url, methods in self.full_urls.items(): urls.append('Full match:' + url + ':' + str(methods)) for url, methods in self._iterate_base_urls(): urls.append('Base match:' + url + ':' + str(methods)) return '\n'.join(urls) pyvo-1.5.2/pyvo/auth/credentialstore.py000066400000000000000000000114161462331236700202250ustar00rootroot00000000000000import logging from pyvo.utils.http import create_session from . import securitymethods __all__ = ["CredentialStore"] class CredentialStore(object): """ The credential store takes user credentials, and uses them to create appropriate requests sessions for dispatching requests using those credentials. Different types of credentials can be passed in, such as cookies, a jar of cookies, certificates, and basic auth. A session can also be associated with a security method URI by calling the set function. Before a request is to be dispatched, the AuthSession calls the get method to retrieve the appropriate requests.Session for making that HTTP request. """ def __init__(self): self.credentials = {} self.set(securitymethods.ANONYMOUS, create_session()) def negotiate_method(self, allowed_methods): """ Compare the credentials provided by the user against the security methods passed in, and determine which method is to be used for making this request. Parameters ---------- allowed_methods : list(str) list of allowed security methods to return Raises ------ Raises an exception if a common method could not be negotiated. """ available_methods = set(self.credentials.keys()) methods = available_methods.intersection(allowed_methods) logging.debug('Available methods: %s', methods) # If we have no common auth mechanism, then fail. if not methods: msg = 'Negotiation failed. Server supports %s, client supports %s' % \ (allowed_methods, available_methods) raise Exception(msg) # If there are more than 1 method to pick, don't pick # anonymous over an actual method. if len(methods) > 1: methods.discard(securitymethods.ANONYMOUS) # Pick a random method. return methods.pop() def set(self, method_uri, session): """ Associate a security method URI with a requests.Session like object. Parameters ---------- method_uri : str URI representing the security method session : object the requests.Session like object that will dispatch requests for the authentication method provided by method_uri """ self.credentials[method_uri] = session def get(self, method_uri): """ Retrieve the requests.Session like object associated with a security method URI. Parameters ---------- method_uri : str URI representing the security method """ return self.credentials[method_uri] def set_cookie(self, cookie_name, cookie_value, domain='', path='/'): """ Add a cookie to use as authentication. More than one call to set_cookie will add multiple cookies into the same cookie jar used for the request. Parameters ---------- cookie_name : str name of the cookie cookie_value : str value of the cookie domain : str restrict usage of this cookie to this domain path : str restrict usage of this cookie to this path """ cookie_session = self.credentials.setdefault(securitymethods.COOKIE, create_session()) cookie_session.cookies.set(cookie_name, cookie_value, domain=domain, path=path) def set_cookie_jar(self, cookie_jar): """ Set the cookie jar to use for authentication. Any previous cookies or cookie jars set will be removed. Parameters ---------- cookie_jar : obj the cookie jar to use. """ cookie_session = self.credentials.setdefault(securitymethods.COOKIE, create_session()) cookie_session.cookies = cookie_jar def set_client_certificate(self, certificate_path): """ Add a client certificate to use for authentication. Parameters ---------- certificate_path : str path to the file of the client certificate """ cert_session = create_session() cert_session.cert = certificate_path self.set(securitymethods.CLIENT_CERTIFICATE, cert_session) def set_password(self, username, password): """ Add a username / password for basic authentication. Parameters ---------- username : str username to use password : str password to use """ basic_session = create_session() basic_session.auth = (username, password) self.set(securitymethods.BASIC, basic_session) def __repr__(self): return 'Support for %s' % self.credentials.keys() pyvo-1.5.2/pyvo/auth/securitymethods.py000066400000000000000000000002411462331236700202630ustar00rootroot00000000000000ANONYMOUS = 'anonymous' BASIC = 'ivo://ivoa.net/sso#BasicAA' CLIENT_CERTIFICATE = 'ivo://ivoa.net/sso#tls-with-certificate' COOKIE = 'ivo://ivoa.net/sso#cookie' pyvo-1.5.2/pyvo/auth/tests/000077500000000000000000000000001462331236700156235ustar00rootroot00000000000000pyvo-1.5.2/pyvo/auth/tests/__init__.py000066400000000000000000000001001462331236700177230ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst pyvo-1.5.2/pyvo/auth/tests/conftest.py000066400000000000000000000012661462331236700200270ustar00rootroot00000000000000from contextlib import contextmanager import pytest import requests_mock class ContextAdapter(requests_mock.Adapter): """ requests_mock adapter where ``register_uri`` returns a context manager """ @contextmanager def register_uri(self, *args, **kwargs): matcher = super().register_uri(*args, **kwargs) yield matcher self.remove_matcher(matcher) def remove_matcher(self, matcher): if matcher in self._matchers: self._matchers.remove(matcher) @pytest.fixture(scope='function') def mocker(): with requests_mock.Mocker( adapter=ContextAdapter(case_sensitive=True) ) as mocker_ins: yield mocker_ins pyvo-1.5.2/pyvo/auth/tests/data/000077500000000000000000000000001462331236700165345ustar00rootroot00000000000000pyvo-1.5.2/pyvo/auth/tests/data/tap/000077500000000000000000000000001462331236700173205ustar00rootroot00000000000000pyvo-1.5.2/pyvo/auth/tests/data/tap/capabilities.xml000066400000000000000000000071051462331236700224760ustar00rootroot00000000000000 http://example.com/tap/capabilities http://example.com/tap/availability http://example.com/tap/tables $security_methods http://example.com/tap $security_methods ADQL 2.0 ADQL-2.0
POINT
CIRCLE
DISTANCE
POLYGON
REGION
CONTAINS
INTERSECTS
AREA
CENTROID
COORD1
COORD2
application/x-votable+xml votable text/xml text/csv csv text/tab-separated-values tsv 604800 604800 600 600 134217728 134217728 10000 10000
pyvo-1.5.2/pyvo/auth/tests/test_auth.py000066400000000000000000000152401462331236700201770ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.auth """ import base64 from requests.cookies import RequestsCookieJar from string import Template import pytest from astropy.utils.data import get_pkg_data_contents import pyvo.dal from pyvo.auth.authsession import AuthSession from pyvo.dal.tests.test_tap import MockAsyncTAPServer @pytest.fixture(name='security_methods') def _security_methods(): return [None] @pytest.fixture() def auth_capabilities(mocker, security_methods): anon_element = '' sm_element = Template('') method_elements = [] for m in security_methods: if m is None: method_elements.append(anon_element) else: method_elements.append(sm_element.substitute(m=m)) def callback(request, context): t = Template(get_pkg_data_contents('data/tap/capabilities.xml')) capabilities = t.substitute(security_methods=method_elements) return capabilities.encode('utf-8') with mocker.register_uri( 'GET', 'http://example.com/tap/capabilities', content=callback ) as matcher: yield matcher class MockAnonAuthTAPServer(MockAsyncTAPServer): def validator(self, request): assert request.cert is None assert 'Authorization' not in request.headers assert 'Cookie' not in request.headers @pytest.fixture() def anon_auth_service(mocker): yield from MockAnonAuthTAPServer().use(mocker) class MockCookieAuthTAPServer(MockAsyncTAPServer): def validator(self, request): assert request.cert is None assert 'Authorization' not in request.headers assert request.headers.get('Cookie', None) == 'TEST_COOKIE=BADCOOKIE' @pytest.fixture() def cookie_auth_service(mocker): yield from MockCookieAuthTAPServer().use(mocker) class MockCertificateAuthTAPServer(MockAsyncTAPServer): def validator(self, request): assert request.cert == 'client-certificate.pem' assert 'Authorization' not in request.headers assert 'Cookie' not in request.headers @pytest.fixture() def certificate_auth_service(mocker): yield from MockCertificateAuthTAPServer().use(mocker) class MockBasicAuthTAPServer(MockAsyncTAPServer): def validator(self, request): pw = 'testuser:hunter2'.encode('ascii') basic_encoded = 'Basic ' + base64.b64encode(pw).decode('ascii') assert request.cert is None assert request.headers.get('Authorization', None) == basic_encoded assert 'Cookie' not in request.headers @pytest.fixture() def basic_auth_service(mocker): yield from MockBasicAuthTAPServer().use(mocker) @pytest.mark.usefixtures('cookie_auth_service', 'auth_capabilities') @pytest.mark.parametrize('security_methods', [[None, 'ivo://ivoa.net/sso#cookie']]) @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_cookies_auth(): session = AuthSession() session.credentials.set_cookie('TEST_COOKIE', 'BADCOOKIE') service = pyvo.dal.TAPService('http://example.com/tap', session=session) service.run_async("SELECT * FROM ivoa.obscore") @pytest.mark.usefixtures('cookie_auth_service', 'auth_capabilities') @pytest.mark.parametrize('security_methods', [[None, 'ivo://ivoa.net/sso#cookie']]) @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_cookie_jar_auth(): session = AuthSession() jar = RequestsCookieJar() jar.set('TEST_COOKIE', 'BADCOOKIE') session.credentials.set_cookie_jar(jar) service = pyvo.dal.TAPService('http://example.com/tap', session=session) service.run_async("SELECT * FROM ivoa.obscore") @pytest.mark.usefixtures('certificate_auth_service', 'auth_capabilities') @pytest.mark.parametrize('security_methods', [[None, 'ivo://ivoa.net/sso#tls-with-certificate']]) @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_certificate_auth(): session = AuthSession() session.credentials.set_client_certificate('client-certificate.pem') service = pyvo.dal.TAPService('http://example.com/tap', session=session) service.run_async("SELECT * FROM ivoa.obscore") @pytest.mark.usefixtures('basic_auth_service', 'auth_capabilities') @pytest.mark.parametrize('security_methods', [[None, 'ivo://ivoa.net/sso#BasicAA']]) @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_basic_auth(): session = AuthSession() session.credentials.set_password('testuser', 'hunter2') service = pyvo.dal.TAPService('http://example.com/tap', session=session) service.run_async("SELECT * FROM ivoa.obscore") @pytest.mark.usefixtures('basic_auth_service', 'auth_capabilities') @pytest.mark.parametrize('security_methods', [[None, 'ivo://ivoa.net/sso#BasicAA']]) @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_negotiation(): session = AuthSession() session.credentials.set_password('testuser', 'hunter2') session.credentials.set_client_certificate('client-certificate.pem') session.credentials.set_cookie('TEST_COOKIE', 'BADCOOKIE') service = pyvo.dal.TAPService('http://example.com/tap', session=session) service.run_async("SELECT * FROM ivoa.obscore") @pytest.mark.usefixtures('anon_auth_service', 'auth_capabilities') @pytest.mark.parametrize('security_methods', [[None, 'ivo://ivoa.net/sso#FancyAuth']]) @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_no_common_auth_negotiation(): session = AuthSession() session.credentials.set_password('testuser', 'hunter2') session.credentials.set_client_certificate('client-certificate.pem') session.credentials.set_cookie('TEST_COOKIE', 'BADCOOKIE') service = pyvo.dal.TAPService('http://example.com/tap', session=session) service.run_async("SELECT * FROM ivoa.obscore") pyvo-1.5.2/pyvo/conftest.py000066400000000000000000000025671462331236700157310ustar00rootroot00000000000000"""Configure Test Suite. This file is used to configure the behavior of pytest when using the Astropy test infrastructure. It needs to live inside the package in order for it to get picked up when running the tests inside an interpreter using `pyvo.test()`. """ import numpy as np from astropy.utils import minversion try: from pytest_astropy_header.display import PYTEST_HEADER_MODULES, TESTED_VERSIONS ASTROPY_HEADER = True except ImportError: ASTROPY_HEADER = False # Keep this until we require numpy to be >=2.0 if minversion(np, "2.0.0.dev0+git20230726"): np.set_printoptions(legacy="1.25") def pytest_configure(config): """Configure Pytest with Astropy. Parameters ---------- config : pytest configuration """ if ASTROPY_HEADER: config.option.astropy_header = True # Customize the following lines to add/remove entries from the list of # packages for which version numbers are displayed when running the tests. PYTEST_HEADER_MODULES['Astropy'] = 'astropy' # noqa PYTEST_HEADER_MODULES['requests'] = 'requests' # noqa PYTEST_HEADER_MODULES.pop('Pandas', None) PYTEST_HEADER_MODULES.pop('h5py', None) PYTEST_HEADER_MODULES.pop('Scipy', None) PYTEST_HEADER_MODULES.pop('Matplotlib', None) from . import __version__ TESTED_VERSIONS['pyvo'] = __version__ pyvo-1.5.2/pyvo/dal/000077500000000000000000000000001462331236700142605ustar00rootroot00000000000000pyvo-1.5.2/pyvo/dal/__init__.py000066400000000000000000000027551462331236700164020ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from .sia import search as imagesearch from .sia2 import search as imagesearch2 from .ssa import search as spectrumsearch from .sla import search as linesearch from .scs import search as conesearch from .tap import search as tablesearch from .query import DALService, DALQuery, DALResults, Record from .sia import SIAService, SIAQuery, SIAResults, SIARecord from .sia2 import SIA2Service, SIA2Query, SIA2Results, ObsCoreRecord from .ssa import SSAService, SSAQuery, SSAResults, SSARecord from .sla import SLAService, SLAQuery, SLAResults, SLARecord from .scs import SCSService, SCSQuery, SCSResults, SCSRecord from .tap import TAPService, TAPQuery, TAPResults, AsyncTAPJob from .exceptions import ( DALAccessError, DALProtocolError, DALFormatError, DALServiceError, DALQueryError, DALOverflowWarning) __all__ = [ "imagesearch", "spectrumsearch", "linesearch", "conesearch", "tablesearch", "DALService", "imagesearch2", "SIAService", "SIA2Service", "SSAService", "SLAService", "SCSService", "TAPService", "DALQuery", "SIAQuery", "SIA2Query", "SSAQuery", "SLAQuery", "SCSQuery", "TAPQuery", "DALResults", "SIAResults", "SIA2Results", "SSAResults", "SLAResults", "SCSResults", "TAPResults", "Record", "ObsCoreRecord", "SIARecord", "SSARecord", "SLARecord", "SCSRecord", "AsyncTAPJob", "DALAccessError", "DALProtocolError", "DALFormatError", "DALServiceError", "DALQueryError", "DALOverflowWarning"] pyvo-1.5.2/pyvo/dal/adhoc.py000066400000000000000000001021231462331236700157070ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Datalink classes and mixins """ import numpy as np import warnings import copy import requests from collections import OrderedDict from .query import DALResults, DALQuery, DALService, Record from .exceptions import DALServiceError from .vosi import AvailabilityMixin, CapabilityMixin from .params import find_param_by_keyword, get_converter from astropy.io.votable.tree import Param from astropy import units as u from astropy.units import Quantity, Unit from astropy.units import spectral as spectral_equivalencies from astropy.io.votable.tree import Resource, Group from astropy.utils.collections import HomogeneousList from ..utils.decorators import stream_decode_content from ..utils import vocabularies from .params import PosQueryParam, IntervalQueryParam, TimeQueryParam, EnumQueryParam from ..dam.obscore import POLARIZATION_STATES # calls to DataLink from the results pages are batched for performance # reasons. This is the size of a batch DATALINK_BATCH_CALL_SIZE = 50 SODA_SYNC_IVOID = 'ivo://ivoa.net/std/SODA#sync-1' DATALINK_IVOID = 'ivo://ivoa.net/std/datalink' # MIME types DATALINK_MIME_TYPE = 'application/x-votable+xml;content=datalink' # monkeypatch astropy with group support in RESOURCE def _monkeypath_astropy_resource_groups(): old_group_unknown_tag = Group._add_unknown_tag def new_group_unknown_tag(self, iterator, tag, data, config, pos): if tag == "PARAM": return self._add_param(self, iterator, tag, data, config, pos) else: old_group_unknown_tag(self, iterator, tag, data, config, pos) old_init = Resource.__init__ def new_init(self, *args, **kwargs): old_init(self, *args, **kwargs) self._groups = HomogeneousList(Group) Resource.__init__ = new_init def resource_groups(self): return self._groups Resource.groups = property(resource_groups) def resource_add_group(self, iterator, tag, data, config, pos): group = Group(self, config=config, pos=pos, **data) self.groups.append(group) group.parse(iterator, config) Resource._add_group = resource_add_group old_resource_unknown_tag = Resource._add_unknown_tag def new_resource_unknown_tag(self, iterator, tag, data, config, pos): if tag == "GROUP": return self._add_group(iterator, tag, data, config, pos) else: old_resource_unknown_tag(self, iterator, tag, data, config, pos) Resource._add_unknown_tag = new_resource_unknown_tag if not hasattr(Resource, 'groups'): _monkeypath_astropy_resource_groups() __all__ = [ "AdhocServiceResultsMixin", "DatalinkResultsMixin", "DatalinkRecordMixin", "DatalinkService", "DatalinkQuery", "DatalinkResults", "DatalinkRecord", "SodaRecordMixin", "SodaQuery"] def _get_input_params_from_resource(resource): # get the group with name inputParams group_input_params = next( group for group in resource.groups if group.name == "inputParams") # get only Param elements from the group return { _.name: _ for _ in group_input_params.entries if isinstance(_, Param)} def _get_params_from_resource(resource): return {_.name: _ for _ in resource.params} def _get_accessurl_from_params(params): if "accessURL" not in params: raise DALServiceError("Datalink has no accessURL") return params["accessURL"].value class AdhocServiceResultsMixin: """ Mixin for adhoc:service functionallity for results classes. """ def __init__(self, votable, url=None, session=None): super().__init__(votable, url=url, session=session) self._adhocservices = list( resource for resource in votable.resources if resource.type == "meta" and resource.utype == "adhoc:service" ) def iter_adhocservices(self): yield from self._adhocservices def get_adhocservice_by_ivoid(self, ivo_id): """ Return the adhoc service starting with the given ivo_id. Parameters ---------- ivoid : str the ivoid of the service we want to have. Returns ------- Resource The resource element describing the service. """ if isinstance(ivo_id, bytes): ivo_id = ivo_id.decode('utf-8') for adhocservice in self.iter_adhocservices(): if any( all(( param.name == "standardID", param.value.lower().startswith(ivo_id.lower()) )) for param in adhocservice.params ): return adhocservice raise DALServiceError( "No Adhoc Service with ivo-id {}!".format(ivo_id)) def get_adhocservice_by_id(self, id_): """ Return the adhoc service starting with the given service_def id. Parameters ---------- id_ : str the service_def id of the service we want to have. Returns ------- Resource The resource element describing the service. """ for adhocservice in self.iter_adhocservices(): if adhocservice.ID == id_: return adhocservice raise DALServiceError( "No Adhoc Service with service_def id {}!".format(id_)) class DatalinkResultsMixin(AdhocServiceResultsMixin): """ Mixin for datalink functionallity for results classes. """ def iter_datalinks(self): """ Iterates over all datalinks in a DALResult. """ # To reduce the number of calls to the Datalink service, multiple # IDs are sent in batches. The appropriate batch size is not available # as each service has its own criteria for determining the maximum # number of IDs processed per request. To overcome this limitation, # this method initially sends all the available IDs and if the # response is partial, uses the size of the response as the batch # size. if not hasattr(self, '_datalink'): try: self._datalink = self.get_adhocservice_by_ivoid(DATALINK_IVOID) except DALServiceError: self._datalink = None remaining_ids = [] # remaining IDs to processed current_batch = None # retrieved but not returned yet current_ids = [] # retrieved but not returned processed_ids = [] # retrived and returned IDs batch_size = None # size of the batch for row in self: if self._datalink: if not current_ids: if batch_size is None: # first call. self.query = DatalinkQuery.from_resource( [_ for _ in self], self._datalink, session=self._session) remaining_ids = self.query['ID'] if not remaining_ids: # we are done return if batch_size: # subsequent calls are limitted to batch size self.query['ID'] = remaining_ids[:batch_size] current_batch = self.query.execute(post=True) current_ids = list(OrderedDict.fromkeys( [_ for _ in current_batch.to_table()['ID']])) if not current_ids: raise DALServiceError( 'Could not retrieve datalinks for: {}'.format( ', '.join([_ for _ in remaining_ids]))) batch_size = len(current_ids) id1 = current_ids.pop(0) processed_ids.append(id1) remaining_ids.remove(id1) yield current_batch.clone_byid(id1) elif row.access_format == DATALINK_MIME_TYPE: yield DatalinkResults.from_result_url(row.getdataurl()) else: yield None class DatalinkRecordMixin: """ Mixin for record classes, providing functionallity for datalink. - ``getdataset()`` considers datalink. """ def getdatalink(self): try: datalink = self._results.get_adhocservice_by_ivoid(DATALINK_IVOID) query = DatalinkQuery.from_resource(self, datalink, session=self._session) return query.execute() except DALServiceError: return DatalinkResults.from_result_url(self.getdataurl(), session=self._session) @stream_decode_content def getdataset(self, timeout=None): try: url = next(self.getdatalink().bysemantics('#this')).access_url response = self._session.get(url, stream=True, timeout=timeout) try: response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, url) return response.raw except (DALServiceError, ValueError, StopIteration): # this should go to Record.getdataset() return super().getdataset(timeout=timeout) class DatalinkService(DALService, AvailabilityMixin, CapabilityMixin): """ a representation of a Datalink service """ def __init__(self, baseurl, session=None): """ instantiate a Datalink service Parameters ---------- baseurl : str the base URL that should be used for forming queries to the service. session : object optional session to use for network requests """ super().__init__(baseurl, session=session) # Check if the session has an update_from_capabilities attribute. # This means that the session is aware of IVOA capabilities, # and can use this information in processing network requests. # One such usecase for this is auth. if hasattr(self._session, 'update_from_capabilities'): self._session.update_from_capabilities(self.capabilities) def run_sync(self, id, responseformat=None, **keywords): """ runs sync query and returns its result Parameters ---------- id : str the dataset identifier responseformat : str the output format Returns ------- DatalinkResults the query result See Also -------- DatalinkResults """ return self.create_query(id, responseformat, **keywords).execute() # alias for service discovery search = run_sync def create_query(self, id, responseformat=None, **keywords): """ create a query object that constraints can be added to and then executed. The input arguments will initialize the query with the given values. Parameters ---------- baseurl : str the base URL for the Datalink service id : str the dataset identifier responseformat : str the output format """ return DatalinkQuery( self.baseurl, id, responseformat, **keywords) class DatalinkQuery(DALQuery): """ A class for preparing a query to a Datalink service. Query constraints are added via its service type-specific methods. The various execute() functions will submit the query and return the results. The base URL for the query, which controls where the query will be sent when one of the execute functions is called, is typically set at construction time; however, it can be updated later via the :py:attr:`~pyvo.dal.DALQuery.baseurl` to send a configured query to another service. A session can also optionally be passed in that will be used for network transactions made by this object to remote services. In addition to the search constraint attributes described below, search parameters can be set generically by name via dict semantics. The typical function for submitting the query is ``execute()``; however, alternate execute functions provide the response in different forms, allowing the caller to take greater control of the result processing. """ @classmethod def from_resource(cls, rows, resource, session=None, **kwargs): """ Creates a instance from a number of records and a Datalink Resource. XML Hierarchy: .. code:: xml """ input_params = _get_input_params_from_resource(resource) # get params outside of any group dl_params = _get_params_from_resource(resource) accessurl = _get_accessurl_from_params(dl_params) query_params = dict() for name, input_param in input_params.items(): if input_param.ref: if isinstance(rows, list): query_params[name] = [] for r in rows: query_params[name].append(r[input_param.ref]) else: # scalars are also accepted for backwards compatibility query_params[name] = rows[input_param.ref] elif np.isscalar(input_param.value) and input_param.value: query_params[name] = input_param.value elif ( not np.isscalar(input_param.value) and input_param.value.all() and len(input_param.value) ): query_params[name] = " ".join( str(_) for _ in input_param.value) for name, query_param in kwargs.items(): try: input_param = find_param_by_keyword(name, input_params) if input_param and query_param is None: del query_params[input_param.name] converter = get_converter(input_param) query_params[input_param.name] = converter.serialize( query_param) except KeyError: query_params[name] = query_param return cls(accessurl, session=session, **query_params) def __init__( self, baseurl, id=None, responseformat=None, session=None, **keywords): """ initialize the query object with the given parameters Parameters ---------- baseurl : str the Datalink baseurl id : str the dataset identifier responseformat : str the output format session : object optional session to use for network requests """ super().__init__(baseurl, session=session, **keywords) if id is not None: self["ID"] = id if responseformat is not None: self["RESPONSEFORMAT"] = responseformat def execute(self, post=False): """ submit the query and return the results as a DatalinkResults instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return DatalinkResults(self.execute_votable(post=post), url=self.queryurl, session=self._session) class DatalinkResults(DatalinkResultsMixin, DALResults): """ The list of matching records resulting from an datalink query. Each record contains a set of metadata that describes an available record matching the query constraints. The number of records in the results is available by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.Record` instances) are typically accessed by iterating over an ``DatalinkResults`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.DALResults.getcolumn` method. ``DatalinkResults`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the images. One can access that VOTable directly via the :py:attr:`~pyvo.dal.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`~astropy.table.Table` via the following conversion: ``table = results.to_table()`` ``DatalinkResults`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.Record` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """ def getrecord(self, index): """ return a representation of a datalink result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has the additional function ``getdataset`` Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- REc a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- pyvo.dal.Record """ return DatalinkRecord(self, index, session=self._session) def bysemantics(self, semantics, include_narrower=True): """ return the rows with the dataset identified by the given semantics Parameters ---------- semantics: str or list One or more term(s) from the datalink vocabulary (http://www.ivoa.net/rdf/datalink/core). datalink/core terms may be passed in with or without a leading hash. Free URIs may also be passed an and will be compared literally, i.e., without any URI normalisation. include_narrower: boolean If true, the result will include matches for any term that is narrower than the term passed in. Returns ------- Sequence of DatalinkRecord a sequence of dictionary-like wrappers containing the result record """ # If the URL juggling gets any more complicated here, we ought # to bite the bullet and only deal with full URLs. Sigh. if isinstance(semantics, str): semantics = [semantics] core_terms, other_terms = [], [] for term in semantics: if ":" in term: # it's a full URI, see if it's ours if term.startswith("http://www.ivoa.net/rdf/datalink/core#"): core_terms.append(term.split("#", 1)[-1]) else: other_terms.append(term) else: # it's a local term core_terms.append(term.lstrip("#")) if include_narrower: additional_terms = [] voc = vocabularies.get_vocabulary("datalink/core") for term in core_terms: if term in voc["terms"]: additional_terms.extend(voc["terms"][term]["narrower"]) core_terms = core_terms + additional_terms semantics = set("#" + term for term in core_terms) | set(other_terms) for record in self: if record.semantics in semantics: yield record def clone_byid(self, id): """ return a clone of the object with results and corresponding resources matching a given id Returns ------- Sequence of DatalinkRecord a sequence of dictionary-like wrappers containing the result record """ copy_tb = copy.deepcopy(self.votable) votable = copy_tb.get_first_table() # find index of ID column id_index = None for index, field in enumerate(votable.fields): if field.name == 'ID': id_index = index rows = [x for x in votable.array if x[id_index] == id] votable.create_arrays(len(rows)) for index, row in enumerate(rows): votable.array[index] = row # now remove unreferenced services from resources referenced_serviced = [x for x in votable.array['service_def'] if x] # remove customized that are not referenced by the current results for x in copy_tb.resources: if x.ID and x.ID not in referenced_serviced: copy_tb.resources.remove(x) return DatalinkResults(copy_tb) def getdataset(self, timeout=None): """ return the first row with the dataset identified by semantics #this Returns ------- DatalinkRecord a dictionary-like wrapper containing the result record. """ try: return next(self.bysemantics("#this")).getdataset(timeout=timeout) except StopIteration: raise ValueError("No row with semantics #this found!") def iter_procs(self): """ iterate over all rows with a processing service """ for row in self: if row.service_def: yield row def get_first_proc(self): """ returns the first datalink row with a processing service. """ for proc in self.iter_procs(): return proc raise IndexError("No processing service found in datalink result") class SodaRecordMixin: """ Mixin for soda functionality for record classes. If used, it's result class must have `pyvo.dal.adhoc.AdhocServiceResultsMixin` mixed in. """ def _get_soda_resource(self): try: return self._results.get_adhocservice_by_ivoid(SODA_SYNC_IVOID) except DALServiceError: pass # let it count as soda resource try: return self._results.get_adhocservice_by_ivoid( "ivo://ivoa.net/std/datalink#links") except DALServiceError: pass dataformat = self.getdataformat() if dataformat is None: raise DALServiceError( "No SODA Resouces available and no dataformat in record. " "Maybe you forgot to include it into the TAP Query?") if "content=datalink" in dataformat: dataurl = self.getdataurl() if not dataurl: raise DALServiceError( "No dataurl in record, but dataformat contains " "'content=datalink'. Maybe you forgot to include it into " "the TAP Query?") try: datalink_result = DatalinkResults.from_result_url(dataurl) return datalink_result.get_adhocservice_by_ivoid( SODA_SYNC_IVOID) except DALServiceError: pass return None def processed( self, circle=None, range=None, polygon=None, band=None, **kwargs): """ Returns processed dataset. Parameters ---------- circle : `astropy.units.Quantity` latitude, longitude and radius range : `astropy.units.Quantity` two longitude + two latitude values describing a rectangle polygon : `astropy.units.Quantity` multiple (at least three) pairs of longitude and latitude points band : `astropy.units.Quantity` two bandwidth or frequency values """ soda_resource = self._get_soda_resource() if soda_resource: soda_query = SodaQuery.from_resource( self, soda_resource, circle=circle, range=range, polygon=polygon, band=band, **kwargs) soda_stream = soda_query.execute_stream() soda_query.raise_if_error() return soda_stream else: return self.getdataset() class DatalinkRecord(DatalinkRecordMixin, SodaRecordMixin, Record): """ a dictionary-like container for data in a record from the results of an datalink query, The commonly accessed metadata which are stadardized by the datalink standard are available as attributes. If the metadatum accessible via an attribute is not available, the value of that attribute will be None. All metadata, including non-standard metadata, are acessible via the ``get(`` *key* ``)`` function (or the [*key*] operator) where *key* is table column name. """ @property def id(self): """ Input identifier """ return self.get("ID", decode=True) @property def access_url(self): """ Link to data or processing service """ row_url = self.get("access_url", decode=True) if not row_url: proc_resource = self._results.get_adhocservice_by_id( self.service_def) dl_params = _get_params_from_resource(proc_resource) row_url = _get_accessurl_from_params(dl_params) return row_url @property def service_def(self): """ reference to the service descriptor resource """ return self.get("service_def", decode=True) @property def error_message(self): """ Error if an access_url cannot be created """ return self.get("error_message", decode=True) @property def description(self): """ Human-readable text describing this link """ return self.get("description", decode=True) @property def semantics(self): """ Term from a controlled vocabulary describing the link """ return self.get("semantics", decode=True) @property def content_type(self): """ Mime-type of the content the link returns """ return self.get("content_type", decode=True) @property def content_length(self): """ Size of the download the link returns """ return int(self["content_length"]) def getdataurl(self): """ return the URL contained in the access URL column which can be used to retrieve the dataset described by this record. Raises :py:class:`~pyvo.dal.DALServiceError` if theres an error. """ if self.error_message: raise DALServiceError(self.error_message) return self.access_url def process(self, **kwargs): """ calls the processing service and returns it's result as a file-like object """ proc_resource = self._results.get_adhocservice_by_id(self.service_def) proc_query = DatalinkQuery.from_resource(self, proc_resource, **kwargs) proc_stream = proc_query.execute_stream() return proc_stream @property def params(self): """ the access parameters of the service behind this datalink row. """ proc_resource = self._results.get_adhocservice_by_id(self.service_def) return proc_resource.params @property def input_params(self): """ a list of input parameters for the service behind this datalink row. """ proc_resource = self._results.get_adhocservice_by_id(self.service_def) return list(_get_input_params_from_resource(proc_resource).values()) class AxisParamMixin(): """ Stores the axis parameters (pos, band, time and pol) used in SODA or SIA2 queries """ @property def pos(self): if not hasattr(self, '_pos'): self._pos = PosQueryParam() self['POS'] = self._pos.dal return self._pos @property def band(self): if not hasattr(self, '_band'): self._band = IntervalQueryParam(unit=u.meter, equivalencies=u.spectral()) self['BAND'] = self.band.dal return self._band @property def time(self): if not hasattr(self, '_time'): self._time = TimeQueryParam() self['TIME'] = self.time.dal return self._time @property def pol(self): if not hasattr(self, '_pol'): self._pol = EnumQueryParam(POLARIZATION_STATES) self['POL'] = self.pol.dal return self._pol class SodaQuery(DatalinkQuery, AxisParamMixin): """ a class for preparing a query to a SODA Service. """ def __init__( self, baseurl, circle=None, range=None, polygon=None, band=None, **kwargs): super().__init__(baseurl, **kwargs) if circle is not None: self.circle = circle if range is not None: self.range = range if polygon is not None: self.polygon = polygon if band is not None: self.band = band @property def circle(self): """ The CIRCLE parameter defines a spatial region using the circle xtype defined in DALI. """ return getattr(self, '_circle', None) @circle.setter def circle(self, circle): if len(circle) != 3: raise ValueError( "Range must be a sequence with exactly three values") self['CIRCLE'] = PosQueryParam().get_dal_format(circle).\ replace('CIRCLE ', '') setattr(self, '_circle', circle) del self.range del self.polygon @circle.deleter def circle(self): if hasattr(self, '_circle'): delattr(self, '_circle') if 'CIRCLE' in self: del self['CIRCLE'] @property def range(self): """ A rectangular range. """ warnings.warn( "Use pos attribute instead", DeprecationWarning ) return getattr(self, '_circle', None) @range.setter def range(self, range): if len(range) != 4: raise ValueError( "Range must be a sequence with exactly four values") self['POS'] = PosQueryParam().get_dal_format(range) setattr(self, '_range', range) del self.circle del self.polygon @range.deleter def range(self): if hasattr(self, '_range'): delattr(self, '_range') if 'POS' in self and self['POS'].startswith('RANGE'): del self['POS'] @property def polygon(self): """ The POLYGON parameter defines a spatial region using the polygon xtype defined in DALI. """ return getattr(self, '_polygon', None) @polygon.setter def polygon(self, polygon): if len(polygon) < 6: raise ValueError( "Polygon must be a sequence of at least six values") self['POLYGON'] = PosQueryParam().get_dal_format(polygon).\ replace('POLYGON ', '') setattr(self, '_polygon', polygon) del self.circle del self.range @polygon.deleter def polygon(self): if hasattr(self, '_polygon'): delattr(self, '_polygon') if 'POLYGON' in self: del self['POLYGON'] @property def band(self): """ The BAND parameter defines the wavelength interval(s) to be extracted from the data using a floating point interval """ return getattr(self, "_band", None) @band.setter def band(self, band): setattr(self, "_band", band) if not isinstance(band, Quantity): valerr = ValueError( 'Band must be specified with exactly two values, ', 'expressing a frequency or wavelength range' ) try: # assume meters band = band * Unit("meter") if len(band) != 2: raise valerr except (ValueError, TypeError): raise valerr # transform to meters band = band.to(Unit("m"), equivalencies=spectral_equivalencies()) # frequency is counter-proportional to wavelength, so we just sort # it to have the right order again band.sort() self["BAND"] = "{start} {end}".format( start=band.value[0], end=band.value[1]) @band.deleter def band(self): if hasattr(self, '_band'): delattr(self, '_band') if 'BAND' in self: del self['BAND'] pyvo-1.5.2/pyvo/dal/dbapi2.py000066400000000000000000000160141462331236700157750ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ An implementation of the Database API v2.0 interface to DAL VOTable responses. This only supports read-only access. """ from .query import Iter apilevel = "2.0" threadsafety = 2 paramstyle = "n/a" __all__ = "STRING BINARY NUMBER DATETIME ROWID".split() class Error(Exception): """ DB-API base exception """ pass class Warning(Exception): """ DB-API warning """ pass class InterfaceError(Error): """ DB-API exception indicating an error related to the database interface rather than the database itself. """ pass class DatabaseError(Error): """ DB-API exception indicating an error related to the database. """ pass class DataError(DatabaseError): """ DB-API exception indicating an error while processing data (e.g. divide by zero, numeric value out-of-range, etc.) """ pass class OperationalError(DatabaseError): """ DB-API exception indicating an error related to the database's operation and not necessarily under the control of the programmer. """ pass class IntegrityError(DatabaseError): """ DB-API exception indicating an inconsistancy in the database integrity. """ pass class InternalError(DatabaseError): """ DB-API exception indicating an internal error that might indicate that a connection or cursor is no longer valid. """ pass class ProgrammingError(DatabaseError): """ DB-API exception indicating an erroneous request (e.g. column not found) """ pass class NotSupportedError(DatabaseError): """ DB-API exception indicating a request is not supported """ pass class TypeObject: def __init__(self, *values): self._values = values @property def id(self): return self._values[0] def __eq__(self, other): if not isinstance(other, TypeObject): return False if other.id in self._values: return True return self.id in other._values def __ne__(self, other): return not self.__eq__(other) STRING = TypeObject(0) BINARY = TypeObject(1) NUMBER = TypeObject(2) DATETIME = TypeObject(3, STRING.id) ROWID = TypeObject(4, NUMBER.id) def connect(source): raise NotSupportedError("Connection objects not supported") class Cursor(Iter): """ A class used to walk through a query response table row by row, accessing the contents of each record (row) of the table. This class implements the Python Database API. """ def __init__(self, results): """Create a cursor instance. The constructor is not typically called by directly applications; rather an instance is obtained from calling a DalQuery's execute(). """ super().__init__(results) self._description = self._mkdesc() self._rowcount = len(self.resultset) self._arraysize = 1 def _mkdesc(self): flds = self.resultset.fieldnames out = [] for name in flds: fld = self.resultset.getdesc(name) typ = STRING if fld.datatype in ("short", "int", "long", "float", "double", "floatComplex", "doubleComplex", "boolean"): typ = NUMBER elif fld.datatype in "char unicodeChar unsignedByte".split(): typ = STRING out.append((name, typ)) return tuple(out) @property def description(self): """ a read-only sequence of 2-item seqences. Each seqence describes a column in the results, giving its name and type_code. """ return self._description @property def rowcount(self): """ the number of rows in the result (read-only) """ return self._rowcount @property def arraysize(self): """ the number of rows that will be returned by returned by a call to fetchmany(). This defaults to 1, but can be changed. """ return self._arraysize @arraysize.setter def arraysize(self, value): if not value: value = 1 self._arraysize = value def infos(self): """Return any INFO elements in the VOTable as a dictionary. Returns ------- dict : A dictionary with each element corresponding to a single INFO, representing the INFO as a name:value pair. """ return self.resultset._infos def fetchone(self): """Return the next row of the query response table. Returns ------- tuple : The response is a tuple wherein each element is the value of the corresponding table field. """ try: rec = self.next() out = [] for name in self.resultset.fieldnames: out.append(rec[name]) return out except StopIteration: return None def fetchmany(self, size=None): """Fetch the next block of rows from the query result. Parameters ---------- size : int The number of rows to return (default: cursor.arraysize). Returns ------- list of tuples : A list of tuples, one per row. An empty sequence is returned when no more rows are available. If a DictCursor is used then the output consists of a list of dictionaries, one per row. """ if not size: size = self.arraysize out = [] for _ in range(size): out.append(self.fetchone()) return out def fetchall(self): """Fetch all remaining rows from the result set. Returns ------- list of tuples : A list of tuples, one per row. An empty sequence is returned when no more rows are available. If a DictCursor is used then the output consists of a list of dictionaries, one per row. """ out = [] for _ in range(self._rowcount - self.pos): out.append(self.fetchone()) return out def scroll(self, value, mode="relative"): """Move the row cursor. Parameters ---------- value : str The number of rows to skip or the row number to position to. mode : str Either "relative" for a relative skip (default), or "absolute" to position to a row by its absolute index within the result set (zero-indexed). """ if mode == "absolute": if value > 0: self.pos = value else: raise DataError("row number not valid:" + str(value)) elif mode == "relative": self.pos += value def close(self): """Close the cursor object and free all resources. This implementation does nothing. It is provided for compliance with the Python Database API. """ # this can remain implemented as "pass" pass pyvo-1.5.2/pyvo/dal/exceptions.py000066400000000000000000000166051462331236700170230ustar00rootroot00000000000000""" DAL Exceptions. """ __all__ = [ "DALAccessError", "DALProtocolError", "DALFormatError", "DALServiceError", "DALQueryError", "DALOverflowWarning"] import re import requests from astropy.utils.exceptions import AstropyUserWarning class DALAccessError(Exception): """ a base class for failures while accessing a DAL service """ _defreason = "Unknown service access error" def __init__(self, reason=None, url=None): """ initialize the exception with an error message Parameters ---------- reason : str a message describing the cause of the error url : str the query URL that produced the error """ if not reason: reason = self._defreason super().__init__(reason) self._reason = reason self._url = url @classmethod def _typeName(cls, exc): try: return exc.__qualname__ except AttributeError: return re.sub( r"'>$", '', re.sub(r"<(type|class) '(.*\.)?", '', str(type(exc))) ) def __str__(self): return self._reason def __repr__(self): return "{}: {}".format(self._typeName(self), self._reason) @property def reason(self): """ a string description of what went wrong """ return self._reason @property def url(self): """ the URL that produced the error. If None, the URL is unknown or unset """ return self._url class DALProtocolError(DALAccessError): """ a base exception indicating that a DAL service responded with an error. This can be either an HTTP protocol error or a response format error; both of these are handled by separate subclasses. This base class captures an underlying exception clause. """ _defreason = "Unknown DAL Protocol Error" def __init__(self, reason=None, cause=None, url=None): """ initialize with a string message and an optional HTTP response code Parameters ---------- reason : str a message describing the cause of the error code : int the HTTP error code (as an integer) cause : str an exception issued as the underlying cause. A value of None indicates that no underlying exception was caught. url : str the query URL that produced the error """ super().__init__(reason, url) self._cause = cause @property def cause(self): """ a string description of what went wrong """ return self._cause class DALFormatError(DALProtocolError): """ an exception indicating that a DAL response contains fatal format errors. This would include XML or VOTable format errors. """ _defreason = "Unknown VOTable Format Error" def __init__(self, cause=None, url=None, reason=None): """ create the exception Parameters ---------- cause : str an exception issued as the underlying cause. A value of None indicates that no underlying exception was caught. url the query URL that produced the error reason a message describing the cause of the error """ if cause and not reason: reason = "{}: {}".format( DALAccessError._typeName(cause), str(cause)) super().__init__(reason, cause, url) class DALServiceError(DALProtocolError): """ an exception indicating a failure communicating with a DAL service. Most typically, this is used to report DAL queries that result in an HTTP error. """ _defreason = "Unknown service error" def __init__(self, reason=None, code=None, cause=None, url=None): """ initialize with a string message and an optional HTTP response code Parameters ---------- reason : str a message describing the cause of the error code : int the HTTP error code (as an integer) cause : str an exception issued as the underlying cause. A value of None indicates that no underlying exception was caught. url : str the query URL that produced the error """ super().__init__(reason, cause, url) self._code = code @property def code(self): """ the HTTP error code that resulted from the DAL service query, indicating the error. If None, the service did not produce an HTTP response. """ return self._code @classmethod def from_except(cls, exc, url=None): """ create and return DALServiceError exception appropriate for the given exception that represents the underlying cause. """ if isinstance(exc, requests.exceptions.RequestException): try: response = exc.response except AttributeError: response = None code = 0 message = str(exc) # if there is a response, refine the error message if response is not None: code = response.status_code content_type = response.headers.get('content-type', None) if content_type and 'text/plain' in content_type: message = '{} for {}'.format(response.text, url) # TODO votable handling return DALServiceError(message, code, exc, url) elif isinstance(exc, Exception): return DALServiceError("{}: {}".format(cls._typeName(exc), str(exc)), cause=exc, url=url) else: raise TypeError("from_except: expected Exception") class DALQueryError(DALAccessError): """ an exception indicating an error by a working DAL service while processing a query. Generally, this would be an error that the service successfully detected and consequently was able to respond with a legal error response-- namely, a VOTable document with an INFO element contains the description of the error. Possible errors will include bad usage by the client, such as query-syntax errors. """ _defreason = "Unknown DAL Query Error" def __init__(self, reason=None, label=None, url=None): """ Parameters ---------- reason : str a message describing the cause of the error. This should be set to the content of the INFO error element. label : str the identifying name of the error. This should be the value of the INFO element's value attribute within the VOTable response that describes the error. url : str the query URL that produced the error """ super().__init__(reason, url) self._label = label @property def label(self): """ the identifing name for the error given in the DAL query response. DAL queries that produce an error which is detectable on the server will respond with a VOTable containing an INFO element that contains the description of the error. This property contains the value of the INFO's value attribute. """ return self._label class PyvoUserWarning(AstropyUserWarning): pass class DALOverflowWarning(UserWarning): pass pyvo-1.5.2/pyvo/dal/mimetype.py000066400000000000000000000064461462331236700164750ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for parsing and working with mimetypes """ import mimetypes from email.message import Message from astropy.io.fits import HDUList from ..utils.http import use_session mimetypes.add_type('application/fits', 'fits') mimetypes.add_type('application/x-fits', 'fits') mimetypes.add_type('image/fits', 'fits') mimetypes.add_type('text/plain', 'txt') def mime2extension(mimetype, default=None): """ return a recommended file extension for a file with a given MIME-type. This function provides some generic mappings that can be leveraged in implementations of ``suggest_extension()`` in ``Record`` subclasses. >>> mime2extension('application/fits') >>> mime2extension('image/jpeg') >>> mime2extension('application/x-zed', 'dat') Parameters ---------- mimetype : str the file MIME-type byte-string to convert default : str the default extension to return if one could not be recommended based on ``mimetype``. By convention, this should not include a preceding '.' Returns ------- str the recommended extension without a preceding '.', or the value of ``default`` if no recommendation could be made. """ if not mimetype: return default if isinstance(mimetype, str): mimetype = mimetype.encode('utf-8') ext = mimetypes.guess_extension(mimetype, strict=False) return ext def mime_object_maker(url, mimetype, session=None): """ return a data object suitable for the mimetype given. this will either return a astropy fits object or a pyvo DALResults object, a PIL object for conventional images or string for text content. Parameters ---------- url : str the object download url mimetype : str the content mimetype session : object optional session to use for network requests Raises ------ ValueError if the mimetype is missing or cannot be parsed correctly """ if not mimetype: raise ValueError('mimetype required') session = use_session(session) msg = Message() msg['content-type'] = mimetype pp = msg.get_params() full_type = pp[0][0] params = pp[1:] mtype = [x.strip() for x in full_type.split('/')] if '/' in full_type else None if not mtype or len(mtype) > 2: raise ValueError("Can't parse mimetype \"{}\"".format(full_type)) if mtype[0] == 'text': return session.get(url).text if mtype[1] == 'fits' or mtype[1] == 'x-fits': response = session.get(url) return HDUList.fromstring(response.content) if mtype[0] == 'image': from PIL import Image from io import BytesIO response = session.get(url) bio = BytesIO(response.content) return Image.open(bio) if mtype[1] == 'x-votable' or mtype[1] == 'x-votable+xml': # As soon as there are some kind of recursive data structures, # things start to get messy for param in params: if (param[0].lower() == 'content') and (param[1].lower() == 'datalink'): from .adhoc import DatalinkResults return DatalinkResults.from_result_url(url) from .query import DALResults return DALResults.from_result_url(url) pyvo-1.5.2/pyvo/dal/params.py000066400000000000000000000342261462331236700161240ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This file contains functionallity related to VOTABLE Params. """ import numpy as np from collections.abc import MutableSet import abc from astropy import units as u from astropy.coordinates import SkyCoord from astropy.units import Quantity, Unit from astropy.time import Time from astropy.io.votable.converters import ( get_converter as get_votable_converter) from .exceptions import DALServiceError NUMERIC_DATATYPES = {'short', 'int', 'long', 'float', 'double'} def find_param_by_keyword(keyword, params): """ Searches for a specific param by keyword. This function will try to look for the keyword as-is first, and then tries to find the uppercase'd version of the keyword. """ if keyword in params: return params[keyword] keyword = keyword.upper() if keyword in params: return params[keyword] raise KeyError('No param named {} defined'.format(keyword)) registry = dict() def xtype(name): def decorate(cls): registry[name] = cls return cls return decorate def unify_value(func): """ Decorator for serialize method to do unit conversion on input value. The decorator converts the input value to the unit in the input param. """ def wrapper(self, value): if self._param.unit: value = Quantity(value) if not value.unit.to_string(): value = value * Unit(self._param.unit) else: value = value.to(self._param.unit) if isinstance(value, Quantity): value = value.value return func(self, value) return wrapper def get_converter(param): if param.xtype in registry: return registry[param.xtype](param) if param.datatype in NUMERIC_DATATYPES: return Number(param) return Converter(param) class Converter: """ Base class for all converters. Each subclass handles the conversion of a input value based on a specific xtype. """ def __init__(self, param): self._param = param def serialize(self, value): """ Serialize for use in DAL Queries """ if isinstance(value, list): # multiple values return [str(_) for _ in value] else: return str(value) class Number(Converter): def __init__(self, param): if param.datatype not in {'short', 'int', 'long', 'float', 'double'}: pass super().__init__(param) @unify_value def serialize(self, value): """ Serialize for use in DAL Queries """ return get_votable_converter(self._param).output( value, np.zeros_like(value)) @xtype('timestamp') class Timestamp(Converter): def __init__(self, param): if param.datatype != 'char': raise DALServiceError('Datatype is not char') super().__init__(param) def serialize(self, value): """ Serialize time values for use in DAL Queries """ value = Time(value) if value.size == 1: return value.isot else: raise DALServiceError('Expecting a scalar time value') @xtype('interval') class Interval(Number): def __init__(self, param): try: arraysize = int(param.arraysize) if arraysize % 2: raise DALServiceError('Arraysize is not even') except ValueError: raise DALServiceError('Arraysize is not even') super().__init__(param) @unify_value def serialize(self, value): size = np.size(value) if size % 2: raise DALServiceError('Interval size is not even') return super().serialize(value) @xtype('point') class Point(Number): def __init__(self, param): try: arraysize = int(param.arraysize) if arraysize != 2: raise DALServiceError('Point arraysize must be 2') except ValueError: raise DALServiceError('Point arraysize must be 2') super().__init__(param) @unify_value def serialize(self, value): size = np.size(value) if size != 2: raise DALServiceError('Point size must be 2') return super().serialize(value) @xtype('circle') class Circle(Number): def __init__(self, param): arraysize = int(param.arraysize) if arraysize != 3: raise DALServiceError('Circle arraysize must be 3') super().__init__(param) @unify_value def serialize(self, value): size = np.size(value) if size != 3: raise DALServiceError('Circle size must be 3') return super().serialize(value) @xtype('polygon') class Polygon(Number): def __init__(self, param): try: arraysize = int(param.arraysize) if arraysize % 3: raise DALServiceError('Arraysize is not a multiple of 3') except ValueError: if param.arraysize != '*': raise DALServiceError('Arraysize is not a multiple of 3') super().__init__(param) @unify_value def serialize(self, value): size = np.size(value) try: if size % 3: raise DALServiceError('Size is not a multiple of 3') except ValueError: raise DALServiceError('Size is not a multiple of 3') return super().serialize(value) class AbstractDalQueryParam(MutableSet, metaclass=abc.ABCMeta): """ Base class for a DAL parameter. In general, a DAL parameter allows for multiple values which are OR-ed by the service. As such, the class behaves like a set that stores all the values. When a value is added to an attribute, it is validated and formatted to conform to the using service (SIA2 or SODA) and value errors might be raised. The `dal` attribute stores the current list of formatted attributes. Subclasses must override the `dal_formatter` method that formats values for serialization. That includes unit conversions and string representation Duplicates in the set are determine based on the formatted DAL representation of the value. """ def __init__(self, values=()): """ Parameters ---------- values : sequence, optional An initial set of values. """ self.dal = [] self._data = [] for item in values: self.add(item) super().__init__() @abc.abstractmethod def get_dal_format(self, item): """ Method to be provided by subclasses """ return def add(self, item): if item in self: return self._data.append(item) self.dal.append(self.get_dal_format(item)) def discard(self, item): # relies on the fact that both the raw and the formatted # attribute lists have the items in the same order. It # uses the formatted list (normalized units) to get the index. index = self.dal.index(self.get_dal_format(item)) self._data.pop(index) self.dal.pop(index) def __iter__(self): return iter(self._data) def __len__(self): return len(self._data) def __contains__(self, item): # check dal format for duplications since the quantities are known return self.get_dal_format(item) in self.dal class StrQueryParam(AbstractDalQueryParam): """ Representation of a unitless, single-value parameter. The formatter is just a str() cast """ def get_dal_format(self, val): return str(val) class PosQueryParam(AbstractDalQueryParam): """ Representation of a position parameter. Depending on the number of entries, the resulting DAL format is CIRCLE, RANGE or POLYGON. """ def get_dal_format(self, val): """ formats the tuple values into a string to be sent to the service entries in values are either quantities or assumed to be degrees """ self._validate_pos(val) if len(val) == 2 or len(val) == 3: shape = 'CIRCLE' elif len(val) == 4: shape = 'RANGE' elif len(val) > 5 and not len(val) % 2: shape = 'POLYGON' else: raise ValueError( 'Invalid shape {}. Tuple with 3 (CIRCLE), 4 (RANGE) or ' 'even 6 and above (POLYGON) accepted.'.format(val)) return '{} {}'.format(shape, ' '.join( [str(val.to(u.deg).value) if isinstance(val, Quantity) else val.transform_to('icrs').to_string() if isinstance(val, SkyCoord) else str((val * u.deg).value) for val in val])) def _validate_pos(self, pos): """ validates position This has probably done already somewhere else """ if len(pos) == 2: if not isinstance(pos[0], SkyCoord): raise ValueError if not isinstance(pos[1], Quantity): radius = pos[1] * u.deg else: radius = pos[1] if radius <= 0 * u.deg or radius.to(u.deg) > 90 * u.deg: raise ValueError('Invalid circle radius: {}'.format(radius)) elif len(pos) == 3: self._validate_ra(pos[0]) self._validate_dec(pos[1]) if not isinstance(pos[2], Quantity): radius = pos[2] * u.deg else: radius = pos[2] if radius <= 0 * u.deg or radius.to(u.deg) > 90 * u.deg: raise ValueError('Invalid circle radius: {}'.format(radius)) elif len(pos) == 4: ra_min = pos[0] if isinstance(pos[0], Quantity) else pos[0] * u.deg ra_max = pos[1] if isinstance(pos[1], Quantity) else pos[1] * u.deg dec_min = pos[2] if isinstance(pos[2], Quantity) \ else pos[2] * u.deg dec_max = pos[3] if isinstance(pos[3], Quantity) \ else pos[3] * u.deg self._validate_ra(ra_min) self._validate_ra(ra_max) if ra_max.to(u.deg) < ra_min.to(u.deg): raise ValueError('min > max in ra range: {} > {}'. format(ra_min, ra_max)) self._validate_dec(dec_min) self._validate_dec(dec_max) if dec_max.to(u.deg) < dec_min.to(u.deg): raise ValueError('min > max in dec range: {} > {}'. format(dec_min, dec_max)) else: for i, m in enumerate(pos): if i % 2: self._validate_dec(m) else: self._validate_ra(m) def _validate_ra(self, ra): if not isinstance(ra, Quantity): ra = ra * u.deg if ra.to(u.deg).value < 0 or ra.to(u.deg).value > 360.0: raise ValueError('Invalid ra: {}'.format(ra)) def _validate_dec(self, dec): if not isinstance(dec, Quantity): dec = dec * u.deg if dec.to(u.deg).value < -90.0 or dec.to(u.deg).value > 90.0: raise ValueError('Invalid dec: {}'.format(dec)) class IntervalQueryParam(AbstractDalQueryParam): """ Representation of an interval DAL parameter. """ def __init__(self, unit=None, equivalencies=None): """ Parameters ---------- unit : `astropy.unit` Unit this paramter is represented in DAL format equivalencies: list List of equivalencies for unit conversion """ self._unit = unit self._equivalencies = equivalencies super().__init__() def get_dal_format(self, val): if isinstance(val, (tuple, Quantity)): if len(val) == 1: high = low = val[0] elif len(val) == 2: low = val[0] high = val[1] else: raise ValueError('Too few/many values in interval attribute: {}'. format(val)) else: high = low = val if isinstance(low, (int, float)) and isinstance(high, (int, float))\ and low > high: raise ValueError('Invalid interval: min({}) > max({})'.format( low, high)) if self._unit: if not isinstance(low, Quantity): low = low * self._unit low = low.to(self._unit, equivalencies=self._equivalencies).value if not isinstance(high, Quantity): high = high * self._unit high = high.to(self._unit, equivalencies=self._equivalencies).value if low > high: # interval could become invalid during transform (e.g. GHz->m) low, high = high, low return '{} {}'.format(low, high) class TimeQueryParam(AbstractDalQueryParam): """ Representation of a timestamp parameter. """ def get_dal_format(self, val): if isinstance(val, tuple): if len(val) == 1: max_time = min_time = val[0] elif len(val) == 2: min_time = val[0] max_time = val[1] else: raise ValueError('Too few/many members in time attribute: {}'. format(val)) else: max_time = min_time = val if not isinstance(min_time, Time): min_time = Time(min_time) if not isinstance(max_time, Time): max_time = Time(max_time) if min_time > max_time: raise ValueError('Invalid time interval: min({}) > max({})'.format( min_time, max_time )) return '{} {}'.format(min_time.mjd, max_time.mjd) class EnumQueryParam(AbstractDalQueryParam): """ Representation of an enum parameter """ def __init__(self, allowed_values): """ Parameters ---------- allowed_values : sequence Sequence of allowed values for the enum """ self._allowed = allowed_values super().__init__() def get_dal_format(self, val): if val not in self._allowed: raise ValueError('{} not a valid value from: {}'. format(val, self._allowed)) return str(val) pyvo-1.5.2/pyvo/dal/query.py000066400000000000000000001024611462331236700160030ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for walking through the query response of VO data access layer (DAL) queries and general VOTable-based datasets. Most data queries in the VO return a table as a result, usually formatted as a VOTable. Each row of the table describes a single physical or virtual dataset which can be retrieved. For uniformity, datasets are described via standard metadata defined by a data model specific to the type of data being queried. The fields of the data model are identified most generally by their VOClient alias as defined in this interface, or at a lower level by the Utype or UCD of the specific standard and version of the standard being queried. While the data model differs depending upon the type of data being queried, the form of the query response is the same for all classes of data, allowing a common query response interface to be used. An exception to this occurs when querying an astronomical catalog or other externally defined table. In this case there is no VO defined standard data model. Usually the field names are used to uniquely identify table columns. """ __all__ = ["DALService", "DALQuery", "DALResults", "Record"] import os import shutil import re import requests from collections.abc import Mapping import collections from warnings import warn from astropy.table import Table, QTable from astropy.io.votable import parse as votableparse from astropy.io.votable.ucd import parse_ucd from astropy.utils.exceptions import AstropyDeprecationWarning from .mimetype import mime_object_maker from .exceptions import (DALFormatError, DALServiceError, DALQueryError, DALOverflowWarning) from .. import samp from ..utils.decorators import stream_decode_content from ..utils.http import use_session class DALService: """ an abstract base class representing a DAL service located a particular endpoint. """ def __init__(self, baseurl, session=None): """ instantiate the service connecting it to a base URL Parameters ---------- baseurl : str the base URL that should be used for forming queries to the service. session : object optional session to use for network requests """ self._baseurl = baseurl self._session = use_session(session) @property def baseurl(self): """ the base URL identifying the location of the service and where queries are submitted (read-only) """ return self._baseurl def search(self, **keywords): """ send a search query to this service. This implementation has no knowledge of the type of service being queried. The query parameters are given as arbitrary keywords which will be assumed to be understood by the service (i.e. there is no argument checking). The response is a generic DALResults object. Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ q = self.create_query(**keywords) return q.execute() def create_query(self, **keywords): """ create a query object that constraints can be added to and then executed. Returns ------- DALQuery a generic query object """ q = DALQuery(self.baseurl, session=self._session, **keywords) return q def describe(self): """ describe the general information about the DAL service """ print('DAL Service at {}'.format(self.baseurl)) class DALQuery(dict): """ a class for preparing a query to a particular service. Query constraints are added via its service type-specific methods. The various execute() functions will submit the query and return the results. The base URL for the query can be changed via the baseurl property. A session can also optionally be passed in that will be used for network transactions made by this object to remote services. """ _ex = None def __init__(self, baseurl, session=None, **keywords): """ initialize the query object with a baseurl """ if isinstance(baseurl, bytes): baseurl = baseurl.decode("utf-8") self._baseurl = baseurl.rstrip("?") self._session = use_session(session) self.update({key.upper(): value for key, value in keywords.items()}) @property def baseurl(self): """ the base URL that this query will be sent to when one of the execute functions is called. """ return self._baseurl def execute(self): """ submit the query and return the results as a Results subclass instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return DALResults(self.execute_votable(), url=self.queryurl, session=self._session) def execute_raw(self): """ submit the query and return the raw response as a string. No exceptions are raised here because non-2xx responses might still contain payload. They can be raised later by calling ``raise_if_error`` """ f = self.execute_stream() out = None try: out = f.read() finally: f.close() return out @stream_decode_content def execute_stream(self, post=False): """ Submit the query and return the raw response as a file stream. No exceptions are raised here because non-2xx responses might still contain payload. They can be raised later by calling ``raise_if_error`` """ response = self.submit(post=post) try: response.raise_for_status() except requests.RequestException as ex: # save for later use self._ex = ex finally: return response.raw def submit(self, post=False): """ does the actual request """ url = self.queryurl params = {k: v for k, v in self.items()} if post: response = self._session.post(url, data=params, stream=True, allow_redirects=True) else: response = self._session.get(url, params=params, stream=True, allow_redirects=True) return response def execute_votable(self, post=False): """ Submit the query and return the results as an AstroPy votable instance. As this is the level where qualified error messages are available, they are raised here instead of in the underlying execute_stream. Returns ------- astropy.io.votable.tree.VOTableFile an Astropy votable object Raises ------ DALServiceError for errors connecting to or communicating with the service DALFormatError for errors parsing the VOTable response See Also -------- astropy.io.votable DALServiceError DALFormatError DALQueryError """ try: return votableparse(self.execute_stream(post=post).read) except Exception as e: self.raise_if_error() raise DALFormatError(e, self.queryurl) def raise_if_error(self): """ Raise if there was an error on http level. """ if self._ex: e = self._ex raise DALServiceError.from_except(e, self.queryurl) @property def queryurl(self): """ The URL that encodes the current query. This is the URL that the execute functions will use if called next. """ return self.baseurl class DALResults: """ Results from a DAL query. It provides random access to records in the response. Alternatively, it can provide results via a Cursor (compliant with the Python Database API) or an iterable. """ @classmethod @stream_decode_content def _from_result_url(cls, result_url, session): return session.get(result_url, stream=True).raw @classmethod def from_result_url(cls, result_url, session=None): """ Create a result object from a url. Uses the optional session to make the request. """ session = use_session(session) return cls( votableparse(cls._from_result_url(result_url, session).read), url=result_url, session=session) def __init__(self, votable, url=None, session=None): """ initialize the cursor. This constructor is not typically called by directly applications; rather an instance is obtained from calling a DALQuery's execute(). Parameters ---------- votable : str the service response parsed into an astropy.io.votable.tree.VOTableFile instance. url : str the URL that produced the response session : object optional session to use for network requests Raises ------ DALFormatError if the response VOTable does not contain a response table See Also -------- pyvo.dal.DALFormatError """ self._votable = votable self._url = url self._session = use_session(session) self._status = self._findstatus(votable) if self._status[0].lower() not in ("ok", "overflow"): raise DALQueryError(self._status[1], self._status[0], url) if self._status[0].lower() == "overflow": warn("Partial result set. Potential causes MAXREC, async storage space, etc.", category=DALOverflowWarning) self._resultstable = self._findresultstable(votable) if not self._resultstable: raise DALFormatError( reason="VOTable response missing results table", url=url) self._fldnames = tuple( field.name for field in self._resultstable.fields) if not self._fldnames: raise DALFormatError( reason="response table missing column descriptions.", url=url) self._infos = self._findinfos(votable) def _findresultstable(self, votable): # this can be overridden to specialize for a particular DAL protocol res = self._findresultsresource(votable) if not res or len(res.tables) < 1: return None return res.tables[0] def _findresultsresource(self, votable): # this can be overridden to specialize for a particular DAL protocol if len(votable.resources) < 1: return None for res in votable.resources: if res.type.lower() == "results": return res return votable.resources[0] def _findstatus(self, votable): # this can be overridden to specialize for a particular DAL protocol # look first in the result resource res = self._findresultsresource(votable) if res: # should be a RESOURCE/INFO info = self._findstatusinfo(res.infos) if info: return (info.value, info.content) # if not there, check inside first table if len(res.tables) > 0: info = self._findstatusinfo(res.tables[0].infos) if info: return (info.value, info.content) # otherwise, look just below the root element info = self._findstatusinfo(votable.infos) if info: return (info.value, info.content) # assume it's okay return ("OK", "QUERY_STATUS not specified") def _findstatusinfo(self, infos): # this can be overridden to specialize for a particular DAL protocol status = None # return the last status to catch potential overflow or error sent # after the results. for info in infos: if info.name.lower() == 'query_status': status = info return status def _findinfos(self, votable): # this can be overridden to specialize for a particular DAL protocol infos = {} res = self._findresultsresource(votable) for info in res.infos: infos[info.name] = info.value for info in votable.infos: infos[info.name] = info.value return infos def __repr__(self): return f" 0: # find the last file written of the form, base-n.ext while n > 0 and not os.path.exists(mkpath(n)): n -= 1 if n > 0: n += 1 if n == 0: # never wrote a file of form, base-n.ext; try base.ext path = os.path.join(dir, "{}.{}".format(base, ext)) if not os.path.exists(path): return path n += 1 # find next available name while os.path.exists(mkpath(n)): n += 1 self._dsname_no = n return mkpath(n) def suggest_dataset_basename(self): """ return a default base filename that the dataset available via ``getdataset()`` can be saved as. This function is specialized for a particular service type this record originates from so that it can be used by ``cachedataset()`` via ``make_dataset_filename()``. """ # abstract; specialized for the different service types return "dataset" def suggest_extension(self, default=None): """ returns a recommended filename extension for the dataset described by this record. Typically, this would look at the column describing the format and choose an extension accordingly. This function is specialized for a particular service type this record originates from so that it can be used by ``cachedataset()`` via ``make_dataset_filename()``. """ # abstract; specialized for the different service types return default class Iter: def __init__(self, res): self.resultset = res self.pos = 0 def __iter__(self): return self def __next__(self): try: out = self.resultset.getrecord(self.pos) self.pos += 1 return out except IndexError: raise StopIteration() next = __next__ class Upload: """ This class represents a DALI Upload as described in http://www.ivoa.net/documents/DALI/20161101/PR-DALI-1.1-20161101.html#tth_sEc3.4.5 """ def __init__(self, name, content): """ Initialise the Upload object with the given parameters Parameters ---------- name : str Tablename for use in queries content : object If its a file-like object, a string pointing to a local file, a `DALResults` object or a astropy table, `is_inline` will be true and it will expose a file-like object under `fileobj` Otherwise it exposes a URI under `uri` """ try: self._is_file = os.path.isfile(content) except Exception: self._is_file = False self._is_fileobj = hasattr(content, "read") self._is_table = isinstance(content, Table) self._is_resultset = isinstance(content, DALResults) self._inline = any(( self._is_file, self._is_fileobj, self._is_table, self._is_resultset, )) self._name = name self._content = content @property def is_inline(self): """ True if the upload can be inlined """ return self._inline @property def name(self): return self._name def fileobj(self): """ A file-like object for a local resource Raises ------ ValueError if theres no valid local resource """ if not self.is_inline: raise ValueError( "Upload {name} doesn't refer to a local resource".format( name=self.name)) # astropy table if isinstance(self._content, Table): from io import BytesIO fileobj = BytesIO() self._content.write(output=fileobj, format="votable") fileobj.seek(0) return fileobj elif isinstance(self._content, DALResults): from io import BytesIO fileobj = BytesIO() table = self._content.to_table() table.write(output=fileobj, format="votable") fileobj.seek(0) return fileobj fileobj = self._content try: fileobj = open(self._content) finally: return fileobj def uri(self): """ The URI pointing to the result """ # TODO: use a async job base class instead of hasattr for inspection if hasattr(self._content, "result_uri"): self._content.raise_if_error() uri = self._content.result_uri else: uri = str(self._content) return uri def query_part(self): """ The query part for use in DALI requests """ if self.is_inline: value = "{name},param:{name}" else: value = "{name},{uri}" return value.format(name=self.name, uri=self.uri()) class UploadList(list): """ This class extends the native python list with utility functions for upload handling """ @classmethod def fromdict(cls, dct): """ Constructs a upload list from a dictionary with table_name: content """ return cls(Upload(key, value) for key, value in dct.items()) def param(self): """ Returns a string suitable for use in UPLOAD parameters """ return ";".join(upload.query_part() for upload in self) _image_mt_re = re.compile(r'^image/(\w+)') _text_mt_re = re.compile(r'^text/(\w+)') _votable_mt_re = re.compile(r'^(\w+)/(x-)?votable(\+\w+)?') pyvo-1.5.2/pyvo/dal/scs.py000066400000000000000000000450671462331236700154360ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for searching remote source and observation catalogs A Simple Cone Search (SCS) service allows a client to search for records in a source or observation catalog whose positions are within some minimum distance of a search position (i.e. within a specified "cone" on the sky). This module provides an interface for accessing such services. It is implemented as a specialization of the DAL Query interface. The ``search()`` function provides a simple interface to a service, returning an SCSResults instance as its results which represents the matching records from the catalog. The SCSResults supports access to and iterations over the individual records; these are provided as SCSRecord instances, which give easy access to key metadata in the response, including the ICRS position of the matched source or observation. This module also features the SCSQuery class that provides an interface for building up and remembering a query. The SCSService class can represent a specific service available at a URL endpoint. """ from pyvo.io.vosi.vodataservice import TableParam from astropy.coordinates import SkyCoord from astropy.units import Unit, Quantity from astropy.io.votable.tree import Field from astropy.table import Table from .query import DALResults, DALQuery, DALService, Record from .adhoc import DatalinkResultsMixin, DatalinkRecordMixin __all__ = ["search", "SCSService", "SCSQuery", "SCSResults", "SCSRecord"] def search(url, pos, radius=1.0, verbosity=2, **keywords): """ submit a simple Cone Search query that requests objects or observations whose positions fall within some distance from a search position. Parameters ---------- url : str the base URL of the query service. pos : astropy.coordinates.SkyCoord a SkyCoord instance defining the position of the center of the circular search region. converted if it's a iterable containing scalars, assuming icrs degrees. radius : `~astropy.units.Quantity` or float a Quantity instance defining the radius of the circular search region, in degrees. converted if it is another unit. verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columsn, and 3 means as many columns as are available. **keywords : additional case insensitive parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SCSResults a container holding a table of matching catalog records Raises ------ DALServiceError for errors connecting to or communicating with the service. DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SCSResults pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ return SCSService(url).search(pos=pos, radius=radius, verbosity=verbosity, **keywords) class SCSService(DALService): """ a representation of a Cone Search service """ def __init__(self, baseurl, session=None): """ instantiate a Cone Search service Parameters ---------- baseurl : str the base URL for submitting search queries to the service. session : object optional session to use for network requests """ super().__init__(baseurl, session=session) def _get_metadata(self): """ download the metadata resource """ if not hasattr(self, '_metadata'): query = self.create_query(pos=(0, 0), radius=0) metadata = query.execute_votable() setattr(self, '_metadata', metadata) @property def description(self): """ the service description. """ self._get_metadata() try: return getattr(self, '_metadata').description except AttributeError: return None @property def columns(self): """ the available columns on this service """ self._get_metadata() fields = filter( lambda field_or_param: isinstance(field_or_param, Field), self._metadata.iter_fields_and_params() ) try: return [ TableParam.from_field(field) for field in fields] except AttributeError: return [] def search(self, pos, radius=1.0, verbosity=2, **keywords): """ submit a simple Cone Search query that requests objects or observations whose positions fall within some distance from a search position. Parameters ---------- pos : astropy.coordinates.SkyCoord a SkyCoord instance defining the position of the center of the circular search region. converted if it's a iterable containing scalars, assuming icrs degrees. radius : `~astropy.units.Quantity` or float a Quantity instance defining the radius of the circular search region, in degrees. converted if it is another unit. verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columns, 3 means as many columns as are available. **keywords : additional case insensitive parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SCSResults a container holding a table of matching catalog records Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SCSResults pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ return self.create_query(pos=pos, radius=radius, verbosity=verbosity, **keywords).execute() def create_query(self, pos=None, radius=None, verbosity=None, **keywords): """ create a query object that constraints can be added to and then executed. The input arguments will initialize the query with the given values. Parameters ---------- pos : astropy.coordinates.SkyCoord a SkyCoord instance defining the position of the center of the circular search region. converted if it's a iterable containing scalars, assuming icrs degrees. radius : `~astropy.units.Quantity` or float a Quantity instance defining the radius of the circular search region, in degrees. converted if it is another unit. verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columns, 3 means as many columns as are available. **keywords : additional case insensitive parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SCSQuery the query instance See Also -------- SCSQuery """ return SCSQuery(self.baseurl, pos=pos, radius=radius, verbosity=verbosity, session=self._session, **keywords) def describe(self): print(self.description) print() rows = [( col.name, col.description, col.unit, col.ucd, col.utype, col.datatype.arraysize, col.datatype.content, ) for col in self.columns] names = ( 'name', 'description', 'unit', 'ucd', 'utype', 'arraysize', 'datatype', ) table = Table(rows=rows, names=names) table.pprint( max_lines=-1, max_width=-1, show_unit=False, show_dtype=False) class SCSQuery(DALQuery): """ a class for preparing an query to a Cone Search service. Query constraints are added via its service type-specific methods. The various execute() functions will submit the query and return the results. The base URL for the query, which controls where the query will be sent when one of the execute functions is called, is typically set at construction time; however, it can be updated later via the :py:attr:`~pyvo.dal.DALQuery.baseurl` to send a configured query to another service. In addition to the search constraint attributes described below, search parameters can be set generically by name via dict semantics. The typical function for submitting the query is ``execute()``; however, alternate execute functions provide the response in different forms, allowing the caller to take greater control of the result processing. """ def __init__( self, baseurl, pos=None, radius=None, verbosity=None, session=None, **keywords): """ initialize the query object with a baseurl and the given parameters Parameters ---------- pos : astropy.coordinates.SkyCoord a SkyCoord instance defining the position of the center of the circular search region. converted if it's a iterable containing scalars, assuming icrs degrees. radius : `~astropy.units.Quantity` or float a Quantity instance defining the radius of the circular search region, in degrees. converted if it is another unit. verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columns, 3 means as many columns as are available. session : object optional session to use for network requests """ super().__init__(baseurl, session=session) if pos is not None: self.pos = pos if radius is not None: self.radius = radius if verbosity is not None: self.verbosity = verbosity self.update({key.upper(): value for key, value in keywords.items()}) @property def pos(self): """ the position of the center of the circular search region as a `~astropy.coordinates.SkyCoord` instance. """ return getattr(self, "_pos", None) @pos.setter def pos(self, pos): setattr(self, "_pos", pos) if not isinstance(pos, SkyCoord): try: ra, dec = pos except (TypeError, ValueError): raise ValueError( 'Pos must be a sequence with exactly two values, ' 'expressing ra and dec in icrs degrees' ) # assume degrees pos = SkyCoord(ra=ra, dec=dec, unit="deg", frame="icrs") self["RA"] = pos.icrs.ra.deg self["DEC"] = pos.icrs.dec.deg @pos.deleter def pos(self): delattr(self, "_pos") del self["RA"] del self["DEC"] @property def radius(self): """ the radius of the circular region around pos as a `~astropy.units.Quantity` instance. """ return getattr(self, "_radius", None) @radius.setter def radius(self, radius): setattr(self, "_radius", radius) if not isinstance(radius, Quantity): valerr = ValueError("Radius must be exactly one value") try: # assume degrees radius = radius * Unit("deg") except ValueError: raise valerr try: if len(radius): raise valerr except TypeError: pass # len 1 self["SR"] = radius.to(Unit("deg")).value @radius.deleter def radius(self): delattr(self, "_radius") del self["SR"] @property def verbosity(self): """ an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columsn, 3 means as many columns as are available. """ return getattr(self, "_verbosity", None) @verbosity.setter def verbosity(self, verbosity): setattr(self, "_verbosity", verbosity) self["VERB"] = verbosity @verbosity.deleter def verbosity(self): delattr(self, "_verbosity") del self["VERB"] def execute(self): """ submit the query and return the results as a SCSResults instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return SCSResults(self.execute_votable(), url=self.queryurl, session=self._session) class SCSResults(DatalinkResultsMixin, DALResults): """ The list of matching catalog records resulting from a catalog (SCS) query. Each record contains a set of metadata that describes a source or observation within the requested circular region (i.e. a "cone"). The number of records in the results is available by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.scs.SCSRecord` instances) are typically accessed by iterating over an ``SCSResults`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.DALResults.getcolumn` method. ``SCSResults`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the images. One can access that VOTable directly via the :py:attr:`~pyvo.dal.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`~astropy.table.table.Table` via the following conversion: ``table = results.votable.to_table()`` ``SCSResults`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.scs.SCSRecord` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """ def _findresultsresource(self, votable): if len(votable.resources) < 1: return None return votable.resources[0] def _findstatus(self, votable): # this is specialized according to the Conesearch standard # look first in the preferred location: just below the root VOTABLE info = self._findstatusinfo(votable.infos) if info: return (info.name, info.value) # look next in the result resource res = self._findresultsresource(votable) if res: # look for RESOURCE/INFO info = self._findstatusinfo(res.infos) if info: return (info.name, info.value) # if not there, check for a PARAM info = self._findstatusinfo(res.params) if info: return (info.name, info.value) # last resort: VOTABLE/DEFINITIONS/PARAM # NOT SUPPORTED BY astropy; parser has been configured to # raise W22 as exception instead. # assume it's okay return ("OK", "Successful Response") def _findstatusinfo(self, infos): # this can be overridden to specialize for a particular DAL protocol for info in infos: if info.name == "Error": return info def getrecord(self, index): """ return a representation of a conesearch result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has the following additional properties: id, ra, dec Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- SCSRecord a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- Record """ return SCSRecord(self, index, session=self._session) class SCSRecord(DatalinkRecordMixin, Record): """ a dictionary-like container for data in a record from the results of an Cone Search (SCS) query, describing a matching source or observation. The commonly accessed metadata which are stadardized by the SCS protocol are available as attributes. All metadata, particularly non-standard metadata, are acessible via the ``get(`` *key* ``)`` function (or the [*key*] operator) where *key* is table column name. """ @property def pos(self): """ the position of the object or observation described by this record. """ return SkyCoord( ra=self.getbyucd("POS_EQ_RA_MAIN"), dec=self.getbyucd("POS_EQ_DEC_MAIN"), unit="deg", frame="icrs") @property def id(self): """ return the identifying name of the object or observation described by this record. """ return self.getbyucd("ID_MAIN") pyvo-1.5.2/pyvo/dal/sia.py000066400000000000000000001017421462331236700154130ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for searching for images in a remote archive. A Simple Image Access (SIA) service allows a client to search for images in an archive whose field of view overlaps with a given rectangular region on the sky. The service responds to a search query with a table in which each row represents an image that is available for download. The columns provide metadata describing each image and one column in particular provides the image's download URL (also called the *access reference*, or *acref*). Some SIA services act as a cut-out service; in this case, the query result is a table of images whose field of view matches the requested region and which will be created when accessed via the download URL. This module provides an interface for accessing an SIA service. It is implemented as a specialization of the DAL Query interface. The ``search()`` function support the simplest and most common types of queries, returning an SIAResults instance as its results which represents the matching images from the archive. The SIAResults supports access to and iterations over the individual records; these are provided as SIARecord instances, which give easy access to key metadata in the response, such as the position of the image's center, the image format, the size and shape of the image, and its download URL. The ``SIAService`` class can represent a specific service available at a URL endpoint. """ import re from pyvo.io.vosi.vodataservice import TableParam from astropy.coordinates import SkyCoord from astropy.time import Time from astropy.units import Quantity, Unit import numpy as np from .query import DALResults, DALQuery, DALService, Record from .mimetype import mime2extension from .adhoc import DatalinkResultsMixin, DatalinkRecordMixin, SodaRecordMixin from .. import samp __all__ = ["search", "SIAService", "SIAQuery", "SIAResults", "SIARecord"] def search( url, pos, size=1.0, format=None, intersect=None, verbosity=2, **keywords): """ submit a simple SIA query that requests images overlapping a given region Parameters ---------- url : str the base URL for the SIA service pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the rectangular search region. assuming icrs decimal degrees if unit is not specified. size : `~astropy.units.Quantity` class or up to 2 floats. the full rectangular size of the search region along the RA and Dec directions. converted if it's a iterable containing scalars, assuming decimal degrees. format : str the image format(s) of interest. "all" (server-side default) indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata; "image/\\*" indicates a particular image format where * can have values like "fits", "jpeg", "png", etc. intersect : str a token indicating how the returned images should intersect with the search region; recognized values include: ========= ====================================================== COVERS select images that completely cover the search region ENCLOSED select images that are complete enclosed by the region OVERLAPS select any image that overlaps the search region (server-side default) CENTER select images whose center is within the search region ========= ====================================================== verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columsn, 3 means as many columns as are available. (client-side default == 2) **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SIAResults a container holding a table of matching image records Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SIAResults pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ service = SIAService(url) return service.search(pos=pos, size=size, format=format, intersect=intersect, verbosity=verbosity, **keywords) class SIAService(DALService): """ a representation of an SIA service """ def __init__(self, baseurl, session=None): """ instantiate an SIA service Parameters ---------- baseurl : str the base URL for submitting search queries to the service. session : object optional session to use for network requests """ super().__init__(baseurl, session=session) def _get_metadata(self): """ the metadata resource element """ if not hasattr(self, "_metadata"): query = self.create_query(format='metadata') metadata = query.execute_votable() setattr(self, "_metadata", metadata) try: setattr(self, "_metadata_resource", metadata.resources[0]) except IndexError: setattr(self, "_metadata_resource", None) @property def description(self): """ the service description. """ self._get_metadata() try: return getattr(self, "_metadata", None).description except AttributeError: return None @property def params(self): """ the service parameters. """ self._get_metadata() try: return getattr(self, "_metadata_resource", None).params except AttributeError: return None @property def columns(self): """ the available columns on this service """ self._get_metadata() fields = getattr(self, '_metadata', None).get_first_table().fields try: return [ TableParam.from_field(field) for field in fields] except AttributeError: return [] def search( self, pos, size=1.0, format=None, intersect=None, verbosity=2, **keywords): """ submit a SIA query to this service with the given parameters. Parameters ---------- pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the rectangular search region. assuming icrs decimal degrees if unit is not specified. size : `~astropy.units.Quantity` class or up to 2 floats. the full rectangular size of the search region along the RA and Dec directions. converted if it's a iterable containing scalars, assuming decimal degrees. size : `~astropy.units.Quantity` class or scalar float the size of the rectangular region around pos. assuming icrs decimal degrees if unit is not specified. format : str the image format(s) of interest. "all" (server-side default) indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata; "image/\\*" indicates a particular image format where * can have values like "fits", "jpeg", "png", etc. intersect : str a token indicating how the returned images should intersect with the search region; recognized values include: ========= ====================================================== COVERS select images that completely cover the search region ENCLOSED select images that are complete enclosed by the region OVERLAPS select any image that overlaps the search region (server-side default) CENTER select images whose center is within the search region ========= ====================================================== verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columns, 3 means as many columns as are available. (client-side default == 2) **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SIAResults a container holding a table of matching catalog records Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SIAResults pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ return self.create_query( pos=pos, size=size, format=format, intersect=intersect, verbosity=verbosity, **keywords).execute() def create_query( self, pos=None, size=None, format=None, intersect=None, verbosity=None, **keywords): """ create a query object that constraints can be added to and then executed. The input arguments will initialize the query with the given values. Parameters ---------- pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the rectangular search region. assuming icrs decimal degrees if unit is not specified. size : `~astropy.units.Quantity` class or up to 2 floats. the full rectangular size of the search region along the RA and Dec directions. converted if it's a iterable containing scalars, assuming decimal degrees. size : `~astropy.units.Quantity` class or scalar float the size of the rectangular region around pos. assuming icrs decimal degrees if unit is not specified. format : str the image format(s) of interest. "all" (server-side default) indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata; "image/\\*" indicates a particular image format where * can have values like "fits", "jpeg", "png", etc. intersect : str a token indicating how the returned images should intersect with the search region; recognized values include: ========= ====================================================== COVERS select images that completely cover the search region ENCLOSED select images that are complete enclosed by the region OVERLAPS select any image that overlaps the search region (server-side default) CENTER select images whose center is within the search region ========= ====================================================== verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columsn, 3 means as many columns as are available. **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SIAQuery the query instance See Also -------- SIAQuery """ return SIAQuery( self.baseurl, pos=pos, size=size, format=format, intersect=intersect, verbosity=verbosity, session=self._session, **keywords) def describe(self): print(self.description) print() class SIAQuery(DALQuery): """ a class for preparing an query to an SIA service. Query constraints are added via its service type-specific methods. The various execute() functions will submit the query and return the results. The base URL for the query, which controls where the query will be sent when one of the execute functions is called, is typically set at construction time; however, it can be updated later via the :py:attr:`~pyvo.dal.DALQuery.baseurl` to send a configured query to another service. In addition to the search constraint attributes described below, search parameters can be set generically by name via dict semantics. The typical function for submitting the query is ``execute()``; however, alternate execute functions provide the response in different forms, allowing the caller to take greater control of the result processing. """ def __init__( self, baseurl, pos=None, size=None, format=None, intersect=None, verbosity=None, session=None, **keywords): """ initialize the query object with a baseurl and the given parameters Parameters ---------- baseurl : str the base URL for the SIA service pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the rectangular search region. assuming icrs decimal degrees if unit is not specified. size : `~astropy.units.Quantity` class or up to 2 floats. the full rectangular size of the search region along the RA and Dec directions. converted if it's a iterable containing scalars, assuming decimal degrees. size : `~astropy.units.Quantity` class or scalar float the size of the rectangular region around pos. assuming icrs decimal degrees if unit is not specified. format : str the image format(s) of interest. "all" (server-side default) indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata; "image/\\*" indicates a particular image format where * can have values like "fits", "jpeg", "png", etc. intersect : str a token indicating how the returned images should intersect with the search region; recognized values include: ========= ====================================================== COVERS select images that completely cover the search region ENCLOSED select images that are complete enclosed by the region OVERLAPS select any image that overlaps the search region (server-side default) CENTER select images whose center is within the search region ========= ====================================================== verbosity : int an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columsn, 3 means as many columns as are available. session : object optional session to use for network requests **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. """ super().__init__(baseurl, session=session, **keywords) if pos is not None: self.pos = pos if size is not None: self.size = size if format is not None: self.format = format if intersect is not None: self.intersect = intersect if verbosity is not None: self.verbosity = verbosity @property def pos(self): """ the position of the center of the rectangular search region as a `~astropy.coordinates.SkyCoord` instance. """ return getattr(self, "_pos", None) @pos.setter def pos(self, pos): setattr(self, "_pos", pos) if not isinstance(pos, SkyCoord): try: ra, dec = pos except (TypeError, ValueError): raise ValueError( 'Pos must be a sequence with exactly two values, ' 'expressing ra and dec in icrs degrees' ) # assume degrees pos = SkyCoord(ra=ra, dec=dec, unit="deg", frame="icrs") self["POS"] = "{ra},{dec}".format( ra=pos.icrs.ra.deg, dec=pos.icrs.dec.deg) @pos.deleter def pos(self): delattr(self, "_pos") del self["POS"] @property def size(self): """ the size of the rectangular region around pos as a `~astropy.units.Quantity` instance. """ return getattr(self, "_size", None) @size.setter def size(self, size): setattr(self, "_size", size) if not isinstance(size, Quantity): valerr = ValueError( 'Size must be either a single value or a sequence with two' 'values, expressing degrees' ) try: # assume degrees size = size * Unit("deg") except ValueError: raise valerr try: if len(size) > 2: raise valerr except TypeError: pass # len 1 try: self["SIZE"] = ",".join( str(deg) for deg in size.to(Unit("deg")).value) except TypeError: self["SIZE"] = str(size.to(Unit("deg")).value) @size.deleter def size(self): delattr(self, "_size") del self["SIZE"] @property def format(self): """ the image format(s) of interest. "all" (default) indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata; "image/\\*" indicates a particular image format where * can have values like "fits", "jpeg", "png", etc. """ return getattr(self, "_format", None) @format.setter def format(self, format_): setattr(self, "_format", format_) if not isinstance(format_, list): format_ = format_.split(',') normalized_formats = [] for user_input in format_: if user_input.upper() in ['ALL', 'METADATA', 'GRAPHIC', 'GRAPHIC-ALL']: normalized_formats.append(user_input.upper()) elif user_input.split('-')[0].upper() == 'GRAPHIC': normalized_formats.append(user_input.split('-')[0].upper()+"-"+user_input.split('-')[1]) else: normalized_formats.append(user_input) self["FORMAT"] = ",".join(normalized_formats) @format.deleter def format(self): delattr(self, "_format") del self["FORMAT"] @property def intersect(self): """ a token indicating how the returned images should intersect with the search region; recognized values include: ========= ====================================================== COVERS select images that completely cover the search region ENCLOSED select images that are complete enclosed by the region OVERLAPS select any image that overlaps with the search region CENTER select images whose center is within the search region ========= ====================================================== """ return getattr(self, "_intersect", None) @intersect.setter def intersect(self, intersect): setattr(self, "_intersect", intersect) self["INTERSECT"] = intersect.upper() @intersect.deleter def intersect(self): delattr(self, "_intersect") del self["INTERSECT"] @property def verbosity(self): """ an integer value that indicates the volume of columns to return in the result table. 0 means the minimum set of columsn, 3 means as many columns as are available. """ return getattr(self, "_verbosity", None) @verbosity.setter def verbosity(self, verbosity): setattr(self, "_verbosity", verbosity) self["VERB"] = verbosity @verbosity.deleter def verbosity(self): delattr(self, "_verbosity") del self["VERB"] def execute(self): """ submit the query and return the results as a SIAResults instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return SIAResults(self.execute_votable(), url=self.queryurl, session=self._session) class SIAResults(DatalinkResultsMixin, DALResults): """ The list of matching images resulting from an image (SIA) query. Each record contains a set of metadata that describes an available image matching the query constraints. The number of records in the results is available by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.sia.SIARecord` instances) are typically accessed by iterating over an ``SIAResults`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.DALResults.getcolumn` method. ``SIAResults`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the images. One can access that VOTable directly via the :py:attr:`~pyvo.dal.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`~astropy.table.table.Table` via the following conversion: ``table = results.votable.to_table()`` ``SIAResults`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.sia.SIARecord` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """ def getrecord(self, index): """ return a representation of a sia result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has additional image-specific properties Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- SIARecord a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- Record """ return SIARecord(self, index, session=self._session) class SIARecord(SodaRecordMixin, DatalinkRecordMixin, Record): """ a dictionary-like container for data in a record from the results of an image (SIA) search, describing an available image. The commonly accessed metadata which are stadardized by the SIA protocol are available as attributes. If the metadatum accessible via an attribute is not available, the value of that attribute will be None. All metadata, including non-standard metadata, are acessible via the ``get(`` *key* ``)`` function (or the [*key*] operator) where *key* is table column name. """ def getdataformat(self): """ return the mimetype of the dataset described by this record. """ return self.getbyucd("VOX:Image_Format", decode=True) @property def pos(self): """ the position of the object or observation described by this record. """ return SkyCoord( ra=self.getbyucd("POS_EQ_RA_MAIN"), dec=self.getbyucd("POS_EQ_DEC_MAIN"), unit="deg", frame="icrs") # Image Metadata @property def title(self): """ the title of the image """ return self.getbyucd("VOX:Image_Title", decode=True) @property def instr(self): """ the name of the instrument (or instruments) that produced the data that went into this image. """ return self.getbyucd("INST_ID", decode=True) @property def dateobs(self): """ the modified Julien date (MJD) of the mid-point of the observational data that went into the image, as an astropy.time.Time instance """ dateobs = self.getbyucd("VOX:Image_MJDateObs") try: if not dateobs or np.isnan(dateobs): return None except TypeError: # np.isnan can only check floats. If can't check for nan, pass it along pass return Time(dateobs, format="mjd") @property def naxes(self): """ the number of axes in this image. """ return self.getbyucd("VOX:Image_Naxes") @property def naxis(self): """ the lengths of the sides along each axis, in pix, as a astropy Quantity pix """ return self.getbyucd("VOX:Image_Naxis") * Unit("pix") @property def scale(self): """ the scale of the pixels in each image axis, in degrees/pixel, as a astropy Quantity deg / pix """ return self.getbyucd("VOX:Image_Scale") * (Unit("deg") / Unit("pix")) @property def format(self): """ the format of the image """ return self.getbyucd("VOX:Image_Format", decode=True) # Coordinate System Metadata @property def coord_frame(self): """ the coordinate system reference frame, one of the following: "ICRS", "FK5", "FK4", "ECL", "GAL", and "SGAL". """ return self.getbyucd("VOX:STC_CoordRefFrame", decode=True) @property def coord_equinox(self): """ the equinox of the used coordinate system """ return self.getbyucd("VOX:STC_CoordEquinox") @property def coord_projection(self): """ the celestial projection (TAN / ARC / SIN / etc.) """ return self.getbyucd("VOX:WCS_CoordProjection", decode=True) @property def coord_refpixel(self): """ the image pixel coordinates of the WCS reference pixel """ return self.getbyucd("VOX:WCS_CoordRefPixel") @property def coord_refvalue(self): """ the world coordinates of the WCS reference pixel. """ return self.getbyucd("VOX:WCS_CoordRefValue") @property def cdmatrix(self): """ the WCS CD matrix defining the scale and rotation (among other things) of the image. ordered as CD[i,j] = [0,0], [0,1], [1,0], [1,1]. """ return self.getbyucd("VOX:WCS_CDMatrix").reshape((2, 2)) # Spectral Bandpass Metadata @property def bandpass_id(self): """ the bandpass by name (e.g., "V", "SDSS_U", "K", "K-Band", etc.) """ return self.getbyucd("VOX:BandPass_ID", decode=True) @property def bandpass_unit(self): """ the astropy unit used to represent spectral values. """ sia = self.getbyucd("VOX:BandPass_Unit", decode=True) if sia: return Unit(sia) else: # dimensionless return Unit("") @property def bandpass_refvalue(self): """ the characteristic (reference) wavelength, frequency or energy for the bandpass model, as an astropy Quantity of bandpass_unit """ return Quantity( self.getbyucd("VOX:BandPass_RefValue"), self.bandpass_unit) @property def bandpass_hilimit(self): """ the upper limit of the bandpass as astropy Quantity in bandpass_unit """ return Quantity( self.getbyucd("VOX:BandPass_HiLimit"), self.bandpass_unit) @property def bandpass_lolimit(self): """ the lower limit of the bandpass as astropy Quantity in bandpass_unit """ return Quantity( self.getbyucd("VOX:BandPass_LoLimit"), self.bandpass_unit) # Processig Metadata @property def pixflags(self): """ the type of processing done by the image service to produce an output image pixel a string of one or more of the following values: * C -- The image pixels were copied from a source image without change, as when an atlas image or cutout is returned. * F -- The image pixels were computed by resampling an existing image, e.g., to rescale or reproject the data, and were filtered by an interpolator. * X -- The image pixels were computed by the service directly from a primary data set hence were not filtered by an interpolator. * Z -- The image pixels contain valid flux (intensity) values, e.g., if the pixels were resampled with a flux-preserving interpolator. * V -- The image pixels contain some unspecified visualization of the data, hence are suitable for display but not for numerical analysis. """ return self.getbyucd("VOX:Image_PixFlags", decode=True) # Access Metadata @property def acref(self): """ the URL that can be used to retrieve the image """ return self.getbyucd("VOX:Image_AccessReference", decode=True) @property def acref_ttl(self): """ the minimum time to live in seconds of the access reference """ return self.getbyucd("VOX:Image_AccessRefTTL") @property def filesize(self): """ the (estimated) size of the image in bytes """ return self.getbyucd("VOX:Image_FileSize") def getdataurl(self): """ return the URL contained in the access URL column which can be used to retrieve the dataset described by this record. None is returned if no such column exists. """ if self.acref is not None: return self.acref else: return super().getdataurl() def suggest_dataset_basename(self): """ return a default base filename that the dataset available via ``getdataset()`` can be saved as. This function is specialized for a particular service type this record originates from so that it can be used by ``cachedataset()`` via ``make_dataset_filename()``. """ out = self.title if isinstance(out, bytes): out = out.decode('utf-8') if not out: out = "image" else: out = re.sub(r'\s+', '_', out.strip()) return out def suggest_extension(self, default=None): """ returns a recommended filename extension for the dataset described by this record. Typically, this would look at the column describing the format and choose an extension accordingly. """ return mime2extension(self.format, default) def broadcast_samp(self, client_name=None): """ Broadcast the image to ``client_name`` via SAMP """ with samp.connection() as conn: samp.send_image_to( conn, self.getdataurl(), client_name, name=self.suggest_dataset_basename()) pyvo-1.5.2/pyvo/dal/sia2.py000066400000000000000000001161171462331236700154770ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for searching for images in a remote archive. A Simple Image Access v2 (SIA2) service allows a client to search for images based on a number of criteria/parameters. The results are represented in ``pyvo.dam.ObsCoreMetadata`` format. The ``SIA2Service`` class can represent a specific service available at a URL endpoint. """ import warnings import numpy as np from astropy import units as u from astropy.time import Time from astropy.utils.decorators import deprecated from astropy.utils.exceptions import AstropyDeprecationWarning from .query import DALResults, DALQuery, DALService, Record from .adhoc import DatalinkResultsMixin, AxisParamMixin, SodaRecordMixin, DatalinkRecordMixin from .params import IntervalQueryParam, StrQueryParam, EnumQueryParam from .vosi import AvailabilityMixin, CapabilityMixin from ..dam import ObsCoreMetadata, CALIBRATION_LEVELS __all__ = ["search", "SIA2Service", "SIA2Query", "SIA2Results", "ObsCoreRecord"] SIA2_STANDARD_ID = 'ivo://ivoa.net/std/SIA#query-2.0' SIA2_PARAMETERS_DESC = """ pos : single or list of tuples angle units (default: deg) the positional region(s) to be searched for data. Each region can be expressed as a tuple representing a CIRCLE, RANGE or POLYGON as follows: (ra, dec, radius) - for CIRCLE. (angle units - defaults to) (long1, long2, lat1, lat2) - for RANGE (angle units required) (ra, dec, ra, dec, ra, dec ... ) ra/dec points for POLYGON all in angle units band : scalar, tuple(interval) or list of tuples (spectral units (default: meter) the energy interval(s) to be searched for data. time : single or list of `~astropy.time.Time` or compatible strings the time interval(s) to be searched for data. pol : single or list of str from ``pyvo.dam.obscore.POLARIZATION_STATES`` the polarization state(s) to be searched for data. field_of_view : single or list of tuples angle units (default arcsec) the range(s) of field of view (size) to be searched for data spatial_resolution : single or list of tuples angle units required the range(s) of spatial resolution to be searched for data spectral_resolving_power : single or list of tuples the range(s) of spectral resolving power to be searched for data exptime : single or list of tuples time units (default: second) the range(s) of exposure times to be searched for data timeres : single of list of tuples time units (default: second) the range(s) of temporal resolution to be searched for data publisher_did : single or list of str specifies the unique identifier of dataset(s). It is global because it must include information regarding the publisher (obs_publisher_did in ObsCore) collection : single or list of str name of the collection that the data belongs to facility : single or list of str specifies the name of the facility (usually telescope) where the data was acquired. instrument : single or list of str specifies the name of the instrument with which the data was acquired. data_type : 'image'|'cube' specifies the type of the data calib_level : single or list from enum ``pyvo.dam.obscore.CALIBRATION_LEVELS`` specifies the calibration level of the data. Can be a single value or a list of values target_name : single or list of str specifies the name of the target (e.g. the intention of the original science program or observation) res_format : single or list of strings specifies response format(s). max_records : int allows the client to limit the number or records in the response **kwargs : custom query parameters single or a list of values (or tuples for intervals) custom query parameters that a specific service accepts. The values of the parameters need to follow the SIA2 format and represent the appropriate quantities (where applicable). """ def __getattr__(name): if name == 'SIA_PARAMETERS_DESC': warnings.warn("The name SIA_PARAMETERS_DESC is deprecated in v1.5 for SIA v2 services, " "use SIA2_PARAMETERS_DESC instead.", AstropyDeprecationWarning) return SIA2_PARAMETERS_DESC raise AttributeError(f"module {__name__!r} has no attribute {name!r}") def search(url, pos=None, band=None, time=None, pol=None, field_of_view=None, spatial_resolution=None, spectral_resolving_power=None, exptime=None, timeres=None, publisher_did=None, facility=None, collection=None, instrument=None, data_type=None, calib_level=None, target_name=None, res_format=None, maxrec=None, session=None, **kwargs): """ submit a simple SIA query to a SIA2 compatible service Parameters ---------- url : str url of the SIA service (base or endpoint) _SIA2_PARAMETERS """ service = SIA2Service(url, session=session) return service.search(pos=pos, band=band, time=time, pol=pol, field_of_view=field_of_view, spatial_resolution=spatial_resolution, spectral_resolving_power=spectral_resolving_power, exptime=exptime, timeres=timeres, publisher_did=publisher_did, facility=facility, collection=collection, instrument=instrument, data_type=data_type, calib_level=calib_level, target_name=target_name, res_format=res_format, maxrec=maxrec, **kwargs) search.__doc__ = search.__doc__.replace('_SIA2_PARAMETERS', SIA2_PARAMETERS_DESC) def _tolist(value): # return value as a list - is there something in Python to do that? if not value: return [] if isinstance(value, list): return value return [value] class SIA2Service(DALService, AvailabilityMixin, CapabilityMixin): """ a representation of an SIA2 service Note that within pyVO, SIA2 services are associated with the (non-existing) standard id ivo://ivoa.net/std/sia2 rather than ivo://ivoa.net/std/sia#query-2.0 as in the standard; users should generally not notice that, though. """ def __init__(self, baseurl, session=None, check_baseurl=True): """ instantiate an SIA2 service Parameters ---------- url : str url - URL of the SIA2service (base or query endpoint) session : object optional session to use for network requests check_baseurl : bool True - use the capabilities end point of the service to get the query end point, False - baseurl is the query end point """ super().__init__(baseurl, session=session) # Check if the session has an update_from_capabilities attribute. # This means that the session is aware of IVOA capabilities, # and can use this information in processing network requests. # One such usecase for this is auth. if hasattr(self._session, 'update_from_capabilities'): self._session.update_from_capabilities(self.capabilities) self.query_ep = baseurl.strip('&') # default service end point if check_baseurl: for cap in self.capabilities: # assumes that the access URL is the same regardless of the # authentication method except BasicAA which is not supported # in pyvo. So pick any access url as long as it's not if cap.standardid.lower() == SIA2_STANDARD_ID.lower(): for interface in cap.interfaces: if interface.accessurls and \ not (len(interface.securitymethods) == 1 and interface.securitymethods[0].standardid == 'ivo://ivoa.net/sso#BasicAA'): self.query_ep = interface.accessurls[0].content break else: continue break def search(self, pos=None, band=None, time=None, pol=None, field_of_view=None, spatial_resolution=None, spectral_resolving_power=None, exptime=None, timeres=None, publisher_did=None, facility=None, collection=None, instrument=None, data_type=None, calib_level=None, target_name=None, res_format=None, maxrec=None, **kwargs): """ Performs a SIA2 search against a SIA2 service See Also -------- pyvo.dal.sia2.SIA2Query """ return SIA2Query(self.query_ep, pos=pos, band=band, time=time, pol=pol, field_of_view=field_of_view, spatial_resolution=spatial_resolution, spectral_resolving_power=spectral_resolving_power, exptime=exptime, timeres=timeres, publisher_did=publisher_did, facility=facility, collection=collection, instrument=instrument, data_type=data_type, calib_level=calib_level, target_name=target_name, res_format=res_format, maxrec=maxrec, session=self._session, **kwargs).execute() class SIA2Query(DALQuery, AxisParamMixin): """ a class very similar to :py:attr:`~pyvo.dal.SIAQuery` class but used to interact with SIA2 services. """ def __init__(self, url, pos=None, band=None, time=None, pol=None, field_of_view=None, spatial_resolution=None, spectral_resolving_power=None, exptime=None, timeres=None, publisher_did=None, facility=None, collection=None, instrument=None, data_type=None, calib_level=None, target_name=None, res_format=None, maxrec=None, session=None, **kwargs): """ initialize the query object with a url and the given parameters Note: The majority of the attributes represent constraints used to query the SIA2 service and are represented through lists. Multiple value attributes are OR-ed in the query, however the values of different attributes are AND-ed. Intervals are represented with tuples and open-ended intervals should be expressed with float("-inf") or float("inf"). Eg. For all values less than or equal to 600 use (float(-inf), 600) Additional attribute constraints can be specified (or removed) after this object has been created using the *.add and *_del methods. Parameters ---------- url : url where to send the query request to _SIA2_PARAMETERS session : object optional session to use for network requests Returns ------- SIA2Results a container holding a table of matching image records. Records are represented in IVOA ObsCore format Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SIA2Results pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ super().__init__(url, session=session) for pp in _tolist(pos): self.pos.add(pp) for bb in _tolist(band): self.band.add(bb) for tt in _tolist(time): self.time.add(tt) for pp in _tolist(pol): self.pol.add(pp) for ff in _tolist(field_of_view): self.field_of_view.add(ff) for sp in _tolist(spatial_resolution): self.spatial_resolution.add(sp) for sr in _tolist(spectral_resolving_power): self.spectral_resolving_power.add(sr) for et in _tolist(exptime): self.exptime.add(et) for tr in _tolist(timeres): self.timeres.add(tr) for ii in _tolist(publisher_did): self.publisher_did.add(ii) for ff in _tolist(facility): self.facility.add(ff) for col in _tolist(collection): self.collection.add(col) for inst in _tolist(instrument): self.instrument.add(inst) for dt in _tolist(data_type): self.data_type.add(dt) for cal in _tolist(calib_level): self.calib_level.add(cal) for tt in _tolist(target_name): self.target_name.add(tt) for rf in _tolist(res_format): self.res_format.add(rf) for name, value in kwargs.items(): custom_arg = [] for kw in _tolist(value): if isinstance(kw, tuple): val = '{} {}'.format(kw[0], kw[1]) else: val = str(kw) custom_arg.append(val) self[name] = custom_arg self.maxrec = maxrec __init__.__doc__ = \ __init__.__doc__.replace('_SIA2_PARAMETERS', SIA2_PARAMETERS_DESC) @property def field_of_view(self): if not hasattr(self, '_fov'): self._fov = IntervalQueryParam(u.deg) self['FOV'] = self._fov.dal return self._fov @property def spatial_resolution(self): if not hasattr(self, '_spatres'): self._spatres = IntervalQueryParam(u.arcsec) self['SPATRES'] = self._spatres.dal return self._spatres @property def spectral_resolving_power(self): if not hasattr(self, '_specrp'): self._specrp = IntervalQueryParam() self['SPECRP'] = self._specrp.dal return self._specrp @property def exptime(self): if not hasattr(self, '_exptime'): self._exptime = IntervalQueryParam(u.second) self['EXPTIME'] = self._exptime.dal return self._exptime @property def timeres(self): if not hasattr(self, '_timeres'): self._timeres = IntervalQueryParam(u.second) self['TIMERES'] = self._timeres.dal return self._timeres @property def publisher_did(self): if not hasattr(self, '_publisher_did'): self._publisher_did = StrQueryParam() self['ID'] = self._publisher_did.dal return self._publisher_did @property def facility(self): if not hasattr(self, '_facility'): self._facility = StrQueryParam() self['FACILITY'] = self._facility.dal return self._facility @property def collection(self): if not hasattr(self, '_collection'): self._collection = StrQueryParam() self['COLLECTION'] = self._collection.dal return self._collection @property def instrument(self): if not hasattr(self, '_instrument'): self._instrument = StrQueryParam() self['INSTRUMENT'] = self._instrument.dal return self._instrument @property def data_type(self): if not hasattr(self, '_data_type'): self._data_type = StrQueryParam() self['DPTYPE'] = self._data_type.dal return self._data_type @property def calib_level(self): if not hasattr(self, '_cal'): self._cal = EnumQueryParam(CALIBRATION_LEVELS) self['CALIB'] = self._cal.dal return self._cal @property def target_name(self): if not hasattr(self, '_target'): self._target_name = StrQueryParam() self['TARGET'] = self._target_name.dal return self._target_name @property def res_format(self): if not hasattr(self, '_res_format'): self._res_format = StrQueryParam() self['FORMAT'] = self._res_format.dal return self._res_format @property def maxrec(self): return self._maxrec @maxrec.setter def maxrec(self, val): if not isinstance(val, int): if not val: return raise ValueError(f'maxrec {val} must be non-negative int') self._maxrec = val self['MAXREC'] = str(val) def execute(self): """ submit the query and return the results as a SIA2Results instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return SIA2Results(self.execute_votable(), url=self.queryurl, session=self._session) class SIA2Results(DatalinkResultsMixin, DALResults): """ The list of matching images resulting from an image (SIA2) query. Each record contains a set of metadata that describes an available image matching the query constraints. The number of records in the results is available by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.sia2.ObsCoreRecord` instances) are typically accessed by iterating over an ``SIA2Results`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.DALResults.getcolumn` method. ``SIA2Results`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the images. One can access that VOTable directly via the :py:attr:`~pyvo.dal.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`~astropy.table.table.Table` via the following conversion: ``table = results.votable.to_table()`` ``SIA2Results`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.sia2.ObsCoreRecord` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """ def getrecord(self, index): """ return a representation of a SIA2 result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has additional image-specific properties Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- ObsCoreMetadataRecord a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- Record """ return ObsCoreRecord(self, index, session=self._session) class ObsCoreRecord(SodaRecordMixin, DatalinkRecordMixin, Record, ObsCoreMetadata): """ a dictionary-like container for data in a record from the results of an image (SIA2) search, describing an available image in ObsCore format. The commonly accessed metadata which are stadardized by the SIA2 protocol are available as attributes. If the metadatum accessible via an attribute is not available, the value of that attribute will be None. All metadata, including non-standard metadata, are also acessible via the ``get(`` *key* ``)`` function (or the [*key*] operator) where *key* is table column name. """ # OBSERVATION INFO @property def dataproduct_type(self): """ Data product (file content) primary type. This is coded as a string that conveys a general idea of the content and organization of a dataset. """ return self.get('dataproduct_type', decode=True) @property def dataproduct_subtype(self): """ Data product more specific type """ return self.get('dataproduct_subtype', decode=True, default=None) @property def calib_level(self): """ Calibration level of the observation: in {0, 1, 2, 3, 4} """ return self.get('calib_level') # TARGET INFO @property def target_name(self): """ The target_name attribute contains the name of the target of the observation, if any. This is typically the name of an astronomical object, but could be the name of a survey field. The target name is most useful for output, to identify the target of an observation to the user. In queries it is generally better to refer to astronomical objects by position, using a name resolver to convert the target name into a coordinate (when possible). """ return self.get('target_name', decode=True) @property def target_class(self): """ This field indicates the type of object that was pointed for this observation. It is a string with possible values defined in a special vocabulary set to be defined: list of object classes (or types) used by the SIMBAD database, NED or defined in another IVOA vocabulary. """ return self.get('target_class', decode=True, default=None) # DATA DESCRIPTION @property def obs_id(self): """ Collection specific internal ID given by the ObsTAP service """ return self.get('obs_id', decode=True) @property def obs_title(self): """ Brief description of dataset in free format """ return self.get('obs_title', decode=True, default=None) @property def obs_collection(self): """ The name of the collection (DataID.Collection) identifies the data collection to which the data product belongs. A data collection can be any collection of datasets which are alike in some fashion. Typical data collections might be all the data from a particular telescope, instrument, or survey. The value is either the registered shortname for the data collection, the full registered IVOA identifier for the collection, or a data provider defined short name for the collection. Examples: HST/WFPC2, VLT/FORS2, CHANDRA/ACIS-S, etc. """ return self.get('obs_collection', decode=True) @property def obs_create_date(self): """ Date when the dataset was created """ cd = self.get('obs_create_date', default=None) return cd if not cd else Time(cd) @property def obs_creator_name(self): """ The name of the institution or entity which created the dataset. """ return self.get('obs_creator_name', decode=True, default=None) @property def obs_creator_did(self): """ IVOA dataset identifier given by its creator. """ return self.get('obs_creator_did', decode=True, default=None) # CURATION INFORMATION @property def obs_release_date(self): """ Observation release date """ rt = self.get('obs_release_date', default=None, decode=True) return rt if not rt else Time(rt) @property def obs_publisher_did(self): """ ID for the Dataset assigned by the publisher. Note that data from a source (creator_did) can be published by multiple publishers and have assigned multiple publisher data IDs. """ return self.get('obs_publisher_did', decode=True) @property def publisher_id(self): """ IVOA-ID for the Publisher. It will also be globally unique since each publisher has a unique registered publisher ID """ return self.get('publisher_id', decode=True, default=None) @property def bib_reference(self): """ URL or bibcode for documentation. This is a forward link to major publications which reference the dataset. """ return self.get('bib_reference', decode=True, default=None) @property def data_rights(self): """ This parameter allows mentioning the availability of a dataset. Possible values are: public, secure, or proprietary. """ return self.get('data_rights', decode=True, default=None) # ACCESS INFORMATION @property def access_url(self): """ The access_url column contains a URL that can be used to download the data product (as a file of some sort). Access URLs are not guaranteed to remain valid and unchanged indefinitely. To access a specific data product after a period of time (e.g., days or weeks) a query should be performed to obtain a fresh access URL. """ return self.get('access_url', decode=True) @property def access_format(self): """ Content format of the dataset. The value of access_format should be a MIME type, either a standard MIME type, an extended MIME type from the above table, or a new custom MIME type defined by the data provider. """ return self.get('access_format', decode=True) @property def access_estsize(self): """ The approximate size (in kilobytes) of the file available via the access_url. This is used only to gain some idea of the size of a data product before downloading it, hence only an approximate value is required. Provision of dataset size estimates is important whenever it is possible that datasets can be very large. """ return self.get('access_estsize') * 1000 * u.byte # SPATIAL CHARACTERISATION @property def s_ra(self): """ ICRS Right Ascension of the center of the observation """ return self.get('s_ra') * u.deg @property def s_dec(self): """ CRS Declination of the center of the observation """ return self.get('s_dec') * u.deg @property def s_fov(self): """ Approximate size of the covered region as the diameter of a containing circle. For most data products the value given should be large enough to include the entire area of the observation; coverage within the bounded region need not be complete, for example if the specified radius encompasses a rotated rectangular region. For observations which do not have a well-defined boundary, e.g. radio or high energy observations, a characteristic value should be given. The radius attribute provides a simple way to characterize and use (e.g. for discovery computations) the approximate spatial coverage of a data product. The spatial coverage of a data product can be more precisely specified using the region attribute. """ return self.get('s_fov') * u.deg @property def s_region(self): """ Sky region covered by the data product (expressed in ICRS frame). It can be used to precisely specify the covered spatial region of a data product. It is often an exact, or almost exact, representation of the illumination region of a given observation defined in a standard way by the concept of Support in the Characterisation data model. """ return self.get('s_region', decode=True) @property def s_resolution(self): """ Spatial resolution of data specifies a reference value chosen by the data provider for the estimated spatial resolution of the data product in arcseconds. This refers to the smallest spatial feature in the observed signal that can be resolved. In cases where the spatial resolution varies across the field the best spatial resolution (smallest resolvable spatial feature) should be specified. In cases where the spatial frequency sampling of an observation is complex (e.g., interferometry) a typical value for spatial resolution estimate should be given; additional characterisation may be necessary to fully specify the spatial characteristics of the data. """ return self.get('s_resolution') * u.arcsec @property def s_xel1(self): """ Number of elements along the first spatial axis """ return self.get('s_xel1') @property def s_xel2(self): """ Number of elements along the second spatial axis """ return self.get('s_xel2') @property def s_ucd(self): """ UCD for the nature of the spatial axis (pos or u,v data) """ return self.get('s_ucd', decode=True, default=None) @property def s_unit(self): """ Unit used for spatial axis """ return self.get('s_unit', decode=True, default=None) @property def s_resolution_min(self): """ Resolution min value on spatial axis (FHWM of PSF) """ rmin = self.get('s_resolution_min', default=None) return rmin if not rmin else rmin * u.arcsec @property def s_resolution_max(self): """ Resolution max value on spatial axis (FHWM of PSF) """ rmax = self.get('s_resolution_max', default=None) return rmax if not rmax else rmax * u.arcsec @property def s_calib_status(self): """ A string to encode the calibration status along the spatial axis (astrometry). Possible values could be {uncalibrated, raw, calibrated} """ return self.get('s_calib_status', decode=True, default=None) @property def s_stat_error(self): """ This parameter gives an estimate of the astrometric statistical error after the astrometric calibration phase. """ return self.get('s_stat_error', decode=True, default=None) @property def s_pixel_scale(self): """ This corresponds to the sampling precision of the data along the spatial axis. It is stored as a real number corresponding to the spatial sampling period, i.e., the distance in world coordinates system units between two pixel centers. It may contain two values if the pixels are rectangular. """ return self.get('s_pixel_scale', decode=True, default=None) # TIME CHARACTERISATION @property def t_xel(self): """ Number of elements along the time axis """ return self.get('t_xel') @property def t_ref_pos(self): """ Time Axis Reference Position as defined in STC REC, Section 4.4.1.1.1 """ return self.get('t_ref_pos', decode=True, default=None) @property def t_min(self): """ The start time of the observation specified in MJD as an `~astropy.time.Time` instance. In case of data products result of the combination of multiple frames, min time must be the minimum of the start times. ``None`` is used for NaN response values. """ t_min = self.get('t_min') if np.isfinite(t_min): return Time(t_min, format='mjd') else: return None @property def t_max(self): """ The stop time of the observation specified in MJD as an `~astropy.time.Time` instance. In case of data products result of the combination of multiple frames, t_max must be the maximum of the stop times. ``None`` is used for NaN response values. """ t_max = self.get('t_max') if np.isfinite(t_max): return Time(t_max, format='mjd') else: return None @property def t_exptime(self): """ Total exposure time. For simple exposures, this is just the time_bounds size expressed in seconds. For data where the detector is not active at all times (e.g. data products made by combining exposures taken at different times), the t_exptime will be smaller than the time_bounds interval. For data where the xptime is not constant over the entire data product, the median exposure time per pixel is a good way to characterize the typical value. In some cases, exptime is generally used as an indicator of the relative sensitivity (depth) within a single data collection (e.g. obs_collection); data providers should supply a suitable relative value when it is not feasible to define or compute the true exposure time. In case of targeted observations, on the contrary the exposure time is often adjusted to achieve similar signal to noise ratio for different targets. """ return self.get('t_exptime') * u.second @property def t_resolution(self): """ Estimated or average value of the temporal resolution. """ return self.get('t_resolution') * u.second @property def t_calib_status(self): """ Type of time coordinate calibration. Possible values are principally {uncalibrated, calibrated, raw, relative}. This may be extended for specific time domain collections. """ return self.get('t_calib_status', decode=True, default=None) @property def t_stat_error(self): """ Time coord statistical error on the time measurements in seconds """ ter = self.get('t_stat_error', default=None) return ter if not ter else ter * u.second # SPECTRAL CHARACTERISATION @property def em_xel(self): """ Number of elements along the spectral axis """ return self.get('em_xel') @property def em_ucd(self): """ Nature of the spectral axis """ return self.get('em_ucd', decode=True, default=None) @property def em_unit(self): """ Units along the spectral axis """ return self.get('em_unit', decode=True, default=None) @property def em_calib_status(self): """ This attribute of the spectral axis indicates the status of the data in terms of spectral calibration. Possible values are defined in the Characterisation Data Model and belong to {uncalibrated , calibrated, relative, absolute}. """ return self.get('em_calib_status', decode=True, default=None) @property def em_min(self): """ Minimum of the spectral interval covered by the observation """ return self.get('em_min') * u.meter @property def em_max(self): """ Maximum of the spectral interval covered by the observation """ return self.get('em_max') * u.meter @property def em_res_power(self): """ Average estimation for the spectral resolution power stored as a double value, with no unit. """ return self.get("em_res_power") @property def em_res_power_min(self): """ Resolving power min value on spectral axis """ return self.get('em_res_power_min', None) @property def em_res_power_max(self): """ Resolving power max value on spectral axis """ return self.get('em_res_power_max', None) @property def em_resolution(self): """ A mean estimate of the resolution, e.g. Full Width at Half Maximum (FWHM) of the Line Spread Function (or LSF). This can be used for narrow range spectra whereas in the majority of cases, the resolution power is preferable due to the LSF variation along the spectral axis. """ if 'em_resolution' in self.keys(): return self.get('em_resolution') * u.meter return None @property def em_stat_error(self): """ Spectral coord statistical error (accuracy along the spectral axis) """ if 'em_stat_error' in self.keys(): return self.get('em_stat_error') * u.meter return None # OBSERVABLE AXIS @property def o_ucd(self): """ Nature of the observable axis within the data product """ return self.get('o_ucd', decode=True) @property def o_unit(self): """ Units along the observable axis """ return self.get('o_unit', decode=True, default=None) @property def o_calib_status(self): """ Type of calibration applied on the Flux observed (or other observable quantity). """ return self.get('o_calib_status', decode=True, default=None) @property def o_stat_error(self): """ Statistical error on the Observable axis. Note: the return value has the units defined in unit """ return self.get('o_stat_error', decode=True, default=None) # POLARIZATION CHARACTERISATION @property def pol_xel(self): """ Number of different polarization states present in the data. The default value is 0, indicating that polarization was not explicitly observed. Corresponding values are stored in the ``pol`` property """ return self.get('pol_xel') @property def pol_states(self): """ List of polarization states present in the data file. Possible values are: {I Q U V RR LL RL LR XX YY XY YX POLI POLA}. Values in the set are separated by the '/' character. A leading / character must start the list and a trailing / character must end it. It should be ordered following the above list, compatible with the FITS list table for polarization definition. """ return self.get('pol_states', decode=True, default=None) # PROVENANCE @property def instrument_name(self): """ The name of the instrument used for the acquisition of the data """ return self.get('instrument_name', decode=True) @property def facility_name(self): """ Name of the facility or observatory used to collect the data """ return self.get('facility_name', decode=True, default=None) @property def proposal_id(self): """ Identifier of proposal to which observation belongs """ return self.get('proposal_id', default=None, decode=True) @deprecated("1.5", alternative="SIA2Service") class SIAService(SIA2Service): pass @deprecated("1.5", alternative="SIA2Query") class SIAQuery(SIA2Query): pass @deprecated("1.5", alternative="SIA2Results") class SIAResults(SIA2Results): pass pyvo-1.5.2/pyvo/dal/sla.py000066400000000000000000000367321462331236700154240ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for searching for spectral line metadata in a remote database. A Simple Line Access (SLA) service allows a client to search for metadata describing atomic and molecular transitions that can result in spectral line emission and absorption. The service responds to a search query with a table in which each row represents a transition that matches the query constraints. The columns provide the metadata describing the transition. This module provides an interface for accessing an SLA service. It is implemented as a specialization of the DAL Query interface. The ``search()`` function support the simplest and most common types of queries, returning an SLAResults instance as its results which represents the matching imagess from the archive. The SLAResults supports access to and iterations over the individual records; these are provided as SLARecord instances, which give easy access to key metadata in the response, such as the transition title. The SLAService class can represent a specific service available at a URL endpoint. """ from pyvo.io.vosi.vodataservice import TableParam from astropy.units import Quantity, Unit from astropy.units import spectral as spectral_equivalencies from astropy.io.votable.tree import Field from astropy.table import Table from .query import DALResults, DALQuery, DALService, Record __all__ = ["search", "SLAService", "SLAQuery", "SLAResults", "SLARecord"] def search(baseurl, wavelength, **keywords): """ submit a simple SLA query that requests spectral lines within a wavelength range Parameters ---------- baseurl : str the base URL for the SLA service wavelength : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SLAResults a container holding a table of matching spectral lines Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. """ service = SLAService(baseurl) return service.search(wavelength, **keywords) class SLAService(DALService): """ a representation of an spectral line catalog (SLA) service """ def __init__(self, baseurl, session=None): """ instantiate an SLA service Parameters ---------- baseurl : str the base URL for submitting search queries to the service. session : object optional session to use for network requests """ super().__init__(baseurl, session=session) def _get_metadata(self): """ download the metadata resource """ if not hasattr(self, "_metadata"): query = self.create_query(request='getCapabilities') metadata = query.execute_votable() setattr(self, "_metadata", metadata) @property def description(self): """ the service description. """ self._get_metadata() try: return getattr(self, "_metadata", None).description except AttributeError: return None @property def columns(self): """ the available columns on this service """ self._get_metadata() fields = filter( lambda field_or_param: isinstance(field_or_param, Field), self._metadata.iter_fields_and_params() ) try: return [ TableParam.from_field(field) for field in fields] except AttributeError: return [] def search(self, wavelength, **keywords): """ submit a simple SLA query to this service with the given constraints. This method is provided for a simple but typical SLA queries. For more complex queries, one should create an SLAQuery object via create_query() Parameters ---------- wavelength : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SLAResults a container holding a table of matching spectral lines Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SLAResults pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ return self.create_query(wavelength, **keywords).execute() def create_query(self, wavelength=None, request="queryData", **keywords): """ create a query object that constraints can be added to and then executed. The input arguments will initialize the query with the given values. Parameters ---------- wavelength : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SLAQuery the query instance See Also -------- SLAQuery """ return SLAQuery(baseurl=self.baseurl, wavelength=wavelength, request=request, session=self._session, **keywords) def describe(self): print(self.description) print() rows = [( col.name, col.description, col.unit, col.ucd, col.utype, col.datatype.arraysize, col.datatype.content, ) for col in self.columns] names = ( 'name', 'description', 'unit', 'ucd', 'utype', 'arraysize', 'datatype', ) table = Table(rows=rows, names=names) table.pprint( max_lines=-1, max_width=-1, show_unit=False, show_dtype=False) class SLAQuery(DALQuery): """ a class for preparing an query to an SLA service. Query constraints are added via its service type-specific methods. The various execute() functions will submit the query and return the results. The base URL for the query, which controls where the query will be sent when one of the execute functions is called, is typically set at construction time; however, it can be updated later via the :py:attr:`~pyvo.dal.DALQuery.baseurl` to send a configured query to another service. In addition to the search constraint attributes described below, search parameters can be set generically by name via the dict semantics. The typical function for submitting the query is ``execute()``; however, alternate execute functions provide the response in different forms, allowing the caller to take greater control of the result processing. """ def __init__( self, baseurl, wavelength=None, request="queryData", session=None, **keywords): """ initialize the query object with a baseurl and the given parameters Parameters ---------- baseurl : str the base URL for the SLA service wavelength : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. session : object optional session to use for network requests **keywords : additional parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. """ super().__init__(baseurl, session=session) if wavelength is not None: self.wavelength = wavelength self.request = request @property def wavelength(self): """ the frequency/wavelength range the observations belong to. """ return getattr(self, "_wavelength", None) @wavelength.setter def wavelength(self, wavelength): setattr(self, "_wavelength", wavelength) if not isinstance(wavelength, Quantity): valerr = ValueError( 'Wavelength range must be a sequence with exactly two values', 'expressing a frequency or wavelength range') try: # assume meters wavelength = wavelength * Unit("meter") except ValueError: raise valerr try: if len(wavelength) != 2: raise valerr except TypeError: raise valerr # transform to meters wavelength = wavelength.to( Unit("m"), equivalencies=spectral_equivalencies()) # frequency is counter-proportional to wavelength, so we just sort it # to have the right order again wavelength.sort() self["WAVELENGTH"] = "{start}/{end}".format( start=wavelength.value[0], end=wavelength.value[1]) @wavelength.deleter def wavelength(self): delattr(self, "_wavelength") del self["WAVELENGTH"] @property def request(self): """ the type of service operation which is being performed """ return getattr(self, "_request", None) @request.setter def request(self, val): setattr(self, "_request", val) self["REQUEST"] = val @request.deleter def request(self): delattr(self, "_request") del self["REQUEST"] def execute(self): """ submit the query and return the results as a SLAResults instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return SLAResults(self.execute_votable(), url=self.queryurl, session=self._session) class SLAResults(DALResults): """ The list of matching spectral lines resulting from a spectal line catalog (SLA) query. Each record contains a set of metadata that describes a source or observation within the requested circular region (i.e. a "cone"). The number of records in the results is available by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.sla.SLARecord` instances) are typically accessed by iterating over an ``SLAResults`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.DALResults.getcolumn` method. ``SLAResults`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the images. One can access that VOTable directly via the :py:attr:`~pyvo.dal.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`~astropy.table.table.Table` via the following conversion: ``table = results.votable.to_table()`` ``SLAResults`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.sla.SLARecord` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """ def getrecord(self, index): """ return a representation of a sla result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has additional image-specific properties Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- SLARecord a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- Record """ return SLARecord(self, index, session=self._session) class SLARecord(Record): """ a dictionary-like container for data in a record from the results of an spectral line (SLA) query, describing a spectral line transition. The commonly accessed metadata which are stadardized by the SLA protocol are available as attributes. All metadata, particularly non-standard metadata, are acessible via the ``get(`` *key* ``)`` function (or the [*key*] operator) where *key* is table column name. """ @property def title(self): """ a title/small description of the line transition """ return self.getbyutype("ssldm:Line.title", decode=True) @property def wavelength(self): """ the vacuum wavelength of the line in meters. """ return self.getbyutype("ssldm:Line.wavelength.value") * Unit("m") @property def species_name(self): """ the name of the chemical species that produces the transition. """ return self.getbyutype("ssldm:Line.species.name") @property def status(self): """ the name of the chemical species that produces the transition. """ return self.getbyutype("ssldm:Line.identificationStatus") @property def initial_level(self): """ a description of the initial (higher energy) quantum level """ return self.getbyutype("ssldm:Line.initialLevel.name", decode=True) @property def final_level(self): """ a description of the final (higher energy) quantum level """ return self.getbyutype("ssldm:Line.finalLevel.name") pyvo-1.5.2/pyvo/dal/ssa.py000066400000000000000000000640441462331236700154300ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for searching for spectra in a remote archive. A Simple Spectral Access (SSA) service allows a client to search for spectra in an archive whose field of view overlaps with a given cone on the sky. The service responds to a search query with a table in which each row represents an image that is available for download. The columns provide metadata describing each image and one column in particular provides the image's download URL (also called the *access reference*, or *acref*). Some SSA services can create spectra on-the-fly from underlying data (e.g. image cubes); in this case, the query result is a table of images whose aperture matches the requested cone and which will be created when accessed via the download URL. This module provides an interface for accessing an SSA service. It is implemented as a specialization of the DAL Query interface. The ``search()`` function support the simplest and most common types of queries, returning an SSAResults instance as its results which represents the matching imagess from the archive. The SSAResults supports access to and iterations over the individual records; these are provided as SSARecord instances, which give easy access to key metadata in the response, such as the position of the spectrum's aperture, the spectrum format, its frequency range, and its download URL. The SSAService class can represent a specific service available at a URL endpoint. """ import re from pyvo.io.vosi.vodataservice import TableParam from astropy.coordinates import SkyCoord from astropy.time import Time from astropy.units import Quantity, Unit from astropy.units import spectral as spectral_equivalencies from astropy.io.votable.tree import Field from astropy.table import Table import numpy as np from .query import DALResults, DALQuery, DALService, Record from .mimetype import mime2extension from .adhoc import DatalinkResultsMixin, DatalinkRecordMixin, SodaRecordMixin from .. import samp __all__ = ["search", "SSAService", "SSAQuery", "SSAResults", "SSARecord"] def search( baseurl, pos=None, diameter=None, band=None, time=None, format=None, **keywords): """ submit a simple SSA query that requests spectra overlapping a given region Parameters ---------- baseurl : str the base URL for the SSA service pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the circular search region. assuming icrs decimal degrees if unit is not specified. diameter : `~astropy.units.Quantity` class or scalar float the diameter of the circular region around pos in which to search. assuming icrs decimal degrees if unit is not specified. band : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. time : `~astropy.time.Time` class or sequence of two strings the datetime range the observations were made in. assuming iso 8601 if format is not specified. format : str the image format(s) of interest. "all" indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata. **keywords : additional case insensitive parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SSAResults a container holding a table of matching spectrum records Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including a query syntax error. See Also -------- SSAResults pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ return SSAService(baseurl).search( pos=pos, diameter=diameter, band=band, time=time, format=format, **keywords) class SSAService(DALService): """ a representation of an SSA service """ def __init__(self, baseurl, session=None): """ instantiate an SSA service Parameters ---------- baseurl : str the base URL for submitting search queries to the service. """ super().__init__(baseurl, session=session) def _get_metadata(self): """ the metadata resource element """ if not hasattr(self, "_metadata"): query = self.create_query(format='metadata') metadata = query.execute_votable() setattr(self, "_metadata", metadata) @property def description(self): """ the service description. """ self._get_metadata() try: return getattr(self, "_metadata", None).description except AttributeError: return None @property def columns(self): """ the available columns on this service """ self._get_metadata() fields = filter( lambda field_or_param: isinstance(field_or_param, Field), self._metadata.iter_fields_and_params() ) try: return [ TableParam.from_field(field) for field in fields] except AttributeError: return [] def search( self, pos=None, diameter=None, band=None, time=None, format=None, **keywords): """ submit a SSA query to this service with the given constraints. Parameters ---------- pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the circular search region. assuming icrs decimal degrees if unit is not specified. diameter : `~astropy.units.Quantity` class or scalar float the diameter of the circular region around pos in which to search. assuming icrs decimal degrees if unit is not specified. band : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. time : `~astropy.time.Time` class or sequence of two strings the datetime range the observations were made in. assuming iso 8601 if format is not specified. format : str the image format(s) of interest. "all" indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata. **keywords : additional case insensitive parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SSAResults a container holding a table of matching catalog records Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError if the service responds with an error, including query syntax errors See Also -------- SSAResults pyvo.dal.DALServiceError pyvo.dal.DALQueryError """ return self.create_query( pos=pos, diameter=diameter, band=band, time=time, format=format, **keywords).execute() def create_query( self, pos=None, diameter=None, band=None, time=None, format=None, request="queryData", **keywords): """ create a query object that constraints can be added to and then executed. The input arguments will initialize the query with the given values. Parameters ---------- pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the circular search region. assuming icrs decimal degrees if unit is not specified. diameter : `~astropy.units.Quantity` class or scalar float the diameter of the circular region around pos in which to search. assuming icrs decimal degrees if unit is not specified. band : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. time : `~astropy.time.Time` class or sequence of two strings the datetime range the observations were made in. assuming iso 8601 if format is not specified. format : str the image format(s) of interest. "all" indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata. **keywords : additional case insensitive parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. Returns ------- SSAQuery the query instance See Also -------- SSAQuery """ return SSAQuery( self.baseurl, pos=pos, diameter=diameter, band=band, time=time, format=format, request=request, session=self._session, **keywords) def describe(self): print(self.description) print() rows = [( col.name, col.description, col.unit, col.ucd, col.utype, col.datatype.arraysize, col.datatype.content, ) for col in self.columns] names = ( 'name', 'description', 'unit', 'ucd', 'utype', 'arraysize', 'datatype', ) table = Table(rows=rows, names=names) table.pprint( max_lines=-1, max_width=-1, show_unit=False, show_dtype=False) class SSAQuery(DALQuery): """ a class for preparing an query to an SSA service. Query constraints are added via its service type-specific properties and methods. Once all the constraints are set, one of the various execute() functions can be called to submit the query and return the results. The base URL for the query, which controls where the query will be sent when one of the execute functions is called, is typically set at construction time; however, it can be updated later via the :py:attr:`~pyvo.dal.DALQuery.baseurl` to send a configured query to another service. The typical function for submitting the query is ``execute()``; however, alternate execute functions provide the response in different forms, allowing the caller to take greater control of the result processing. """ def __init__( self, baseurl, pos=None, diameter=None, band=None, time=None, format=None, request="queryData", session=None, **keywords): """ initialize the query object with a baseurl and the given parameters Parameters ---------- baseurl : str the base URL for the SSA service pos : `~astropy.coordinates.SkyCoord` class or sequence of two floats the position of the center of the circular search region. assuming icrs decimal degrees if unit is not specified. diameter : `~astropy.units.Quantity` class or scalar float the diameter of the circular region around pos in which to search. assuming icrs decimal degrees if unit is not specified. band : `~astropy.units.Quantity` class or sequence of two floats the bandwidth range the observations belong to. assuming meters if unit is not specified. time : `~astropy.time.Time` class or sequence of two strings the datetime range the observations were made in. assuming iso 8601 if format is not specified. format : str the image format(s) of interest. "all" indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata. session : object optional session to use for network requests **keywords : additional case insensitive parameters can be given via arbitrary case insensitive keyword arguments. Where there is overlap with the parameters set by the other arguments to this function, these keywords will override. """ super().__init__(baseurl, session=session) if pos is not None: self.pos = pos if diameter is not None: self.diameter = diameter if band is not None: self.band = band if time is not None: self.time = time if format is not None: self.format = format self.request = request self.update({key.upper(): value for key, value in keywords.items()}) @property def pos(self): """ the position of the center of the circular search region as a `~astropy.coordinates.SkyCoord` instance. """ return getattr(self, "_pos", None) @pos.setter def pos(self, pos): setattr(self, "_pos", pos) if not isinstance(pos, SkyCoord): try: ra, dec = pos except (TypeError, ValueError): raise ValueError( 'Pos must be a sequence with exactly two values, ' 'expressing ra and dec in icrs degrees' ) # assume degrees pos = SkyCoord(ra=ra, dec=dec, unit="deg", frame="icrs") self["POS"] = "{ra},{dec}".format( ra=pos.icrs.ra.deg, dec=pos.icrs.dec.deg) @pos.deleter def pos(self): delattr(self, "_pos") del self["POS"] @property def diameter(self): """ the diameter of the circular region around pos as a `~astropy.units.Quantity` instance. """ return getattr(self, "_diameter", None) @diameter.setter def diameter(self, diameter): setattr(self, "_diameter", diameter) if not isinstance(diameter, Quantity): valerr = ValueError( 'Radius must be exactly one value, expressing degrees') try: # assume degrees diameter = diameter * Unit("deg") except ValueError: raise valerr try: if len(diameter): raise valerr except TypeError: pass # len 1 self["SIZE"] = diameter.to(Unit("deg")).value @diameter.deleter def diameter(self): delattr(self, "_diameter") del self["SIZE"] @property def band(self): """ the bandwidth range the observations belong to. """ return getattr(self, "_band", None) @band.setter def band(self, band): setattr(self, "_band", band) if not isinstance(band, Quantity): valerr = ValueError( 'Band must be a sequence with exactly two values', 'expressing a frequency or wavelength range') try: # assume meters band = band * Unit("meter") except ValueError: raise valerr try: if len(band) != 2: raise valerr except TypeError: raise valerr # transform to meters band = band.to(Unit("m"), equivalencies=spectral_equivalencies()) # frequency is counter-proportional to wavelength, so we just sort # it to have the right order again band.sort() self["BAND"] = "{start}/{end}".format( start=band.value[0], end=band.value[1]) @band.deleter def band(self): delattr(self, "_band") del self["BAND"] @property def time(self): """ the datetime range the observations were made in. """ return getattr(self, "_time", None) @time.setter def time(self, time): setattr(self, "_time", time) if not isinstance(time, Time): valerr = ValueError( 'Time must be a sequence with exactly two values, ' 'expressing a datetime in ISO 8601' ) try: # assume iso8601 time = Time(time, format="isot") except ValueError: raise valerr try: if len(time) != 2: raise valerr except TypeError: raise valerr # It seems astropy either has seconds and microseconds (the date_hms # subformat) or no seconds at all (the date_hm subformat). SSAP # probably doesn't allow microseconds. Rather than fix this # via a new astropy subformat, let's get by with local string # operations. literals = time.to_value('isot') self["TIME"] = "{start}/{end}".format( start=literals[0].split(".")[0], end=literals[1].split(".")[0]) @time.deleter def time(self): delattr(self, "_time") del self["TIME"] @property def format(self): """ the image format(s) of interest. "all" indicates all available formats; "graphic" indicates graphical images (e.g. jpeg, png, gif; not FITS); "metadata" indicates that no images should be returned--only an empty table with complete metadata. """ return getattr(self, "_format", None) @format.setter def format(self, val): setattr(self, "_format", val) if isinstance(val, (str, bytes)): val = [val] self["FORMAT"] = ",".join(val) @format.deleter def format(self): delattr(self, "_format") del self["FORMAT"] @property def request(self): """ the type of service operation which is being performed """ return getattr(self, "_request", None) @request.setter def request(self, val): setattr(self, "_request", val) self["REQUEST"] = val @request.deleter def request(self): delattr(self, "_request") del self["REQUEST"] def execute(self): """ submit the query and return the results as a SSAResults instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return SSAResults(self.execute_votable(), url=self.queryurl, session=self._session) class SSAResults(DatalinkResultsMixin, DALResults): """ The list of matching images resulting from a spectrum (SSA) query. Each record contains a set of metadata that describes an available spectrum matching the query constraints. The number of records in the results is by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.ssa.SSARecord` instances) are typically accessed by iterating over an ``SSAResults`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.DALResults.getcolumn` method. ``SSAResults`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the spectra. One can access that VOTable directly via the :py:attr:`~pyvo.dal.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`~astropy.table.table.Table` via the following conversion: ``table = results.votable.to_table()`` ``SSAResults`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.ssa.SSARecord` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """ def getrecord(self, index): """ return a representation of a sia result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has additional image-specific properties Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- SIARecord a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- Record """ return SSARecord(self, index, session=self._session) class SSARecord(SodaRecordMixin, DatalinkRecordMixin, Record): """ a dictionary-like container for data in a record from the results of an SSA query, describing an available spectrum. The commonly accessed metadata which are stadardized by the SSA protocol are available as attributes. If the metadatum accessible via an attribute is not available, the value of that attribute will be None. All metadata, including non-standard metadata, are acessible via the ``get(`` *key* ``)`` function (or the [*key*] operator) where *key* is table column name. """ @property def ra(self): """ return the right ascension of the center of the spectrum """ return self.getbyutype("ssa:Target.Pos")[0] @property def dec(self): """ return the declination of the center of the spectrum """ return self.getbyutype("ssa:Target.Pos")[1] @property def title(self): """ return the title of the spectrum """ return self.getbyutype("ssa:DataID.Title", decode=True) @property def format(self): """ return the file format that this the spectrum is stored in """ return self.getbyutype("ssa:Access.Format", decode=True) @property def dateobs(self): """ return the modified Julien date (MJD) of the mid-point of the observational data that went into the spectrum """ dateobs = self.getbyutype("ssa:DataID.Date", decode=True) try: if not dateobs or np.isnan(dateobs): return None except TypeError: # np.isnan can only check floats. If can't check for nan, pass it along pass return Time(dateobs, format="iso") @property def instr(self): """ return the name of the instrument (or instruments) that produced the data that went into this spectrum. """ return self.getbyutype("ssa:DataID.Instrument", decode=True) @property def acref(self): """ return the URL that can be used to retrieve the spectrum. """ return self.getbyutype("ssa:Access.Reference", decode=True) @property def filesize(self): """ The (estimated) size of the image in bytes """ return self.getbyutype("ssa:Access.Size") def getdataurl(self): """ return the URL contained in the access URL column which can be used to retrieve the dataset described by this record. None is returned if no such column exists. """ dataurl = super().getdataurl() if dataurl is None: return self.acref else: return dataurl def suggest_dataset_basename(self): """ return a default base filename that the dataset available via ``getdataset()`` can be saved as. This function is specialized for a particular service type this record originates from so that it can be used by ``cachedataset()`` via ``make_dataset_filename()``. """ out = self.title if isinstance(out, bytes): out = out.decode('utf-8') if not out: out = "spectrum" else: out = re.sub(r'\s+', '_', out.strip()) return out def suggest_extension(self, default=None): """ returns a recommended filename extension for the dataset described by this record. Typically, this would look at the column describing the format and choose an extension accordingly. """ return mime2extension(self.format, default) def broadcast_samp(self, client_name=None): """ Broadcast the spectrum to ``client_name`` via SAMP """ with samp.connection() as conn: samp.send_spectrum_to( conn, self.getdataurl(), client_name, name=self.suggest_dataset_basename()) pyvo-1.5.2/pyvo/dal/tap.py000066400000000000000000001131571462331236700154260ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for accessing remote source and observation catalogs """ from functools import partial from datetime import datetime from time import sleep import requests from urllib.parse import urlparse, urljoin from astropy.io.votable import parse as votableparse from .query import ( DALResults, DALQuery, DALService, Record, UploadList, DALServiceError, DALQueryError) from .vosi import AvailabilityMixin, CapabilityMixin, VOSITables from .adhoc import DatalinkResultsMixin, DatalinkRecordMixin, SodaRecordMixin from ..io import vosi, uws from ..io.vosi import tapregext as tr from ..utils.formatting import para_format_desc from ..utils.http import use_session from ..utils.prototype import prototype_feature import xml.etree.ElementTree import io __all__ = [ "search", "escape", "TAPService", "TAPQuery", "AsyncTAPJob", "TAPResults"] IVOA_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ" # file formats supported by table upload and their corresponding MIME types TABLE_UPLOAD_FORMAT = {'tsv': 'text/tab-separated-values', 'csv': 'text/csv', 'FITSTable': 'application/fits'} # file formats supported by table create and their corresponding MIME types TABLE_DEF_FORMAT = {'VOSITable': 'text/xml', 'VOTable': 'application/x-votable+xml'} def _from_ivoa_format(datetime_str): """ parses an ivoa date in ISO 8601 format: YYYY-MM-DDTHH:MM:SS.[mmm]Z :param datetime_str: :return: corresponding datetime object """ # TODO Replace with datetime.fromisoformat(date_string) in Python3.7+ try: # with fraction of seconds first return datetime.strptime(datetime_str, IVOA_DATETIME_FORMAT) except ValueError: # and without return datetime.strptime(datetime_str, "%Y-%m-%dT%H:%M:%SZ") def escape(term): """ escapes a term for use in ADQL """ return str(term).replace("'", "''") def search(url, query, language="ADQL", maxrec=None, uploads=None, **keywords): """ submit a Table Access query that returns rows matching the criteria given. Parameters ---------- url : str the base URL of the query service. query : str, dict The query string / parameters language : str specifies the query language, default ADQL. useful for services which allow to use the backend query language. maxrec : int the maximum records to return. defaults to the service default uploads : dict a mapping from table names to file like objects containing a votable Returns ------- TAPResults a container holding a table of matching catalog records Raises ------ DALServiceError for errors connecting to or communicating with the service. DALQueryError if the service responds with an error, including a query syntax error. """ service = TAPService(url) return service.search(query, language=language, maxrec=maxrec, uploads=uploads, **keywords) class TAPService(DALService, AvailabilityMixin, CapabilityMixin): """ a representation of a Table Access Protocol service """ _tables = None _examples = None def __init__(self, baseurl, session=None): """ instantiate a Table Access Protocol service Parameters ---------- baseurl : str the base URL that should be used for forming queries to the service. session : object optional session to use for network requests """ super().__init__(baseurl, session=session) # Check if the session has an update_from_capabilities attribute. # This means that the session is aware of IVOA capabilities, # and can use this information in processing network requests. # One such use case for this is auth. if hasattr(self._session, 'update_from_capabilities'): self._session.update_from_capabilities(self.capabilities) def get_tap_capability(self): """ returns the (first) TAP capability of this service. Returns ------- A `~pyvo.io.vosi.tapregext.TableAccess` instance. """ for capa in self.capabilities: if isinstance(capa, tr.TableAccess): return capa raise DALServiceError("Invalid TAP service: Does not" " expose a tr:TableAccess capability") @property def tables(self): """ returns tables as a dict-like object """ if self._tables is None: tables_url = '{}/tables'.format(self.baseurl) response = self._session.get(tables_url, stream=True) try: response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, tables_url) # requests doesn't decode the content by default response.raw.read = partial(response.raw.read, decode_content=True) self._tables = VOSITables( vosi.parse_tables(response.raw.read), tables_url) return self._tables def _parse_examples(self, examples_uri, *, depth=0): """returns the TAP queries from a DALI examples URI. """ if depth > 5: raise DALServiceError("Suspecting endless recursion when" " parsing TAP examples") response = self._session.get(examples_uri, stream=True) if response.status_code == 404: return [] try: response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, examples_uri) try: root = xml.etree.ElementTree.parse( io.BytesIO(response.content)).getroot() exampleElements = root.findall('.//*[@property="query"]') except Exception as ex: raise DALServiceError.from_except(ex, examples_uri) examples = [TAPQuery(self.baseurl, example.text) for example in exampleElements] for continuation in root.findall('.//*[@property="continuation"]'): examples.extend( self._parse_examples(continuation.get("href"), depth=depth + 1)) return examples @property def examples(self): """ returns examples as a list of TAPQuery objects """ if self._examples is None: examples_url = '{}/examples'.format(self.baseurl) self._examples = self._parse_examples(examples_url) return self._examples @property def maxrec(self): """ the default output limit. Raises ------ DALServiceError if the property is not exposed by the service """ try: return self.get_tap_capability().outputlimit.default.content except AttributeError: pass raise DALServiceError("Default limit not exposed by the service") @property def hardlimit(self): """ the hard output limit. Raises ------ DALServiceError if the property is not exposed by the service """ try: return self.get_tap_capability().outputlimit.hard.content except AttributeError: pass raise DALServiceError("Hard limit not exposed by the service") @property def upload_methods(self): """ a list of upload methods in form of :py:class:`~pyvo.io.vosi.tapregext.UploadMethod` objects """ return self.get_tap_capability().uploadmethods def run_sync( self, query, language="ADQL", maxrec=None, uploads=None, **keywords): """ runs sync query and returns its result Parameters ---------- query : str The query language : str specifies the query language, default ADQL. useful for services which allow to use the backend query language. maxrec : int the maximum records to return. defaults to the service default uploads : dict a mapping from table names to objects containing a votable Returns ------- TAPResults the query result See Also -------- TAPResults """ return self.create_query( query, language=language, maxrec=maxrec, uploads=uploads, **keywords).execute() # alias for service discovery search = run_sync def run_async( self, query, language="ADQL", maxrec=None, uploads=None, **keywords): """ runs async query and returns its result Parameters ---------- query : str, dict the query string / parameters language : str specifies the query language, default ADQL. useful for services which allow to use the backend query language. maxrec : int the maximum records to return. defaults to the service default uploads : dict a mapping from table names to objects containing a votable Returns ------- TAPResult the query instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response See Also -------- AsyncTAPJob """ job = AsyncTAPJob.create( self.baseurl, query, language=language, maxrec=maxrec, uploads=uploads, session=self._session, **keywords) job = job.run().wait() job.raise_if_error() result = job.fetch_result() job.delete() return result def submit_job( self, query, language="ADQL", maxrec=None, uploads=None, **keywords): """ submit a async query without starting it and returns a AsyncTAPJob object Parameters ---------- query : str the query string / parameters language : str specifies the query language, default ADQL. useful for services which allow to use the backend query language. maxrec : int the maximum records to return. defaults to the service default uploads : dict a mapping from table names to objects containing a votable Returns ------- AsyncTAPJob the query instance See Also -------- AsyncTAPJob """ return AsyncTAPJob.create( self.baseurl, query, language=language, maxrec=maxrec, uploads=uploads, session=self._session, **keywords) def create_query( self, query=None, mode="sync", language="ADQL", maxrec=None, uploads=None, **keywords): """ create a query object that constraints can be added to and then executed. The input arguments will initialize the query with the given values. Parameters ---------- baseurl : str the base URL for the TAP service query : str the query string / parameters mode : str the query mode (sync | async). default "sync" language : str specifies the query language, default ADQL. useful for services which allow to use the backend query language. maxrec : int specifies the maximum records to return. defaults to the service default. uploads : dict a mapping from table names to objects containing a votable. """ return TAPQuery( self.baseurl, query, mode=mode, language=language, maxrec=maxrec, uploads=uploads, session=self._session, **keywords) def get_job(self, job_id): """ Returns the job corresponding to an ID. Note that the caller must be able to see the job in the current security context. Parameters ---------- job_id : str ID of the job to view Returns ------- `~pyvo.io.uws.tree.JobSummary` corresponding to the job ID """ response = self._session.get( self.baseurl + '/async/' + job_id, stream=True) response.raw.read = partial(response.raw.read, decode_content=True) return uws.parse_job(response.raw.read) def get_job_list(self, phases=None, after=None, last=None, short_description=True): """ lists jobs that the caller can see in the current security context. The list can be filtered on the server side by the phases of the jobs, creation date time or Note that by default jobs in 'ARCHIVED' phase are not returned. Parameters ---------- phases: list of str Union of job phases to filter the results by. after: datetime Return only jobs created after this datetime last: int Return only the most recent number of jobs short_description: flag - True or False If True, the jobs in the list will contain only the information corresponding to the TAP ShortJobDescription object (job ID, phase, run ID, owner ID and creation ID) whereas if False, a separate GET call to each job is performed for the complete job description. Returns ------- list of `~pyvo.io.uws.tree.JobSummary` """ params = {'PHASE': phases, 'LAST': last} if after: if isinstance(after, str): after = _from_ivoa_format(after) params['AFTER'] = after.strftime(IVOA_DATETIME_FORMAT) response = self._session.get('{}/async'.format(self.baseurl), params=params, stream=True) response.raw.read = partial(response.raw.read, decode_content=True) jobs = uws.parse_job_list(response.raw.read) if not short_description: dj = [] for job in jobs: dj.append(self.get_job(job.jobid)) return dj else: return list(jobs) def describe(self, width=None): """ Print a summary description of this service. This includes the interface capabilities, and the content description if it doesn't contains multiple data collections (in other words, it is not a TAP service). """ if len(self.tables) == 1: description = next(self.tables.values()).description if width: description = para_format_desc(description, width) print(description) print() capabilities = filter( lambda x: not str(x.standardid).startswith( 'ivo://ivoa.net/std/VOSI'), self.capabilities ) for cap in capabilities: cap.describe() print() @prototype_feature('cadc-tb-upload') def create_table(self, name, definition, format='VOSITable'): """ Creates a table in the catalog service. Parameters ---------- name: str Name of the table in the TAP service definition: stream (object with a read method) Definition of the table format: str Format of the table definition (VOSITable or VOTable). """ if not name or not definition: raise ValueError( 'table name and definition required in create: {}/{}'. format(name, definition)) if format not in TABLE_DEF_FORMAT.keys(): raise ValueError( 'Table definition file format {} not supported ({})'. format(format, ' '.join(TABLE_DEF_FORMAT.keys()))) headers = {'Content-Type': TABLE_DEF_FORMAT[format]} response = self._session.put('{}/tables/{}'.format(self.baseurl, name), headers=headers, data=definition) response.raise_for_status() @prototype_feature('cadc-tb-upload') def remove_table(self, name): """ Remove a table from the catalog service (Equivalent to drop command in DB). Parameters ---------- name: str Name of the table in the TAP service """ if not name: raise ValueError( 'table name required in : {}'. format(name)) response = self._session.delete( '{}/tables/{}'.format(self.baseurl, name)) response.raise_for_status() @prototype_feature('cadc-tb-upload') def load_table(self, name, source, format='tsv'): """ Loads content to a table Parameters ---------- name: str Name of the table source: stream with a read method Stream containing the data to be loaded format: str Format of the data source: tab-separated values(tsv), comma-separated values (csv) or FITS table (FITSTable) """ if not name or not source: raise ValueError( 'table name and source required in upload: {}/{}'. format(name, source)) if format not in TABLE_UPLOAD_FORMAT.keys(): raise ValueError( 'Table content file format {} not supported ({})'. format(format, ' '.join(TABLE_UPLOAD_FORMAT.keys()))) headers = {'Content-Type': TABLE_UPLOAD_FORMAT[format]} response = self._session.post( '{}/load/{}'.format(self.baseurl, name), headers=headers, data=source) response.raise_for_status() @prototype_feature('cadc-tb-upload') def create_index(self, table_name, column_name, unique=False): """ Creates a table index in the catalog service. Parameters ---------- table_name: str Name of the table column_name: str Name of the column in the table unique: bool True for unique index, False otherwise """ if not table_name or not column_name: raise ValueError( 'table and column names are required in index: {}/{}'. format(table_name, column_name)) result = self._session.post('{}/table-update'.format(self.baseurl), data={'table': table_name, 'index': column_name, 'unique': 'true' if unique else 'false'}, allow_redirects=False) if result.status_code == 303: job_url = result.headers['Location'] if not job_url: raise RuntimeError( 'table update job location missing in response') # run the job job = AsyncTAPJob(job_url, session=self._session) job = job.run().wait() job.raise_if_error() # TODO job.delete() else: raise RuntimeError( 'BUG: table update expected status 303 received {}'. format(result.status_code)) class AsyncTAPJob: """ This class represents a UWS TAP Job. """ _job = {} @classmethod def create( cls, baseurl, query, language="ADQL", maxrec=None, uploads=None, session=None, **keywords): """ creates a async tap job on the server under ``baseurl`` Parameters ---------- baseurl : str the TAP baseurl query : str the query string language : str specifies the query language, default ADQL. useful for services which allow to use the backend query language. maxrec : int the maximum records to return. defaults to the service default uploads : dict a mapping from table names to objects containing a votable session : object optional session to use for network requests """ tapquery = TAPQuery( baseurl, query, mode="async", language=language, maxrec=maxrec, uploads=uploads, session=session, **keywords) response = tapquery.submit() job = cls(response.url, session=session) return job def __init__(self, url, session=None): """ initialize the job object with the given url and fetch remote values Parameters ---------- url : str the job url """ self._url = url self._session = use_session(session) self._update() def __enter__(self): """ Enters the context """ return self def __exit__(self, exc_type, exc_val, exc_tb): """ Exits the context. The job is silently deleted. """ try: self.delete() except Exception: pass def _update(self, wait_for_statechange=False, timeout=10.): """ updates local job infos with remote values """ try: if wait_for_statechange: response = self._session.get( self.url, stream=True, timeout=timeout, params={ "WAIT": "-1" } ) else: response = self._session.get(self.url, stream=True, timeout=timeout) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) # requests doesn't decode the content by default response.raw.read = partial(response.raw.read, decode_content=True) self._job = uws.parse_job(response.raw.read) @property def job(self): """ all up-to-date uws job infos as dictionary """ # keep it up to date self._update() return self._job @property def url(self): """ the job url """ return self._url @property def job_id(self): """ the job id """ return self._job.jobid @property def phase(self): """ the current query phase """ self._update() return self._job.phase @property def execution_duration(self): """ maximum execution duration as `~astropy.time.TimeDelta`. """ self._update() return self._job.executionduration @execution_duration.setter def execution_duration(self, value): try: response = self._session.post( "{}/executionduration".format(self.url), data={"EXECUTIONDURATION": str(value)}) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) self._update() @property def destruction(self): """ datetime after which the job results are deleted automatically. read-write """ self._update() return self._job.destruction @destruction.setter def destruction(self, value): """ datetime after which the job results are deleted automatically. read-write Parameters ---------- value : datetime datetime after which the job results are deleted automatically """ if isinstance(value, str): value = _from_ivoa_format(value) try: response = self._session.post( "{}/destruction".format(self.url), data={"DESTRUCTION": value.strftime(IVOA_DATETIME_FORMAT)}) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) self._update() @property def quote(self): """ estimated runtime """ self._update() return self._job.quote @property def owner(self): """ job owner (if applicable) """ self._update() return self._job.ownerid @property def query(self): """ the job query """ self._update() for parameter in self._job.parameters: if parameter.id_.lower() == 'query': return parameter.content return '' @query.setter def query(self, query): try: response = self._session.post( '{}/parameters'.format(self.url), data={"QUERY": query}) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) self._update() def upload(self, **kwargs): """ upload a table to the job. the job must not been started. """ uploads = UploadList.fromdict(kwargs) files = { upload.name: upload.fileobj() for upload in uploads if upload.is_inline } try: response = self._session.post( '{}/parameters'.format(self.url), data={'UPLOAD': uploads.param()}, files=files ) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) self._update() @property def results(self): """ The job results if exists """ return self._job.results @property def result(self): """ The job result if exists """ try: for r in self._job.results: if r.id_ == 'result': return r return self._job.results[0] except IndexError: return None @property def result_uris(self): """ a list of the last result uri's """ return [result.href for result in self._job.results] @property def result_uri(self): """ the uri of the result """ try: uri = self.result.href if not urlparse(uri).netloc: uri = urljoin(self.url, uri) return uri except IndexError: return None @property def uws_version(self): """ the version of the UWS serving this async job Asynchronous TAP jobs are managed using a standard called Universal Worker Service (UWS). For instance, starting version 1.1, you can have long polls, which save on monitoring requests. Normal users generally will not have to look at this. """ self._update() return self._job.version def run(self): """ starts the job / change phase to RUN """ try: response = self._session.post( '{}/phase'.format(self.url), data={"PHASE": "RUN"}) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) return self def abort(self): """ aborts the job / change phase to ABORT """ try: response = self._session.post( '{}/phase'.format(self.url), data={"PHASE": "ABORT"}) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) return self def wait(self, phases=None, timeout=600.): """ waits for the job to reach the given phases. Parameters ---------- phases : list phases to wait for Raises ------ DALServiceError if the job is in a state that won't lead to an result """ if not phases: phases = {"COMPLETED", "ABORTED", "ERROR"} interval = 1.0 increment = 1.2 active_phases = { "QUEUED", "EXECUTING", "RUN", "COMPLETED", "ERROR", "UNKNOWN"} while True: self._update(wait_for_statechange=True, timeout=timeout) # use the cached value cur_phase = self._job.phase if cur_phase not in active_phases: raise DALServiceError( "Cannot wait for job completion. Job is not active!") if cur_phase in phases: break # fallback for uws 1.0 or unsupported WAIT parameter sleep(interval) interval = min(120, interval * increment) return self def delete(self): """ deletes the job. this object will become invalid. """ try: response = self._session.delete( self.url, allow_redirects=False) response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, self.url) self._url = None def raise_if_error(self): """ raise a exception if theres an error Raises ------ DALQueryError if theres an error """ if self.phase in {"ERROR", "ABORTED"}: msg = "" if self._job and self._job.errorsummary: msg = self._job.errorsummary.message.content msg = msg or "" raise DALQueryError("Query Error: " + msg, self.url) def fetch_result(self): """ returns the result votable if query is finished """ try: response = self._session.get(self.result_uri, stream=True) response.raise_for_status() except requests.RequestException as ex: self._update() # we propably got a 404 because query error. raise with error msg self.raise_if_error() raise DALServiceError.from_except(ex, self.url) response.raw.read = partial( response.raw.read, decode_content=True) return TAPResults(votableparse(response.raw.read), url=self.result_uri, session=self._session) class TAPQuery(DALQuery): """ a class for preparing an query to an TAP service. Query constraints are added via its service type-specific methods. The various execute() functions will submit the query and return the results. The base URL for the query, which controls where the query will be sent when one of the execute functions is called, is typically set at construction time; however, it can be updated later via the :py:attr:`~pyvo.dal.DALQuery.baseurl` to send a configured query to another service. In addition to the search constraint attributes described below, search parameters can be set generically by name via dict semantics. The typical function for submitting the query is ``execute()``; however, alternate execute functions provide the response in different forms, allowing the caller to take greater control of the result processing. """ def __init__( self, baseurl, query, mode="sync", language="ADQL", maxrec=None, uploads=None, session=None, **keywords): """ initialize the query object with the given parameters Parameters ---------- baseurl : str the TAP baseurl query : str the query string mode : str the query mode (sync | async). default "sync" language : str the query language. defaults to ADQL maxrec : int the amount of records to fetch uploads : dict Files to upload. Uses table name as key and table content as value. session : object optional session to use for network requests """ baseurl = baseurl.rstrip("?") super().__init__(baseurl, session=session, **keywords) self._mode = mode if mode in ("sync", "async") else "sync" self._uploads = UploadList.fromdict(uploads or {}) self["REQUEST"] = "doQuery" self["LANG"] = language if maxrec: self["MAXREC"] = maxrec self["QUERY"] = query if self._uploads: self["UPLOAD"] = self._uploads.param() @property def queryurl(self): """ the URL to which to submit queries In TAP, that varies depending on whether we run sync or async queries. """ return '{baseurl}/{mode}'.format(baseurl=self.baseurl, mode=self._mode) def execute_stream(self, post=False): """ submit the query and return the raw VOTable XML as a file stream Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors in the input query syntax """ # theres nothing to execute in non-sync queries if self._mode != "sync": raise DALServiceError( "Cannot execute a non-synchronous query. Use submit instead") return super().execute_stream(post=post) def execute(self): """ submit the query and return the results as a TAPResults instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return TAPResults(self.execute_votable(), url=self.queryurl, session=self._session) def submit(self, post=False): """ Does the request part of the TAP query. This function is separated from response parsing because async queries return no votable but behave like sync queries in terms of request. It returns the requests response. """ url = self.queryurl files = { upload.name: upload.fileobj() for upload in self._uploads if upload.is_inline } response = self._session.post( url, data=self, stream=True, files=files) # requests doesn't decode the content by default response.raw.read = partial(response.raw.read, decode_content=True) return response class TAPResults(DatalinkResultsMixin, DALResults): """ The list of matching images resulting from an image (SIA) query. Each record contains a set of metadata that describes an available image matching the query constraints. The number of records in the results is available by passing it to the Python built-in ``len()`` function. This class supports iterable semantics; thus, individual records (in the form of :py:class:`~pyvo.dal.Record` instances) are typically accessed by iterating over an ``TAPResults`` instance. Alternatively, records can be accessed randomly via :py:meth:`getrecord` or through a Python Database API (v2) Cursor (via :py:meth:`~pyvo.dal.DALResults.cursor`). Column-based data access is possible via the :py:meth:`~pyvo.dal.query.DALResults.getcolumn` method. ``TAPResults`` is essentially a wrapper around an Astropy :py:mod:`~astropy.io.votable` :py:class:`~astropy.io.votable.tree.TableElement` instance where the columns contain the various metadata describing the images. One can access that VOTable directly via the :py:attr:`pyvo.dal.DALResults.votable` attribute. Thus, when one retrieves a whole column via :py:meth:`~pyvo.dal.DALResults.getcolumn`, the result is a Numpy array. Alternatively, one can manipulate the results as an Astropy :py:class:`astropy.table.Table` via the following conversion: ``table = results.to_table`` ``SIAResults`` supports the array item operator ``[...]`` in a read-only context. When the argument is numerical, the result is an :py:class:`~pyvo.dal.Record` instance, representing the record at the position given by the numerical index. If the argument is a string, it is interpreted as the name of a column, and the data from the column matching that name is returned as a Numpy array. """ @property def infos(self): """ return the info element as dictionary """ return getattr(self, "_infos", {}) @property def query_status(self): """ return the query status """ return getattr(self, "_infos", {}).get("QUERY_STATUS", None) def getrecord(self, index): """ return a representation of a tap result record that follows dictionary semantics. The keys of the dictionary are those returned by this instance's fieldnames attribute. The returned record has additional image-specific properties Parameters ---------- index : int the integer index of the desired record where 0 returns the first record Returns ------- REc a dictionary-like wrapper containing the result record metadata. Raises ------ IndexError if index is negative or equal or larger than the number of rows in the result table. See Also -------- Record """ return TAPRecord(self, index, session=self._session) class TAPRecord(SodaRecordMixin, DatalinkRecordMixin, Record): pass pyvo-1.5.2/pyvo/dal/tests/000077500000000000000000000000001462331236700154225ustar00rootroot00000000000000pyvo-1.5.2/pyvo/dal/tests/__init__.py000066400000000000000000000001001462331236700175220ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst pyvo-1.5.2/pyvo/dal/tests/conftest.py000066400000000000000000000012661462331236700176260ustar00rootroot00000000000000from contextlib import contextmanager import pytest import requests_mock class ContextAdapter(requests_mock.Adapter): """ requests_mock adapter where ``register_uri`` returns a context manager """ @contextmanager def register_uri(self, *args, **kwargs): matcher = super().register_uri(*args, **kwargs) yield matcher self.remove_matcher(matcher) def remove_matcher(self, matcher): if matcher in self._matchers: self._matchers.remove(matcher) @pytest.fixture(scope='function') def mocker(): with requests_mock.Mocker( adapter=ContextAdapter(case_sensitive=True) ) as mocker_ins: yield mocker_ins pyvo-1.5.2/pyvo/dal/tests/data/000077500000000000000000000000001462331236700163335ustar00rootroot00000000000000pyvo-1.5.2/pyvo/dal/tests/data/datalink/000077500000000000000000000000001462331236700201225ustar00rootroot00000000000000pyvo-1.5.2/pyvo/dal/tests/data/datalink/cutout1.xml000066400000000000000000000260741462331236700222610ustar00rootroot00000000000000
the caller is allowed to use this link with the current authenticated identity
ivo://cadc.nrc.ca/MACHO?54150/cal054150r https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/data/pub/MACHO/cal054150r.fits.fz?RUNID=e3h6rssb161oyf7j #this download ad:MACHO/cal054150r.fits.fz application/fits 18616320 true
ivo://cadc.nrc.ca/MACHO?54150/cal054150r soda-146cec58-9031-4568-9aeb-380eabb50942 #cutout SODA-sync cutout of ad:MACHO/cal054150r.fits.fz application/fits true
ivo://cadc.nrc.ca/MACHO?54150/cal054150r soda-142c15fb-f9ff-4ad0-b35f-7efb07d9bfea #cutout SODA-async cutout of ad:MACHO/cal054150r.fits.fz application/fits true
ivo://cadc.nrc.ca/MACHO?54151/cal054151b https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/data/pub/MACHO/cal054151b.fits.fz?RUNID=e3h6rssb161oyf7j #this download ad:MACHO/cal054151b.fits.fz application/fits 21692160 true
ivo://cadc.nrc.ca/MACHO?54151/cal054151b soda-2d68bb35-50e4-459b-bfb2-ac2b5da1a5d1 #cutout SODA-sync cutout of ad:MACHO/cal054151b.fits.fz application/fits true
ivo://cadc.nrc.ca/MACHO?54151/cal054151b soda-fadba418-47ec-4a3d-b8ab-8368f3081082 #cutout SODA-async cutout of ad:MACHO/cal054151b.fits.fz application/fits true
pyvo-1.5.2/pyvo/dal/tests/data/datalink/cutout2.xml000066400000000000000000000142311462331236700222520ustar00rootroot00000000000000 the caller is allowed to use this link with the current authenticated identity
ivo://cadc.nrc.ca/MACHO?54151/cal054151r https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/data/pub/MACHO/cal054151r.fits.fz?RUNID=of051krube49ud2e #this download ad:MACHO/cal054151r.fits.fz application/fits 18987840 true
ivo://cadc.nrc.ca/MACHO?54151/cal054151r soda-40d74211-597b-4aa9-a153-f5ac67b77021 #cutout SODA-sync cutout of ad:MACHO/cal054151r.fits.fz application/fits true
ivo://cadc.nrc.ca/MACHO?54151/cal054151r soda-8e48c445-8f76-4c01-abcf-bdb1c2f7caf5 #cutout SODA-async cutout of ad:MACHO/cal054151r.fits.fz application/fits true
pyvo-1.5.2/pyvo/dal/tests/data/datalink/datalink-obscore.xml000066400000000000000000000315771462331236700241020ustar00rootroot00000000000000 calibration level (0,1,2,3) publisher dataset identifier short name for the data colection telescope name instrument name internal dataset identifier type of product timestamp of date the data becomes publicly available RA of central coordinates DEC of central coordinates size of the region covered (~diameter of minimum bounding circle) region bounded by observation typical spatial resolution dimensions (number of pixels) along one spatial axis name of intended target dimensions (number of pixels) along the other spatial axis start time of observation (MJD) end time of observation (MJD) exposure time of observation typical temporal resolution dimensions (number of pixels) along the time axis start spectral coordinate value stop spectral coordinate value typical spectral resolution dimensions (number of pixels) along the energy axis dimensions (number of pixels) along the polarization axis UCD describing the spectral axis polarization states present in the data URL to download the data estimated size of the download UCD describing the observable axis (pixel values) format of the data file(s) primary key timestamp of last modification of the metadata
2 ivo://cadc.nrc.ca/MACHO?54150/cal054150r MACHO Mt. Stromlo Observatory 50 inch MACHO 54150 image 2008-10-08T00:00:00.000 88.81762854444203 -67.77109486072844 1.0245546113974644 polygon 89.70783791485876 -67.41840881752057 89.74786512306278 -68.14322955012662 88.22311503462467 -68.14813181576002 87.81208257298478 -67.77896886699358 87.82899304985233 -67.4197778334441 4.353822892 4181 macho000071 4200 50527.522581 50527.526053 300.0 300.0 7 5.800000000000001E-7 7.8E-7 5.799999999999999 7 https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/caom2ops/datalink?runid=b7vdfnrcr8kq35a1&ID=ivo%3A%2F%2Fcadc.nrc.ca%2FMACHO%3F54150%2Fcal054150r phot.count application/x-votable+xml;content=datalink 00000000-0000-0000-93cf-fed05e0e60f6 2019-11-20T19:29:40.929
2 ivo://cadc.nrc.ca/MACHO?54151/cal054151b MACHO Mt. Stromlo Observatory 50 inch MACHO 54151 image 2008-10-08T00:00:00.000 79.9564431173079 -70.32295142824165 1.0420934836421956 polygon 81.03835269676311 -69.95434045997953 81.07118698080274 -70.68523150491481 78.84035765138141 -70.68806135194416 78.88155357298983 -69.9513684574919 5.020573004 4175 macho000006 4168 50527.527002 50527.530475 300.0 300.0 8 4.800000000000001E-7 5.800000000000001E-7 5.799999999999999 8 https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/caom2ops/datalink?runid=b7vdfnrcr8kq35a1&ID=ivo%3A%2F%2Fcadc.nrc.ca%2FMACHO%3F54151%2Fcal054151b phot.count application/x-votable+xml;content=datalink 00000000-0000-0000-b8fb-fed05e2e4e02 2019-11-20T19:29:40.960
2 ivo://cadc.nrc.ca/MACHO?54151/cal054151r MACHO Mt. Stromlo Observatory 50 inch MACHO 54151 image 2008-10-08T00:00:00.000 80.01859124602015 -70.30750032450052 1.0248990302163568 polygon 81.01558843560966 -69.95461093880266 81.06567094624911 -70.67927922356017 79.34988020864029 -70.684376201833 78.89013903241694 -70.31495449099631 78.91029632344201 -69.95565099163903 5.020573004 4181 macho000006 4169 50527.527002 50527.530475 300.0 300.0 7 5.800000000000001E-7 7.8E-7 5.799999999999999 7 https://www.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/caom2ops/datalink?runid=b7vdfnrcr8kq35a1&ID=ivo%3A%2F%2Fcadc.nrc.ca%2FMACHO%3F54151%2Fcal054151r phot.count application/x-votable+xml;content=datalink 00000000-0000-0000-b83a-fed05e27f58e 2019-11-20T19:29:40.960
pyvo-1.5.2/pyvo/dal/tests/data/datalink/datalink-ssa.xml000066400000000000000000000513431462331236700232250ustar00rootroot00000000000000 Spectra from the Flash and Heros Echelle spectrographs developed at Landessternwarte Heidelberg and mounted at La Silla and various other observatories. The data mostly contains spectra of OB stars. Heros was the name of the instrument after Flash got a second channel in 1995. This resource contains data associated with the publication 1996A&A...312..539S. For advice on how to cite the resource(s) that contributed to this result, see http://dc.zah.uni-heidelberg.de/tableinfo/flashheros.data SSAP A measure of how closely the record matches your query. Higher numbers mean better matches. Observed position RA and Dec as a 2-real array as required by SSAP. Target RA and Dec as a 2-real array as required by SSAP. URL of a preview for the dataset, where available. Access key for the data MIME type of the file served Size of the data in bytes Title or the dataset (usually, spectrum) Dataset identifier assigned by the creator Dataset identifier assigned by the publisher Processing/Creation date Date last published. Bandpass (i.e., rough spectral location) of this dataset; use something generic like 'Optical' here. Creator assigned version for this dataset (will be incremented when this particular item is changed). Common name of object observed. Object class (star, QSO,...; use Simbad object classification http://simbad.u-strasbg.fr/simbad/sim-display?data=otypes if at all possible) Redshift of target object Equatorial (ICRS) position of the target object. Signal-to-noise ratio estimated for this dataset ICRS location of aperture center Angular diameter of aperture Midpoint of exposure Exposure duration Midpoint of region covered in this dataset Width of the spectrum Lower value of spectral coordinate Upper value of spectral coordinate Number of points in the spectrum Local observation key (used to connect to single orders). Type of flux calibration Data model name and version System RA and Dec are given in Time conversion factor in Osuna-Salgado convention. Spectral conversion factor in Osuna-Salgado convention UCD of the spectral column Unit of the spectral column Flux/magnitude conversion factor in Osuna-Salgado convention UCD of the flux column Unit of the flux column Type of data (spectrum, time series, etc) Publisher of the datasets included here. Creator of the datasets included here. IOVA id of the originating data collection Instrument or code used to produce these datasets Method of generation for the data. Process used to produce the data URL or bibcode of a publication describing this data. Statistical error in flux Systematic error in flux Type of flux calibration Bin size in wavelength Statistical error in wavelength Systematic error in wavelength Type of wavelength calibration Resolution on the spectral axis Statistical error in position Type of calibration in spatial coordinates Spatial resolution of data
0.0 304.446675349555 38.0329313131891 304.446675349555 38.0329313131891 NaN NaN http://dc.zah.uni-heidelberg.de/getproduct/flashheros/data/ca90/f0011.mt?preview=True http://dc.zah.uni-heidelberg.de/getproduct/flashheros/data/ca90/f0011.mt image/fits 100800 Flash/Heros p Cyg 1990-10-11 09:28:07.500003 ivo://org.gavo.dc/~?flashheros/data/ca90/f0011.mt 1993-05-18T00:00:00 2017-02-22T13:19:40.966036 p Cyg star NaN NaN NaN NaN 304.446675349555 38.0329313131891 17.6 48175.39453125 NaN 5.40127e-07 2.78147e-07 4.01053e-07 6.792e-07 23181 ca90/f0011.mt NORMALIZED
The pubisher DID of the dataset of interest Recalibrate the spectrum. Right now, the only recalibration supported is max(flux)=1 ('RELATIVE'). Spectral cutout interval MIME type of the output format
pyvo-1.5.2/pyvo/dal/tests/data/datalink/datalink.desise000066400000000000000000000125111462331236700231070ustar00rootroot00000000000000{ "uri": "http://www.ivoa.net/rdf/datalink/core", "flavour": "RDF Property", "terms": { "this": { "label": "the data itself", "description": "the primary (as opposed to related) data of the identified resource", "wider": [], "narrower": [] }, "progenitor": { "label": "Progenitor", "description": "data resources that were used to create this dataset (e.g. input raw data)", "wider": [], "narrower": [] }, "derivation": { "label": "Derivation", "description": "data resources that are derived from this dataset (e.g. output data products)", "wider": [], "narrower": [] }, "auxiliary": { "label": "Auxiliary", "description": "auxiliary resources", "wider": [], "narrower": [ "weight", "error", "noise" ] }, "weight": { "label": "Weight map", "description": "resource with array(s) containing weighting values", "wider": [ "auxiliary" ], "narrower": [] }, "error": { "label": "Error map", "description": "resource with array(s) containing error values", "wider": [ "auxiliary" ], "narrower": [] }, "noise": { "label": "Noise map", "description": "resource with array(s) containing noise values", "wider": [ "auxiliary" ], "narrower": [] }, "calibration": { "label": "Applicable Calibration", "description": " Data products that can be used to remove instrumental signatures from #this. Note that the calibration steps such data products feed have not been applied to #this yet.", "wider": [], "narrower": [ "bias", "dark", "flat" ] }, "bias": { "label": "Bias Frame", "description": "Data products that can be used to remove detector offset levels from #this.", "wider": [ "calibration" ], "narrower": [] }, "dark": { "label": "Dark Frame", "description": "Data products that can be used to remove detector dark current from #this.", "wider": [ "calibration" ], "narrower": [] }, "flat": { "label": "Flat Field", "description": "Data products that can be used to remove the signature of non-homogeneous detector sensitivity from #this.", "wider": [ "calibration" ], "narrower": [] }, "preview": { "label": "Preview", "description": "low fidelity but easily viewed representation of the data ", "wider": [], "narrower": [ "preview-image", "preview-plot", "thumbnail" ] }, "preview-image": { "label": "Image preview", "description": "preview of the data as a 2-dimensional image", "wider": [ "preview" ], "narrower": [] }, "preview-plot": { "label": "Plot preview", "description": "preview of the data as a plot (e.g. spectrum or light-curve)", "wider": [ "preview" ], "narrower": [] }, "thumbnail": { "label": "Small Graphical Representation", "description": "A very small preview suitable for displaying many at one time.", "wider": [ "preview" ], "narrower": [] }, "proc": { "label": "Processing", "description": "server-side data processing result", "wider": [], "narrower": [ "cutout" ] }, "cutout": { "label": "Cutout", "description": "a subsection of the primary data", "wider": [ "proc" ], "narrower": [] }, "documentation": { "label": "Documentation", "description": "Structured or unstructured metadata helping to understand, interpret, or work with #this. Such information can range from processing logs to weather reports to technical documents on instruments to related publications.", "wider": [], "narrower": [ "detached-header" ] }, "detached-header": { "label": "Detached Header", "description": "Machine-readable metadata for #this, which in general will be necessary for its scientific use. Examples include FITS headers distributed without their data blocks or PDS label files.", "wider": [ "documentation" ], "narrower": [] }, "coderived": { "label": "Coderived Data", "description": "Data products sharing one or more progenitors with #this. This could be a lightcurve for an object catalog derived from repeated observations, the dataset processed using a different pipeline, or the like.", "wider": [], "narrower": [] }, "counterpart": { "label": "Counterpart", "description": "Data products sharing the target of the experiment or observation that led to #this but of unrelated provenance. This could be observations of the same object in different wavelengths or along different axes (time, spectrum), but spectra of dust of common origin but different laboratories would be #counterparts as well.", "wider": [], "narrower": [] }, "package": { "label": "Single Download Package", "description": "All file-like items related to #this and #this itself packaged together in a single downloadable archive.", "wider": [], "narrower": [] } } } pyvo-1.5.2/pyvo/dal/tests/data/datalink/datalink.xml000066400000000000000000000133651462331236700224430ustar00rootroot00000000000000 Data links for data sets. Publisher data set id; this is an identifier for the dataset in question and can be used to retrieve the data. URL to retrieve the data or access the service. Identifier for the type of service if accessURL refers to a service. If accessURL is empty, this column gives the reason why. More information on this link What kind of data is linked here? Standard identifiers here include science, calibration, preview, info, auxiliary MIME type for the data returned. Size of the resource at access_url
ivo://org.gavo.dc/~?flashheros/data/ca90/f0011.mt http://dc.zah.uni-heidelberg.de/flashheros/q/echdl/dlmeta?ID=ivo%3A%2F%2Forg.gavo.dc%2F%7E%3Fflashheros%2Fdata_raw%2Fca90%2Fn0011.mt Split Echelle Orders #progenitor application/x-votable+xml;content=datalink -1
ivo://org.gavo.dc/~?flashheros/data/ca90/f0011.mt ndndtdihpgea #proc -1
ivo://org.gavo.dc/~?flashheros/data/ca90/f0011.mt http://dc.zah.uni-heidelberg.de/getproduct/flashheros/data/ca90/f0011.mt The full dataset. #this image/fits 100800
ivo://org.gavo.dc/~?flashheros/data/ca90/f0011.mt http://dc.zah.uni-heidelberg.de/getproduct/flashheros/data/ca90/f0011.mt?preview=True A preview for the dataset. #preview image/png -1
The pubisher DID of the dataset of interest Recalibrate the spectrum. Right now, the only recalibration supported is max(flux)=1 ('RELATIVE'). Spectral cutout interval MIME type of the output format
pyvo-1.5.2/pyvo/dal/tests/data/datalink/proc.xml000066400000000000000000000176041462331236700216170ustar00rootroot00000000000000 Data links for data sets. Publisher data set id; this is an identifier for the dataset in question and can be used to retrieve the data. URL to retrieve the data or access the service. Identifier for the type of service if accessURL refers to a service. If accessURL is empty, this column gives the reason why. More information on this link What kind of data is linked here? Standard identifiers here include science, calibration, preview, info, auxiliary Media type for the data returned. Size of the resource at access_url
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz procsvc An interactive service on this dataset. #proc -1
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://dc.zah.uni-heidelberg.de/getproduct/bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz The full dataset. #this image/fits 68708160
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://dc.zah.uni-heidelberg.de/getproduct/bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz?preview=True A preview for the dataset. #preview-image image/jpeg -1
ivo://org.gavo.dc/s/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://dc.zah.uni-heidelberg.de/wider.dat An artificial thing. #preview image/jpeg -1
ivo://org.gavo.dc/s/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://example.edu/when-will-it-be-back Something requiring custom semantics. urn:example:rdf/dlext#oracle text/x-prediction -1
An interactive service on this dataset. The pubisher DID of the dataset of interest The latitude coordinate The longitude coordinate Factor to scale the image down before transporting (this does not currently take into account cutout parameters). Region to (approximately) cut out, as Circle, Region, or Polygon A polygon (as a flattened array of ra, dec pairs) that should be covered by the cutout. A circle (as a flattened array of ra, dec, radius) that should be covered by the cutout. Pixel coordinate along axis 1 Pixel coordinate along axis 2 Set to HEADER to retrieve just the primary header, leave empty for data.
pyvo-1.5.2/pyvo/dal/tests/data/datalink/proc_inf.xml000066400000000000000000000161561462331236700224540ustar00rootroot00000000000000 Data links for data sets. Publisher data set id; this is an identifier for the dataset in question and can be used to retrieve the data. URL to retrieve the data or access the service. Identifier for the type of service if accessURL refers to a service. If accessURL is empty, this column gives the reason why. More information on this link What kind of data is linked here? Standard identifiers here include science, calibration, preview, info, auxiliary Media type for the data returned. Size of the resource at access_url
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz procsvc An interactive service on this dataset. #proc -1
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://dc.zah.uni-heidelberg.de/getproduct/bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz The full dataset. #this image/fits 68708160
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://dc.zah.uni-heidelberg.de/getproduct/bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz?preview=True A preview for the dataset. #preview image/jpeg -1
An interactive service on this dataset. The pubisher DID of the dataset of interest The latitude coordinate The longitude coordinate Factor to scale the image down before transporting (this does not currently take into account cutout parameters). Region to (approximately) cut out, as Circle, Region, or Polygon A polygon (as a flattened array of ra, dec pairs) that should be covered by the cutout. A circle (as a flattened array of ra, dec, radius) that should be covered by the cutout. Pixel coordinate along axis 1 Pixel coordinate along axis 2 Set to HEADER to retrieve just the primary header, leave empty for data.
pyvo-1.5.2/pyvo/dal/tests/data/datalink/proc_units.xml000066400000000000000000000161601462331236700230350ustar00rootroot00000000000000 Data links for data sets. Publisher data set id; this is an identifier for the dataset in question and can be used to retrieve the data. URL to retrieve the data or access the service. Identifier for the type of service if accessURL refers to a service. If accessURL is empty, this column gives the reason why. More information on this link What kind of data is linked here? Standard identifiers here include science, calibration, preview, info, auxiliary Media type for the data returned. Size of the resource at access_url
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz procsvc An interactive service on this dataset. #proc -1
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://dc.zah.uni-heidelberg.de/getproduct/bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz The full dataset. #this image/fits 68708160
ivo://org.gavo.dc/~?bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz http://dc.zah.uni-heidelberg.de/getproduct/bgds/data/gds_big/v6a/2010/GDS_0644-0035/i_s/eq010000ms/20100927.comb_avg.0001.fits.fz?preview=True A preview for the dataset. #preview image/jpeg -1
An interactive service on this dataset. The pubisher DID of the dataset of interest The latitude coordinate The longitude coordinate Factor to scale the image down before transporting (this does not currently take into account cutout parameters). Region to (approximately) cut out, as Circle, Region, or Polygon A polygon (as a flattened array of ra, dec pairs) that should be covered by the cutout. A circle (as a flattened array of ra, dec, radius) that should be covered by the cutout. Pixel coordinate along axis 1 Pixel coordinate along axis 2 Set to HEADER to retrieve just the primary header, leave empty for data.
pyvo-1.5.2/pyvo/dal/tests/data/mimetype/000077500000000000000000000000001462331236700201645ustar00rootroot00000000000000pyvo-1.5.2/pyvo/dal/tests/data/mimetype/ivoa_logo.jpg000066400000000000000000000304551462331236700226530ustar00rootroot00000000000000JFIFHHExifMM*JR(iZHHC     C  " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?((((3Ɵ_wxHTյ{y_QoCHH<}xTW~SI {m?㯊hiڗ+O,i|[\Փ8e .-=~s,}n-+$S|=v= RԮ̛9Z="aI6'U|TϮ[ÿ|/~K+" 5/hd7gC4[vr3I=KkXfUyg,@KZ.y6n~FCW6ܘ>ҺxQEiF/;fVNA5_ rGvk}]v1^xGσAMQ崘1X_8 ;ɵǽp(WGź瀾#$[ N";!3,+o׶_GKO4z$6^".3,[ 2vg\1(;QAkk}~/N}yEs<]|MY0ȟJb]ѱ1QL((((+"|)-C_6By7)9 hjj6^sjwuU(ufv =IKZMτ4\LR/Ex8-j{cXȯ> _ռ@큮ۏ'lgHcxu3SݢDF*v>vǃ5mg㇍V=qtx^=w |_ g'1j2Oxz6ٌhE'_9Y@:hᾓeW`;VMy.τrE% YV&ZiڿIX|X?ڿT6ҝiZ4-l=P5_W%oqqx"cimH}"iO!>i+j&LMeG1\g%{nEiBÆb?(裖9%mH%yըLMՍT HoFkN',3G`Gj҄V[kJ&_1 Z1#Z%6ſ]j@I'mō/H?q~ɿiH[~@ŧ?ķ:U;lpx=$QnG~+ľ" &Kq{x^N W è<́ftgz)kA~^OT|1⧆V~,^Yuf}au8Iھ_Y|Axkv3m+VFٮ6t=ϊOخk4neԭ-vztJXAcPy@1 W%<=u2kVJD\F )̽d#u< W8gpPߑ  #-~'ςr|ҢK\'xQERK_~_4*tH/)&+H r!C^ !In)(lρc>#IB36Po/ I'_&zNJ(XJ|KH4n DFA:Uؘg_9q_2γ⿉:׌Yj(pfFO2y 訸U8ϰIJoJzyω_>&MO>(<[} צeAbDX@+WYsCzgKXKjv_&Z]Wx=Ӯ-=oiw%1yE$*VBH|H*t?cΥ(TzF5Iq h_4%U?_Os_^s(G8.v>? py ImxgIQN5*nw~U\jT}42eG|)?wGiS^Rߩ?PRMGv6}g, , GmkF9 ~ifi0iVzeJ+{X(TW3@ciJGgZ5<Ϩ8b?WVL0ERS5A-6eW'Ѝ(?4;ye/FU Z>jцFa3#ӵtVf_189Ƕ*s܃޹d*)oU>ufp@(=1#=!G WTfm]tԙͳ5Hj?~_/n#D8׻8+(>&_ xkw*mM4DHyWq`x&OxO( D&@ bfo*.VǡթO<ڌ*a&_|cs:F4c= exú~#u-:YmތF#чPG# *]&Sܓڽ[Xx].n%-yQIX6mz"N<)j֨>~g:Q[^DoNg969?儇|~~|;ொ x$4r)G"Sf!݇$7&3#ް'_OIz/-Dv$^ ~%% >aʸS?Eyϊ> 7C=Pj&'d),2)+$2%FSЃ^_}y>$xs>,$~iv$wb0Wįa⟊73x_2r@AizGaF:yc_W_i_c:亖v ybog_|9-{'^_cS^x , Y r @\X6}|z37OV :G5ÿ w~Kt)5ęydNeBpV4k{;)1$r 2H8 k#c.JRԼr1P;U7Si O#;Uwো<0Mwmn,2;WsW1T3ky>2,|L7WP}N Gc~d9ʈ$7#YHWK33Jay"iKi`>FEckmCRռej.uFM TPG O/+O4q6&h&wŮ`YR7݅*nGQU2|VӼkw_xcz,7Ҥ+X#ɻ3uk.G]&4G4/lbIHcS[ijױ9vw̐Ik-kL<5)>X:vUc \:rk?/<-cNSgl+ct>َ58ؚ 9MˋZPU<|k^3|%?K I$Ȍ~`XG|Y2ikwi6mysoFUo`ag3:dS4u[R3u24Iv'bI54T-,z~cJ4[_>ξ7?l]VIcsǗPhڵ k| .Hf=ǜ;o෋fT_ɑ8e<3)"~)wgÖ vE*H+8>!J+I~}O|fˡ3qCwV#7cJIT Z7?g4i'o j~Z+m*a6#z֌^j BKͤw^Lzʤ{c䱍ي9RI힝kaƦ+k+vW cPIU#FP/f#HFꧨniv>0&tMǺFnurubLA=o~T|ICTi2M̋O6Oz/w<= &s9<%̑Ċ;\ߍ~#|8qKh[-Mk5I1piš&{[@?eucB5+n[?tDxX[*; 6";/ҵWin X<1 Cj:> ~ դ$s^elNyս>7c< OJ5}7Xu]:ao Yz20 "o_?>#]Z2558򼪳M=$޾GRe񇇤}G:4=g7O0Sdlcg /CYY#chϮ/fr>.;ºp뿂lm[ox^)?yk%ֹ/~xTPIӼc,,}g}VVm[ i->E$dUvإIt?&݋k~7Lk1X<7n(4XH^}+dȪf;; H*uĮ>Z Æd뎿m(:mmPKKbι}*`z?zl~&r^ySHF~ǧ'?l] |p|]+\iQiR<ĩoKVq&FTRȀ1#̐_~?~hڍg3OrG٣+"$eLjx/u}_R_;6Q;H5F@AHOS^rSSvG/OL2A8ҼIMLE޹q+\^N!gq9xV]iڷ.|-o)V|:ϯ)GcR?#1CxÏo]6N)E|!oyH[s.QԂr+(k=:7 AKk_ƥ 4GcR᭬k⯎oWIKssXrd-pЄAi$=7[Ꮚς5xS_Gui\%A,3K^ƥ 4G:?ƸzLsϴiqKo(}TCJ04TW<7[(th,:LɊn^UF=:7).~M/-wCkIYJ8, 8&hA?ws EoŎFܬ(x2]?U^J*>r3IPH,m<7xR[Bjv,p?td\ wjil\|Hg$// hu_,my9{:.&Sq=^yᇄ~>&K-NHdin'$9gw<}0+(?>.^,u[thth<{ujC'ԩKo&e+~Z5+gLj[='ngifX >B{<ꮌ#@& +) `z{+׿d|5s~9G˿oI5$\*pWK/<_Z-/NYK巷Z'Gb?_-U'_'qJ^z<a>mu]sᖫ;]]Xipt+l<0/%4$uRΧsM$A'6riF ߭+WSs0 uix?\6Nmfpm"D獑I<2_j_5U,`L`wy~`_8oxƙ t-5Kv01I$ Gi$} :ωP|C-ҭU5{+id?vuy"i3_?e;eݎp^ 3%'RE^J,x 2DyIQnT>b224VxV[-Hyֶ>k;wBd*XpЃxşgkV7M*,[C2`\rvH ʕ5kE_(ҿVKhPnRt絑!e۫0lD_x1fm>g<*2y!G.:O NOot ZEMd6|,8/i2c܀>goJxc⇁uk8>'ep}F}זwC㯅W,Sm3!h`Ă=sQjKG|I̞sL|5t b1X#"G)qki¯w j+j@7;QA0os*gSsJ؟o΍? mP\=Zk 'xdޏMd_@~trI&\݌\|a?^ܑ/T~~?sںO;&#@ lJ08_-y_Z֨>igXQG9Uxb+?'FQ zlM]6uW?1jzܤ |MBqt Z,¥0_+R!͆;6ً\2O~k> f,/ xV y> c]0@Gۢ|/ҟ 4Dͨކ B+~~>x?i>%)mά\\1#=(EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEpyvo-1.5.2/pyvo/dal/tests/data/mimetype/test.fits000066400000000000000000000757001462331236700220430ustar00rootroot00000000000000SIMPLE = T / Standard FITS format BITPIX = 8 / NAXIS = 0 / no data in main file EXTEND = T / Extensions may exist FILENAME= 'swp06542llg' / original name of input file TELESCOP= 'IUE ' / International Ultraviolet Explorer ORIGIN = 'GODDARD ' / Tape writing location CAMERA = 3 / IUE camera number IMAGE = 6542 / IUE image sequence number APERTURE= '' / Aperture DISPERSN= 'LOW ' / IUE spectrograph dispersion DATE-OBS= 'nn/nn/nn' / Observation date (dd/mm/yy) DATE-PRO= 'nn/nn/nn' / Processing date (dd/mm/yy) DATE = '18-Feb-1993' / Date file was written (dd/mm/yy) RA = 0.000000 / Right Ascension in degrees DEC = 0.000000 / Declination in degrees EQUINOX = 1950.0 / Epoch for coordinates (years) THDA-RES= 0.000000 / THDA at time of read THDA-SPE= 0.000000 / THDA at end of exposure COMMENT * COMMENT * THE IUE VICAR HEADER COMMENT * COMMENT IUE-VICAR HEADER START 0001000100071204 1 2 013106542 1 C 1445* 4*IUESOC * * * 3600* * * * * * * * * * 2 C SWP6542, NGC 7027, 60 MIN, LG APER, LO DISP 3 C 4 C 5 C PROGRAM:NPBRB OBSERVER:BOHLIN DATE:1979.2609.260 17SEP 6 C 7 C 8 C 9 C 79260123556* 9 * 218 *OPSDEV14*112438 RDXSPREP 2 IMAGE 5614 * 10 C 091608 SCAN READLO SS 1 G3 58 *112511 SCAN READLO SS 1 G3 58 * 11 C 091623 X 56 Y 72 G1 99 HT 106 *112525 X 56 Y 72 G1 99 HT 106 * 12 C 093518 TLM,FES2ROM *114939 TLM,FES2ROM * 13 C 101100 FIN 3 T 3599 S 97 U 109 *120916 FESTRK TRACKING * 14 C 101150 MODE LWL *121950 FIN 3 T 3599 S 97 U 109 * 15 C 101239 TARGET FROM SWLA *122052 TARGET FROM SWLA * 16 C 101436 TARGET IN LWLA *122532 TARGET IN LWLA * 17 C 101542 EXPOBC 2 59 59 MAXG NOL *122642 EXPOBC 2 59 59 MAXG NOL * 18 C 101755 FESTRK TRACKING *122948 FESTRK TRACKING * 19 C 101919 TLM,SWPROM *123115 TLM,SWPROM * 20 C 101957 READPREP 3 IMAGE 6541 *123556 RDXSPREP 3 IMAGE 6542 * 21 C 102029 SCAN READLO SS 1 G3 44 *123631 SCAN READLO SS 1 G3 44 * 22 C 102045 X 60 Y 76 G1 82 HT 105 *123648 X 60 Y 76 G1 82 HT 105 * 23 C 104652 TLM,FES2ROM *123621 * 24 C 111319 MODE SWL *123646 * 25 C 111545 FIN 2 T 3599 S 98 U 109 *090650 ACQ STARTED * 26 C 111646 TARGET FROM LWLA *090952 TARGET IN SWLA * 27 C 111841 TARGET IN SWLA *091005 FES 761 IN 20 0 0 * 28 C 111953 EXPOBC 3 59 59 MAXG NOL *091059 EXPOBC 3 59 59 MAXG NOL * 29 C 112211 FESTRK TRACKING *091257 FESTRK TRACKING * 30 C 112328 TLM,LWRROM *091415 TLM,LWRROM .200000E 02 * 31 C 112412 MODE LWH *091534 READPREP 2 IMAGE 5613 * 32 C 33 C 34 C 35 C NPBRB*1*02*BOHLIN * 7* *N*00007027*0*0*1* 70 36 C 21 5 94+42 2 3*999* 0*0*99.0*99.00* 0.0 * 0* 120.00* 0* 37 C ' i cf@fh= %cc 38 C 39 C ?4 3 t ] D " 4 3c3D3 4 40 C 41 C 42 C 8 r 4 M , i ) K n c D H 43 C 44 C 45 C 46 C 47 C 48 C 49 C 50 C 0 0 0 0 0 020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 51 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 04040 51 C 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 52 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 020 0 04040 52 C 0 0 020 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 53 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 04040 53 C 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 080 0 0 0 0 0 0 0 0 0 54 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0 0 0 0 04040 54 C 2 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 55 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 04040 55 C 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 020 0 0 0 0 0 0 0 0 0 0 0 56 0 0 0 0 020 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0 0 04040 56 C 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 020 0 0 0 0 0a0 0 0 0 8 0 0 0 57 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 04040 57 C 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 58 0 0 020 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 04040 58 C 20 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 080 0 2 0 0 0 0 080 0 59 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 080 0 0 0 0 0 0 0 0 0 0 0 04040 59 C 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 020 0 0 0 0 0 0 0 0 0 60 0 0 0 0 0 0 0 080 0 820 0 030 0 0 0 0 0 0 0 0 8 0 0 0 08030 04040 60 C 0 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 0 8 0 0 0 0 61 0 0 0 0 0 020 0 0 0 0 0 0 020 0 0 0 0 0 0 0 0 020 080 0 0 0 04040 61 C 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 2 0 0 8 030 0 0 0 0 62 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0 0 0 0 0 0 0 8 0 0 04040 62 C 222 0 0 2 0 0 0 2 280 0 0 0 0 0 0 8 0 0 0 0 2 0 0 0 0 0 0 0 0 020 63 0 0 0 0 028 0 0 0 0 0 0 0 0 0 0 0 020 0 0 080 020 0 0 0 0 0204040 63 C 0 0 0 0 0 028 8 0 0 0 0 0 0 0 0 0 0 0 0 0 8 2 020a0 0 0 0 0 0 2 0 64 2 0 0 0 0 0 0 0 0 8 0a8 0 0 0 0 0 0 020 0 0 02020 0 0 0 0 0 04040 64 C 0 0 0 c 0 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 028 0 8 0 0 2 0 0282020 0 65 0 2 0 8 0 020 0 0 0 0 0 0 0 0 0 2 0c0 03020 020 080 0 0 0 2 04040 65 C 80 0 2 0 0 0 0 0 08020 0 a 0 0 0 2 0 0 080 0 020 8 0 0 0 0 0 0 0 0 66 0 080 0 0 0 080 0 0 0 c 0 0 080 0 0 8 0 0 8 0 8 0 0 8 0 020 04040 66 C 80 0 8 0 018 0 020 0 0 0 0 0 0 0 0 080 0 8 0 020 0 8 0 0 0a0 2 0 8 67 0 0 0 080 0 0 0 8 0 2 0 030 0 0 0 0 0 0 0 020 020 8 8 0 8 0 04040 67 C 0 0 0 8 02080 8 0 0 0 0 0 0 0 0bffff0 0 0 0 0 2 0 2 0 0 0 0 0 0 2 68 0 0 0 0 3 0 0 0 082 2 c 0 0 02080 0 0 0 0 0 0 0 2 0 8a2 0 0 04040 68 C 0 c80 8 0 0 080 2 0 0 022 8 880 0 0 0 0 0 0 0 0 8 0 0 2 0 0 0 0 0 69 0 0 0 280 0 8 0 0 0c8 08020 0 0 0 0 0 0 0 0 088 0 0 0 0 8 0 04040 69 C 0 080 2 0 0 0 0 08280 8 0 2 8 0 0 2 2 3 020 020 0 0 0 0a0 2 a 0 0 70 a 0 0 0 2 8 08080 020a0 0 2202ffff0 0 0 0 0 0 0 0 0 0 0 080 04040 70 C 08222 c 0 2 0 0 0 0 282 0 080 020 0 0 0 a 280 0 0 2 0 080 0 820a2 71 22 020 020 0 8 020 8 020 0 0 2 0 8 0 080 2 280 08022 2 0 0 0 24040 71 C 2220 8 0 8 0 0 0 0 0 2 02080 8 2 0 0 0 2 0 020 082 0 0 0 0 02020 0 72 02088 0 0 082 82020 0 280 8 8 8 8 820 0 a8020 020 8 0 2 2 0 24040 72 C 0 2 0 8 0a2 0 088 0 0 0 2 2808020 0 220 0 2 0 0 0 8 0 a 0b0 0 0 b 73 280 0 0 0 0 0 0 0 2 0 020 080 0 0a080 8 0a0 0 8 0 2 0 fc0 0824040 73 C 0 880 02088 0 2 0 0 0 2 0 0aa82 0 280 0 0 880 08022 0 0 a 288 0 8 74 88 c 08a28 0 0 a 080 0 820 0 2808220 8a0 0 0 0208220 0 0 0 0 34040 74 C 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 75 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 04040 75 C c1af6fbcc 0 0 09d707b 0c6 e 03dc8 c32474e366a29253f47413320314d64 76 29 03d8af6 51a1c9090 05e7d8233122e2a19282e1d32 f18117da5abab 0 0 0 76 C ac30 0ff 0 439abbba0 0445aa4 4371f2a31342b1d1d2c2f151914161d502d37 77 70727e207b197a227b14259a1e92209420951e96 1 0 1b8e1 0 0e02ce0fa3540 77 C 7b 07b 072 07b7b7b7b7b 039 0 07f4c4541ae547c979951ceae9580808645 0 78 03a3234363a302f2f3335332b35bdad 6c1f6568282 0 1 08282404040404040 78 C 0 0 6c2f2568381 0 1 080803b3535343b35313036373633338977877945776b 79 796a7b2f7f7e7f7a7a34777d7f7e8e487f40404040404040404040404040404040 79 C 993837611019 e89 0cc 040404040404040404040404040404040404040404040 80 404040404040404040404040404040404040404040404040404040404040404040 80 C dfbffbe 0 0 0 07f374399 2 2fe66fb30 0 0 0 0 0 0d0 0 0 0 0 0 0 080 81 0 0 0 0 0 0 0 0 0 0 088 0 0 0 c 0 0 0 c 0 0 0 0 0 0 0ff 0 0 04040 81 C 4f1960 0 0 0 0 0ff 0 0 0ff 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 82 0 0 0ff 0 0 0ff 0 0 0 0 0 0 0ff 0 0 0ff 0 0 0 0 0 0 0 0 0 0 04040 82 C 83 C 84 C 85 C b2b 0 0 017 0 0 0 689a1e02ce0e1 0 021 0 016809e32 0 0dcc1f5a15e3b 86 b1dad9565668c969c92fc9 0 061415142 0 0404040 2 3 c d e404040404040 86 C c12 0 0 0 5 0 0 0 38a9ae02ce0e1 0 026 0 016809e32baab 6c1f2568381 87 0 1 07f807e7f7a7a3477 0 061414140 0 0404040 3 635 b c404040404040 87 C c1a 0 0 0 5 0 0 0 38999e02ce0e1 0 02b 0 016809e32bdad 6c1f6568282 88 0 1 082826b776a7b2f7e 0 061404141 0 0404040 2 432 b c404040404040 88 C c25 0 0 0 0 0 0 0 0899de02ce0e0 0 034 0 016809e32 0 0d8c5f2a0ab42 89 acdac579927eca7aca34c9 0 0714a4141 0 0404040 3 7 6 d e404040404040 89 C c30 0 0 0 a 0 0 01e89a1e02cdfe1 0 01b 0 012809e32 0 0d8c1f29f493f 90 addad581637cc97aca34c9 0 071424141 0 0404040 3 312 d e404040404040 90 C a f 0 0 016 0 0 01e8a96e02ce0e1 0 035 0 012809e4abdad 6c1f5568282 91 0 1 082826c796b7b307f 0 061404141 0 0404040 2 42f b c404040404040 91 C a14 0 0 0 0 0 0 019899ee02ce0e1 0 042 0 016809e4a 0 0d8c4f2a0ab3e 92 acdac679927fca7bc935c9 0 0714a4141 0 0404040 3 73b d e404040404040 92 C a1a 0 0 014 0 0 0238a9ce02cdfe1 0 042 0 016909e4abaab 6c1f2538381 93 0 1 080807ec97aca35c9 0 061514141 0 0404040 3 1 8 d e404040404040 93 C a20 0 0 0 a 0 0 0 5899be02cdfe1 0 034 0 016909e4abc6d 6c1f2528381 94 0 1 080807bc97bca35c9 0 061514141 0 0404040 3 122 d e404040404040 94 C a21 0ffffe2 0 0 02389a1e02ce0e0 0 042 0 012809e4a 0 0d8c1f2a0ce39 95 acdad481637bca7bca35c9 0 071424141 0 0404040 3 321 d e404040404040 95 C b e 0 0 028 0fffffb8a9ae02ce0e1 0 034 0 016809e52bdad 6c1f6568282 96 0 1 082826c796a7b307f 0 061404141 0 0404040 2 631 b c404040404040 96 C b14 0 0 01e 0 0 0 f8a98e02ce0e1 0 02d 0 010809e52baab 6c1f2568481 97 0 1 080807e7f7b7a3479 0 061414140 0 0404040 3 4 0 b c404040404040 97 C b19 0 0 02b 0 0 01089a0e02ce0e1 0 024 0 012809e32 0 0dcc4f6a1473f 98 b0daca768e6cc96ac930ca 0 06141514a 0 0404040 2 729 d e404040404040 98 C b24 0 0 0 d 0 0 019899ee02cdfe0 0 032 0 012809e32 0 0dcc1f5a15b3e 99 b1dad9565669c96aca30ca 0 061415142 0 0404040 2 327 d e404040404040 99 C b2a 0ffffef 0 0 023899be02cdfe1 0 02e 0 012909e32bf6f 6c1f6538282100 0 1 0828268c969ca2fca 0 061414151 0 0404040 2 1 7 d e404040404040100 CCOMMENT IUE-VICAR HEADER ENDED HISTORY IUE-LOG STARTED HISTORY *GEOMF 11:20Z SEP 21,'79 HCHISTORY ********* GEOM. & PHOTOM. CORRECTED IMAGE ********** CHISTORY PCF C/** DATA REC. 11 1 1 1 768 8448 5 3 6.1 5.0 2536 .00000 1PCHISTORY 0 1684 3374 6873 9091 10586 1PCHISTORY 14371 17745 21524 25105 28500 1PCHISTORY 11.000 11.000 11.000 11.000 11.000 11.000 1PCHISTORY 11.000 11.000 11.000 11.000 11.000 1PCHISTORY TUBE 3 SEC EHT 6.1 ITT EHT 5.0 WAVELENGTH 2536 DIFFUSER 0 1PCHISTORY C MODE : FACTOR .178E 00 1PCHISTORY *FICOR5 11:20Z SEP 21,'79 HCHISTORY ******** DATA FROM LARGE APERTURE ******** CHISTORY *EXTLOW 11:20Z SEP 21,'79 HCHISTORY @EXTLOW: OMEGA= 90.0, HBACK= 5, DISTANCE= 11.0 CHISTORY :HT=15, DC#= 1; ISN: 0 PSN 1 SIGS= .444 SIGL= .421CHISTORY B 1= -.283235346667D 03 B 2= .376096600120D 00 B 3= .000000000000D 00CHISTORY A 1= .964207510446D 03 A 2= -.466539532721D 00 A 3= .000000000000D 00CHISTORY LINE SHIFT = .000 SAMPLE SHIFT = .000 CHISTORY *SMOOTH 11:20Z SEP 21,'79 HCHISTORY *ARCHIVE 11:20Z SEP 21,'79 HCHISTORY *ITOE 11:20Z SEP 21,'79 HCHISTORY ***** FILE OF MERGED EXTRACTED SPECTRA ***** CHISTORY *** GROSS, BACKGROUND, NET & ABSOL. CALIB. NET *** CHISTORY *ETOEM 11:20Z SEP 21,'79 HCHISTORY *ARCHIVE 11:20Z SEP 21,'79 HLHISTORY IUE-LOG FINISHED END XTENSION= 'BINTABLE' / Extension type BITPIX = 8 / binary data NAXIS = 2 / Number of Axes NAXIS1 = 7532 / width of table in bytes NAXIS2 = 1 / Number of entries in table PCOUNT = 0 / Number of parameters/group GCOUNT = 1 / Number of groups TFIELDS = 9 / Number of fields in each row EXTNAME = 'IUE MELO' / name of table (?) COMMENT IUE MELO file data containing G, B, N, A, & E vectors COMMENT Each row contains order number, npts, W0, deltaW, & vectors above TFORM1 = '1I ' / Count and data type of field 1 TTYPE1 = 'ORDER ' / spectral order (low dispersion = 1) TUNIT1 = ' ' / unitless TFORM2 = '1I ' / field 2 has one 2-byte integer TTYPE2 = 'NPTS ' / number of non-zero points in each vector TUNIT2 = ' ' / unitless TFORM3 = '1E ' / Count and data type of field 3 TTYPE3 = 'LAMBDA ' / 3rd field is starting wavelength TUNIT3 = 'ANGSTROM' / unit is angstrom TFORM4 = '1E ' / Count and Type of 4th field TTYPE4 = 'DELTAW ' / 4th field is wavelength increment TUNIT4 = 'ANGSTROM' / unit is angstrom TFORM5 = '376E ' / Count and Type of 5th field TTYPE5 = 'GROSS ' / 5th field is gross flux array TUNIT5 = 'FN ' / unit is IUE FN TFORM6 = '376E ' / Count and Type of 6th field TTYPE6 = 'BACK ' / 6th field is background flux array TUNIT6 = 'FN ' / unit is IUE FN TFORM7 = '376E ' / Count and Type of 7th field TTYPE7 = 'NET ' / 7th field is net flux array TUNIT7 = 'ERGS ' / unit is IUE FN TFORM8 = '376E ' / Count and Type of 8th field TTYPE8 = 'ABNET ' / absolutely calibrated net flux array TUNIT8 = 'ERGS ' / unit is ergs/cm2/sec/angstrom TFORM9 = '376E ' / Count and Type of 9th field TTYPE9 = 'EPSILONS' / 9th field is epsilons TUNIT9 = ' ' / unitless END xDz33@)FFDFϜFXFlZFyFFleFtFF F|FFDFF«F?FFFFFBnFArFFBpFjFFIFF\FhFF}F`F|FFF^FFjF}FDFFyFvAFnF F1FFFFFpFcFFFFaF)\F]FmF(F_rF}fFFFɽFXFF]F$FFFFEF^G"GGHZ\HiH'HH-H W\GBWFEFˇFFFF|FFF5TFWF@FYFFF>F})FF"F/FciFC$FFEF"F#FSF;FwFFFeFFFFOFGFFvF?FFS FAFF"FFaFDF%tFHFFX5FF;FsFXFFDWF]F{F_tFPFF&FFPF%FF F'sF4FNKFFF)FZFFF@FhFBFF4FF-FFF@FzFFFFלFF9F&pFҜFFwF2F_F6%FFIF,F͇F{8FQFFFFFWFb(FFF_FfFBFFAG'GH.#HEJHZGG FhFHFKF=F^FvFFFF,|FFFژFF8uFF[DFFcFFמF@F F>/FKF%bF|FF2G-XGGGKFBFgFFTFFںFכNF?F8FJFFFr4FFoFF%FF`F+'FF`'FxFrF;HF_vFFFfF/FiFFFoFFF/FF+jFmFF>F22F)FYFZFF;F"FxaFEF F_FIFbFeF6FaFWFFFFͳFx|FFVFiFtBFF/FF@FFEzFFF߼FFF#F3FXFH9FnFaFFFFgFFFF3FFEFFYFF\FF#FFTFFFFFFF˝F-FF]FyFFu&F(F+FFFSFk FfFZF_FhFqyFFF!F`FFEFTF݆FRF;F FPFЕF6FF|KF)F>NFFFuFFF$FFYFFFFfFFFqFHFUFjhFFFmFwFF9BFF%FEFMFFFF0FbFnF,-FѵFFuF`FFFF#FUFuJFZFHFyFFoF FFF#FF}FFFF&FjFF{FkFrFSFlFF}FF8"Fq@FFcSFUFF0LF߼sFk;FFFuF F^FFFF\/FFF5FmFWeFPFrFzFg_F*F~FFΒF|\FFFXFWF:FIFFoKFmFZFc{FQPFx@FFFF@FiFFF}FsFFFFFSFXFFvYF(=FgFF=F#F FVCFkF4FFUFrFFG$FaFedFFFwFaGFnFdF9F|>F7FFyFJF=FFFF3FF 0F:FFFFTFFYFuF$~F:FFXFF#F?FFRFFeFF FFFFFJF$FTFE"F]FlFFAFFF,FwFFECńz~DzBDg_&EC9DD5E]Aɴ,:<D#ىC)̉BWC0C0DE,yD> ŎŏJDκES'ð<ENEn)E>Dy%5BE RD;ď&E45E"!ľDl:`Ľ*D5Î؍ԩzQmgpj1CE 2ECkDnÕX5DȯC<6Bt?COpuJoYjDDńC{DVE NPkxD E^E)FGHDH7H@HtHxɡGFרEn7DיšܤDҦ)Ey%E NE?DES2DByDҵCCU DS3ĒĬPC30E&CGD 8CD$C$xCC'DD|CYkjdECCY.ą"D DDE R£Ƞ?ĈOC ŎŏJDκES'ð<ENEn)E>Dy%5BE RD;ď&E45E"!ľDl:`Ľ*D5Î؍ԩzQmgpj1CE 2ECkDnÕX5DȯC<6Bt?COpuJoYjDDńC{DVE NPkxD E^E)FGHDH7H@HtHxɡGFרEn7DיšܤDҦ)Ey%E NE?DES2DByDҵCCU DS3ĒĬPC30E&CGD 8CD$C$xCC'DD|CYkjdECCY.ą"D DDE R£Ƞ?ĈOC This is a “light” version of the full Gaia DR2 gaia_source table, containing the original astrometric and photmetric columns with just enough additional information to let careful researchers notice when data is becomes uncertain and the full error model should be consulted. The full DR2 is available from numerous places in the VO (in particular from the TAP services ivo://uni-heidelberg.de/gaia/tap and ivo://esavo/gaia/tap.vo-dmlhttp://www.ivoa.net/dm/VO-DML.vo-dml.xmlstc2http://www.ivoa.net/dm/STC.vo-dml.xmlndcubeurn:dachsjunk:not-model:ndcubedshttp://www.ivoa.net/dm/DatasetMetadata-1.0.vo-dml.xmlivoahttp://www.ivoa.net/dm/ivoa.vo-dml.xmlivo://org.gavo.dcGAVO Data Center1TIMESERIES0BARYCENTERTCBJ2015.5ICRSObservation time (JD in barycentric TCB).Integrated flux in G; Use -2.5 log10(flux)+zero point to convert to magnitudes, where zero point is 25.6884 for DR2 fluxes in G in the Vega system.Magnitude in G, Vega system. Converted from flux using the formula given there. If flux_error/flux<0.1, you can use 1.09*flux_error/flux as a good estimate for the error; else the distribution is so skewed that you should work with fluxes rather than magnitude.Error in G flux.Gaia DR2 source_id of the objectPublisher-assigned title of the data setGaia DR2 RA of source objectGaia DR2 Dec of source object
2456920.5128546842921290000.02.0244633198665625106712000.0
2456920.5868590062659600000.02.12635918930161776519500.0
2456934.71934151562149850000.02.357379601891061323700500.0
2456934.89549362941934760000.02.471832249920307621371500.0
2456961.89449747331734390000.02.590533097882719547525800.0
2457003.4517497892104220000.02.38067213968078322162500.0
2457003.62791153141916840000.02.48193534119698727515800.0
2457031.44824773961690810000.02.61816298069168623871700.0
2457159.5289318261786750000.02.558240523066988533130900.0
2457176.6052401572000550000.02.43552647443061717323600.0
2457176.78139499341995940000.02.438031295473038531137300.0
2457176.7813949941865150000.02.511615588522033735917300.0
2457217.48488136571567590000.02.700318789111836519312300.0
2457217.5588855411545590000.02.715664252034944317547200.0
2457243.22822660380051200.05.9299803836130423535790.0
2457243.2282266511594470000.02.681859119019044618471700.0
2457276.9587075891714040000.02.603347618259917324701500.0
2457277.0327569814386006000.04.2219148617232065148650000.0
2457314.51514880643684.4916.772456544623715137729.0
2457314.69113340551330620000.02.878264882849528332975800.0
2457340.69249006731747150000.02.582574518630597324057400.0
2457340.7665252461694830000.02.6155846430100111095400.0
2457381.17602146931449470000.02.78537692226139927228900.0
2457438.90777355531788970000.02.55689235560183215526400.0
2457494.62331029451770970000.02.56787198909823326964400.0
pyvo-1.5.2/pyvo/dal/tests/data/query/000077500000000000000000000000001462331236700175005ustar00rootroot00000000000000pyvo-1.5.2/pyvo/dal/tests/data/query/basic.xml000066400000000000000000000016051462331236700213050ustar00rootroot00000000000000 OK
23 Illuminatus
42 Don't panic, and always carry a towel
1337 Elite
pyvo-1.5.2/pyvo/dal/tests/data/query/dataset.xml000066400000000000000000000021701462331236700216470ustar00rootroot00000000000000 OK
image/fits http://example.com/querydata/image.fits
application/x-votable+xml http://example.com/querydata/votable.xml
application/x-votable+xml;content=datalink http://example.com/querydata/votable-datalink.xml
pyvo-1.5.2/pyvo/dal/tests/data/query/errorstatus.xml000066400000000000000000000016131462331236700226200ustar00rootroot00000000000000 ERROR
23 Illuminatus
42 Don't panic, and always carry a towel
1337 Elite
pyvo-1.5.2/pyvo/dal/tests/data/query/firstresource.xml000066400000000000000000000016051462331236700231230ustar00rootroot00000000000000 OK
23 Illuminatus
42 Don't panic, and always carry a towel
1337 Elite
pyvo-1.5.2/pyvo/dal/tests/data/query/missingcolumns.xml000066400000000000000000000011421462331236700232720ustar00rootroot00000000000000 OK
pyvo-1.5.2/pyvo/dal/tests/data/query/missingresource.xml000066400000000000000000000005431462331236700234450ustar00rootroot00000000000000 pyvo-1.5.2/pyvo/dal/tests/data/query/missingtable.xml000066400000000000000000000007301462331236700227030ustar00rootroot00000000000000 OK pyvo-1.5.2/pyvo/dal/tests/data/query/overflowstatus.xml000066400000000000000000000016761462331236700233430ustar00rootroot00000000000000
23 Illuminatus
42 Don't panic, and always carry a towel
1337 Elite
pyvo-1.5.2/pyvo/dal/tests/data/query/rootinfo.xml000066400000000000000000000016041462331236700220620ustar00rootroot00000000000000 OK
23 Illuminatus
42 Don't panic, and always carry a towel
1337 Elite
pyvo-1.5.2/pyvo/dal/tests/data/query/tableinfo.xml000066400000000000000000000016061462331236700221700ustar00rootroot00000000000000 OK
23 Illuminatus
42 Don't panic, and always carry a towel
1337 Elite
pyvo-1.5.2/pyvo/dal/tests/data/querydata/000077500000000000000000000000001462331236700203325ustar00rootroot00000000000000pyvo-1.5.2/pyvo/dal/tests/data/querydata/image.fits000066400000000000000000020205001462331236700223020ustar00rootroot00000000000000SIMPLE = T / conforms to FITS standard BITPIX = 8 / array data type NAXIS = 0 / number of array dimensions EXTEND = T END XTENSION= 'IMAGE ' / Image extension BITPIX = -64 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 256 NAXIS2 = 256 PCOUNT = 0 / number of parameters GCOUNT = 1 / number of groups END ?=;c?LRW? oʱ?a ?n}?LGx??B64T?㲙E`?q}S?\0 ?Yv+ ?i`?Ʉ\?٬X?6GE?ڰc?w/@M?w}d?f?^rR ?P?+4?dsHM?ȵCrw?L?%!?= ?HtR?R3{?@қ ? ?KR?۸5r?u|-?"{ʌ?ɶ]>?}?F4C?ǎb ?7X?̆@?B?SH?ͷ]?K ?{?=R'?h?j?鲡K ?4o4?~0M?cQ,?Ta |?X@?(?F?gz?ߚWO?g?/b?tU!?ŨR0?̿(?ub 9?Z?@?ܺ\F?NX͓D?ؗQ?HL?r o?d/a?׉ ?ev?NV0?jW+?`-rt?48?D?D?aW5 z?A?s%;d?Nk ԛ?E&sM?(59?R?ɸ52?h'>?ҲP ?d>]u?ڐA J^j?1?.x?%?hf)?ؘS-t^?z"?r?/.?O??q_Q?%je"B?_T?>dٺt?+v?rd^?ZF"?fBu?#bX?L#?Ϲ?/$?ӱ ^?.T"?5*?y+U?f#?}q^X?˃$mtc?2{?gf?Bd? `yZ?.v?ۮώAf?ߴ?퐖()$?фY?>:@? i?E@ ?)aު?6) ? V?Q+=G?U\2`?ئy헞??±D?BdB%?zbe"?{IU?*`?z/?z??n?2DV.?Sl?T}t?]lL?tm ݤ?>T?v ?-3$Y?^r?`vW ?Ѹ?͚??V?﮳?׺?螸46?ޜ?l J?p#t"?6U? bU?[`4s~?ל?'?dj&?+ ?Ӑc?W U?=?V=?S?/?ө?h?$8v?5T0?DV?3>?HĦ+?w]7?ʊ ?ӡҢ?̻VfW?QCZ?܉tߊ.?c? bsc?,m(,?WW/?_??<k?HYB?u?/ 9'?yH ?͇5L?ᙖS?LLju?z$b[?}jm?O~?r?-<-?9?[?uˈ?(&?50F33?c]?+)? q?)?<>?txN?0M?g& ?T?7@??I@?m?-^\ʶ?ضː L.?$ ??f&0`>?PG?s/?jk?Kf|? n ?dx?+W?W@?ܾs?㆝L.?~3?wp$?E^M)?eF@?t0?Pp? |?̈́!D?EKt?pb~μ?L R?mBe6?⒖@?i?l?A6.?j?K_-`?,Cp?dQ?V9׳?j/>? QD?(4`?ԫN?Jul?҃$L!?Љ-?]@?︳b?NC?Lj?+?ꖢ$?P'?6?ԥQ?GG?م?L~gVp?AAb?Z@?Y3?+"?@?@P?0?Dr?]S|?[a5?zk@?,O~z?QZz?1S?ֹٺ?.?54?F UU?ݸzb?J!j?ǧ9?櫲~? -?p?RP٘?ޔ.8?&_?jmp?>6?HC ?B ?G^29?bܟ?g ze?v *ڤ?;6i?c ƙw?K?8I?a?s?z ?Ŷʤ?قWG?В2 ?|H*?n6?٨El?H`?=`j$?#d@?vr?wa?ц?wa,?3]?He&?Fz?w͎?ĝAW|?m'Q?թ#T?3y>x?sC?q`Y?/??ͭ lSt?GG?p?tʦ??}Dg?8z,?5wn?0m?yJ8?ۭFx?3TF?Wpm;?5OW0?_x?x)g?i?`?h",? ̨R?+]?5-l-t?x?WW7 ɶ?Tp ?mcX?7"2h?SzI0?/c?وLkY?G~?k?f-(?5ٕ?ղĸ?VP?d<(?N]?gЕ?qk?[j? L?d;?'!M? ?^W#D?l?r?ٖ3?SJ*X?~n%??@?\d=0?0?'z9"y[j?W[?W48f?^ O?]?_ ?YU? s?h!?&z8L?SE;?u%fw?P]al?" 5?k`n?eUlX?R^j'? U$?8jv?Fr?X?=|?9>?eGQ5w9?)@?b"?VZ3vg?W0?cvX?DP?źD ?\?DBB?؅47?։Y ?1 kA?4?*G9n?$tXI?8?}ȇ?湫}?[zH+?ꇋI?FS?P{?f}%?̪t?Dx)w?|>?H?t7?U?Շc? " ?k]?Uvz?'v?p?L\ix?FkS??壾?g?CPE?)h*7f?ov45?ӭ8]Ռ?,K?4b\?I?HzV??ɳq?d@?1?)?CVgP?ۣVΖ?;>BK ?&~4?ז??W܁A ?0gR? cn?Ŝ?gx?3h2?E,?H?nd 2?x`z?!P??| $F?kZ?Վ?ہ-?6 3`?钸o[^?<.7? T?o0? d?1?s6?GB?o7Lr?f%Q/?)%K ?I.?'iU?=Ee?ܷ"?ߪ?tsh~?n:?]?l_{?Y>?Ѷq?,d?ظt??G&8"d?e.`p?&Ebո?os?Õ/)'?DE?: ?҉c?:eR? P[K??ǿfD?lW?rW{?'?X?.I°?ctUz?S?豈?% ?ɋi?z<}?գW?aa/?8>G$?Bt?O7?F?؎J8?Ō"*?˷tv?j+pS?f?d4?qM ?UU?Ɠe??+Ei?3?-vF?dN?.A~??sL?ߞ((?PQRv?ѪYMP?|OX|?~C?8?,Ӳ?tDbu?3;?'E?/`?Á0.d?n@Z?^?,]y?nr/X?šEg?c ?zF??D?֣g?/9%d?QJ$?|fH?y.规?[E?Ҩ$? @?:"b? C;>? hP?†=? @?kT?> ?IB2?PZy?2G?8j?u蜨?-Ў?2CP@d?h@?q?EBXP?R-?0Ѥ?cCMa?65?p ?eW?"? 31?WjU?M ?!? 00??䁢L׀?xi}??iN?`?mﷸ?ѱ pId?!4L?tR?MHnZȨ?$gH?Đ4 m?X1 ?꾰0?i~?J@_q?,I|n?vp?,.M?R>Yt?fB?a?Vw? ?өr%ڀ?8&%?T☿?Bϝ?@|H?l$8D?Џ ?Vb{?e*i?A2K$?䄖ю3?΂-U?3ûh?>M?4_z'?d#?||?Mk?`Fͽ?[z)?A)?ㄱ(ѽ?Ey?r?K X?؇'U,?Ĥ?>h?#>?;S:?,b'?5i%Z?o? h?`??H?Jfu?H?֩3XL?߭]% ?h_p? S!?q C?vfN?2=?Ata?\!?_?b?9?u ??tUў?oyv?$:?s xԮ?;l0PE?y?Ԋ ?=py Y?Ȑ?A _l?C?h^?#DY?|70?\2?x"e?|9p?UC,?wʼ?[3`?x ?_h H?Oh?~pOC?žx?\N4?w=(+6?㜘' ?& ?ɂh?aՙ?ۂCax?Uz?T Z? ʫC?=o0?͉ν?zOX?n7?tem?ĉ@u4?)m(?NC ?a{?Hsȅl?AǀL?ր??gUeQ?/j?4t?ƣm?^&?H1:k?Dy?t?GsP;?㔿@l?P ?ܳV?!y7?:98?0r@?*[??#kFT,?x?\z B?iR? W.?O(?м$a|?h'x?*3\? l?w0?8P?aM?B ?ݨlٿ?,#J?oLqh?W?"t>?Ψpp?,~n?4%XG?>F?I?[lk?X?ƎK+t?ֵ}'?E$d?;L?vRfǼ?f/&?êO?ޜ6U? YY?w%N?'h?!?Ii^?)۫>?ڳ0~?C?p{~?%O5/? ء#(?Z1?z?w .C4? wْO?Mx ?b?[)P?1H6)?Ү ?Nle?ƱsB?tnP?zV. J?u_l?@x8G?C5e?.b`p?‚/r?.Y9?܋^gn?=qO0?}kv? eYB?O:u?7Ȍ?MdP?\ٗ j?۷z(e~?eHG?,V-t?/n{ ?́#?Hӟ8?L?G.n?9j;?"<?Ç׀?Fsf?z?؊ r? -A?{z?V?Q@?.I!o?1{1?C,?h??Erp?\S?#ѯ?i,x?[1[n??B/r?G?a@?%ę?/m?鑗?Jh?⾼f?" %?y-m6V?.yi?P-F`?̡}?p#a??-HR[4?p?mx?aՆ? ]Wz??]wY\?KA"?;?ZZ?A$??#f]?kMZ(?uFl?&׏D? &4?1O?zQ?B⺮?ݔ*?mf?!?*щIN??" "?y8t?稡dS?~K?إGK:?쩛Bt?<8?L?"78 ? 9?#cH?_?=>?4H?坤;?.?TF ?<0m[?h?Fdr|?W)N?$> C?ҘG? ?d b?^,*?:>z?/t!0?A6>a?ƞ.GW?۟V}=?9?g3k?xД? ?w"`?~fP?4?֗Ղ?,^~?U:{B?,[H/?`1?ږ&>?jl?l}?nj?~X?yK?>N?,M@,?Q?5?~>:?w?r? J?mpl?LL>?S^?b?}y0?+?wj2k~\?΅g?1 ΍?8- ?Ғ O?ƑEƶ?>Q?}I?M? H?x m?A ?9i'qV??:wR-?*iJى?J=d?IEP?iA]?94?61v?#.?ƾI??OrY?M-A?Gm|a?,? nS?&6?6wn ?ԤN?V)?TlP?X8r?0\?=^ ? $ƍ?O2?M?GB:{?*K?ӫA?^?ĵl?USP?ѱKf?3?:M??|?o ?_ ??iZ0?wm ?Cz,?К??E?8?AKve_?p@$?fS?'_ X0?s+dv^?Ѻ3?'ߘ?-?3lư?Л>@?+|r?eh ?M`1?s@'g?i?;H?mu 8?ʣ5t`?Sf-?ֆO%va2?ꤧ=?sD?֫t/J?8Q d?v=?7Ǥ?eU\?K06?TA?"v?uQ?$?T*?o ?L?c*5o:?ża{D?Zk?^K?%* ??@KD?|g?D%?3X?C?NnX,?n0?؊Z??}?֞; x?٭GEiA?D_?fPW3?4\q f?g]?N^?Ҍ? .;?\/?ߏJ(G?J4`?s{?LH??Eox+?3Ey?A ?Bn?8G4?9?w˧H?t]?7?D'?sP?؆oQx?G+{?q镳?oL?&OGŕ?D??V 5O?. ?>?кRC?P̀?PU?j80?nW7?@Bx?@k$.d?^JP?dsp?޸K$|?0Z"? ?TR?? Ƙd?`lh?{mWB?e4#r?}?}2C7?~?پ%?k?q ?kZ׹?w?äI?nBA?.^M@??߉ }?x6?薾 ?/(8?d` ?8 U?딐m?hP?$F|d?팖>?wT?#b]?om?JXD?M׌?Ixi>? ?߷6PZ^?旛T?#ݞ?&Rtj?rzk?L! :?o?s$W,?G}/?(CY?tcw?+'V??X2?϶i?Í?ֽ?Ӵ?ڼ@/Ǽ?"Mk?+#?⦉??ozข?#jT?R9@?ރ(?<@0?-ߖ?/?CH?ɴN?¥3A\?%H?rRx?"l{?4edg?M ?nWS&?B??Բi~?$1%Z?W)Q?yCXK?C؇?ǭX.'?-?{OS?Jqn?࿾6d?`?<?ӻ \?`GD?MW]??? :X?ổ*?7k4?Q ?vB۹p?@B?63(h|?ݐl?`C?kT'?܂6?ɿ?98Y?؜P?vV}$?Go?FcbB?P&q?u\?ݲ2?>ӮP?^3?-M?7ݎ?Dfy? R?vGy?FW?ZHml?wqo?D9?Ҭ _S4?a%?ꔢT?5`?5SB?Hn?\?Ҍ~ F&?tЧd? K?byE?Yd?k?Vo|?I9??jӴb?t?簼ӌ&?yG?Cc3?ڄ ?jӈ?;c?ٞ3w?ҏjz?ؕ~?QV?5?Ӄ?䇏4g?eys?g{'?̚o`?'s1e_?ndq?ԁ?R?qH?նI^%?15~6~?KH?ƵfqC`?%/?%i.?H?=?s%.?歎? ڒe?k ?6>?Z1?؋i6"?N+,??@c?95?I7 ?a@h?zM|(?h S?r✜?p?(XB?ꚳL?'?—~5?(rx?{$?޲͏-et? o?my? ZE0?|a?\C?$eqA?[x?&_?ۮ?QQ? "?FH?G߱? ?>,/G??Mw&?Ǡ?v?g? V? ^?s9 [?\F"ے?p2?__ ?ԂՆ?s 7 ? C sd?1È?d A?*1L?T3^5?Wqo^?s?_ȶ\?;75T?W;2d?ޕKY?kG?NI)?8zq*?GQ9,)?Oq[?" =>?ԁ<(n?ؐkt?P*Y?]wE?-7T?z!?煆?v|mX?f)#?OV]ѣT?Ivb3:?5&?꾔z? U{6 ?ֵe'~?7yl?L q?Jʀ?k9?RP?⇅?;?ۖ! e?/g1?ew=?mbx4??39s?kH?Uh5?!Rtq?sa6w?_),O?֒':B?~a?Zݝ?`7?t:-?tj0?P~?cmv?m?͂úl?鵕?وnX?.ו?ڮԔ?*Pp?Gs1?5\#P?l ?av?^?zC?"!x?;?n~?&X?庸>f ?ݺd+~?WK?ʎ:Slt?l#Z?b͍?J3\??e? v?ChRH?p(K?꘮?*AC+M?ݹ|?#Y?,Zk?吴7&? ϣp?Rz?4X?ۏ(?o2?~s `?ʫ*D?ZǥT?ٮG,?uأ,?RkV?|?䎃 ޠq?8|\?I,<7?8=?EX{ ?ӷt?z_3G0D?"rcj?cfQ?PHɸV?T3|6b?O[?BYt@?Pb?=pR֤?5ZD?LcI?吓%Ҙ_?O$?ғh~?r5M?o?"r+r?9LYD? P=?.!R8P?y<g?kMߜd?l?ÛTS?4.?Ker?9E?xG~?P??)?&(?$RLsl@?Pek?=@?bD?2X? /x?̟ZR?R?\p?I?P(?.?? 0?('%c?۔=N? ì?e4?نz f??oX?F2?2u峧?чs?݊,h?T\(?U-Nj?eͰtj?;>O?S?/ιՠ?ʍq`?f.?j(?˛?\X?7(?U?<ڌ?ڠwM?<;G?Cv?8O@?&FV?qy?tr7?jY?ɺ-?N' ?&c[?j;?Ld M?{=z?^?Yvex? Yt?|1/@?8$?0:ڔ?˜?5A?&HX?9?$$@?2]#Tp?p?7?' ?ǫٻd?!G,?,O?p?b*?%;?8A?,0?葀'?I??Ď ?Ќ|'?@ِ?P?$?i4 I?./?4%?`mEg?CP?.uE?)N'?rOp?ׂ?˃Sm?̲dd?U#+z?۾&?*$?OQ?*V?U?kL[?ѕ.3?O:H?)9U?ޘ3 Y4?ͭ?xp? X?(i6:? ˊ?I#?.\?L?&Z@? 6Z?opu?(j*>?F?W? _-?Kޏڴ?7lK? 6?I?) &U?A?'q_? I?b4Wj?ҬiYW?ׯ2v?:x5?5×~?PK?5@? ?k?lb?ⴧ@?Ţ?5?VZ?הA|?GNb?Q@?2;w٘?C ?GL2?Ġ?_? 'r?13?Wu>?}?<`,?DZ.? ^NC ?Z?\.嗬?X?#?R'Ƞ?(7ȶ? +*??c?c?p y? ??9^P?ZѸ?s4?S ?.QӬ?ћ{? Fp? ?fS=?s"݀?l<"?KzA?FFW?7?dG?gYCI?{?ώM'?iv?=vҪ?͗T?w,a-?4 &?*6y?j$0?ens@?Ռ{?톊?'cx?O?du*?t;D[??ÓIB?ٙ ?Gܼ?h?܊n~h?QpP?t]~?֝JUN?ҾU?Q(?O(?Ʀ?{?d.<^?'t?佱 <?hbH?z??ھH?.אK?ȣvE?="v{?d?mB? =? jJI?U(l?hD2θ?ai?F&,8?,C-?=nX?3_Sl? ?ݦ}R?%Sd?w8X?.s? ArdA?XZ?F-a?`M?(?P? ;G??ߙ??3wg?;U?e!V?pZK6f???IE]?K?ЂV%?+aUd?V?NLn^2?(A?شQ?޺?M {y?6P;?+d3H?܂VR$?򊿆?vQ ?[?ep6?M4?7${?? '?r!p?Jՠ?!?>\Ծ?ڱ#uj??e4?"7?Г#-?D%2#*?ﴜbT̨?4*? @uv?CAm?c0?~-?ĢƂC?B(%C?q ?Vp?]ߟL? @ 0? \' ?! b?0Nʀ?޽q?=>?)3/?\y{T?$b??~rs$?⸍\&??Ře'vz?1D?GL?FK?ƙ'?ԓH?b{^?wހ?sRm?*?o^?"8n?*)?9b?rZ?A?<.?ޏtsx?%[Z:x?T?P$(b?ǷR!l?]|? q?JQG?RJ?1?>ξ?\}E?u?U??T3v?Ռ,"?Ihɓ)?-?šX?XL?Sݹu?莳%]=?O|>?q?fvF`?ݝ?PE8w?&3? Kb9w?,k?;V?\0q?g@?ʲLH?ܭ6((5j? ?F'g9$?ܓf??@W8k?}M1KKo?]O&?⮴?iȦ?Ư&zE,?IdX?㽬u?^g:?`?+XD0?{?9eT?5$bƨ?癰??״Z?傳 Q?d ?̉Ǟe?TyؼT?4*?kU?xa?T?#A%/?7?N~`8?Va}?B(7?~s?muF?ї}%jR?h ?BNԓy?Ҷ[!`?J? +?Ni?JHq}?ْW;w?/yGH%x?@@?I_? ?ۨ?ؔvy:?G#8Z?L„?`Ҹ@?Nuʎ?ׁb.? 9is?vH?|˅?D{] ?=:q%t?M?zu2?5@?{A 2?.9o?IU;?4ظ]h??ԩJ?jjCW?ҁ,m?8?ѭ68\?&qG?q49L?n !?ވ?1Vâ? -8,?ڍis?B?|Ag?>?hNV?$c?}?lB?Oy?$x?vY?!逡%?.?3_{?i?ҳr?A=?^h".r?ٜ?Σ||?Ysu?#?nf?:ٺ?s*,?e?DxwEP?;>?~ ?J #?n=1?P?w? b?嬶m?s.?|g??>ͷ"@?'Հ?KT?.?J?9v+$?Cb?! ?(݌׿?׬j?9shZ?p?A݈?*|8?6 Ț?(Τ?ėZ?K7?crIE?烷SH?V?1A:V?x?fI?N ?.Ľ$?ꮮ*W?qx?Y-p?HTL? :FH?8t?Sr?ךQ?G?x?䡐FJ?qȮn?H?4?h??h?^?y($ǟp?\(\?4ݬb?X!`K?=BXG?ܛS`:?/]벺?+gV ?hot?dX[?8h ?`N?f<1? w?3?*GǷ?Ym7j?Qd?M.S0?+?喸 a?4B? ?#B='? ?C?٩UB$?{nF4?T\,?1B?׶r?uQ̀?.Cn?`'ҳ?w>Ƞ?TDXAve? z֘?gam?5r ?ʦ4!?C7w?܋? Z T?`J=?iu12l?,p?ۃ&. Z?+o?䅺 ?]gG?lY;?3|"AO?m?۷_?@?푅|T? .?5?߈?s͞F0?j/?l? 2r] `?*+?&~8Z?]箈4?| !?G;H?jU?-')oj?.H2?v["?)6W?b8z?6X??AT2?]?Hav\gBd?%v?ƀ?MG?ϿJ,?E;? Y?K ?9=LF?M|S?%;'5?ژkXb?[1 1?_'?yYVg?PU?۾Cm?Ҍ7Y?\?W)[?tX?.?b2?; ?[zG\?'?4ߺ@?!U9? ?gS!?J~fN?W W?L`?j}?Lefk?;Pj?R?RU2?)P_\?g?%j?pd?Ol 0?KRbS]?n K?ߨ?j_? ?%uj?G?非)fu??v'?Z?;?3?(?/|?onX??=j?,ެ?޼?d ?h:M?P?)?2?B3m?ӂЮ?4l;|?θ^l?SUxWQ?ߤJm?d U?7J?]=3?$P?굄 6u?8mm.?Sk?2?4 +?Vk?Ғd?FQ?9au??aH?A,f8?Tz?QSH?/s?{о?cPTǐ ?>J≡?IpE ?t!̋?yID8?[D+?эs? >??o7?qԶ8?ezr ?Mx? BN?|2?-?sJfr?] ?,ʝmm?7(պ?ѝf? ?뜾ps?谾<?(93n?E&?db(? ;? ?YڐP?7}K,?t?\mP?mw4>?,4À? @?sp?I1t6|?p?mn?{+6? x۰?"7%?g(?"+?`"?Qh?3n_ ?!:ò$?%+z?Va ?NEP?wP ?f=?$f4? X?雳U+C?Ϣ`n ?竣gI̵?㓡8?乚8?6wr?4~8?G?Ut?)FqS?,m?Je#?ՊE??刣?[l?œ:@?|p?2}?mpE?DzG?|Z!?>9B?%>'?2m?'ħd@?Y?62d?4T?%ɱ?瓰p?H?`?oR??(д?+Ѡ?@?_ Ui?srl?R@?@)A?E)?X"Q?Y ݼ?"o0?͔ 0?\#?Ra-?K N?wͱEw?Q^?B?ye?M&Ύ?Bj`?jح?z;%!P?Pk?l_?NR*?e}?Kȹ ?E?j5b?UO ?0Y?z+GD?C? k?ڰ>u?_@㲳?G!NE?Mb#s?C?p?X5?+w4?2Rr?hR?ski"P?!4?V[2?ԁed?n?@-tX?_X`FV?^)(?jq `?zz[D?<;Gb?bM|?8?(~?mm b?是ϡM?4?mx?ΘYw?wFP?}Ñ?Hܑ?Yn?w?Nc ?k f-??Hce?߸?:(?4{$?;^Q)V?&Jc?•T(?ϴ?՗.?L?5D9^?j 9?4 P?%??C@?۞cU?W?_)?H3ԟ?::Qd?2tP?*qق?eRx?GY@?^j?߰q?`+}i2?&ؾx?s5Gl?p9 ?h]?Y;N.?$(gǣ?hu'@?]`?'q?]p?*`?V΋? ?[D?hڰ]4?,w`?5SΠ`?x{?IF?ǣj?3Q?n?捦8'/?g+ɠ?Ye? e0?AՓ?* 1?|w XD?y1y.?F%Tv?~C9>??{j%?=?r}?Ad?+.?Z?wTN'?1 ?f5Լ?ݐ6Q?٫Ff?sjC?o{?s?ǜeW??w?STU%?uy^?ގ!,x?"t?V?Qy0?ly??lp?K"7?A~ ?ӗM?q<ɡ?D]?Sz?h?^U?H:n?صT/L?k p?V?z?y?s v?A?>o0?VvE?u"NE?:?x9??[_ ?ʟ?{km?K`?̰m` ?p?: Wk?>mT?V+J$X?!b?&:?lY4?Q/~?4*7W ?#^?P?牻 ?\I?t_ ?🂮.'?֏??k0?A4i???j:?-qm?ބ?@ J&?%Y6H? e/?Do ?D?Rct??vW?H?v3j?;Pq?;I??ҕ3j6?|g?Ǧ6sެ?Nᷩ?T|!?>b0?2?밵S?kHTŧ?w#(?Gq?zO ?O ?4T q?Q)? ?uRE?/"?vOr?c4X?x ?q?Uv?Q;(M?E{r?㿴v?>hC?\C6H?m ?0{?V?ە>?ґp?]#?1qi=?R?2?dzlqM?H-?Ԟ,?G?@&[>?X?i9L]?H7UK4?1e)1?A>]B?ץ?ҳc4?(?ފԓN ?5x|?R pO?dK.?Pgt?˛pD?N?]߉?7O?  +N?lx?1?t$x?^!o?S?(S$?/vq?Ǻ;?odT?&?Iff?Cb?TAK?쎳+?t>?׹QYN?|@?d?BE?߳?Wս?ޓ6f?ĄHO"|?ӬJ?wV1lM ?@w_??f^?|Rn?7wh?lѡ??#}?ݻ2??i?-(:?z(?^?l O?Rgp?k?DZW%v?iMlh?1SZI0?ц?6(g4W?WR#h?L?mqO?y,1?G_!? If?3Gs|u?P?`N?k Y?EJ?ail?K:0?C^]?:wHv%?2P@? d?z@X]?ٲM?6,s?(W}?U}N?d̉?}֦? j? u?}Ph*?vh??Ж,s}?أ<$Yp?fDep?7' ?Y!,?uZ:s?Dy?%?fY9?N?};;k?Sh/(6?E^D??\0>? S?sP?Ô!?(}4?P ?؝WN ~l?yr"9?^ZJ;?樬9gг?Q>q?A_`4`?Ί?m ?ڢ 3l?6]q?јܿ?S)?>,u"? >]p?69?̴P?X87v ?{g`?5%?e d?ퟪtq?bJ?_ɔ?x6C?߈&(?"a?؎T?ؕVV?/L? @?CV7?sdd@?È\'`?0I??ܼ[n?9`Y$?fHJ5?S+0?¥?Ӂdž0?`2 c?ݐoS?m?O\?ܮ5c?랔u\?ߚ?澞?y%?+f?D?8 C?޸?iGBP?xK+?Iã§?!e?rb?+?:|?"V?0p?@a?gN?P?x?1?|gm?P ?E?'w?+nh?nD?E?J?EK?FMv?^?=(?ІV+:g?{P`?4i? ?>u Aѽ?lB?^Mr'?ȫf?˭AAK?̡A4?.!x?ښfbB.? dkv,??ܸ忰?j¿X?*l?ҧk.֦?s;?=l?4/?ɁoB?m?i.?{q?4_f?AZ?ڔ Q?*1 ?쀇0?P?Оa?7x?ᰅ.~?6Z(?Y ?5J ׽?3)3Y?ꅱV?$?LO^7?O ?+txQ?ă^h?e>6ƞ?2~Z?W?'1`?aY? i5*.? }7?~Zk?ͺt\?=\?1{?sLQ?龛T̬? T$s?iHP?V%\?V6 ?X?juw} ?y6Ds?7>+?ze?ҿ?;̄:?vzStR?Gt???9k(q?.g?E6e?ʺ9O??|Q#?V?[0Y?V?R$D?l-g%?/9uL?p۶(?dr?q޾A?e-?ѥeyt?pw,?}u0?ӟd?˅?HΥ6?6 ?G{?m?'Y?$\м?U?XRO?p=8f??8?AY?^(@?d"?j_z?޵!?v?KY?tuz?'q ?t暏 ?:?ڜF4MO?śǟ#P?'?;榖?DE]?-%?qK'? K?6?Y?e68?fv?Kȶ?5kp?d?jR?cы???64R4?m?V|e ?~KU!?%?a?Ϸ,?=Q@?C:[L?E`gd?kLOR?ɾس? k6:?qr ?O*XI??ч|?;T~? lRl?I 8?10T7?ƦJzn@?J?tDP?,/?:"k\']?!n刍?HIF?YJIT?g?߽59f?Sen&N?ds'?-Yx?$&?9+@?;T?tQ?*]f?+PiW&?*!' ?'ٝk-? [?^b?Tz?GJN?!4?d5?$A??ft`\?۱ ?gc9?ّ?ѷb?ŝ??h;sԥ? ~w7$?X˭[?^?w_4)?f=?d?ߠ>[?WS̢?-?&6F?7???:?XE ?(3t)?P&?StH?ίʀ$?N,d?[?h?W8D?{@_?t?ղk?ÛzrX?Ү?s?GM(T?جAhP??E?iz?'c?N1v?1?]N^lT?ѱ?y?Ҳ ?ea`ny?ñb?$?L:Efo?ֹ-t?IjJ?]j?>?a 8?ʋ^(?_DZ?F)De?X#Q?āo?.m`?⪓a?iq?u\?Ρ՛?qkh??MLE?9?iI#?1PV???вTc?͘~?ғ,?Ƀ\B?vU0??oT@??50?E0=fp@?5|{?N#?w? >B?:C?nc)z ?ii?f/?Y]@?Ж9?ڐ-FlB?)ZUV?ڧ- b?8C? ??ɿ%?LnV?aTp֧?Gƞ?N+C]1?@vG3?rw\?$?&~2?aՉ?01T?0?2|?QgC?=$E?JHU?b`"?ɚ?f==ь?ڶ ?Mq?О?0*?[K?u"?r:??ea6?e_ׇ?c2a!?AR*??[8?L?e0?#0?]n?׆B@?*?T? -b?"]bP?ּƣd???ֽD?ho!?ΤYAD?#P?u_ ??0XC?e:q?tqx0?F /?7EV?@gy?˴f7 ?8 X?-:?ي*6?jl(N? dXt?s `?Ll '?Mn?UѬ?L6 ?^P?O2ɔ?]?뉺D}?I~? h? ~?̉s(??=B1`?TY??/Ј?NX?-8@?ҵ0f? pZ?AAF?ˤH1?\zD? 0>?V#?i?kW2?Iy?? J4?Gg?#)Ȩ? խA?钌D?#|?Fj.?.=&?ԥzGx?/JKy?4;?W/8B?i,А6?#X?Z?֕b?Uwp?d=>?Eˢ?C W?E=?I |@i?Nn"?[?м 8?C$K?t=d?1n?KfgN??!7m|?I@?R?ː ?A|4? 6?o<8?4 ?+m?fl?LȌ?8&?8?X 5p?ʉu?Tx@?:u)?NU?1\$i?n`h?,;HX??.d8f?fy(?߃&? .?\s׮?ډ8qL?ĀL?[w;?|Ki"?x\?W8?B?v'q?8s ?Z?<?֠M(?Wf?3^?5."?R0?Ẫ6?/v q?ͫU?6q}?DH?ތׯ?1??oZH?]3T?SWN?K݈??뫳D],?賫?GGK?׼4?tw1?PPL?ǂ~?xT?AlF?ԵWyr?o/?Y@?ޔri+H?u*Bg?Qk ?ʦ4x$?JqQ0?-?G̐?aE?p{ ??҇fX?]?CY6?U4n?ӏ٦?0bF)?˔-?{#|?1s?/NX?M`?㢎鴭,?ؙxEP?1O?3-(?킡,?Etd?x$T?9Be?dh?بtEƾ?7A?<ř?I?.OO?(BbN?)ۜX?L? r(?kV;2Q?⮅s_G?{q?[1 'b?Y;\?ڦ./?JV?7ʨ?Ϥ?i i?3?1̱>?3$:x?Q??+?] ˪?uNs?;?pV?'k1??ﷰ??&?g8(??A6x?p٪?!U+?w4h$[X?,(?(J?Mi?̿~p? Tg?ݏῄ?u?^Bj?aB?迷Cw?ɬ`?Ý`? r?gG?܎Z?Nc?Uxe? Z?w'ˀ?QH?b6 (?I?Й9-p~?t|4?㛜H?1קi?3?ĈG%'z?ƭ?q^A'6?H @m}?Ik ?0'?F'd"?³"?3E??P@?Ķ?v?BNg?Ȟ! ?Β?,mDT?9y??Vf?6⋓?[{VJ?A=?# 8?jdV?q}Z+?ۃn?Ͷۖ?ba?sl?!d?y?]1?y^4?M#t?kO?ԜE^p?s?%)?Hp?P(=n?⦤ E5?ɶZc&?8;4 ?ƒtp?m`:?3 ^ٸ? M@?{wx?J3q?5/?2ؒ?ЦKv l??UoL?v{8?|X?|󷔀?w?8 Ox?Uo?ȕ? b?wtc?$y?Gq\@?~*k^?љ?8`?a?öFt?8 ?ʦ?3ڧ3K?r?s^? T ?ư.??S?b??Զ\?i\=BX?V0?9D ?$b6"4`?BV?w0??+Z? \S? y@?Rqn?砫& ?\?\{?Cjn?j{?n?l2?G[k? ٧`?a?܏?뢇}? yU?6%k2?W ?5=? V?f`?tVf?P|5? M_?ᄐF?'?츂?XaO?Rzx@?P_?v&?z@?||t? l?2?gDj@?DR@?1e?33=?R|?˶{?4q{?]Q?0?Sl?Z+w$?9x\?}Ϡ?j>;-?6 P?>?ɯu?vT߹?֦?̃V?gҀ?DŽi)?߳R0 ?Y?w8?R?D1X?L?(L?ű?ӷvB?i?<7A?p|ő??΃ڵ!?z9#(?`9[Q?,|u~?IӾ?LZ?)M?̭d4?E?~e/??絿W?ZP>F?ςix?ͧ?{V??<=1fe?8?p?ۡQ?3&(]]?鰸Qm?IFiV?꩑kP?#Ȍ?< ?ͺ_Et?ټlX%:?zv? ˊ?ηX?Z 8?vp?:ay?_HO2|??>@?J]?8"?S22?s(*?24i<`??d?\9^u?DMJ\?Oڪ?z}9? PX?%?U$z,?\ #H|?~;?}?6"@?]ď?<~0i?Ѓsju? s?}{od?-sLu]?ڧ2?pg~?Yn?(ter?: f?5\?TU?EI@?z8?׵rZi(?܁?ٰc?D)ߊ?2P?@?=E#??I[?hS?Xh{ ?#*=a?|\i?N?W;!J?**]C?OOƃ?Gi?y?VI@?CR? uB?BL?X7?Tό?ɗUE ,?R;䯼? UԊ`?YZ?Gl[z?1@?y m?y24oZ?j&%?s?&K?曒 ,/?='{?߁=5N?1B?j`?jvɪˀ??y ?Kb^P?՝hɕ?8T?~ۄ&?1}&t?Xe? !bw?m`?-ܹ?7M4h?s ?${^?`r?tD?Ӎ#|?׹Ed-?_ל/z?Ҕ?[q?=aD0? n?_,?o_?mݶ?u?U?@?Kh1?nqP?<?TtC>w?ŝL8@?,~?6 -?=CU ?gף(?q" &?S*x?3&ڤ?δ!ɵ-? ?Ӷ@?r~?*?P5=?&3?ФZ&?j 8*?~J&?х# ?zy!?Wtr?Q .?e.R%?ԃ'h?X_'?xs?+/h?BN?T \B?֐?ȀSr:6? ? ?L?@?W8?(?}?d8p? cp?\ r`?u[?J? oz?0/>?=?zD?g?lK?]N/PP?|^a@?hш:&?JF ?k?z?i&?% ?Ny@?灛dxL?&pQ?$iQ?ly[?n`_?;?'A[?ϡ?كi?߿mx&z?/$r?K)\?F?1@? ?~cj?Ii?<بﻶ?m3?^ ?{qi?wE[?堚X?M=e?Y'+H\??Ȍ?)]ބ?Ѱy?qG]^8?֬i?󾦦?1|aU?h-1~֖?rp?ӠG+? R8??&?ذAx?ڒpNI?E?TAtE?Ь)i$X?gx?ޣW~:?+"C>?ļš8`?YU=?,[ ?QwG@?K(a?H'*{?՞M?Ӫ? !~?"F?哵H?s?K4?>/V!@?Ut?Lf1X?%n@?„\:|?M"! ?rqp?K;.x?D/d$?,UH?̳Y?];]?뙆 lN?Ha?K?mR?Ӟr5?P.t?d8I?E?ᯣSG?wT?K ?j?ڥ޿4r?䏯bp?ts|?< {myD?ãGx?iqg8jC?ki?UC?#8?p0?rDv?}\?ű? c>?U7-!?v`?T?ZO!D?v./ц?/O*?킏Nd??hl?R\?W%+?}|?r\?~ft?`ݓ?;?SG*@?q1)?ԇb?k!}8?­+P?]KzH.?_E@? V>?0?5D? (0?ʼݧ?za4?~{~?wrpv? ) w?2+?o~K8? I>R?ݩ:F?Бx)n?Vp?dTH?욏X0?-K?[s^w~?|^en?IhӨ?UQˊ?v?fY?Gڹ?JZ?p!?@(?RDu?`4?g}ia?0-QA*??B,3?zй?st(hH?""$|n?ޖrbd?WR,?,gg?nkw?]?,H6a?;?=n6>?}?$@=1?zs?+kxF?>?~|W?) ?ꝃ/?>l8?䂏ȁ?>k?!P?]?dߤJ?R&\d?z&?'i2?O?"cN?Mmaٜ{?4}!?IZ??D?TmUs?Թ!?JtO?sFbo?ͣK?;2?%-?^y!/?pS?"0?o \?7?/?NL?.1" ?(^P?o8?T]:l?Hz?i@?/!?RNg?Kx.4?o$s?8w?]-7?m&T?N/nf?4*?w+?%т]?6@[?͆*k$X?l+?a,?J8ǜ?=i V?2DeR?׽-?|S?ϼ7y`?Gz?-?9ה?-]R?ז:?>6&?LeD?¸^ ?˫JMp?TS q]?.%P?9 ?+mZ(?*?^Φ)b?= ?JV\ ?"?:.\/?[?-d0?#}$I>?.9^ ?=N=?Zz? KŐ?E^6?m+jg?\tH?ڴ9s֍?b ?+?Pzp?_X ?"bÂ?T:,6f%?aFJEy?ҩ{N ?蟳e?bz9?)d4?f1?@330?Ɂ7(t?8#?Y`'?E5Z(?<>oUR?ďx`?$@?W<~B?L?bPj>h?ӱ?ϥnĬ ?Dm_ch?^DX?2 eP?o[0?6?YP?Y[6?۫Cw?ا? ,?t)(?F? D??ݰOB?|D.?kU;?MB?/{?uX?o|?xt??5^P?7 ?۩2CE??3F?מB?åH`?ѠÓj?d?U"?I^?& /7?h l?w>_/E?qّ?0F?6'i?۳?Ǘ**??Lj?㒿%?RON:?žC04?ц?Оܔ4_?~C?IW?_?K~V?7 ?N+?FXQE.?nE?Ӟī\?W>?+?p?τ @? #`?lz ??pd]U?Tdu2?\S ?V\r?v}?\>(?J*?]:7n?5]_g?|?cI?$xOf?ME?穇Ռ?Rm?t=t4?dn>?-)8?C]0?cW[I?;~1v?RY?h4]=?3 ?97]85?:2$? 7t??×o?Gg?귤z\?nO?q%W??M !?ꧠXa? 4?{xO?w](?}?Kkh?N? fǵ?ב)?B>?+\P?j.? T?/bb ?Ԁ0r?fD4=?{ng#?AQ?DJ?W ?~DIU?%Ц?翇2?³?8?ʰ ,?]D%?.^i?ߢa\b?z~J?Dj?O؋|?u?H?k-?7!v?{_y ?w?#"-{? W}Bm?.,?\r*Iˀ?}?ӳq?.g?ZeP? O?}r}%;D?Nl?qSw?-q|?=4P?ޛs6?e}Pn?tS?*lM?y!nhx?@ ǁkU?6u{?J+?ӄ%w?9:ex?T0?Yvu3V?fT?h$aA?b~J?=*Nm?  ?ң?zpM?lg/h?]A˿?ْ M&?Chb?|G5H?JH?v8?s p%?@?-'?2mӘ?D?u?P#(x?\?Ki?Ƕ?}K?)Jóx?k#M?PѰ??!ɫ,?0?y ֱ'E?Ir\?T]?/ϼ?V?ߞI ~?N$XSh?R# ?yUNė?붳/t?Sw#oh?u]Z?~kf?磮U*Ln?)?:wݦ?9%!b?/Ĉ?{ ??→il?-\?;DQ?4]G?wyR?՞)j>?Nj?4?޸E[?.?1<?GsE?#a?T?3J?-P?&̾?ߊ"B?愭H?cM?襫12?:K?"*y-l?㉮ZLU?m/?b&?Ɇg)?Ј?_? Z?c+ ?Ɲ?kVt? w?L 1l?;??7?إ?[?ը?Wj? hn>?F?&?=~7?#~wZ?N˞(?ZW3-?d˘?7U??68Ύ?jD?Ј+[?D]}?Bȳ?CM?T?]Gj?gX?H=D!? ;?6r?&?: g?)`@?l ?ڵvK?T? 42C?0"?x,?閖.d?֗hfs?ܿ4?;l擠?ԑ,?(?¢/C0|?b0?>vH?`1Ht?d ?(B/7?³5O?%/?@?%_?}?勗mF?*!;?XlE?Y?YVS?"}I?{;{?7c?*!?c6?հwje?*({ ?gdt?CT?[;V`? DRD?rn?"2@?+Aם`?̽Y?#z?;̳Y?ӣ,F3?.??2r5TW?N&?0j2?ȞnH$??_4d?﫳ncE?b䳙?ؾɴDH?%KK?B7 ?a?DIKB?D%?Gg?ވ?j? g+qt?U]?iU?Kf۳~?;ESw?Ћ?ճV?ߪd~4?Ũ/?ٝh?m Ҽ?ꋤ5?D?<*?G*?zЯ? $N?bX ?5?=,V?|,%Qq? zJ?Fw}?6^?psj?5l?'F?ͩsd?,n+?R݄?N{?Y`_?Dp:?Ѐvs?0l܋A ?Z?6?诿@?|ˑ?V?蠯"?FҪ?ʻk_| \?b0aE?d/R9?dJH?p.;?=?4?VMT b?؜?`{?،z??\?or)?ˁk!d?p?\x?Ѥ}?Z2!x?;@?~vIq? G?*N4?}i?Q?לx`?㤏~ض?̀'i?f ?ԗ,?yU1n?N] ?KUc?O㼍?ކ]0?|cC?6%c?ܶ0Y?_?YF'?ǵЎ?Ȗq!?g? J?]Ǫ?9W \[?ᆲ"?Sa(? r?YO1'?݆k?ߧqP?6V*?Gyn?< 9?&kx?b/?1|?D2~l?1 ?͙8 ?~?? s?şƂ?i"?[nu?{t?)Ch?'"T?;Y?ܮ ߣ޼?B5,?"F?1,? ?_(0?ih'?<?4?G&\: _?{?f?[|- v?\a?J?CC?<?6v?ܔ1 ?T^(L?'R?c%^?#q#??'\?ה/P?1 Y)?M?$-?%֓?[?~( ?(aŶJ?DG?'"jP?|?.y?An1'?œ"$v?yde$??E &I?زӛ?Iǥ?^C? #7m?1O?Ã]֬? |?]Vz?tt?4-Bx?7P?3W?ؽ1"?11?nZ\? !QO?&?5SRB? 7;?.E?o&grf?[0^?Y,^ n?ێղ?uYQe?Փki?~Hт ??\C?S?Jڻ ?ؼ,p?o٤?,?7u?SFw?M4?9N?pЭ?R?$#?ą9 ?;o?uo7?ף?>A?贘?2S?V?(d?l~?&+S?^*k"P?meoX?*?vmg?Fȫf?s߮?K!L?8Yv?~Z?E7Ҏ?7?z4v?br?˴f`?cs ?mF6-Z?exj?ѭ|ߜt? [3?6f?j̰?A?5ѩm??^LM?VG?딤$1?+?!Of??H_?B?]0S?꒑vt]?יe(:?O.T?`L?;}?aT?2f ?qmBl?ϸXM?ɚwpD?tr?' ?z0l`?DrWf\?/?p6*+?[*?I>?J?D?$v0?LCTπ?rncN2?z9 ?&, 7??qXL?J {?ꝩ?ѳ\?j J? 7?ې?QS?B? ?>.?˜o`?°σN?8h2t?fI?⚱ ?ʕ(%$?؉M ?>Π?^c7?1}`?^?.= ?n'hh=?(?\?|6MҶ?Rp3 ?;?#Ci_?㫤ǻ?TZ J?ѣaQO??0?ׇ"V?8?AcB?}?ހj 8?31D>?Մ$y?*6#$?zepcoT?w9N?@O?:>T?q ?ADz?ͫ_8?+b`?˃3?r +#?'/?R I?ٗ7?ȞĦR&?jIt?᥋Z ?Nm?߀3iB?4|?`? ?҈?T?q,hd?B&?wl%?7)?s t?TD4?]# ?E u?(@:?L]??Ve?ݡtA?ˊkX?G%J?~l?L|?!5q?F?T5,?rVS|?)g?˱ž?ꡪ,??h-qQ?֔?$A?`K}j?3G?;x?q?p?壑q?ݷ*? 3?[+=?Th(@?E8??γہ?lJf?g?E14?`?*;gO; ?Ib_?ʹto?}3?Əzx^?x9?FQk?L-?tIov?]=?8x?<e?t?Sx?y#U?MٲX?A ?P`*B?I @@?&? b7?{bwf??!y?_~cp?r:? [?Hnnqh?,<|??LI?_CN?\O?ݡ?&`6?/??9Zd?v,^L?d?ّE?ɐ}`?N5l?̄?څB4? $?#??h|?j?!?Je?UwI?zF?Fb?O`V-?҅P?FK \?" D?k?IPؑ%?NX? ?:|?gI@?O3?pbX?ڜz?ӑ*?iM?ԊYnb?V?灚L?d`fÿ?$?ϕn ?=?ٌS??4C?wc?A VU?$™?ꉙJh?ݯ-?y?*R?Ko5d?(^X??'U*(?эE-?$7?ّAJ?"j?0_r? >D?lJJ?g?o?8؆u@?eʡ? Fp?)R9.? n?G V?H6T?H??O?{ 6d?' ?VX2?g??͉?n;R*?FFv ?=]$?_$C?ʮ.?oh?h@?IDzOT?$qF??hr+?ma_0?U?/)5?߮m|D@?<^1]?^ %!?? ?5?"?*W~?`d6*7?#d]?14)? vDx?˖W,?!d? ?ku?.а?ĸc??xk?̽X^?~.?oD\?^[x?b:5?Wv?ǃG?.Rok8?oo?ۂJM?sz?Zn?teF? KyJ?R3?@L?$T?=bF?Ol w?a%?z:t ?:ۙ?HjNe?}?fH3A?ԲHu3o?OSP?e`?#mEQv?B9? w<,t?T?~ĥf??;?yl?#?ݩV>?. ?jwT?G2BT?iK?ݱ".?ӹ[8?[|F?%쇛k?Uc*p?رU=&?T'?hW?AOD? C!?&0?3? <*?=\P?G\H?קR`?i;o?ra?rIU?O,l@?u-}ߌ? ݂??&?َ^d?€!̰?իˠpz?INǐ?凂?ڋds6?p98?bFA?C?@^b?cB`?_?`v`?<?A^$ ?Dbi?]҇?ܿ)z?N }?<?#jf `~?ron?Q&E?5Xz?z\?62?H?-NH?ף@l"?C@@?%=L?.T*X?'=M:?ѿ$/?l0N?}m?Ӌxv&?WS |=?sL?S/?gѫ?#$?+7L?(׈?ٝҀʭ?ql?2A?7`I?V??dD0c?䧆*?bwke?O׈j?X?ȾH?얷jH?'?pM?jjo ~?%OJ@R? ?Ģ۲;?u[4?ǜɤ?Ty?K~b4?5T&?dԉ`?isv??$l?>)`?,&S4?omh8? Rv?ꚮ?0x@?]xb?ߝ?yE?ɢw?dmb?mrEe!?Z?c(r?}y?Sh|?59?չV?wJ?ہA$?#?qE?L;F? }?&3D?B 4?c)r?HX]?T)ġ[?޽C?( ?5Ѱ? a?nಐ?~K%?S?s23:?pL?N9;?~jnڀ?Si~ ?Ai*?CFfX?Jy?7`=&v?`?`\2?J?`&Wf?%"p?o&{?E{"?tJ?R0?]]CP?Y?a؂?ɚ4x?EiZ?mq?O)y?Ԫ|PA?֥.Y]?lq?`ч?a7(@I?E?`z|??楆]?xkl3?ͺ\-a?xӛn?T-\?N2-?,c;?F( `?sP?ݙ o&?y?t?LjZ|?KqH?B[\?^{ ?4ﰐ R?Fp?rZ?ᴒ?"|?Xx0A?٥ ?n&o?ƌU`?=t?z9?1煙?(E?-Hk?v ܦ?P?擉B?W>vs?v@?5;3?I`?Iۙ?2zL?} ?BN ?]??S?=??-Yɘ?300?`B?» ,R?!97Tn?J ?؝֢?0Dcf? NB?E5=I?8?: /S?MQu?rf` ?ְV?"G}?+ ??܋j?@>G?ޣg,?ɃtD?0C? JR|?H?Rw?no?r2PT? Ř`?No?8U'?z(bі?营!?raİ?;|?ʉwRr?¹:??7vP?cVe?g N?b,߭?w ?CX?rn?'0?LS:?K??گ?v\R?^R`?Ʊ' @?$?6aZ`?NT4?+56?ʽ?b@?8'?f4?+r?~w?tZ'?JJ? x? k?b*#QT?V6{3?ڌE> P? cïw?9?M'J{?<T1??5 ?WG$?o"Jj?ﯓV/?ߝ?b?wbUi?ǁv?޺D,?(dZtZd?1ZT?iL?Eވ?ݝ?,]?kZ?HU?Rʒ3?&`鸀?e= )^:?X[? (mA?9~H? c#?}9 ؤ?_D,I2=?yJ;? {?Xt3?UX3,?]}?g?~;?{Z?5?8E+?R,_?Ł|8H?꿀?9~P?(Z|x?҉4f?P_I?aFLD?LUo?I?B38?VdR?YWWH?ڭos??ZP?c&$?mo'?Ƃ:Q?rw~[?ݲI!? e#?[# ? Na&Q?y*Ɗr?ޏ??'3?C ?F(G?X9|?5Œ?- ?G?W]en?S_?EQ]~?1W?6?x~?о z?蕯fе?b?' v?hUB?Ͻ?K? V(e?gAn?³LW?߽Ksr??ŀ5SH8?>ph?%b/?sh0?F?vs|>?=8z?Իk?Xa?c?YG ?ۿ11`?ś?.9a?T8?Fo?[M?ou?LpV? 3r?͖3?9t?uP? +?.8@h? %Y?U?ؔ4̎?Z`?FABA?q*y&?䬼2?ñK3?IhQ͛??1??Ҽ Oͬ?!"?" D?+pab?_*n?߭݌?5?"X?' ?݀?79:RJ?XD?žwlP?:M?"5sf?[z?S'?c"uth?ܞGYj$?iv?Z?͔f?႑o?t\?pÍ?EUr?7[f?Ƿm?.7jɥ?zτ?aaɜH?~53e?:x?㐵EX?!#?ҠOn77?P?#KX?gpP?+gn ?$)?D%HK?E(?w‰?fFō?v?([ ?5)?jL$5?tψ?紅[Җ?-8(?|?A?E,?Ϯk?M\?@͜Lf?U?Ou?e4?D y~?n0?p@f?ڊ?iL?,i?Ц̷R?ki?3̮~?֭%X?I#?3. ?js?ITG?Q: حx?X8:?͉+asS?Tw>?TL _s?d6L?K?㬷$?p?_+d?鯌#?R+[?'V? `?.Wi?>V? 9??K?k(Mϒ?%u?H>?<*P?CGY0?tN?*Q?D5p?К)cv?ø?X?႘ӿ?1?8?>ѿ?f2]GH?"L?͊*`?ݛh?x?[8H ?fu ɶ?`nAuG?g9q[i?pPT?OhI?&w?V0?d a?@K?דX67?᲍J?M?fts?$a ?x$???;M|D8P?,M ?ַMn|?dt]o?H~h-?ݒ^("??ԙ_E?X`C?FB.X?.=?yB?ه?,h?ef 8?d'E ?ߙ?44?]J?;!?`WJS?ԋ b?(?Ol?r?/Ц?m`?2g?SJ?@E? Ռ7?;:.? `?A4f ?ߎ&? z?jw"?Q?_ (|?Al>?w?B4ދV?c?ܑg[?dk?}7?6=0?0ʯ?;]'j?s9Y?ﴹ?A!r^ ?z?ф?`i: V?ȏИ?"nLbP?̊ɍ?,a?1K?Aռ?AcM??3g.?ѼLz?Bu N?rR;TZ?ͧ ?ܚPc2?rO"?{`s? ?[$M4N??:X?GZ?h:?G61?ctlB ?ͻ@Ӏ?V<)?g&??L7T0?߬?UX?"h ?DG0?$\;f?DP?Qe?ܿ\N?BL?^R[?Ȇc?$|?v?Ҭ(?[[H?◘ ?%u?Ԝ>?|?`.IA?#bJ(W?j!&O?D? }?]?В(Ia?zЊ?D!~?r\] ?a?hX+p? 3N]?M? }J$lR?˜Тp?PX?᳁A :??FwD?S?ƃ=r?j? ds?␤f?G-Zy?ۤkX!"?Ta?ےT?SP ?f=۹?j3A(?/WB? ǥN?i?{ 2?b?ZFUf?۠Omt?7H?mZ?Ƃi/?&3f?W?tbL?7֠? [~Z?S1?ȏ ^j`? kȋ?ݲ_kס?I6?TX든?x.?Hvj?پ3>`C?3{~?[ڱ? ;-N?άe?ސ$G]? A?呇g}?x?7"p ?J,+x?Ѿ׫?qQk?F?^~? ڧ? Z]?`u?hG? u3j#?^.\;g?‰wI?vvP?:?0?I/?׮ E\x?$|S ?U?&f?Ai8?ѽ&:?+ x?F'?_Z?]ɦ?׬jӏ?+[mݍ@?4?k?³-?"ީ?ڜ4l?ŵ(H?4ȉ?6@V?y.d~?אc2L?\c?h~?T!p\? `?M2?z?лw@?֤4 ?=KL4*?J4?0wh?戹{a??d_?Ft?ӝtbB?]?Ae*?)<܇?ݻɠ?/$֣?ۼ27\?30?8ei?DMk?N?Dv?5???҅؀?D?G1?} ɷ@?o\?;=s?=H?֝:n?-~4R?ϳ*]v(?蔿?sX^?ϙ;?g?#V?/~k6nS?l]1Q? v?d%?4m}8?DA,:?nFPX?%Ȑq?Y]E?C9@>A?Paي?0?HoS?zR?^C?W?9?"А? n@?ߙ ?%6?5? H?~?*?X? ?Ԏ`?xl(?Ũ?Pb?5GBm?_LEE؀? `o?<>ݲ?x?e?#ʻ?}\@8B?h}n?sk0N?)@gCZ?(i?B[?bh &?c :l&?1,? ?O?b^?fri_?J)^)T?Sba|?5[?ê9|?Jw<{x?A}z0?{x?Ny7O?o!>?#fXCz?p?R\ ?ld?Cx[?}?GRe8?|`?'?90{C? ÜQ? (d?ó5?u*?+-|?/^?X<?ys5?AtRa?Z?*?<ڑ]|?jf ?Ve!?ۆ?s3?ĵ48?؀]6?_@?yB?ݚR@?Z?ʾ?-?W@?3-R?3|DVh?I[9(?n=?P6Bx?24R?lW^s??,/B/I?Yp?5P$?o?O+)?6?[5t?g.'/]?C? [3a?%؉?V4l?>0q?GOP%?JX?I3zB?&?n?W8?qpJ?.+-?QL>?Й$?n;]F?3^?S p?ԝiJ?W=j?܌?߶?aN$?*S ?86??8iw?ћH?}z?M`_?bʺN?D|A?ꮢj?>gp?*&y+?Ei:z?J /g?ɸiYh?4i4?5P^?Tv?Ǹ؏?z@?-IF2?8m?#r d?~Ѩ=|?tql?@?xal?٬bl?ޥ?Fdw=?E.t?ӀY?s;??D| ?y ? V?>?쬚F^?)i9|S?犦\B)?A:|?ap?=J,g?2;?k :?bJv?kҨ?Y(`-?0`?'M?c%Jܫ?܍wvV?/5?,sٙ=?2Ltl?;Txb?Gk?^YY?t?Ŭ?.N+?`B ?Sr?ߓudD?ik]T?u?㤨?_sE?99ul?Z6h?A?X_?l<0?x/X?٦?F{?띜?٩Z9>?|7" ??˶n?9h? B?uлZIA?D4?HEP?&Y?*?#ۨ?@?ڂP?Y^H?%DN?c f?L`?+4;?{L ?m'S?_n?I;N?ڣʜ?ͬ+ߔ?$|-m?ܢb?Eo1)w? @?c#,?B[%qM?0D ?v?ێ5? N›?D w??.a$?d!;b?ԣNUdB?ܛRZ˥?ICC?Qn@?Md?Pw?h0?iZ?ڌz?kPT4?MlhT?Uo?Д?m r?tظ? >E?mL ?Wk?G?J?}W:3?ȍ8?2n2?xr;V?{-9?9Fx?̨'X?$]?{&?cPS?T$? Z?D.tD?T}5R?A ߇H?w?o?7-?y"u?Go?ιa0?+W(/?ԴpH?r[e??W|@?܀®P?Pa6(?Ә!m?^g?׹Pe?O???HVyP?k0?#@% ?fL?O ?l 5??zR*z?׵?{*R?J~w?+Z!>?u?&W#}?ԲY?i`iI+?I?^Z??(=)@???ٝ-?m?\z8?h>9?:%_vL?,8}?q?%?͹fã`?JRz?SH?~殮?1B9BS?DUC?C* ?q? +?b<+?Uqp&?s3x??o:?-:?Ra4?t?êuDT?qIٜ?$L?^Σ+?f53?ڸ?lg" A?aN?7ޛ?fJ|!?ݠD ?Ɲ^^p?"??x1V'?Kv?,}k? ?ԣv?@财?\Vw?4 6\?(?Ը@OH? {T?}J7R?ë\ ?nr/>`|?/W?Dg`?ݷ~G? ?]?ǥ?G?FBt ?ё!A^?SQNp?%?1í?[8@?ҏ+!?F B?֤/?p?)m~?Ndl~)?5NxT?ܨ?P?eKN?ױ?i>)?QeE?u2+h?䈌lC?=Q? Z a? a?qb?530?Ci+P?]l?aL$ $?F<+?C+c?MZiU?#?QaG?N5c?ze3P?w"oM?۪4f?Ĭp̐?^{(0?y*X?#=?f\J^"?miu?l?灗)he?.r*Ͻ?勃b?2 ?+A?/#B?Y2tL`?RJL?Rp?!O??mˆ?78 ?tnޝ?%!,?&nQ)?@9|?DS?0RL?"g?*T?؈p\@?44?1 =_N?ކ ,?ذ?芝h?]?$2?{Y?=Y\? 4?l"?US}?G1?ꃽ&8?jM?SLt ?"V=?rEW;?NRxn?v#?(2?( ?_2x?2"f-h? Z>c:?7?$?d(?N?;-??>Ի??1M?Ge_ێ?x?b?РG F?"-Քڸ?u`?YF?uz$?>b>?=Yg?|k? ?/>? ?c?);?ҵ׋|?QMף?J7m?{WuQ?r$SUb?Kp?glO?(bxt?ٛ2?F?ְ@?~LP"?K۸? -Io?ٔ't?ɗZ[?0dh7?6·@p?ּڂ&?"Ty;?jo?i2?.th`?ˢ{?;n2>?4;?մSZ6?St?!?eN*=?@`(?M ?P&?d?[gh?यs?c0qA?ӆfN?x ?|(?>\?c ?/E?ٗyRB??ӘfM J?ʂ?JakCa.?눚? @u?c?L?rZ?: }?Н?KW6f?3}84?#I8?wȗ^?,J?@m'o ?I@?ډ fl?nC,?a?BH?ܮ=l? R?v*?rPu?l_Cw@?Ot dy?K{Mr? 90?ѐl?a? 2t?0_C?4?}]6?k8?&?XBw?ܙcN?C=#?!z?ɖF?SP?Oys?{ ?O,* v?UX?>mɥ?`M1?^D?:Xw?~N?YTS,? \??(?rH?A$?ݝ?m{z?:>pl?©p? @?PA͆?D? ?ge)?9a.N?eG4?ҩ.? ?;p?_/?MK)?|ď?g(J>?ﺖ?ytcd?R\aB?-ze??+?YRMI8?q\?Gpy?٣n?!BQ ?ДBU?""t?oes?Ϭ?GA ?yπ?LF?z\eB?SY@?G\?dٰ?7]=0?.  ?]/G?0,yz!? wP8??,\Z?CE?IfN;?d:?\ZF>?R?6 ?.ٳ`?2Ȏ?V.K?iIm??ȚM{t?93&x?S ?6ˬ{?1?wY|?Fn?CMd?/g?Ή4?c;Dy?/? LX?-(?٤xuؠ?o5? A?a'E=F?`"?BsH=~?,?Ы `D?MF u?,EU?-A?ö2la(?= W ?a??r?m-`p?G?u<?0HL?n&?y(t?߽` #0?I R?X?0@?w0 ?h-?Rea?7?Vf?@?N?MA±b?⊴C2?VY-X?R?.V?fl3?M`i?}?p ?צ$??|Z?$Ǵ?>R?)|)?jǥ&?ob9?_7?'C?3 K?Ҩt?M $?%C0?mWd?X'?ƻ?P0O?f;_?面>1?Hk7J?C8)?l]6?2FS?AfZ?ۊ5??ꎆ {?76&q?ˆ>O ?B?Շ7ͻ?!L?KebYd?SA??LE ~I?υ)?ayT?G +?M(?V?t3?E?ݴ2~?Ζ?ULX?Ŧ;Y4?{V۟%??D?MW>?讼h?91~̺?~s*?ũ?8F?էEk(?ݎA~?Q.ő?Z*L"?"ХO?T?jnKi?´j:?ߍ ?nFX?.E@?g?&5Om?x;qA@?'|[?̿ut?O^?3P?JOJbh?|?M4?9 O$j?g?yG?Ƕ|x?6[{nN?w ?/T{Zo?dfг>?uD?ܨK?/a???u$ ?FB?R?W1F?û(4R4?p _?߱:qf?kX?IK?ټԨ@?si?Q}T?'N??VX?߮3@Y?Wua?U: ?/̽_?z?һ*l?ܦhR*?ǒr$/?$Kll?>l,?i +?|GNⴻ?S?w0???=X?A?I)ic?Yl?겲m?]H3(?p?|:2?)a@?8om?䞑Er?ɅOl[?# Xx?S ? #e?:#x?=b)?>?u_??7?c [?h?,aL?yR J?ހ)E?? d,?㺊?⩖?@n(?81x?9׈;?ְ?1 w?B2x?+l[?L1A,?$p?¯m3?Bd?0F?ӗxM^?\u?^?RE?}ߺ?vLk0?l1O?\h? nz?Ǘ˦?k?ﳶAB?T? 84K^?,]\?GG>s?Vi? ?eѤ?F?K/Ѣ?){?|*X?᫿@?/u s?R(J?,?LOD?4@?JnA?t'?Mo&I?rPW?ęUj?s00?g2?.Oq?ޚaJ?b Zp?J?ߚR?_?ԋx?յtJ1T?5-?ǁd?o,?y7W?\v?ZI.?B?Vȴ? ?Uiu?Zv?ո?@?4 |?&{uQ?7Q2y?6\1?+w?3R|Q?چ ,=?k? T?~Qs?ʴHz?T?9쾱?$?d?e&??]8?m(?3c_? :?~e?*A^?ӯ~Ph?ˢT\?p?0?5'|?,e[\?꾾W?+Y &4?;?0?չO?en2d?qq^ ?2߾?,qIc?]AGJ?鏙"`?1w?{OMd??Nݕ?"r??E,?Eg?ꥱU?O?1?btC>?oD ?b92?9J?8?YP?$%E?v:?[D?h1;(?AW?Y-s?z M=;?v?3j?eM ?h?l{0?; ?"Xx~r???Go?S?)Z' =?eŪ ?v ?9$$?ʉ%U?'!?aA?%:?2h\?L/&`?"?Z-{?RLv?YBe?نQ?TT@? vA?Jl|Jd?HXkWv??V ?@ ?K`?h? (?'?$=bޠ?LL ?GT?݋ K@p?l?^.cZv?ӛϐ ?^҅#T?z  ?ʔԓ#?MtD?͋t?%$q?Q"?h ?Dˤ?֪?KSn?8?bJq;d?#m}?75+[?ݾ^Ҥä?G%?Sݟ&?K=+)X?ŠR?74G?4 Ō@?l9?.h٢G?+x&?x; ?ٟeG@??0FN?z'!?mgpM?9gV?#?F ?㈤?ne?s?w(?G3j?ׅw ?y;A?@Q?TJ1?豹Q?̭Ml??屝iO?z6?<7 ?u0?P˗\? |q?Ծ6?W?\&`?X#?T?5v?׌EmRN?ލܟ?wSKgL?[g?: ?AKp?XA`3?Ђ x?K |?o$H?I: `?.vץ?YI9?棰I4?۶(?ZhD?Ue1?C??RtwR?ɰ?B!⭸?KSD?aq?-5?1br!?kF՛? ؐ??N?(?+?8n0?fϠ?B?Pk?3 RA?ᠵ eP?C?淯e?L`?㳹r?'2.u??M?._5]?&? w??kmS?౑ة ?+?*&?s╮Y?A/1?Wdu?,E?}Hx?[SE? e1^f?ՙq? L} 4r?V>z?ܮ?NC ?^?ݐU0?ճW,?ۍ?xNH?6c?׏pXY`??㑴K\y?ڒHL8? ?\^?3b!?焵^^?g?ia4?I\]u?<@_?S< ?k3h;?W?kX?r?RO?̳9?> ?l*?8mQ?9P~i?ʹE?PRCQ? $?nP?ha0Z?N?JF?X腕_?9p?ue0?okM?܏(?ɐ?cJ5?A B?#?Ǘ)aA?s Ÿ?y˟#?}q?2?H`Cj?a~?7כ-?s?)ʐ??i?%0d?y >?3s?n nX?'}?P?[,?X,O ?՞_&?TG?GAq?#U?^Nw2?íX[͘?k*Vh?BE?3[@?=I6n@?;f5?I,a ?pd?f^?M$p*[?ܘVdiz?f(?֍.?b+6$?ˏ@v?zE?ٕ*X?mB?dT?%u?;?]+R"J?Ή-?`>@?`?I? t7$?G?X?{'}1?P!qC?_@?¾?oe+?.'S ?\~?Jڥ?zӬ?,mGQ?P8?v ?g,?,y}}/?&UrPj?ǵKGD? 0Z?^i>$rk?&GO:>?VOb?"?ש'wb?5T?fHel?Xq?3n?dQ ?P+D ?0&t?'+0?;xU?(;ӻ`?z6?}@:_??yP|? 7?D A?e:?}qݺ?Vї?效D?杲D??!}d? ??!~a)?Dzۘ? (?ᨾܰ?5*N?ˀe!?[}93?LA?‡ί? w?]G˲?u؟.?Lƨ(?\!?A H?3SRp?`P?Ԋ٫@?r?qc ?[$@~?~״?h]?vh?5z(&?RaWƴ?F]k?'[?Kt5? &-%?bJ\?&F?B ]?lhx?6:V -p?ΒFd?9*`?իS?G(s;8?2? ?Ֆɐ?eH?>[@?idI?ޠv?`"8t,?z@?b@0?,xc?oe?a/?Ӷ2?ᜒ@(?ꝉdс?J==&??aQY 7t?)(T?(e{?C'?]?M??3W?i9{d ?PZ?rEK?ۘdž? @?4??:*\C?Όl4{? c?]b?Y-?Z?+B?f?FQK ?a8?jl"?;aƣ?hE$?בu??ev1? LV-A?skV?Va?^t2?]`?Mټ?n7- ?c@?""M? ~ ?_P?N_"?^NhH? 5?-KʌX?i4|?ͲP$͘?3tΐ?l?Ŭ`?՟A?mB?:?yv?cf~&?%;?b0?k䍝?uo?_?Гc5P?i$? xV?n??h?/Yt?}< ?JEK?t^@?ף9 v?d?O?%r-j?;?~?#i,?_? ?E W?i?C5?* |U?sKBz?א?T ?NQ?Xx?Rۨ8?#Rv|?m+kt??Wo ? }+X?Lڽ ?K9d?Δ)K`?)I|U`?uF?rI `?֦l5tp??k(M?ٹ>cN?ج lgh?,s"?e^Ϙ? r%%?X#? K;?䙛`?y?Ԧ?0IE6?X}AO?2H2?K=%?̱@?(?>j?taVb?#=? "?@!?Pv у?/+5=?S(?zUJ9?Ym? '?]2?Ġv1?>X}?W5x?lTo?G\?8??ߨpf^?7m ?'qPl?6 sm??E7A`Z.?rH@?84?X;[?k?5#Lۢ?Iq%?eq,?7gH]g?ّ)?z8?燱(n?GZP?6J?kk[3X?Y?Ø2U0?OVO?6?ᦅf=+?4RW?sL?ߥJN?|{e?\,b?]s???_m?l`?Жn?V?&8|}?ϒmX?`֨?8g?|u?U6?ӝ:?qF?rzgZU? p?~VyZ#?Yڝ?\I?킟vjR?ٕ[1 ?_!?dN?^µ%?K=?E`E?Y;?O ?Dc=?zʎ ?O?`??^洼?X ?ּS@?SIz?ԒK%V?QRo?L?/J?e?8~0?|B?řDx?~bI?)f?m?![S?8]R?g3?"+Yo?cO?X, ?zЇ?ײ'0Gt?➭? nZEH?C]?ɞ84 ? Ga?d(?̿p)?i+?ۃ3(0@?.+ĴS?*?!#?W=֩=?Ǚ4h?tb?ɐ:|? @-"?瓭ga"u?‹6YYj?pjr?ǃ+j?u~?팭=h?QjT?b?បGJ?̊y?>3?b?Þat?V?VZF?G?Q:~?/?W*z?N?V?T ׉?,.L)?͓dT?@p?R*?頦0?}L2!?b|8o`?a\*?k`IU?! 댷?U[[@?8g??m|?ܦ׫?3\:?W'H??7u?wfz?3*')?(Z@?K ?EPe?bN6$x?ض!x?Q?YK?RҒ?N\s?躢?Q?:??(Ec?/AM^?( xW6?ڛ?uAk?Ɯ"?ދF!/ ?K1M?N$8?Y&?>?ʟ?R#?˴j!?[5r?9On ??m,?k?V?׋ ?AӪ?V.?$4z?A6G6?0z?~|?˿[H?~O?Ҧ9?h*?~\J?cmV?H~?c ?W{6R?6s?w?qe?eݿ?ڨЩl?ӿ G?ӢikJ1?9ݦ?Ib Tj?cې?pd?Y?e?ZSTku?s:?E|.?ۺ[?tfuQI?Y6ݛvV?4K?Qp R? ze4?4ش?PUE?B?p?kN?&Y?`=c9?)?:f5?q;? ?Il Վ?/?T'?Pڏ}?Z_[?Pv?"?3?ީ?,O?ֵl ?빫?=QոI\?rJK(?G R??{@٨?=}dӰ??o9X?'M>?xR? J8?f^(?-!`?btp?–٭k?ڋ2?+8?$xZ?ŭGw? h?%ۅ?"?6P?uSr*?vfq:?Qi8 ?r35?L<(B?޶7m?՘.p?1k?A5??y1T?@*St?62?QY?&2,?lՋ ?`=wC?أ "P?hz7?dO:?Qǀ?&?m2?L6?}Z#?{䞕(C?jJe?Ѡ&ۆ?Iv?>3P?Ӂ;?7r X?2D?XZ!?9ܟN?U??W&??%P"H?A(? ?̊y?ڈfN?8v7`??@?Pl?ɗ^UtD??~v|?`%n?g?(@?wD?{4ޔY?ɿ\?ݷ$?o ?|1?@|.?ʡ<9$?Fu?vz?ߺ<?T+?\?u4?f=T?A= = K?l/I? n?/9?ߵP?DIQh?pK;?6O?A/?J\?ڈg?|jVI ? $a0?iThp?埐6}'?L:p ?DkZ?筼ty?Ue<?#=Ea?k3Ie?㉄?嶏(?글?AW?ݔ0D9?p@?R_i?٨{c?Ą?9+`?ˉƷ`?)&??%l?،ܒg?'>î*?HJf7?_X &?jiN?m,?$â|t?A`?1`H?퉈{ы?ܓ"K?Ŋgn?5+h?&x?9'D4?6R7s?}{?߀_?яXp?4G-?]yc?t3w?Pa"p?Th9;^?\.Կ?'?׆9ͬ?™?͡UV8?!Ĭ~?0( ?I0?yͦ?D?gC'?,?Jr?[U ?b?vP/~?MEp;?jZxv?~o?ӓX#8? !4?wg81?wJX?R?? ?![6?1cU6?;T?Ez?Uz?wm)? 5?tsk?J;?Q~? ?0?x?iee?Q5X?De?g,`?$D7?+!B w? ???7fx?0BnS?vջ?>ً$??݂]_?āW2? ?\? jud?˔?g ?%UqHh? ?2iK?*?ӎSs?PUW?y$ ?; 瑷?u:W?к47R?ד'Ir?2wI?ct0?!i:?($P? b`?!=?h Ɂ?ϝ')O?KW?wt?WJ8?1?nFԑ?#b?y﫢?̪ ?$?3G?Wtg(ܾ?3y?&;5?m}Ndr?S[u?iq !?H=3?bUɁ?V?)щ\??Iᑉ?dيa?D4b?դS?{8?7T[*@?կ?A'$?1,.)?ܜ)?qwc?69,b?6,?v?HOP?rF{?Á`?ŀ?5>3((?}?bTQB?t] ?$ 0??Ӂ9%T?П6;?VUk2?6H?E l>?{#`?MWjj?ƙst ?m ?q?@#k?ҏV?{?چY?R u$?膀2?oQ?ύn?>"?16 `?ˣˣ?էy \?]@m?姽7?}?AK?P44?́¸?> ?szHV?ȭZŅ?ThX?-LJ^?Oۋ?ܿ~?β ?Ŋ7??t?"u?3nnt?봢tmT?MH?ׇCK|?#09?q ?'?r؉L?Ӿ笓?Yc?ݩ?nh? z?u=?^k ?ĵ/`?(#x?zU`?3pc? r?isj?4ȁϲ?O%?v?:ףƪ?帇m+x?YV?ٞd?3PR?y|?K?%+ ? ?Ֆ't$?Jx?fc?Fk6?Vk?ˮ?4@?>kQ)??Z&.?r"0?"u?۫k?S'ń?I ?,? I?և3fO?!ƕq+?h"?ͩw ?mZe&?v툈?ߗ?Gڅ@?Ss}pB?Zr?NG?0ư?gƒ?/q?&Q|?I?qa@?k?轵mgd?$? U0?B2?C6+'?V?1+&w?kEy~?ÛHpE?ۆ?uڴ ?һN?jԁ\,? T?ܼҡe?u{rY?{n?{ζF?Ĭ?#6(?(/?Υ&0[?i^n?ΑO?Ot?ޝf v~?6Ϣ?(^t'?0|?̢{?nu ? ֘{?נc8+0?<?}f?y??o?8J2D?Q3?w6_?y ?l<?=t?)M굼?Nl?ƬԊD?\??)t? $s?ݔгl?@0?ȫH@?;W?e%?ߜe2?x_?~A?3S?ZHHD?";?![ ?݇߻?1mg~?8r?빟 {?P߮ v?ɹ)J(?x$?>?j?\PĶ?'a@?I(h?Ǜ >?\LJC?ي?fJt?Hlf5?cwY?,Z˿?ݐw??м$?yt?)sN?.(c{?M;D?mgl?N_E?ʐV*?ꋾQ?GGU?L(Q!?}c:?=5??,ȵ? 5?U. ?Wy? G~?/Hr?oѯ?hY^\?df]?-wkM?li?B?զk>?ﰤ#h?LveH*? v2?O?ˑ%3Q?Ю6vT?З9[?߇bq;?quI?M?ӷ ?l4?5?h|?͗`?`W ?ia?˧|??{$.?E??ڡF?ԇrN3?|Wsrz?ed?d3?ڊcO?瀻?a?6c3?I?%?E&pXG?$1-?װ>R?r ?׾?d?\uWf?iE 1^?8e5D?¤ P8? Y?Ɋt?Ʌ??e7,?/rbT?8MA?䌋?abx>?&Z?v_? h?lW??}j]8?C?ā?f?;g|I?dF?ԓ{? ?IMP)?G?4:?)?ƥ?vf?f%!?G?ݍ0g?ٲ$?ׁ?%s?1??Fٴ..?? ?؝ ?A2M?Zg`?'W?3|?cC:?jhaX?Ѝ'e*?EU"?/(Y ?!?;?2@8?o?q$9?'N.?OI`?W(?V?IN ?+? ,p{?m?ھ@?>U? @s?kJ2L?#aU?17A? _{?<?.<{,?yu?R؈(?whP?8J? q8?Fz\?0^?rR ?TޮF?̩?u:?t\!F?Fy.?gO?ۀ0?P\?Xcɦ^?M Gh]?(?iU@?;=m6(?O!qp?&_?o$?o[?7P?3]s?V ?v?\SUG?U͚L?8Lfd?ލΫt?Έ_?Ĕ?7L?uNh?{2?I܄tf??_?Se??9P H?qڞB?g@?|Uf<]?םEkv?N?37?38?*'?\?ʾ6W?4(?N%?.-y>?Jp?¯ek?nA|?ӛ5YF?t?=Tc?1׍c?ֱּJ? %?}^}??m ?nD??#?j&E??7p?&%zA?g?,ԟ?3UD?EbL?dRCX? ?aEۈ?՗R?h?3~y?si? 1"?_?R?$u:G{r?1V ?cݣK?N?Ǚ?)W?XY?"Gꅍ?czE?H4x?ɔ-l?*ɼs?̝]*eL?Џ%v?z>?D?a[,?֋d.6?|v?Hx-?yM/:0?MF&?4#C?+X?^t?쒜@bj>?߸p?߰&!?otD?,0?mc??7?B`(@?wV?c!5@?GL*?g|-J?ʑnG?文?؝,?oww_?N ?ߛW[&?zJ?#ӄ?W-?F))'?=RA?s( ?_ @?,Jh?-cO? .o?/ 5(?3_ E?҂N?2?=}S?T#ӹ?GE?1Ғ?2:+Y?" 9?&*?d?=.t7?x?(,`$?=va0?kj?5?H;p[?9էe?j?UKVl?tf???Pka?iI?I6?O`?ґ\?Պ?5p?喑XJ?gn?*X?8?4%?l?lۂ|?{ɽ"?ͅ?/9F ?㸪G?探?R?au=? ?gl? C;͊?f"ާ?%u܎d?Т |?$i?vUd?ޣ>?$^˅?[3de??- ?@^#F~?M|?Yw,1 ?o(,2H?Љ׻h?Xm?Z U?’R?v(Fp?`ק?mv?~ٰL? Г`?L\g?^[:?6X|?\Vp7?SY?h?(/?Aʣ?~?ٲ@?APVS?ϟB?{y?-? H?AQBM?ݗo?gbbI? 4F?Ѭ>R?42?'=?Mx?gģ5 ?T8'@?|R #>?A! ? gd?Et?^c?5௉??vǃ?C?Z'? W*{?w+%?'bb0?vgi^?y:p?滵"?еV[\?gv?ъDH?ʞi?-?ox?5ه?)|c&?8b?ҳY`?Q?ED?lLS?A?ٚ2?ّ?g?Ag?>?M4?z?I}?nMp?no?t  +?"P 3?␤,„?팳$W?sPmX?MJ?$z?7f?:[?ɢwf?f?N*??㸣&C?~;cc?ƾ ?j2G?*F?۴#** ?eOgX?Hq:?~9x?ܞCz?/aj?"b?#rg3?ɞi ?Փ&??ؕuG?8|?W>?+?LÇ? K~?~(|?(e{6u?+!k?#ʳG)?]fI?9C9?)?~OKf?WJ$?wjH9B?۶OŴ?_\M?qb?x?턉?^8T?0ˊ?lq4״?!BR|?>΃2 ?tI?~1R?Ӥv?rH W+? ͹Jkt?ۏyfL?v}H?(?&|?n ׷?({?}G?V{?)3i?i뢸?v d?e"?ϳN?F-J?!ǒE?T(`)?ƼIp?@28?|+o?> ?ݏot?G->i%u??οt?h?[s??R?لn,B?n? |e?tE?|N?РNX@?ya??D?=h?4)?䷀?? `?~?8M:?vQב@?Kםa?ql??윑`?uU ?R[?{ϑ?>"?ą @?›p?Jz?vD?Bj?}&g?Q??P%,-?d2U?:K?1 ̞?㵅]x?āȌ8?o~_?g:N?l?m??bƸ?פ0?8Q?H??";e?Q$Y?<+) ?Ԡ6 .?B0)3?6?F?B~?˾ ?':,+$?l+?1/X?wH"?zy?L732?W ?*e<;?wN?:"S?ȈK?,!\?k 4?Atn?'8?.).?Ց1n? ЀB?LD$2,?;z0$?cFnN? ]j?™Z?} ?r>`?0)|?SYp%?dqu?)֑Li?1e?1A3?Jl<|??(Ӱ?48m?|dZ hl?#.? acRP@?ŏ"?~?FCO`?:D]?ĵ?X~X?6?U?`BL?QuP?װ Uk? k[? "Š?+8?@?p?gV*??|_?ЇԄT@?H?iq?P77?W6?4b?**?9W8?魷;?acp?XBA"R?(?MF?n A@?Y?/:]$?0U?S?Cu?=Kz?X¥Y?[E.K?ܕ#F?) O?g?J~?̀]PB'?q/Ӂ?Ű #?ʚ澲A?x?>J?րt7?H:?" u?Bэb?xKp?Y?㗤n?/?0p? s ?4+@?=?H㳮?5lM?B+&?j>s?Өr?>G6?у%â?өИ?HJ?T9?^^eYv?0!T?m]?Tm0?Ґ\U?*~4?vC"?濉x"?u?\h?>?K^ ?dJktt? av?]xs"?Us0Ј,?5?WSS`?9?A2`n?+0H??LJm?4?홒.>g?S$!??dju,?B.G?˖T?ce(~V?ƴp{ ?v?=: ?묘H? 'L? 6}?D9O?#h"?ߧ+LD?n`?sDx?)-IeB?1v?cB?U\⇖\?ԀYhp,?vx?Kqku?+ Ob?n:r ?ق ?@#-?Jr?Ϝ?֯\N?e?ܐV 2?Qؽ?~"q?@2^?"|Al?Ptf?5C?| ?/ ?W Ii,?ʗ@b?堵?Dܙ?ڋ??:?W5?ӄ[G>?ȟ? ?uufD?ˢ)#f?؛h?IQ;?, Lu?*ɊA?_cg?ؙ?bTrx?QgwQ? X~?bO@?-?fT3K?@(ʠ?յ8?G?p@Op?_F?2M?i@d ? @7?+"ʟ?]HlZ?짌:8?c:Ğ?B?d&?МYb?{Ȁ?rpm?R?=q?ꐤ?꼖U ?6rS?=8z?Q?i继70?}^C?ݱ=r?qD?!Y? N?oSY? m?1`?҄}J74?q3D8?w" z?DT0?6`? t$O?9IƋ?TZk#6?'G?0?p?r`?҂q?,G ?| ?Ж;?ר ?aEw?h?aur8?x",?,hѻ?/2j?/M F?I%)?(ey?ad`?Wک?S"? pL?DL?ՄݕO?殺/? {?`h ?p1??j G?z ?چv";H?o?ޖmr?r]? Lel?ӦR A?6L¸?Y,c?eQdh?oUwO?2Mqx?\-\ ?ⷠ ?pJ?z}i?撫?M9&\?[$v?c\g?Ј[V?R@?@g?ў?"5?"ج}?Ū7x?g71|?fe|/@?5?)Ƕ8?ԶM?W'@F,?\Z$ ?LTٹ?0/? ?Nɘ? ?̕0?Ƞܐ?ގ4x?~h? ql?I?X1 Z?S$O?@?U-3{? fD?i!6fJ?"x?Ϗ|-?Br?4C?4a?Z.`??P?6E?0y?-?f?~ g?m?B1?Ǘ5]?h%?ٍQ/?T?:Ps?*{s ?옳?ur)V?]!3?Y?D0?ݴ.TiB?}?ahN?''^?4cO? w?c?=Iʚ?߃h7?1?E?'m[?۬?L?dv?u?)~=?Үz#Ĭ?xx|??<'3v?#l ?rҒ?? x6? &.j?ˏ?N_T?㸙V+?`tu_?@S?}W??UbQ?R?:@?hwH?#]g?_?="~?Y\Ó~??8O#վ?Cw?],?RSY?Ct?N°O?C ;^?DZd ?ť8>z|?JcH?"Ea?dP9p?žz?ڷ-`?h{fx?Mda?Ȯޒ1?)??DXF?ȯ Q?؀c?^$)@?w,T?jOY+5?uxV?DS'8W?Ng8?Rqq?)K%?/?a8Kx?j<?럻~?L?0nX?.>v?إt4?-["?i'YE4?ݦT?߂gƩ?օ"h?RBp?fD?~h?tH7~? ?уw^&?:]?_??%n;?{qv=?]@?m6| ?+n?^i?4Zg??Q?s_Kj?SU\l?јQ%M2?Tecj?Z|?"M2V? =@4??d?čh?Ay'p?͆p$?We=k;r?Mv?QY?җs?@C?dĶ5:j? $jTl??`?(?I_3?:?P͡L}? ?몟i? t?[2?j&?;i!??DUu?vzY?J?杖L?ۖ C?ڙ'? i?ֳ2Q?q{?[+PH`?6?兒t?2)7wy?y*w?ܷ WN? 6j?Ǣ?*ߙ?:"?Os?U/x?A>foo?tv8?Nʰ0?b>3? ?K?/.?Q?ý,p 8?0$"&?덪?$rc{U?ڰ}R\?g7+?(e6?4C.??Kz?ĵ?5Eu?̠?PgL?tOa?fG-?ەbw? rm?ꀺ `b?Pr?>?ݮ?٧T^?> ?:?Ԟ$c|?̈́#`?Y&rXl?~ xP?;x? ;?,Ayo?UX?玗̔? D?$w?2 |?\aE?}I?ޓ eB?)L?oS ?ו #s?ԭ?S,?zy!?-"?R.F? ?+?uH=?qU?嚀A ]?~| ?$k~?o@T?:B ?USD ?[?Z%?h?o@l? z?m*-?f-(?DĎh:?R?h1l6?zC8?afn?bAH;?]I?E!z?X?; p?r?qJ?(ݒ}V?z?n??f@w\T??zn[?PY?×{?鍰 K0?}'?=0ks?C9U?ݍdͼ?o{? Zv[/?ώ?u?G*?ފ>?sm`??(]dS?vɵL?V;??Z?ȇ ?c?!%?ԩB?*3Eo?؛4?bx?y. h??Es]?:m?,|?8(h[ ?嵁S?ʹ˅h,?$z?Oq ?Q/bV?@~2`?O6 [?>-m?-p?=?D ?ݳ/1?䠸=&D?Ow?L?G >2F?aXr?M?F:>?݆0?# B?ʀmq9?Tq&S?,ŏ?]~Y?d!?z8*p?!V ?܍^?R!\\?CY?T+l?ףAg? I!?Kj?n?/7L?GO8?^!4?䘿Mک?< ?YI `?Fs1?؟#H?pY,?Xl?6?Pm ?ܯAH?(?Rc?NCsC?/Q"|? sPN?$ݩ?H7N?(d"0?9rG?25$?Fhي4?3O44?DG?ԘhV? w?Sbl?@?SDP?J?yR?Z{ ?"?l?L*. ?׳g ??.}?珍j"?ۜ8`T,?4Y?ójT?!Jy|@?Q^?k̚?~H?V?cv?Zb?D!'?P?ܯf?,-I?Ї^5U*?Ubp?T&??A?3?rE?s ǽ?(*x?{֛B?O~?p&0? di;?cی?vP |jv?gb?ъʠ3ޮ?D?݃?+YΰH?"h?;$?{ JKp?n? ;t?S7^? Խ?ԮyoH?a>??Ȳd?pE+nF?0 c?( j?E?+ ?>X?+=&?9Qp?b$d?Z\?\!?ސ?i ?ڒ<?oI`?98?C䧑:?pEV?3?S?J^~?$? ??9Sڀ?ذ I*-F?]?ߞ̏:?}/xj?d E?& ^@?@??&Ŝ?Āk@?Ÿ2ֹH8?@TG?0 ?Fg?Ӻum?Տ;+?託?6ĉI?i7B ??Nwh?YQ?˜Xj?ؿE?񈌮?e$?Z?܆/g?>?2RN?z?-C2?_璱?ywE? d??IT?0|^?H}?ةn?mn?m(?C=F?}N ?Z ?G|)?o!?^@F€?c ? ?p' G'??J?D$?%֝, ?ὺu?w>cT?ѡ׽n ?#H $??.? Ks]?@c8?Չޅp?=eT?͏Wv?&9v?HT? kFxV[?DH?xEh?װ]Ս?=V?[%ִ?ޒ6?? lC?qaD?[ ,-?>>WP?̊ % ?,v4?Ug?J?ܽv?1|(?Ç(?B!(J?!?ߒOR9@?lM?hK?xc?HH?앂^?╌@Xe?X ?@YbP?"f?4,?j*@0?18Gq?AM?Mˎ?vO ?/8;0F?%Y\\?'B7O#?V?舧?mͦ?(^ ?cZF4?.&?h}i?ö]$?ڥD4?Dad?tD E?Gc?kWQ?0\?Ch"? r?%?_?t- ?P*H?sb0?d[e??Ք:?ܭ;-f? Nm)?Mj>ܨfv?_Ep?ޫ\aPN?¬p?U'Zn?ۋsE?R#܆T?/p ?Gq? TL?,:D?5^2?hVl?Ֆ"?ゕU?zK?1y9?&j?̪c?0/!?5->?U~NE ?ː/?W Q ]?vl;#?\h?.YH?T]s?pb?bL??rD?9?GY?ٮ)m^?£=S*?_v+?qP?n,8r9?k%?"|*j?U͗?4?ԙ.?w5?r?eoۀDX?  ?:f?=$3?eV?ݛl?ʗBZ?ܗ<`x?4hN6?ʴu3P?R2?eO;?5ekz?h?{rO?ڢ7=?#逍A?YU2?tXBOm?BY^?y,?8AU+p?ȇyL?"|?t>??w@?(3F? u?^r>(?؄g$,?1; ]$?T~?}!8?_F65?*+b?.&?wR? (`=?xZ?c7?!둱?ڛy?:eF?.uT?fg&?N5_?#gf?d U?75L!v?H{?8o?Ca?7]d`?q?G?Ju?V@?l|??Qkr?B'/?O@?[F>2h?헑yZ??.`?@}?e?ԋS? ?7O? 6?o?^Y?2t?r?.=?&*o,R?ِ/?~VL?4L?~?)zl2?af?&n?͍ ?,҇B? ?Ԝ.A?wS?)w ?x~?WC4?ux?)`?~e?_d?ue,L?^Ո?# K??_Q?À?heŐ?rc,?ɇ? 31R?C-??㌼Sg?s?;eQ.?L_?"H"?܀0WF? a?-bT?l[?~&?N:w?ww?+,?8 ?r+?_|ꀝ?^a??ZD? pJ5?h9#(?Ř?4?ͤ [@?3i(?T'f??8>?繊s2-?.y?Зl-?‹ڈ?֎Qs ? y?ӂ"?H` ?C_=?v ?w|H?ZX?1ȴ>?3?* Z[?4L?@ kLJ?}{ ?ᦷp e?=F?#,҆?Lʮ?8K?.=?+'?OC?.{Մ?D ?bKh?i_X63?(?ԣL?/y4?,eP?奨oW?}K2?dlY? 217? Z?Oڃ~?I`\?9(^?j.?5a0|?Mm?i$d?a???v?ֹ*eF?M&;R?ҫfi?پ0?N#?꼛3?ӑ=}M?*^?cm;E?ٴp?j6??eVz?ː?$Lr? Nj?7?{B?7 lу?#0?}7V?zi'C?꺁[8?ϙ?-8?wD?N@?GE?LEG:?qA}?霎??sUg?L3Tp?; ?>JX?K䰦?EƄ?񉁥T~?*wh_%?❮u?bo;?k%~N?fHcf?CϨyf?.?};z?~?}~?9i"?ybT?vfR?NjX0?иL?lMJGj?E&l&? ?@&:H?9YT6??Ṇ"&?nr? ?Ǫq?zȠ ?ƬofF? ?] ?GL?" ?>jʖ(?Su|t??ΖI?A墦v?Ÿ9E?@ѧ`?="I{?1mk]"?a ? u?ኆ2???`m&O??h 7?֬yȡt?y_:/?ׅ&?8J9Z?t4?ZvFd?"z42?WG?ƓQ?a͕1?yŇ&?#$Đ?Ѓ\t?"@TM? D@?~{í???٧;sX?$?m ?B%x?6)R?(Ye?K𳆙`?&K@?UJ?-?ӑߟ@?Rq?vyIu?:*?8_?u ?*om?w^`? z`?짌?gۤ8?,WC?#HG ?6@?(?֮?5N )?W? 5H?މ)Z\?<`c?*FL?4HH?!Y?%)qr?ŕ?݅PuI?bn?|^k ??V(?h.?[J(?l?[ȎNg??“F?ڲ1T?Ѻvy.?鬡J?ȉ*6?T+?捗`h`U?:p(K?GI?ɠ4&?䱠nù?h$J?*7ub?InZB?Ǵ?!Z?Ư?: _?mP q#?gtF?4[Ǔ?陴G%L? a/ ?@sA?GKw?軼z?+?P#p?}?Ip7)?SPVXe?ijՌ?ZF?S?-s?LV0?c?A?e!E݈?ꀜѴ?H?եX?lM= ?`ut?th?oM20?:?L }?ȦK?N2?NJL?,2?"E+?튍!T?gF?qUp?_?:{b?Mr/ ?S-?ףҏ?J@)?p??_?IK?j?Qݽ?b'?x ˖?Gī?/<-? 䘊?ո?̴T?or?p5i?~D9y?H?8 R$?Ss?Əf?r?¬q?n ?r?e?%q4v? 'B?׾=E?}\j?6L^?z{?X2?߇b?jL֚?jo׳?NϨ?R`H?r_ ?}H*U ?26t?Y^D?Ch?0?WG8P ?#ͭ6|?^h?eձ?@#?1Rh?/^$?1?s=N?ؗ>?$ÑS=@?Xݘ?#gW?xKm`?ZH?ᵈM/?X*?42?3%;?؋i2? }\p?s*RN?W>ۘ?ՋLq ?G?怜H?P@f?v!?c93?K"E?;D}s?k)"?6!?@Rsl?ߦ_0?)[e?ަG[e?YW?[ ?,`?Fr?G`$š?n?J!?*fJ?cK?M #?珐q ??2l?䟒>h?w{Q/?i?|D$V?WK?['5?Zi?v?&?0GE?{^QՀ?@ /T?^B)?Bo-?wz?^h?}? ??<?x;3?`M?+?Hi7R?h4?VD?_Y?5 ?cSi?ا!n*?{1?1MDžB?TZޘ?vT?3ck?8~R0?㖄w32?n[?:?'a8?Y_?ZL?0а?}u?܁{l?3pG?)bM?0& t9?d/|?Z*R ?!H3zҼ?+0?O8?럤=tH?Q[X0?Md`T?E{?#? `?od+ȃ?:ڠ?UO?5ZR?`O,p?gO?mkQ?*u?p ? WK?!Lei?ꈫ,?XH?ȑ)t?k~Ym_?TuB?*?ފ@8?ڣps8&?-O?d )?ilj?a3?͜A\H?_'f?Em?EP ?B?ۦ?މ dV?[J1z? ?6lˮ?42?ӤW?-PV?1V2?~R?eP?lG?S3|?અ?u~? WS?L ?xj`?/L1 ?,r?N?ԁZ?B^?_i^?`b?Ϟh? ^L??$jE?A^?\r6? ?.);?3!?H=#?Bk8?5IL?Gw'Þ?㇫Zt?࠿j?Hղ???y,掝M?1C?0b,Ƀ?vں 0?߻qz?'BQw?j^?ֻM?&[?ts:"?ѝ/,?JKk0)?+ ?Խa?k\L8}?|=Uw?뙵y?Ӆ?j?䬐~m1?o^?GE~N?䒠(???ޤp?67d?r?0`L,F?Z?8C|r!g?R7,?9@o?NA>h\T? O?ʚ?3&BH?QbI? 2?KV ?C7?K$,?K9f?Mn?ر]58?'1 t?F9?9F"@?M'7?*P ??+?r_k?Y`?@S'd?{ ?e&?L6?[&?*?< Q7??H? m?9,4?3_@?!;?0NO!?ۉsu2?(M?'Q?T&?\?k}?:)M?Gl?ӷ*Njx?v[g?;S^?睐,~Y?禘oY?sS?sRw?Mbd?hF?;* z?2?C>(?ׄX?ďb?M?MLn0?qX?m#Ҫ?3g?~ơ\?ʸ^?)ٻ?D>YVI=?(t?A%#?o/?m??i&?ì?u|>?B?H?q5p?~?vKt،? Ԫ?L0.?D40?>{L?vP?ͮw?f @?u`?[?!;?ybm?[G??яY 2?wu?Rr?Y?3C}A)?њ}p?n 6?BoÙ?y?^֣?胁H? j ?YGwݴ?g?ƂW?M@UЈ?ҟ?U΅l ?0B$l? $V?x ?Ҵ ?W2T?pU[?$?|`?Im?͖r?[.>p?|]l?guQb?o? yI?d9?M6m?߫uŃ?-m?Āf? 6ص~?5?Qn$?VR?dua1?}q"|?5t?j=2|?H)wL?* C@?ӀD?KJ?|-x?&+c?^GwP?wy?_7?}R)nK?ȿ5 ?ȱ_?K$5?֊d?U@?9G\0?ׇ#/??8/#@?#P?^À?ׄ(Q?ӕY0f?#O?Yvґ?0Nbx?֙r?د d?͗#D0?WB@Q?־?͕1Ƹ?grQ?.]r?׭  ?U?2.a|?{IW?:0;:?5vX?TcV?ڴc\?IB?˙gl?e?B@?.g?װz p?eg z?zg5?j6ؔr?V?'ǾT?DA?/TC?abB?C?3G?vP{?/?ݛ|^j?Q9?]8?xL?f ?WS|?B? u9?F'v]?e ?k?AN?< ?ځG: ?Iϓ?,p? Ag?t"?|^r?5v?vP6]?LL~?XD?/@?Q+,?Yῠ??)-U? ?YǦ?D1HH?s=aM7?MWJY?7=?f仞?S!լ?Ɲ'/0$?ߥ4(?WH?N#e?u? 0?ƕ/qa? DC?? hc?}qI?j{`"?+?c?{y?]a?BV?0?>Q#h?ū^$\?nZ`?XA?!ݯrh?l6 ?r} ?O4??z'-Z? Zd?뺵%? [&?* d@,?@?nj$D?{}g^? [ƌ?m?Gd2S?>A?rȏ|?N??DLe??q9J?ʿ?밂@ f?wE?(vI(?v ?/3b?S&?&8p?@?#?f ? ?ǀ?E?B?Sێ@?E??J?룏j ?jKZV?w[??3WD@?e?EDv?zs?Ӏ?Ʈ?z`?퇥(嫚?gY*?~ul?W9?98?hH?י|^?O֠?gpW0?R4e?çiv?,GXz?h&?i?Sv?ߜ[e/j?T G?U4?te?L縭R?~?Ezt?柘=?7B'?IPH;'(?ȧuIA?}/:|,?75?(&~3?H(n?$% ? e?b5n?0yO1?!N𨗌?%$h?[΢J?ۢ<?mSr?@y?Q[ X?ښ?yop?>-@?@ %?J?Lᶹ?۪c?o VW? ?J/ ?53-?Wt2?Z?? 1:*?IQw?}};H"?h@Mjt?y?NH?b6?dE?C˽O?QX??vT[?S?SH4?X1@?]P5?fr?۟u?)" ?:69?.;~?poM?z;?w@ t?v-s2?IdM?¼?Ǧ#7?W}%?JO28?+?{>?բ y?3I? H?(V?=p?Z?޹82-?F;b?p}g?ӹ?|]??xV7`?d?!C?O;9?׭=?ᆣw]?Zvf;?@?ؽ ?s坝?<?`WM)v?Ƞ^?fzk?d?WYG?t/?x9P?|D?#?2$}?HͫC(?+!? =?%HW`?bf?ٽNi% 6?/1D?ݽ_'T?.Bl?0?8Ǐa?R͆+?_?R1"?М2Kywj?\;?+A"?DRL?Ѱl?/+z?Fd?A?䧳ktM?рJ8?O~ ?ℽ?D=?2)?Ml?؀i?Ȁ'6?V'd?b8X?%7C?~[Ѷ?蠜c??|K?i7?dO?뛝M+?`0h?N?틪W??CBG`?"%?Y8?ܷ6$?촎̎?T0"?s篈? lޣ^?yK-҇?0":?A`Y?f̼?D?ʄ?D7Kk?V9M?ۋ^x? D?Ű?陦bM?rm_?4?EB?qwF?垨?A?͙?Ί+?(Z͌?&NJ9C??e{?mJ?jOR`?R.5Ÿ?bZ?umx?nX?S ?/At?X;? ?P/?p?6f?iNAed? v8?u,?b)?M7?҂ג66???!y?F32?_?V?c?6/Y8?n41?}Irx?k??쿀{?ƈ?܃A,?*Gz?+%o?Ժ2+?Y?߲2@?hQx?'=kg?6=0f?ɥv?VVa?vH ?!~}@?lv?]3lP?ޞsf?H?CP?#b ?Lf-?˜m.?=-?`J?q2)^?Lw¼?2a?:뇠?j']?ǃo8?%3@k G?&i j?[`?{?qG?w ~~?v7#0? %ʜ?W8?x ?Bh;;?#*]x?Fu?MX?؀08f?˦ty?"F 6S?݈?'`?/SsK?.fx?h;nQ?hj$?H ^=?ؒAe?m?֨B?^?*c?Z$?ӟ:?w?Ɍ?Ơ ?u֡b??⨇^,?WIm?i%n"?G:9?B~q?a@ .?75?ժC?.?f?%=q?:e?=P?+H?5?fڻ9?[_I???,?!?^GW?+W?={?.Fa? |?e4?+?ꉥC}?3b5?iv,? ?sw?snŐ?єm ?ڨ?*8?x+?LG?NW?8k?p[Ô?DK?,??zdV=?ƒҸ?Vg?@*?ÌSc?oJn?BRm?%J?ȁR ?[#?s8?q=l>x?gT?!W@?d>v(?͹lO.?Ȼ-`q`?@Ǖ?dU?տDl?3(?t*_?\?5C?ћF? 1H?^u?U ?0E?pfѴP? L{?ѩOX?Φ[-?Ðܘ?^z+?Ƣ%3? (?Ujy?pPdUF?֖ӟ?yHb-?̽6^?F?'H?1AI?8)??$wJ=??lQz?sP?U>?w<&0̈́?1ڱS?(l?w=F>?W?〛◣?,9ol? DU???i~?闌P?r? 䆌7?+_Y?$tO ?E$&?!C?:i?xbr2?I?aWm?|J7?経.L?eA?6 3?x>?כ(R?q)P?㜇`R?@ ?߅8Ǧ?q(r?نWT? t?7Yp?~.?T\?sO?9)?^1PNJ?ӗx?젎T8?3mx ??9:?^ts?*?Ď?Ly{ ?qӔS ?ɌS$:?Ty?S R&?íĶ ?#j?bKV?"?ݴ}?dذ?%l# ?Z?x?*$:+?L?pc ?3?cz?ͣ ?ve??~qp ?%,?ۓ"7 ?P?LXȸQ?J.‹n?r;? Nz?О; ?,?$>?|^? ?gn?"&??_C?MǏEN?b4F?Jg?бByO?9~ȱM?ʤh?λջ ?0:Z?կY^?8R?`g*?CN3?↶w??C76?E،S?YX?n=?f?x?x?%+|Y?6{j??m v?1B?Y?=sUdj? pX?,f?^[)?qذ?пԨ?FTC?蚫!?jk?d?oP?ͧb?!Cs?19?R5hH?G?6]zm?՛K? }xl?T?vq]?=>4N?Ы2h?*?qR +?Dx?Zn|?y1?5D?σ8?*Λz?~?Du5?%ڿ?߰ T ?"ﳀ?ބp?=uP?߇?i I?kEH?~m\?u@,K?94?脉7#?PD? C:?JA,N?~/)?nOP?Ocy? `:?/`L8?\NS?g?D%r?XL?3.1%?W?Ѝ!1G7P?]\?#O/?wa?H?nH?۔aD?ں*{j?9&?%:)?QsXy?98?U?2`" ?uI?$(?ڰ*?D>0?+?5>4V?ºd?:Vy?cl]|?yV?hYc? m?f}6!?|?Et?^a+??,'F>b?D?` ?:Xbao?ն}?z?UPB?"aQ?C@̺?ĉ2?]zM?'i[p?[ ?.7?cI?ʼnP?8-29?PZ@?M;=?lY? c?ìVk$?WKyp?ȇR?a~A?ƶHf?mhK?h)k?|M>?"?T'?ջ7¦$?_Yf?w`?6?;a P?0j?ӹp?\?͞'\Xe??@?ca?>`?ް?>d:?/?ɩNZH7>cN?c8`?L(?9?=&?뚗2NV?Kx? U2?# ?6J?&'̈?"?: @V?"\d?y? օ?q?fwU4x??إ?(pk?8?p3?ʣ ;x?E/Fp?Ūd8?Ҙ僛 ?UgA?s>?"a?׈謦?jb?|\T?Ժ/,0?jW&?|&P?f;&? s?ڏC?PD̀?ﳑ??N,?1?v^?F<'7?Ӆl&? !r?>__nt?곜?((?u?.RH=?e?Ö/?z}?`pa?ew)?%p?;%ǚÜ?Gei?4`?;]??Ձn?鎍?S?٢yCHN?Fg?iXUf?d?jL!?ԚO?M?E-b?ݒV?q ?גwx?I?v,?(I?=xM?^?Kg?KդO`?p)R@? *?+6?C$??L?Ӆ_?h+?ԁp?NCo?Å?ʜbQ?s#{? 0n3?Ir?3?l(?Ӛ?أ7D`?Яg}z?r!6?/?|+9?k?*O#?\!0? [/m_?u?L?n=v?F?CfC? }."??\ހ?׫1J?Jˮ?xpT?yv?ƒy g?rz|??\Wvo*?H?*_ ?܋?x$o?b.y?ŪP?)? _a1 ?Z%r ?+F?'% !?V@[?ҤVR?]'?8!$U?א#?*mI0?-_||?i1?-Z?aYQ`?T/?U4?,?'~?9@(?? ?GLH?ꪌNT"?E?*3?! L?Jye,?%1?o62?a?_{R?JJ&?qd?BW?$?Nq:?? l5r'?լ^?ؚeI?Ŧ7[2x?uk?L Pu?|Zǭ?S??4!EЉ^?¯ìd?? n?4D9Ou?R/^7?ٵm&?O$,?J{?9 ^?*De?QV?Չ?Y{?c4B? ?{Y@?[73?G{$?z ?R^?㣌Rh?wrcD?tN?3W?;O8?+ˮB?K?UY??FiP?*:-K?={b*?yJ?y*? ?n ?~;?ԇlb?(?qj ?yig2?͖Y"?8gt̿?G8?b?yz7R?Ô<$%?໣GC=?ג_?? ?G0چ?xR?e~?fxU?:8z?~40?!1\?@}C?omr?&?ܝ䃌p?տV ?b!?1?D.1?8M<0?כO]x?赲[?{?5?A`? Y?D?=?˖ˣz?5?Ԉ?  ?=&?A~[L?<`V?4M?ȑ?ם2m^?0ZF? f?)?jޠ?L٘?ϕh(?'9i$?g&2?ʘ]?ة/u^?jƪ?SIHp?d \}-?فB?j?g4?V?ʿQ͌?-X?ݿ>eR?йZ}(?o?}ad?R?Ly 7? Vw??E?ġV%?nĂ,?E ?s?ʃ [?(1`+0?'8?!`?(z?@'u?ѧ+1 ??#g6?ѽ?D?KOWY?-' `'?ӛw?Qm ?{`2?I;?6F? ?@V \?0k?7'#Kk?ȧA?vzn?ء>l%D?^䱆?*?U ?Ox`\?ۿ,:?xE ?GAK7?U"a?PW\?%y? ?oG$?gYG*{? v ?h6?6⧛b?nU[4?!t `?/?s[~%?al h?ŕ?:͞`?q|p?c?i?k ??Ȥn?3!?ᤶ9?:E~?4d]7?2)'9?HJQ?%o?0ez?ZG?0 h?븦ƒ?̐i?FT?189??آi?3}?c τ?ǫ?Z=P?,~??Q$h4?uz-?; ?lb?ʹ?2?=JT?+߰?'%\}?Q?;?` ??-SQ??o ?x҇?nJ?zפs?bvf? &"?iD??R?rc>h?HA"/?f1\?u@΂?sB>Z?ٻ͘?ݵ S?/dU.?C F|?R?)A)? ? ,^?5{?ԟq(?[gF?ŇT? Y?)?cΘ8?~v?VN3n?uK3 ?#[Jma?e@~?S!s?x)5?ԗ#?p{\?+]?C=v?繼 ?,h*?:c8?J_h-?{#H?P?jo30??LK+?cF+T&?$y? 0 (?ߜvM4??DK?':$?ڋPU2?F95??~? ?]N?z.?x_? ׸fv?ת1>?c7X?/<`?!8?? SG8?F?o?ۄFs?^."<?y? ?hg?jNz? JæN?g*?彂?uz?2{H?BIH?,l?5?@Ø?Q;A`?‰\[[?]?C d?޾C,?x7? ,M?h3P?Fd?DS2?pZbk??zU?0Z?c?NŽ?ZR+?2hD?`c?.??!d???(9/?N?Ǥ?C? ;S??œ ?%=-3?[ ?=?A?Ղ3N?p6eM?sO?œ?Nㅍ?x?/*]`?q?{xr?L?PnFYy?H8?EF?#Y?r^;$|?QOy+?xz(?>FW?x80P?<-M!?ڮ?p/?5:?r񛈴? :?ȦBXÎ?dgp?+Rd0?ҀOr?Ԣ?)EZI?Ȍ ?쩚ݸv-?z%um?Y+O?aO@?wZ^?2SC?}G?6B?Z:?os`?$Y??׎&g?A~^N?1sø?v P?>H?ؿn?9ka_ ?RQt?dQ?hˊ?I8?셭9M?M?/Є?\j_?e#P?k㌄?%fL? vN?#9?%s߃?(a ?3;|?ߋfa"?7p?OS4z?ONF?M?yԤ ?&8KV?KFm?-?^U?8aFm?N?ٌs?ÍQ?+[T1;?ADY?V ?yjz?y4d?:Ásp?a?W|?qT?죓?+?ކv`?R|?݂bn ?޷[T?~˲'?~S60?!K"O?ۂY?W?|3㛥?[Q,?\_0$?;?A?̴?eض?l?ʑ=?)k_?ݯ?ɂ~?,*z? zm 9?ty?P^?R/p?}?EE]?7ڠ?Ȳ"?漁A$?pXE0?dd8?Ȍh^?P?8}1X?ye?cdPk?f9>H?P?#P?)A|T?ܗAҸ?pȇ?ܤQr ?{,??B?FQ?!A@?" i?T?KL?!2L?n^ZXbP?e\!M?ڽt B?nq?h3ȒP?L?Z?Pc?:&?TqO?o?vn bU?r3?d?A(e6?QD?Ǔ}?㉗U?i>^ ?ĨE?ymڐ?1?˹fg=t? ^?X ?v>@?8:?L9:y?63={t?"T?[k%+?~r&?abu?\/?>y?+?Fyaz?C{w?I??-?Э?-D)?:?ﹷL,?C?ʼOWC?F6X\?FN"kD?ՉYV?0(nR?wX?x2Ȍ?{:1X?B?6φ&?՘4?҆t2?Η#[?Tw?еʀ?ޒ`5?gi̠?"? ?f?ݸV?^8d|?c+tP?vq?ڦ̞Z?$?e<&?[?u4?{ގ?kG?NJ Ӵ?ӏS/F?c|d?N@?P1f?,v?UT?Ko?ջu,?% ?ǐI?[?ymQ?ܣǨ?ELZ?6h0Y?2?Ѻ:ZaB?/J}?5Tbf?چx?B !?!2?Ȣ]?݋|4?؟s2ʹ?z?5EP?Y z?S$?w6?k!?~?>D s?].?5{0?+!99?KAgq3%?jZD?*)4x?I?Z%U?&pD?li?/d-l?(K ?ᘕL-?6E`up?K U?6Lx4? ?Z`?͗ј??vlpn?维rFg?9NŪk?]X?j%aL?M ?@?2?̰k0t??17?]áٌ?|j?"?C?f-E??*j?6?X?Bb?o _?l.P?:Tj?ʔeP?/ ?಑?~X?ޤtW4?̆El?Yɽ3c?בp?"~Fe?Kɋ2?ߵͽ?=?B&??bV?| |^?v?-bC?*yS?ջx ?AH'?rnݦ4?^NM? sm8?G.J}?_~?oU? ??o?}y?9?3X?F?p?" ?7~]?N,2Y?W?Z|Ol?rd3u?qW}?UDž?隰,?ͻ^?-8? )??gr?+<ݮJ?Uv?f?t.B?ƑFW?]\jJ?#1g?찙6?>~#?A/?-09?|~?Q?gg?煩?ؿ ?^?,?[D?6?i*cM?Ǒmm?zsa?ہ (?(KE,?A獾?B|?T@?(f?/o?Vϼ~Q?V?9h?I?=.?ߍIfv?ٓ5l?Υ̕?,3|?lm? ?zj4ޣ*??^?&N@^?S ?'5N?Oj^p?qqpc?JrVM?ל/ v?l/g?2:L?)&ƺ?Jl?D `?W@f?E4?a?%(?B?zAB?|Dte?܌?9(UCT?֝r?Iߴ?yr?3.HP? ??q2)?п Ar?Ua?wv?r?YPbJ?vH)OQ? ,?ph+5?̀Ǭ?{E+?ފh X?ȴn? ?ͻ ?мi@sB?Z?Ƭ?}JgG`?ӊ,{?s|?}?PnG/^?:,?Mߨj?얜?5$-ѳ?b?eP ?Ҷ1$?H?E1X?R|,D?,p?I ? /?ڐv?3T?΃J?^@?'?Z??d1X?<t?m9969f?݊Z ?ipg@? {?Bz?qa{?Se5?3`i?j?ZA10?Ք5q5?0j2k}?ƅ얘?ҽ2?s٪Tu?zK ?W?j{9C?͒Nr?YsId?F@W)? ??=)L?X?ʼf?樤ddo?@x ?^y ?)1?+(?Zk(?](9A݇?;n?㋂hU?$e*?[F ?u:%?U?? ?O̕?ϸ/bS?˝F(?ixX?绩 ?A d?KP>x?|+ͱ?? ? z?)^.?|@?ıy}?B??ܨ"P?(?L*1.?E,??8@?c^v}U?װ ?orS7p?dJh? qF?Hv@?ԍ.T?&m?Л? ? ]7]?7?~HM])?xߥJ?3s?A?j 9h?*c?! x6?ޣοz?M\?d?چ(?Z+(?|S`?$OB?Wo?Ćߡ?~h?jN?V9 ^?ïT0?ߩ?CF?j?\m7ϩ?g ?YxT?C?׊]8?U6JWr?xEbm?J"?ZVX?cO?F^ ?ٗ[.p?p$??8:?QQ1)?*I?!ɹ?c֨? ,t? #(??ʻ?j}$?޾^?X.?AiT?&$-?Ӓ?gB?"+%0?펤^?%8?5ԦƆ? ?+D?ᐉMx7?bK*?ⷴ `?vJ?夠"?C|S;?BZ[?Ԇ?9i*,?㒷Z?KN !nP?(4?ƾ\?Joc?8%U?aٚ*?.٫Y$?e?;U?6w?j)?;\/7?ֿ cu??Ȍ?~?RlZ0?8JW?jGK?5?'78?TK?`s{?Nr?u?!?ܜٽ?&lE?7]A?np?9??j?$Lwє?镖U ?r5׻?ҘNS?{s&?ښ?U9%?S+8??ٍw)?"Mh?gR?[3:?W@@?聠x?5Ծ?J?y <2L?!|0?ۖR(?y?&A?_Tˆ?ĬvT?)dtl?E*)?Wݮ?G, `?@b?Ѫ?spɪ ?Ik?? m?6?IDW?3d"/?ax?{+E?=? å+?Vw?j8L?ݍQ?=?b?Z ?˗HC@?cS4;? s?|$ER0a?\k!?PE(?h޴w?~Y?{A-?uAV?Ȥ򪈤? ?j?֭?Re{R?d>0?c$[?+2?ҚwEWf?MT?\?e?#ؕ9?t?Ƀ4j88?ˤ6 4?q?N?&h?ȃ'(?G",?pƧ(?JNx?D0G?. ot?֥R@ ?fP8?N_ K?WAр?* %^?۰czv?㳭1?eS|[?6g??jmiAh?z!$?-l?O)h?F1 8e?^xw7?D}?;g|t?aaߚ??tyvGF?v-s?''p?ܟU<.?"S?q?ӡS9?I"?ؿS>?  w?ȝ~q?b޿?ס;tT?q??GU?]+se?JM$?^G?fp[͐?"?a?r?Ȍ}}P?ʓ1?ɧJ?4X?m?$Bߐ?1 GK?E͈<6P?16?ϲ+?eS?@t?Mg\{?]7?ȟq8?^;=?@bT?GY?GEP>?̭G7 ?⼥6?ć4? +?w?Z ق8?x?{?wRK?|?a ?n<_D?V+?ϏI&mL?mF"?٩.?К7A?~G ??/HP? #g?0u{`?Or?yk?H|x?%OP??˖? cbl?0ܞ0?ߨ9?nMQM?:=x?zi? +?%9?ӳN??l L?Ke?r7h?.;?lg?"?*+r?(?ڤG{?[e ?B(?ДVxd?^%f0?R?oU?m^rh?ظhr?l,?B? ?lf-?f ?Ȍ?u ?TQsZ?[<Ġ?ѸI?nH6?cjC?kۤT? Na?kT0?ni?-x3}??]?X[?ƚ?چ+h{b?[R?–D?J?jez;?r͍(?T?Wޤ&#?ȉBL?rȠ?I?_?ጳ2?XG5>?L|?L?ҒJp?'>@?}!?lV?iD#_?؇h?oi G?d?s?Y᯻?LP&v~?K?޸l6 ?Ch?a~?ܕX\ڈ?ӫ>R?s?R8?wtj4 ?8q,?/'i3og?W ?Dˡw8?׀!?z%?C642?hg}?utd?`.?p?y9;?9~wM?ݏx&?OP?~ҢĮ?m;?F?~>T? O?ysG?g#?y??(?ܜ1`ch?JG?9V4P?t>"?$T?^m`?榉L(#?ꊂ hs?63X?pVX?;y1???E?Zj[?8?S/?:?`?y]ۀ?ꜩ?*??,?\yf?z9?vzB?͉s?ڭQn?7m?\PT?k̶}?I*j?D+? ]?<?ij2|?D7?Y[t:P?fW?Z?ė^&wx?^G?+{@.~?̋-?OQ?Q!kl? u7?v6I/V?DK?.?A5?ͧ?yF?6?)r b??_9!t?d?Ү %*?il?ե 6?Ϫ[g?漇&?$Z`?d|7? T&~?z7k"H?j?x=G? ?N?u?sN]?w;m?!c?SWڋ?qG%A@?R0צd?qd0*?vUw? 0T?g?c~6J?ݣ7?M"?Bo IL?\16?V|{?̄?e'x?é ;$?'Ӑ2?kP??؁?O)`?L?㎜y??l?Ӎ7)?˺{_? x?TR*J\?Cx?F?bx?'T?w=˒?ނ"+I ? _?}+4? _F[9??(?og?h?װTt U?\Ÿ?1K(? jfd??W?ˬ?E?5?I?Ģ> k?ħ֋(?Y3n=03?5bX$F??8]?wC(?todc?gC?u0?yK-l? #Yv?-a?t#x?&c?+eZ?mr?ʇ' ?J-?^yP,=?aIz?ኒXC,?΁?"*?M_ձ? *`?$y|X?\3 ?n' r?/`?5O? )]?Dh?dqn?Y ?i% /_?08L,?AD?/)?pCr?Ru??WȔ:?2&WK?ڏbYD?j*$?i?ȎDOˉ?#|)?h?a ?Vh?Sf?ÂD?鼲Ch?we?Җp?&`??P4?/?:ӏ?[DS?Гa?ý yg?'%6?gE ?h?\pp?^WP?\ީR? ?x[l ?HN ?h?+p??ӅK^?@Zl?ƜZm=L?2}?i]?o-0?ՑX>? JY ,? օ?H.^?ұJ3]?n8?xDT_o?֚y ?މgP?C??I?du@i?DL0?\?] ?DV?N??x?sb? ֩?3!{?Zf??ȡY?# ?UgoLt?d ?C ?o+%?v)?}_F?ޖꎨ~? g ??V3%??=b?hPZ^?TUTP?z~b?4޴?ˎj1??Ҿ8?ꖪrOp?ş^j?0yl{?Qjq,?_Ѷ?ؐsӀ?M,k? 4q?B-t?&0r?|3?k7Wk?볷#?OU?ڒ|?Z0?47@?΄yo0?H?i,?̹W?HW?zd B?٢2 o?_q=?N=Rl@?P7"?:N=p?@Z.?|W?Zpx4?EsV/?nВ??lS?=+?!jQ,?ˌ,MX?ǭbX? l@?*x?_>"S?t7|m?qI ~?ِ?MB?Qj?e?L:x?C$?\°d?v'?onh^?GT??ґ+ڂ?zǸ? ǛZ?.?5(L?tg?&G[N?)T|x? mZ?FO ?ωj?۝hho`?&|H ?x?؄"ź?>M~O@? n 4?Ia3s?SM8?UʆQl?hx?ěB ?I4CG?c+Xg?הڄf?֯f?6 ?!& y]?õ5?Lf?弹b7'?)B??ˍ0[p?&9?,҂?RP?\O$?K b?ܣg޾?;ǐ?ё: ?MK?Xp?'?e;23?؈M?;pjP?ݦI$^v?ķ[?%(pS?ӍȧL?#7?x@{ ?Kic?(Rz?FzH?, .%?׏?]d0?b?B8eVL?,[?鮥Z?' d?.?뙥]?(ϓ??4 ?ߋi7??&?C?ގ=k?bcZ?`8'ֆ?u+?]FW#??tAq?ķhGx?KG?i(? G)F?Կi?L2sR?gnIT& ?TO?P7@?w 4?0J?^%j?VP?,p?29}?uZJ?& ʋR?膬3]bl?կm?>$lr?y?杏{r?Z?5G_D?훿l3?Lɺ@?q`Hj?9l?d9,?ůGb??ڕ?Xu ?|}56=?xd"?¶,@?rl#y9?H'͘A?I;t ?@p%?~ ?eF f?ܡs-.?D;c?r}ĽE?2l?.:3$?d'/ ?P{?@8h?GSn?x.?B` P??~V?N~?/?Շ}?ߦ?M?mh?I?w!?/,j%&?Am :k?x x?ƒNa?yՖ?S&օ0?OK| ?JbK?/T?:͠?%?C˜?ܖ:"?30?*7?.X˃?[?#g?XC{՘?erBu?̤G?F`q?_?]2?o~?So?* ?/֋=??8??򈵖?z?jO}H?.z?,QVD?h?mL?2±$?a8? d(?ɖޙ?JQ?֧f?֩R?¢Nn?sC p?_'A?Ӟgk?o*@?k}?%Z%a+??ׄ[},? yYb?چg?id|?u?\?͎`)?_|R? $z?!?ހ? S:?؉)e?ݢy.?ɞ? ?p.&w?プ[$?߀*1;?b< ?눫8??r+>/?9T* ?4?Dfh>*?2Bʎv?ݤN?>E?Z(4?sPw ?$=b?#g? R8?)f?> R?*L ~?:?Q3G8?ٻ{ ?v?G-M?Z?dKƊR?Iw`?ԀO3t?%P?v`?W&?c*8?弴y?ჰnM? ֠?0H?ZpP?+sP?bP?g>E?yyc?Ou]?Ryk?~H*[M?ѐ?E:҉M?G+6?Q ?ʮhTaP?_"E?S&?Z?1D*?b T%??;&?e.?!?֠*?᫮Uv?hd?+Ę.?\1 ,?ծYÚ?7?SȈ?ىT"?ߚfm?5?jh?^܎S?Z ?Z $? Xv2?BAm:?-p8/?ގ:?. ?F!$>?`Q?΃o?-?uA`??g4?Ƿ?ƞˮu?$/X ?|2X?h\c5?иsK,? ?Mi~??C]h?h?tt?З*?=Q>"?=B?WX??HPw?ksVC?pp?B?QܵdeL? 4r?pF?]?x?⫌M%?N ?5&1G?Z)Z?FmC?S?&?1%?-Kͽ?>?= ?a=~?eEBdJ?':?3t ?H3?kJ?> ?Oz?@30?ߢZ䋺?Di:?<Zu"?lrn͎?@?C ?K!u>?? DAx?ӳ~?\Պ?A?,>-j?@$g?qaj=?^_??r?؅`?Ij&?xuW?K.?%9-?wa?ґA?Nx9?q_?{T?N Xo?PjU?˰+a2?5׃?h?}N?4pp?/J*S?nUq ?Opk?qߴ?A_ Y?]q9?l^ȍ?Q2?A1U??^ d?T?k"K2?CX?G\?ǿ^t?6?Vwo?n(d?c.??5?ca?y~S`P?]޽?yG}? &%?`%?bd?`?x?bcP? 6T ?@?)2Foņ?~j?v8G?6`v?靈~?j,YҐ?tn\?]?Bl?~-h?g폝"?ジ(u?r7?F?ؙD:?"5?b?Ryq[?>(v?la3?ӻ?a?(eﻺ?&.???&Pר?=?t 7'@?ߍ?? #?Y@?~E? xν٪?@|?Ty)C&?زtAj?Ț& ?撛FŻ?(-?4P?Kf? D?f; ?{o7h?OZ̖?2L\? v$V?Nm? Z`oR?ݿ`?ö?e"?яV$?Һ]mZ? 6P?g 8?şlU0?=|b?᝿9M_?b_?I???Vk?peц?C?X?}B/̀?s-?"?&a75P?n$\? 0?2 : ?*PS?]ffI?HLu4?$2,O?ԚY{?  d?ьGHP?08?`IrK9?Eb`?p\?+%?帠w?d?ׇ!: ;?\8IJ?o7?K¢?e?[.?wEb&?|$ό?޼qT|?w@?#2?RX?钊X?矣8?5?ܑGy&?ְZ? ?3#ԑ4???8z`?A6=x?S?x|t?Q`ؼ?Uߕ?sdΠ?4r?%?UrG?Q5?ɷx?>lD?'n?n?B?(9{??38?R!?Н6F?w*r"??LL|?.y?!#E1?~#[?Ԏ-?b9-?p ?)v?cCP?ne?7n?7+?؇Ik@?vB?8u?%P 9?_w ?eL M?PM@(?cfEVi?]H8?듃=?哂IH?vuY?0'p?ؖi?dž4Q?5R?4L?`.?Vy0?V$?ኅdQ.?A2U\?5V/?ڙ?ӜUP? 1,?nt?&?o㝦cf?7>P?f ?=?()?KX?#-+f?QsW?o7?,?ߕ5!F?ld@P?2ˆ?x?>?ín Y??(^,?o? =? 6?tW<?4-"?C *#?oj$?:&Œ?4?7C|?Cp}!?M?iQ D?pr@??hA?$"?$?1uɍ?@=?Rj?'X-9?D46.? ~p@?#J?SR?([?G[)?X5a?&nH?1?x)?æe?D?Ȼݬ?>h@?a,g?~8v?|s򲤕?殧M0?_B?O/?&Q5> ?j|?xC?q@:J??agRK?N~?ú?D>L ?RQ*j@?H;?u[2@?j*_!??Ŝd"?zJ?.iJ?Y5XT?H?篫;"K?X܁?aWt ?|UiZ?h9A?kS^?Ӥˢ?~ T?lf*?ǁox`?ݧKT ?%?^?n< ?_㷵"?-ZqD?Z/4?+١>}?1;W?0)i?ݑOoL??Y|=?s?эœ~?05QP??A \?"JXN?ʐ?H???A%?)J?yN? ¥?iw*?#|+?ta?s~?Py??)G"L?Mπ?ڴTl?_ "?2`U @?7b?w?+?+9? ?x,%?NF?r(Z?/Tw?3}wt?,̝D`?rhy?ݒ-q<̬?r? ,n`ӄ? ϐ ?*7;?4%?҅x\?.Ŭ?C+?@`?]E^?jx$ ?{>?KD`?M ??9 Gh?N <T?5YW?РV ?\NH?۹2g2?y~?Jy?K=??g%w?G݆?ՎU?y#w?6Q?vvwCS?!Ҡ9^?fP?z$a?ͭ)<5?}?g`?5 7[?}5 N?:he?'?(?Dix*?IH7L?8h^?׍-B??'teT?ˈw5?9G(?\x ?wOF?i?W?LGN?t?8W?P ®?T˿(`?]BTx?X\q?:JĐ?սBŴ??bP?אΥ?HÀ?Ү.z?l #?};`?`?Z(|?O}?Of0??z?/?s#Ò?U^@?*x&V?"??Ê9R^x?qB?sc~@?A?߽ !?K<@?ҫaP?5vZ?> P׆? wM?B6T?ݮ X?{k?I ?bw5?]e-?3kޚ ?n:p?C(?츮8 =?٬@?۟'S6?+n(?_;J?P?Mo?-0?A߾0?뢏g?ST h?[cp?Hf?L*˵?wk^?Ȧ:?T_?nk|o?J|P?6jE?㹗 O?נ?U!.?ёq&?_T?B[ `?Qh?@?-Sx?ײ.0?l? {N?GgB#? 2?|y=?~bG`? P%D?W<??5ޠV?9>dy?O?1!yl?\8?%:/F?ԸV? &?,%R?̆s?)ı?Ch?Ԑ,?g|d?ۊ{?'ꠌ?Xv'h?_,T?'?օ`?盋 ?vxA?`{t?Oxs?粶l?䴇J?㯶b?ʘl?mC?#v?0ϗ?4V#?㹅nK?>Nt$S?edd#?=ڟ϶?F[?4e`Z?jp?r?DY?R#O?7JU?BY]?8?h? CAm?ܡ? ?'jhB?H?뉺?٧um?!e@?g?L%?z @?kH@?aT?Ijr?'1?叝l?=?iM?d?x ;?}pys?B?&}X?/|n?vJ@Yr?<0?}?:A?^M$?=]?4vS?ܨ7\?sd֟?:YL? GP?R)?}Uje.?r4.?\/?͓?x]б]?1?>Q*3Oc?ݚV:?ҋQEd?\K?s?(J?4?&LAO?娫C]?تg ?,To?ظ0?5h?{in?ҙHn:?߾ :,?&)I?rB?Ỵ?vR]r?z % H?Xǣq?_>,?Pa?܃PP?vK^?q-?UjB?_@?Bŷ ?FΆ'?N;_w?zR?ViR?JX? ?2Ig $?ݷX ? =$?Jw?$F"?mg}?V?Y[?ΫBO9?o6?~MV˨?Fd?”V?ç<\?ӥF?ȇ| ?I":?.?K'YP?s7?2^?q<ݍa?yX?c?>? ?Slg:?0VOL?r?1?ṯ̌Co?;sۃr?U8?>U?ݚai?^S6?u7j?\Б?n>?LIQ[?2JH?&?̣?D?P?Պ]XO(?N&q?Gdg?sD?<-?Ю`?24 ?I?1tn?,V?rA5?Az?dfm ?Y7 ?E={?3 `?8?Eޫ?Ԇ,l(?ƪЕ?S?ҧ6? ?Bx?Ӏ'?-5N? >x?Q8?5@:?_w6?qju?݁\U?ґҐ+3?KCX?QWmr?Y:a?X?:~?߾&}P?]N ^?:E4(?V)^ ?rÉÀ??eNL?70?Ù`? ?P<?j*?֝;݋,?2ZN?펩d ?ͱ7{?H7PI? }}_G?5a&1?JN?|v@$?8__4u?Wr ?sT?ő9tp?f ?V?GC@p?얛G9?Ӳ? $ ?^ ?_O"?)i?' ?Ğ?t4?C`E? tIb?/)Hx?ĄXk ?ۘd?w휯i?ĉ?\j?yd;.?~| ?=)P?]o?Tm+?#tH}?ڹ)jFP?!pf?H5R@? eG*?5K?S?()?ԗ*0?צI"!?}mNl?9`?3`??z| ?x8g?h?DOe…?lMA?h往?A_t?g2?׫#i-R?שdu?5$q?H?םTv8?4"@?W!?L.;m"? Ŝ?k>]?{,?Ev?^lp'?ij0o?ͨISt?fO\5?fa?KWVe0?c?Tg^_?|_?i?oQT?Eʠ?SԵ???<[?V୆?9f?xNu46?/ qiF?t8Q`?E w?蝜V? QLG?~$?e\;O?)?PZ?Z?5&B>??cJe?ጇ? !H?䊴L?٪'??qD?)59!?#?!P"?͟@?D?%f[?^4 `?ѣu?ޮl?^l?y?r+?P^??zciY?7`"`??Ɣ43L?̘^?ϩЍ?i?h:?Wt/ lX??:Y?jQH.?r s?,??Q?W ?[ D? xH?Ղ+b?્XM+?A.?R*A?hYmx?ĴGs?l.7?)bö?ҙ9*??4e?&ƍy?\m<?},Ro?Y~?P#?i?`f!?ցht?\?קw4*?ƯX?E?š6GN?Ò?o? ?$`0?FL?ѩi'I?A{ ?j96?y=?|w"?2/Iw?ؔNj?靦w?,M~C?lძ ?%Zwng?U S('8?:w??5.0`?v>H?8yڮ?Z:*?l &?T?0LlGO?ן??☞ֶ?Dn0?Y?B3azt?FB5f?Xcͽ?.`_|?ڥ?ƴKd?$=.i?(?/O?(?[E?0*Ap?m W?? 68?nh ]?ԔO;?Kޒ?n0e0ר?tX2?L?룛s? 8Ys?;*"?n+?4/6?^օ?6z-?uTh ?wZ?˥?}AX?/F?옼W{4?Y7%?ӞUʾ?en? `??k[#n?y~?۽&}?-(?ɵ_?Q#?^XC?ꍮ@,?̵$Kn?n$?X, ?6?ӓ? g?2_Vr?jnKu8?A(ؐ?݂*bbr?w?/?Ϯ0?wN[?gt@?kpf"?|E?~(?n2z?ط?ޠص?!B8?4 p?A<+?wZ?ff?ͯIo`?ߜh?T~ ?vFg?B?Hv_?$?^fw?BuPl?Ӣ?\FQ? :K@??R|u0e?'N??Am?~(j?6w?& >q?ݕ|8?e;42?rUi?ar<Ί?Nx6V?媝e?٪|?9(m?ɱ??Q?@?'?{ßk?Ah?^j5?b?3d?z"?U}???l`?ϞenN8?;%?\N76?r"??ٖ ?R`?qݪ?HEG](?+n}?~S\?ӟqw?MQMv?׹?D?WY?^| 4?#ғ?-8π?'߉\?hv?F$Ժ?4ijhP?a`?ٶ3".?&d?!]p?#$2?ɒd{? 7w?Ҍ??t8Ӝ?A5?ϩd'o? j(?1l??&;Jxy?9\?#N(?;V?P{?3GJL?zv+ ?ВX?onaH? %90?wBU?*R?Qv?0 ?aJ?*DD?y-c?ٍMX ?=ޒvuD?G ? 6d?TeW?g?r? 'Wm?TK?ϫV??W^?кؑz?r?(Q1?Ϝ3H?ͭ?'?6Twb?6r?OGo?|?nw?&?d8̇f0?Wl\?ŷs?͡@w ?9K5xN?j /?{p?l3?`q?E u?S.C?X̚?I'RP?,?ti?ڳ 첔?`W&?Ɲ8?{ӊґ?,???Q(t?r?ꠦSε?EG p?[(^??3CYq?Ϗ ?P???eN\?|?_zD?o?LoLc?C3*? =f*?֞S]?Ts )?@?u%D?+ ?Đ,?E8!?dTl?i4L?yGʂz?';N?٦o&=?C'7?nw,?U5?-v@?Z̩?H?䝠-?ݙ~~}?h19? {N?!M,?KGo^x?ީӿV?"1?j ?k+Q?Ѽ ?XgJ?ìsxP?!j?R$Ae?DJT? EQ??k?L_?b?Вh?0j5?Իdr^.?m4? ? u%?H(?H9(?l?>?,Y?k?#"׳p?pt?o2OJ?B۱?"{?(7?-z?v T?c'G]{?`ޖ? Ji?r/?H%_.?,k{?)?AC5?1V@y?HAN?a3?ke]?U`?̵X@?뚼Y?h9h9?e4~? {y?/-?' ?P?JTo+_?l>?\@]?b\`?fH?i㵝?@?0[?ez?.,?6`?j X?w?Ӝҭ?q1? ~?6L?-:?SN'?ny6 ?{4 ? u?-2S`??T?O,?!2l?&ˍ?9aT?ŋ*?8#@?z|_}?9?5;Rk?"ǪX?h@ޛ?-)h?Qq[4?r_p?%$np?ņuT0??%?򠳌?U? IQ?Bԁ?Ơo|?&@?$ ? ?D?v3](?~Ԯk?Ѳb؃?~ؾ?$s?[?uC23?o|P(?ӏmJY?ًh69?ʇd?|wg?̇MZ{?RGk?`޼TB?h2Ott?ݼc-|?e'Q?a0G? ?cuqK?-x,?~l?]9s?hh{?#Tx?ž\?^L͵?Wm?g~d?~?[A?ꘫ¸?sRQ?噥l?û( 6? ?L?JS?ۼ?Α?\?7kQ?%"z@<ˁB?,-9?넄Sn???ˆV?| \?n*T?[أw?) lӌ?pw? ?2?qϡ@\?σ.T?:{?|.b?R?\os?sf/? {?p??`oa'??G/[?Yh?0#E?n`?麿?~#X?BE0?t?k~?Zs?ݽa?Iu?0 N?iF?kat?܏KF?ԍ?X?ؑ^?!_w?׸?|i?66i?Fz?T4B ?(?gX?ԂL?ѭy/)?cc5?0ƶ?_d?~h?6{? E?扶tّ?6H?)aM?V93?8s?-ּ?m)R?ZmB?腘Q?ұ!?k}?rUB1?s=*^?bl?ѝX~?Ԫ6'5?2u: H?9h?n;?f;?Xq?]4eJ?C[+R?yaA?m*sql?y u?^3€?2`?{u?`Ύ?Ӭ#vt? a?kT-?}?19?ވP??y6KH?=4?B?`Ǒ?h%?ܤ]j?ZU?= ?}L?$??Ɠ+g?ܱ?Q-?y?P g?g'&1?wȐ?N4Q?>\?ުS4"?_r˚?"$])?ތ4R??H?ʧӧ(? (~?\Dh?kE"?`o ?Q6o?hll|Q?ֽŽZ?`n?ug#?@?N(R? R?$P? =??ۉ"?)ؾ?;ԭj?E6ɫW?#DgFc?:sd{?=2?\ m?шQT?ۦ ?d:6?Pi?X6=?(XGz?V"?"z?v!X 2?Ikډ?-١+?ބjj?؋{Lp?p?U?׊"?ό?s%?,2F`?%?珝?5 ?퍽?u?nF&?wDwW"?Y'Q6?y8&?Ϲ$e?{x?78k?,?)Se?ffw ?0I{V?+9K?W\?4``?Dh?vh?d7ٜ?y7?PNd?ߐ?M!N???שT?;S&?\#?%Q?Իtht?P8?߅@?gA ?ë==(?n )?Tl?r x+?I/D?}?FQRZ?R?dYe ?Hx~?E$Mt?V ?c̬?Ens?X=)?? q\?>R?j$,$,?C3"?s{ ?={eE?S???ܝj?י?vɰ?X +wR?+NV? LDF? ?gٰ?_#? i?2y?Qʥ?㗢?W .??_W?ݪ?2|Ӱ? ?*P?"Uް?s?ހ9? ?D]?ˠƄ?7K0b?xʩn?=ˠ?[i?7 9?dl?ي(%?E?딉?*k?ۣU?#Ái? }?cO)?5;\%?{=?ʎ ?Xt@?玂hSX?Wy?e~+C`?aE?҉Z?В@Jp?jgT?޴X ?:p?BAp?lCW&oR?ﻁ=\?މ?]Y@?ڛù?S9Z.?_v.?!tzf? 6,?L?u#$?l%?EKE_?୺}Mк?Fp?* \?_?GSmA?צgI?+aߋW?U`?О?&"x?犷;?jG?ܫeb?z?ڻ7id?[J?룫)?~$?G?|HFM?&Ե8?3K? G?pʯQ?5"(??ۘh???RAH?GX]|?5m??dٶ?sZR?~VEs?ƍ8́?W䮹?}Fv?ݗ=??AV3>?5[P?z Bz?~2E?Jʟ ?ϽsL,??W4?Z솆$? g?c\?v1g?gJ$?aA.@/D?3ll??H芾?X=?λ d.?㪠kV]?̵7ɬ??|i?ߦ>Ȝ?7pyu?uf?]b.?]ž?ԛ)o~?FI @?@~2z?Bf?d+x#?3=>0?-M?e%?mxx ?'ԡ=-d?Z|N?9f8?1K&F@?۴32?D?ˣ=??m?r?¿?ߑ@?pa?N\?mLǨ?\ϩ?뺱?OZ(?Y$@ـ?ᔑ?ި8I(?럡>ă?KL?I\?Ψ3)?҅D8?J?(iɯ9x?>?nt@?\?j?VOk?zgpјB?h&U?-h?ա9P? {}?@?*~S?7~?ڿV@&?Y![g?M6X"?,j?v?~?Ь0?ҳ%+p?We?IM?ᐁe?qv"?W_N??xO?d`n?-݌? dy?6(o ?㽨?gp?π?d?mU"l ?6?vP #?Q?D?B: ]x?xb?;h?I+?^_#p?g9t?5ī?|-?+k?㺫/0?،D?zɇ]?"?|?NiB\?`ۗl?ň6K?Z@sD?mi?5oWH?罃g?oQ ?h{$?qE??+dF?yU?&foJ|?i& J? \]?bWV-?㽍?QT3?$?ڕ[?rh?ܝz?Ƈ̡?<\f:?Q??ñ]S0?`?ۂg)?YJj?qK+?n3?N?|t.6?#t?ֹ"=?GX`?I?ʅFp?Tݨ?*_)?ZgE?WXIK?(9{Q?ׅ ?|QT? ?O?S=`"5L?ΫQ?/~ ?Wm`?{H?[٭MPl?]ϓ?ԋ?(?l>R?RQI?*`8?F?auҸ?6:?o-vo?x?3T!?eYt[?J[d?#?lz3?5/?.- ?JH?K I8r+?֦]prX?i?:ͧRM?k V*?f*?y^*?V do? Ӕ?.!;?Kt5`?HX-?m興F?C<?=?6C?Y:X3?X ?m ?Г]Gf?a8t?~?W7?绠-?~ ?A V?3P?}_?+J=N@?в€v~2? XrC?׊Yڌ?n'O?5B?՟f?;?n:?׸|V0?é!?ܳ;?)GSp?%Z ?s}O?1dŠ?#;?p+?(Cz?Dq$?l+(ƣ??g@(?=kc? Siڐ?b ?G}??R?1X4?Ĭ=?KZ|ar?z ?.v.?qm-?09$?&k?B/†I?l?^zޙ?C?| ^l?l7?W ?qаv*?`B? n?kx?;_S?vX?~%ǫP?v/?;pk0?_L?'q?˜?:[?:K?~?彚[?{/ZR'?v?Ƨ!?Eei?#kI?G&?&ͬ ?ǟ8?AakJ?4P?S-?xk?滨 i!?R?:X?쁨r??t&?J?3Qc?kP6.?fM?5N ?]#K?̑n?x6?F4az?{? E?xHp?Vݯ1C?4W6? Wڸ6? `p~?:Xlp?Ӱb?l,on?;CR?[ v?$&C?@F?S!?DCL?/o?Vu1?޵5N?fto?Nf?=/v?{ð?oz”?5n?'C+?űCr?ŊRb??H"<?V ?VO{I_?Ύ?۬k?xGӺM=?t?"PX<:v?'0{?ayVm?D?OU+N?۽?lp#?! ?Z5?XH6?r#?Ѯ-sT?>_?kQE?3?:?Aj_?I\8 &?첷}Р?5F?tQVZ?$@ԯ?^2:?>p?B(?Ըݮ?t5?5?۞b?`0O?dv0:?釀r?pS?X3?W ?LD?B4?i͏?x9U߀?y2P?9V%7?]ۘu?1ʒ^??z} ?) 7C@?Ҷ-M?ߛ{ ?`CΡ?달~?CW?MPB?Pɒ?s%&?r?lvV?קJ?=Sz? 鶱?i "?\Tֈ?/x?֍?JYqQ?7t?ƪk?b@?)?b~?Α?ʷ[s?evf?؇e|&?@5?$ԃ?ؠFGh?RD?vo|?֊K(?mbt?luh?C~Tk?馡 N?7oJ:?zqd??Ay4?MM_?H4ʘD?bh3?ULY?u?Z :$?/?@ꋿ?hAl?˧?q߻8m?䖛QXg?J͡?ivXE?}?m"@?rq0L?%J"?!}b?Hg?I1vh?G@?mVu?鐖|v@?P6,?ixtk?9?/q 1?Z?H x?3y"?Ʒ٠?7k?ER B??3_#??Ԃ{?ԋ}?r?c`"Y?Bܗd?h}pG?¨ P?I Z?͠33P?KN)+?vt?'p?aw?USq? |H?}^;h?HK̆?ƦY?h(?~ ? {?pR?= 2'?fEX?9k[t?ؒ4?}!k{?:4?;$C?gx::?#|O?zC{?%>?J,)?Ɖ6l?Di?U6SJI?=ؤiz?/a#,?\x?A!r\? +?e;?Ңyl?+c?W?Ntu??]a]S?+8?^bWҬ?gCQ?ش̥=?~C?\P8?/W ?1au?"5}z?YOl? X?*?׃ ?Ę+l?08?PH?7P?܃=?ZG>? ?5@?}Ծn?b" ?ϻ"_? b?P3?A\"F?ȏ?t|?m֖?k2E?ⱐg0?j^?rY?ΐ?dh?}g? (?Sr̖r?t?=^ù7? ;?JS>_?N)L?D??ҽ`?!5BZ*?6dI%T?Q?ė2?x7Uh?R+??Ccm?n\?$?bŌ?wHc?ID?(u?QSR? ?;/??rB:?zC'?߫i?h5?k֘?+Q?ƭXR1?mr.v?؜]v[?8" ?L;m?{+? @?/I)P? O>?n?v#?ON'?&q?h?־>?oCP?(?IwT.d?^t[?=Թu?֍Ԓ?0?#??ҏ?(VG?Uzw~?0ly? ޯ?*4?fI`?u(?.j1@?lWf?Eڠ+?|i ?bN?24yb?*?cHH$?=u? D?ȪM3\?k?+ ?؆Ii@X?@O? یق?&m?p_}*?+^?Lv.?m.[ ?޻M~v,?Ĩed?Jut?榛4v?@ӿw?寒f"?݂X%?挟M?Ԃ(ŷ?oz*?)H?\}>?bK(?Xfަ?w7ǫ@?Ʉ`?1th?dw?Щ_?:4?j~?ndi?dy! ?6eB?hS_?i ?*`ܬ8?kP9t?5X}|?=? K?fd?NqNj?j_%?ʍ=Q?u ?득Z?S?#?J4[?`2B?KXQI?ƻ[? }?HY͕x?žtW?_l>?i٬?];?nXu_?Q)Y?O?❆!Q?dg?Ϙ.ڝ?㵬r4?>?ʌv?徨֚?)(H??g ?* ?ƫ= ?e;$ ?02fh?"ڶ?H72?Eg/٭? K>?'(?0P?-}a?d7#?Iv?=x?l^dt?k?5z4 ?bwe\?D*#?R(o?@?J0?TI?=̏m?~zR?yP=?=H8|?;&7E?=H ?"?c۽<:?'k>?z, V?߱S ?2~c?H>?RϼN(?2O}? ]B?tTI?0۟?A<@?rb?xSeNP?8=?>|kà?fܚ{?YK?Q$h{?iF+R?8r?u,ψK?2?axҋ? G@?@~?\S?VKX*?.?.Ux?~'.?~%st?ŭE@?,x{?4-?WP?՚yB&x?H?jk?s;? Z})?ې4o?iN?, ?Y(H?8Q٠?N4?ze?ݙ"e?Ҽz @?%hj҃?&Bt;?o%?i]"?>er?a$Z?Ljmj??0?.h,O?RS:?5 %d`?[P?|j?a?Ԡ!k2?q6@?g{3|?~$&t?ӏ˂?:?apf?}dW^?@h?>C??[)??8z?r#c "?Ơ%?eWE??DeJ?GP?sEy̛?=-bs?] Ɨ@?׈v ?ϺWh?]q_H? B ?^o?8K?ޖ}]?b P?ŌxI?߭衴?ok?@DTP?Idn2?͵I?p6K?;h%?8d?߰]B?߯+j0?KO5?Rp?5N?Ԇ3L?/_E?| J?;=^?}RgW?0O?jDM?O' o?TG8?X)/??B^,?Pw?U?Ak꽀?샨 ?4?rWL?$=)0?mv?6_C?ڸθ*ڈ?0ݤ?7[Y$?⹨X?Y@?c-?sl3?ƄM3?  4?⛬A?ᜂAN?d,6?ȬoH? TekL? ]zb?Q?< 8? l,?? ]ϱ?R~5n?X΋)??#!CpR?:?ٯJ?24H@?q?LFi'N?O9?|['?!?=^?zY?E?%'?GQ2{?/P?٤?|?|]8?B:Z}?{ @?㷠??\ ? ?$N.?`?勍?pd?K谚?ܔx?x>Z?Tq}p?7f?4#cK?):2?g(?/P?AzAf?2?@x?֛:`?]Av?={Z?;:t?Ob~?Ӄ|Kd(?ԴK2W?wU?BtS j?Ŋ?ZX?޿!N,?Ж !R?sʱih?ΐâ?vrPD?- #4?2zV?훫>]?ϧ$BV4?5؎^?'J1F?fΓ:?&j ¯?H>l?E?TWS?سR4?ʌrz?B!|?KH[?Q˵?GٙT?f{?@m4i?Pc?x9X?cJ?nIH?z?#Rq?ޒ!w?IKߧ? h?F-?;9 M?F]H1t?h;?P*??eFX?/L?I- ?$1?#T@?aYX?t@b?!Ѣ?}]*$'h??n!? D@?l?ϱX?ZP *?lHPA?n y?Ra?p8f?f#.?Բ-?PTgz?r?hB l?;|? U?&D?Ӱ9L?&͐? Ț6l?}t=?“EK|? 7|I?͵@m?<}θ?hR|?]ʮ2?LT?3@?Li{`?BZD?"pyc?+?R;]?v+W[K?rp?XE[X?Ӻ&պ?0~>?к:?g?pȵ?An?v'9x?́_l?g? ? 43p?Ӌxf?5\Ԅ?31]?ŜX?5_?8М?D#U??w\.I?|>f?k?0?\K(?f|?̒3?7G?#a??=Z}?TeF?/(}P?ЙQ(H?(?o+?S6?#4o?)ח?3Q?FY 5?堰&F?he?| 6C?ٟ>n?y?̕&p`?VE?m+`M?\O?.B_A?w?} ]?様?5(ЋS??е?anb?89?e8? b?ֽ8c ?L?4p?$?4R ?C3?ڦ?MӦ>?𬣯j?ׇe$?d(? !W? X?Hѩ?ap ?^KC?¨?D?? W?0^+ ?_WvH:?" ?{2d?اTò?""K?`6?|8O{? =ˊ?С?ڢsk:?| qR?P? 2?Dm?ڲrjJ?)?ܱ}?薡BM?&q?׾ -?ABP?9PI?Εb܀?״$?݃?ǾmӲ ? BT?ʔns<&T?_L?@Q?Vr̐?A^?$?:7?ԗayb?G lJ?ع`0? [?׽0?劏sV?[[?[4>/}?ȚdI ?0r&?Vy ?6}?"Im?,?/T?X O?GȄg?ҲG@?G?ݛkN?h%5i{?i'V?Ə@fK~?"ea?O`?Z>?y*?#x ?DiȤ?f7B?~ ?cuc?HxkW?LAB?X37?UIe`?ǃ3?̩r)׈?I??z>Z?^;z?|k^?ߴ$yN?b3(?74yW ??|?6y?$?ӛ)Mz?r??ѐ?iV?[h?) ?FPh?؛]?|ZH?/kH?KNrA?b3Q ?q`?`Ӑ%?Aw?@A?H?Z@?ox~?(]niX?̀H!?;$1?(q?# ?ֳ?K?LNv?Ƭ?+YChy?G٠?RБ ?A9`?`?]ӧ?Z8{`?!?~0?@?7$Z?4\_0?I]T?ZT? b@T>Y?Ʊ3c>?r;?TU?F=?-?F&?$<+?R51?V}?k\?T V?te*?EV?i\?oq??;8?9ZU {?.l4E?zX?Fh ?푏?S . b?ܥi.?L:??Vl|?D8?D-?/a?e`X?d[ ?Ç?&n*?Yl[h?@Q?Hϟb??M?鮝 ?Fr tB? @vO4?`0?"}/V_? =.y ? ?WѠ?˾ֈT?4 ?Wjɾ?U ?f,^LD?4"?ԭ(R?|K%?p[PVd?"Ɔ?\+ ?@:?x?ぅ-#?4b&:/?&sȾv?#?,5r?WL"?9$(??-?㖣?-"b?"?ք?ऀ?f / ?Ҿ?!?0?۰?H| P?O*~?݅uX??7?E?n,ep?>u?Ĉ?1$Ą?otH?ֹ{'9?(>uw,?C)ZI?5bS_&?!F???ث6VD?kax?9)yĞ?APp?r?KǠH?#L?IhxN?F:Z?D ?Ϻ <8?o ?N2 s^?Y^?5z^?_*?Ї6?z94?ôf^6W?{5$?Rv4?¹3?ۖ Ȗl?X=J?i 0?k&?ЯA?Dq@?2?N?uH ?ІgO4?_D?ҺC.?Wz6?ָeg? R??-W?ʌk1d?u/?l]>?bx?H?Җxv?ц+}Ȯ?KL?dp7t?Y.?IV?G[KV?ЯBT@?@!?N;}4)? 2?f!*?꣘Y?Iq&?h /6??7Jx^?ʂ򆋞t?nY?Ѐ۬c? (l[B?0?G$c|a?x?VN?I?m<2 ?ps-?4b,??!~/`?;NV? ?Q(b+- ?~͒ ?{?Qi?H%?8F?-?8!?훘hk!???[?KW?հ%Cp?I!Z?½ ?ٷui?*/J?Uk?J@,?,e?? 7v?c?dCp??9Ҝ??I?[d5?693,]?ܺڬ[?ԵJB`?t̠?!4D3?dbeŎ?FWi?[{%?Ζ8?oL?.&*?Ք4 ? [a{?QY?jD8]҃p?ra?@1?K?̎70?Nj?J?&+?P?ɏ:6L?o/(?X6?T%V?9}՘'?f=d?2,?ԀnBTp?σY?ޠF/*??|gԭt?=jȵ?獢2jg{?ߔB?z3 R?ނfSn?=~w[?u / ?.-M?ױ&Hd?ʷ8~?|?ػ33N?`D?ȿ-/??e?PNA\?֬h"Cv??F?NucF?(O?hEw?G]?_ x?Гop?3P?굎]?[P?㠸?ӧ@?Б\RF*?zR?妣bً? ?q_p?^רZ?'/ML?)~ ?>ט?B0?B)?*?~2?WNI?mJA|W?>}p?5Y>"o?Ղ%~S?;Ω?XoE@?!4?нHN?Ň]?إ*?ܰ1^?a_T?DE?{姍?\b`?lx?L?-@?ĸ?eIh?ԥ1?]O?VHom?p6?쐭V8? d?{( ?ĉl?őm??ܒO?./LJ?4`?醠Cn?Gw?!! y?jR??'>?5z_?R?[—? ?z'?9C?в?޾DwF?-$X{?eMy*?Qx?H/??? %Dϩ?V?+@?_~ ?x ?Nwy+?n>E@?.ƅQ?ӌ!Ƭkx?Lȏ?M?0Q~?Hm?%$b? ql?ұe+6?ƈ?R[2n?J%?q ?S̞"?fX?>[\W?$̜8?틪/%?8O2J?4>p?`P9͉-?1=?^ubkn??ua@?5Qe?'?PxV?zi;?Mѕu?8Pޤ?tR?'{?ZZ?*?77? L?^VP?¼8?UZل?G ?q"?ES?GMu?So?WԠ/G?ԩ1o8?S\?rMH? Uvd?Ǐ3? yh? Mr?i{ǰ?,txO?.P?qn`?aI?;?(5?҉m?'?z)x߸?):?S?u2)?E_-?Tw?f??(n?ݚk?Լ(?\ ?L? ?dÅe?^ ?p Z?ٕy>?50?ٍIb?H??h_xX?v?t?~?ȼPP?C,?Բ|$J?-??%}C?٠?<9ȭ?ܥ ?46䛪?w@?ҁ_$?9WP?u MZ?'Tw,?ۿEqR?7Zq%=?V+3?$5? ?FN<\?r?Xõ0?ͫ1>6?߇١>x?.鋗z?狧w3a?䇹?Y:d?z`P?դz_|?p_?s?pض?&q? =8?'j ?;h?־RE?} ?wm?٩R?Ò9Ŕ?!@[ݢL?$7L?"m"?㯚W1?IH?_߽ ?D `W4??eX?dZ??ˑfLl?ɂ\?ҕZ?SFv? $DQ?,F0?ҙrBq?իA{F?Wl?喟C?a\u8?tЦ}?㍫..?om(;?sV ?lB?Za]c?~C?懊?<1^?t/?ܓ[U?࣡W?>pS=z?pݰ?aT?P?>?YI@?+}?S?Кn8#?tv?K義?R??ayڭ?Cx?k{?a?ݦ?H"?ɸ`?Ȼt?cS~?稳*di?Ya>?jg'? 1F?ܖ!?2ӈ*?Pæ?v? a悖?w9F4?Hz)?BJ*[P?7+C?3y}p?WȷJ$?#5G?qP([?Ϸ2?pa?ÂO\d?WSd?)QsP?F8,XR?ȩ?UEa?z?OU \?]Hb@8?jl?ߚ3 ?7?9Ű?<{Y8?',FP?R?b򓁵?t?J0?줻??%NG?&?r8' ?=D2?b?ꮞ?ŝJ^ 8?=4eC?=G h?Ma?dsK?wD ,?@=?&=%?ػ@|??pFϳh?ԙ N?3ج?hY?y ;d??,;m?߾1?O.ad?Z?/n?ۉXK?Ik@?ۼ?˟_?8<@?腹T0h?M?%I ?"?u?>?ApL?X?!~?ِq?"F?.?G6?l.&?ISH?J>^?ȷ??Mɓ?Q?%:?LR|?[: V?Ih?ΝU'? " ?|t ?ܺV~?h=V?_U ?j h:?_VwD?J_?ϸIC?P?+"?]A?l/LT?~-?D ?챣[Ⱥ?!h?j ?y ͧ?0^c0?>?B?h@?_Qs?ƍC+?] ?hq?j/y?&-uv? I??M?d?-4?=v/?4'K?j?^X?G:? =?Vi|? CC?ʱHz?]?A~?uT>?^"/,??µ33?%eo?˱,v?*`z?ݹUh?@x?Ǣ?3x{o?,E?垯 ?~P_)?0$ˊ?lN{k?9?VH&?٨B?_"o?5c?> ?W\t?:lE?G?j6?l_?ɗJq ?Xzl1@?N1D?/-!P?'0?=I疈?jm?TT]?F? Oi?}?<bcd?W?nl?E$,?V7?tv@?ON?ȥG?i;? (~<9?'? c?VVK?^'6?گjd"?ڕ#!?$|?rx?QX.8>? ?X?$+V?ͧ4i=?MdM4?": ?a b?@Ӟ?ר5?n?[#R}?cW?фp0?Ƹ?l?!Ё?&U?h?E*ֳ?}ET?zAb?]ίA?)]Y$?Fr,;?Mɔ.?#?3h??Ej{m?xz5]?{a?բ/j~?HAi˦?kw?~r:?q`?}? ȓ?֖j˚?o8P?}pT?̫0?ġ\?ωRĢ?LIb@?ج z?ПP9C?U>m?IiJ^p?p?Nbt ?~(?낰+!?u2z#? DB?HEn?~$I{??pOP? "?_z? 顼 ?;sf ?:zX?~?hF ? 3?XV?Ĥ]S?&?ń?&3\?p*?>?8?j$D.? hn?vףj?_bێ? 8?EҨ?KcN?Y#?/L?iE>?,VY?Q0?W&?"@?qwڂ?87܎?^LS=?Ѣ?GGg?֑Й?딚7? $^J?!u? ^0?쬲)NZ?kr&?6A ?۫H=?,u?ץ͉Kl?d1 ?4wj(?uC6?1xj48?\5?Xh?Cr?J7f?<Й?ξ.u삨?EhR ?9Xd??0?Pot@?PZ?%?hf?v6++?.c?H?ooB?\ `?^T H?۝ ?8?ף?>f?dP???rxZ#?MR??41z?е ?g}OKJ?ўls|?Z''0?5a?ђ>?YU~_?3UzP?Ӗ@ʆM?ڂ5q ?ד%{|? !Y?r? ?*#T?ðüo ?(b?ҏ ?Dz'h?)6$?Qۇ?? 9?D;?cO?Bα?lm)?Bf@?b ?{n?ҖC){?@?dlW?(?j?K/?ՏS*p?-_ʳL?T'?Na?#Z?k?"^?|+e??@:Y?B3f?oR'M?hgq?۰Jx?q?NGYE@?O!V0?]? M.?+a%?]E?ȇ,ʌv?(?h?ͯT? F̕?9C0?(5?F ~?F&.?\l?VK?ؑ?BhY"?Ӧ̥4?Ə?an?rip? ZǠ?X[h?5?DZ?9,?I00?,d-?L?/LDa?ci!?rP?87,?F| ?T'h?x"?["^?ep?6N?3LP?N X?SpB?# E ?j٠v?lB?>KY?G肿w?i?_$?L?ڥE?Xt? \?t5 ?/fr?OhMu$K?t ?SB@?Kt?( ?琈og?́u ?ɐ،?yO?Nl4?-Z???ⱣU*?%2|??곣ɌAY?髸9?oI?.E?}u0?HZ[H?_LW`?b^?殆&?xa?JU7)?̇!?Д 3T>?y$?{4?摋d?G{h?r."Q$?w{r'?!?2^1?z6@?. ?s}]?ڊG"?8?ѵy?6Nj?Ѯ/ ?L#?{{+v?/ NL?Z?j??6zEN??ꕽ𣷢?ţG?Ͼ?Ŭ/?H?3aBh?Nz|?O?%?_EΠ&?h.v? ).^?tP֖?VT?J5h?U?m?CG??+Zi?x|?:?ڧ=?$o=?z??ે'8?&@& ?E4kݦ?SG(O?̆o*0?`'J5?inm?wΨ/-?L ? 8I]?Ý+$?c?kp?oږT??6JU?MLd?ֹw4U?ং!?m+hv?Cvy.?V8g]?'Ҡ?Ki?sl6?搩kB6|?lD?̕_t?G[?.z8R?ڂP?*u??ۿ#?ܮM+U?4?m?b?G8?)9pP??P?Şd?C%?D6?Ľ{m?A|J?¯g??l?j^&$?ې,?܄(t?'!.?~to$?h?= 5ž1?y/B?r?jZ.C?FT`?9ə ?kd?> ?2O?ރՔ4?)?h`&?!E.?F>?W:N?(?Μ%?U?h?k?"?#6ϝA?og8?JH ?ε9?oib?t.??'y ?p?ûS?1(?I Xe?qj<?]:H?鍍LEƯ?7|?c{?Ėau?ܗkd?7y?(3m?`2@D?Mxؚ(?BȺa?47WU?]~>?!v?v?&T?yc?9l?]a(?0<?ѿW?nlV?O B?qW?9K[?^- ?Qӗ ?ήI ?f 4T? u:?={o?_d\_j? 75l??at?rVI(?*qT?wu?1]R?X?HBj6P?¹ 7x?ퟑD?~mp;?s? xi^??o?wCgN??SPB ?)Z?"~5?vUZ?^`?M31?ĭљ?pC:N?\@?Ok"?RHq?yz?AZk?~44?GZb?ߐ"mI?ua!^?Ҝx?)?ED6?9 *?t?jRpH?/چ?^?_hY?[,?%?Q8?bHA?h{?2SM?wZ0Z?驖?ȘD@?eP??r?T0?{QeL?֍?S:?o ?3e?Tƌp?'yL6?<֔?gS?82?Xͤ?6u?6s?$?r{@?ⴱ<?=5FM??)S?(5ЍV?.:$ ?rM5??XX@d??ٔ<`?+tm\?Fk?z<0?*"?_xI?$)^.?m9Cp0?LWCĦ?(R?+yw?䐤 _?T1?ϰ?Cy4?_hS?t!z?.?рgW?\WړL?6?[> ?2 ?>?7Pxk<?ג\h?X.f?ћ}>?[6?Vg?߻a$?c=Kr?\̡)X?9?/wL?V\LX?錒, =?? uA?4?`=.?Ő+?O^?X??f2#?,R#[?u_Y?ض{?ɼ=g?㞸wG+?׶fp?tHõ?̹֘?2?{7S%?f1>m? [x{?%orN?嗟K?EK,#?W?Ơ ?13?֢@~/^?r8?m3?"Ō7?Jh ?w)$$^?J\?\-\?ukF"?Ӎ%*?I/p?ɫ+B?s`$]?즎?;J?.Ac?/{9x?ʽ5* ?M>?U 7?x "g'?@T?l+@?T!]N?kz3?Ԃ'#!?zط?]-T?Ԧ[?LV?$ [?p?ŗM?ƙ5?Ӷwt?pl~!?֗y?[fb? ?@C?'?;H?!1?W8ܵ?ᝣ+T?ݳ°?'D??ӥ9 P?w_?ܼa?_$b?L":g?Ιx'?Nn?1g))"?Da-?Eӿ?*]2?~r ?猠>&?1ĸ?M\?LoP?3卬B?̸~?v`LMK?lw&?:{?Gܟ? 4N?6{Z?p;?+>^7?ߞ΄H???۠p?U W?6޿?ƶ? x?fV)?G?ڶ.?HӶ?n?r]a ?H?o~?%? C@? !N?xׁ0?k??\%^fR?%r?ps|L?sBf?Ix?ՓS3`?̇g?L? ~?Wp?ܼЇي?0?9!?ߤpz74?@XV`?c]}qjr? Nq?>?c%=?{rW?: qH?[o?.:~?(?ut ? 5[??5w-?aa?Y9'B?P? m5H?ٰw)?1~B?##j\? { t?…tT??_&?إ7Ӟ?!G;?㶀+?$&?/#߻P? ?ѯ&Ҋ?cc[?K ?\?Q?x ds?_B"? }?v|??S?8G?#A6d?E?c ŕ?'^ a? C?ʢLa?yZ&?GfG?Ř^-?B Y?*]ux)?UA8??BՂ?#?i8?T1+l?ڀs?i5?n?㔳}/N?-%!d?^]{k?y\?Va+?8?Ѵ?]"+xOM?cQ?z7?/<`?[.d?Qa?ד5t?FIDH?8- ??Yl6~?m ??1?E?ɐKƜ?ب7s$? ?P?4qh?V*?Bi?jb?pʛ?F?mR?z9?lꂠ?pEf?\3?Ԥ?'A?cUn?J~?Ò?%D?zgsw?7>?mv?;?b?:{?KQKh?D⑰?|pM?rLx'?xv0?lf?~S18?m ?:k8?6\>?3]x?f,Ƚ?3e8?Gư?0Y|? ?z?3?J5?2"?mEc?`0Ǻ7??|׬?꛼?P0?AV?߬tq? Z>?i@?w0O$?ܣb=?{LL@J}?rfLä?W?t?n,[?>?ڐ,?E??29B?9Mi3t?Ѭ{,?E?ZI?|M?d^q?Q?o 'Y?~u`L?5[M?>[?bd8?yA,g?qc~?\;W\?^?嬹p?Vf?U9Ah?-QHu?!?8_? V.?l(_?+X?ӌ,?Ʃg?-~\? X|?'Q'@? Ļ?-?Y?;MҒ'm?f8 ?-?қd޲?(I˛1?y`?B/a^?EN?ӄr?6n? ?]q ?h ?ʠ(c9?(,?))@?)+"6?QaA~?W?E ?͓\|?N^( ? w?(j?(G?UNK?޲Im?j?~jaq)? ?}7?D)r?QcN? MJ?Űſ?ۢ/gkr?B?H<{?YG%?V5?1@?2S`?:??ԊCL?%H5Oo?٦ ?mj?\?WmL?ь?(?Zҧ?)?oNQc?x?~˷?qr?+?#%dI?'U{?۷oo?fy=?=j?L?~t|?!?Hx@?]j?g??"bZ?n/? 4l?ʅ? ?ì4-?ڮrzi?r"&?93?|*?Z&_?[R?G {?}lj??bŸ?_;/K?:?X?셟F>?#?j?c%?;_B?tm~?.?הۙ?d2?*bJ?kױ?q̵J?b?6>?4`& ?M?5ޗ,?؃'j?m?Yl?]٧`?U/?JCN?_n?μOq?(*W?h ?9?[p.-?GÛ^?^?HC:?L?d(?ɟ3?~ڔ?s2?n4?\/`^3? Cs?BGQ?⌲ ?oaTR?򄎎?k3?폩?W??`T?Ηe?Sm (?X?;Ww]{?'$ `?ٲbA?G9cޅ?7kL?_N?gY옰?9}d?Sds?S?wk=?P:y4?}#y?p?"? er?G_Àb? ?&ԯ}$?4B+i#i?9Glv?j#?U:\?\?-z??.rk?)P?vo?+X{;8?h]yp?ؖ?,83?wGl?^ߣ?<%?6AY?zڭ|?iN?[0(t:?:*!?ͬ?J?#e_?#8?TӨ?@?]?["E? `?`n-?類ʸ?]+20?ٝF? ?JQcF.?,̲?̩t?Q?7/6h?l<Yf?殬??Ԧ C?V<@L?aU?j?}vm ?JbD?K?AD]4?+rX?1x?o$e2?y`H?Mޭ@?gܡ?RiH? SYZ?*T?.sx?. 7?!e4?t?}?_?&K?[|Tv ?)l~?ц^h?j?AXK",?=3C?2jyh?Ae)B?%EN?,h6?~>?Yz?"z?riw2?FFe%?FR?&l@?+}!n?ḫN?(9G?p*/l? IX?Q[?㦂k&?!-?ΟB}?٦ONΤ?֎E?l?겞?*ӹ?T!?-?q$&?^??woq?A.W?9>ɞ?jQ ? (U?l-N"?2]?P36?9@?L?zfc?ҙ?(:?hP5??פ1? ?j:Ԃ;?W7?רѣ?%۟8?ߦC?] ?$?@0h?7?AR'?ѥ=?`#?涣}7?* ?qݠ?)퟈?;Ex?z0?!J8?%o?P2u? Z:?Md?ɱ!'?wBݓG?,Ģ?вV?(?34?\gJ#?q:`?RC2w@l?Z%\0?۟emR?[@?U(z3? H`]??Ūx?ܻIB?=a$NU?jko ?#0X ?.f\?G?h s2?Yr|?8p?^E?c 9X?8͒?70?sB?Ӆ ru?M> ?[͋m?hc?H Q[?ex?rB$? 9ꀖ?Ö?^?I\?C~?zF?,x?QB ?G~"1\?Ͼ?n=?Q^?[}!>?p ?U%3/?bh^?kh? ?-]G߮?n.?^!??ڟ%3M?4kc?. .@?x? yG?61?^M?q!?_|?$ɬF/?}!eQ?z.?VSf?&oT@?#u6?l?_t?m0?+Qx ?w?q?~R?@?ѻ}?* cߘ?[@?޲/?T2+?C%?!y ?ز!@?J\j?r?ċuJ"d?Ï2h?Lmn?ir?X?r?$ki?JP?эLL~?ELR?p4@?ۼq?/P?t_#?: ?| o=?Nu0?G,t?^GkP?S?FZ0?ҫN?r?5f&?bj|9??G0 4J?IYiD?-cZ?y6)cS?!u/?eb-7?:qgu@?]qjL? V(?:SՊ?Fc+?Hel?wkQ?G*M@??TҰ?K| ?n5?|=?ۤpP:?6?~hO?=,Om?k:k?ͭ/D?ְ[e?(| ?ұgqj$?3Iy?Jq?E5^?eA?]x?ƌj?-0?ȴ?L?5?N2?Ix?fӚ?؃m-?墳~]?֯?ﰱz[?]ا?RC`?ٕ?~P?9!??̸?2K]\0?w)?щ-:?*a?ifh*?&%9Bs0?ȗ$?@?/(b?@?+?(rjf?¶~*?te,?PNPzl?- ?TJ[S?)id?"H?պE?w4?՝~W0?ܹ{?Û?.9 ? ?x \?pr?U[<|?-f?``"N?$p?ʎY0?"Kd?QPO?ۗ?O?R1?ی?P$??G'?@J?@ u?`'d?zl/?h;z???pVP?AQ?1?70"E?Ke.nx? 0?-dH?)?U?R?,?KN? ꭧ?ԿVNF&?ḭ?ڠmrQ?Ɋ?rX?uZ|/?:؎? ?((;LX?3 @?, ?Y9??77?L?0>?ɞGhS?ݵ4?hr1P?#ѽ?2f,( ?]E3:x?짶 v?*RĉuS?_,T6?DQ-?.n?l~{1?֤Ց?ke:?4bf?OQ?wP \?me?.RO?p)x?藕 .?ئ!+?Li7tN?ݍ?őQp??j~ZQ6?^IِD?)?rMrZ?ьGG ?K69?葤sK?ٛ5}?,A&?wb?*we"[?7 @&l?#?Y1?K?o?9 t?sG?:9?Ҝq.?K?eͼ?ۭ?r3X?؁TH?-3+?NAz??2?|b@?ּFh?ALO?I8#?ل6&?v0?Yቾ?]?bG?œ9z?${0?F֬Y?J y?]r?"43+?KQ?RٗL?,?q? ?օ?t}> ?M{?n \?UN?Ȓ$??pG@`?4a?r ?שK4?p?wdr?qr"?L鸨?S9fǢ? 7>?ÚQj?̻4"?SK?ۗ4?,3i?C04?CN?ДŒ^|Z?Pό?(?RH/6&E?,l?U-^j?RV'W?Lp?⨉q?ڭM?^?1?]?]wv?a)5p?ɦc?ٹSw? =?M'?9!8?qa@?i?ko??B?M4|?iS?֫Rh?EM_?W?T˱d?~=??T?u+?~Z?K8?a?d\?`?{?:-V:?ն,?oZ:q|?$׀\?wU8'?X7^-?L&\?1>ׁz?4$Z?貁z^?6B?"ƯB?✅c^?ҳ1,?ޕ?X"?dc(?W?OzT?.T_?hkH?_ I? SEd?pL?2L?,?ӹ|?+r?Zd?Ļ194?ۗSC'?Hl?BUo ? x? H?0?d?g ?,H|K?;i?ẇc|'?v8?Cɻ?獾nBZ?s?}~(_J?*A_`?{5m=?Q]F?^L#j?ڜKN?X:?k`x?,99"?0?X?趍gx?g?蘺?~/Qy?\?X"?@?Z!X?ɧ?K? ՟?D5|/?eׇZ?'C8?NJR?ڲj?3 ?Vn?ƺ?ZGó?OrY`?'t28? 6ҽJ?_? Avx?3qIJ?.!?`W?\?ޚjd?$Lr@?_t0K?ݸO?yO(?Ut?ݰ)'*?.???7?`?s?݅ ?cT%?o?l?Vm?ڷ dw?p?ӕwV?#,?kҺ?ɀMk?? >?[?m[?!@+@?偽27??BM8?Z:?tx(?\?˕_?~@?KPy4h?h?KN?)_?:%?B@?op?=Y*? 9r?a1~?wqz?^np?ɉ rt?kOӮ?;?ëqBCDP?Ԋ[V?ݱ8S?{_i? W?28?(0Oy?].DV6?h_?Ͻ,!?.|'?zb=?|rB?#RgcB?ⱓ ~?ʾo?º6? ]?>?ݱ.?߹@m`?"g?9?E?;?.L?C o?*!?+?~֘_?%u?B?Uź?Zq?fn?6^?f#D?n2~-?? ?†?]0u?ñ# ?SuL?[];?swN?3Й?R^T?5m.?v?Ƅ̚8?`?ߙq?i[G?Zy?ǩ?ȥ2;7?嫶}?G >?ܟ? ߮?Y֠?ѯp? ?yAT?U[(?zK.?^cZ? ?Y)2u?̚u??ڎ!4?C0?Y:?,?ݥ:&{f?7&?ު?ܿ|?X&*0?S`*-R?zw?zH?`Tp?T?3'?٬@^?Na?+y?F<,??D7z?nݞ?Ք7??ob'D?w?۫{`?l ?m\mu?AM4?xCMu? ?-i?t?ujWނ?ۦЎ?m?r)ې?4v?Bv8?XmS*?K=!S?Rx-?mN ?+CR??J+"?ᾫ? %4?Т'b"?0) ?7F? H*?1E? Y;?{!p?j(X?I%İ?fM_?8z`?r?dG6?|q}h?ג ?(k?b&?_xN?!?}\ |?:?#?v9A:?F&?,[L?ɰoD?ϧE?:0p?\N 0?_<y?l#\*A?R?F?2ѹ?<rp?/)QL??6Wi_?8~W1?}r??ZrD?WX$?p@>?ǹs~?oM?àaJ?^?'6?3%E9?e”?h hK?:@+$ ?뉴oF?2;q?u[B?8?*XCT?6 A?ӶM!?HX?ڒ?!5[X?ު_?m;2?9?e~x?›?,o{?g?$ &?y7?F? -?<Ѹ?ς?1p=#?#?kT?N?\mk[?O?V?VqL!?^jE?cf M?V&?C6‘?EZ[9?n. ?߂W?Ν}V?Ȱ?jײ,?t7?cø~?39,?rqʁ? G3e?A^$u?i 8?q6?3 ?Kq?u0?*:?vbN?琤Q0?'ꇒ?;ȼ?4ݘ(6?֜w{S?ZkP?3r ? ֬?av}O?9fhW?Wj]x<@?k?w3?705?5tT-?1??ib?N+l?!?q?ew?ߢ\fK? %u7?г?^1B?pJR?LC?9Ǟr?ۗB`è?䅝i?@0c.?Jn?Hc~? y??. Ȃ?؃j?`\af ?I?g*8?x_?n?#2<?> ;?Ԝb?r \I??ߓm?oˆ ? ??6'?QSz?9]f? 1?R.!?g?xϨ?6?l/?^Z?6@?<$g?& b?љ[Mj?% ?jS?]%?޿?-#"0?fM?N'`?Yvu?M)?E)M@?p8ꁂ\?l͈ח?n8y?Q?bB/?]@?ScT?bs݄?*%?}`?VޡHt?`B??<[vvI?k q?ʔ ?to ?_ng?WXM?m?+ ?l~oJ?t[ۆ?uO8SH??Uji?= ? i?Z4?הt4?f0 ?բϠ@?q>2>?&_`?3[?ZCK?D'p?W9?>xvwd?θ9?w8 ?!3?r11?Yʩ?k}?ˌTv?Vx(?qJJ?p?Ӱ.P?f2q?OIZ(?!V4f?ذ?8n?/-N?d3x]8?R\J?NU?ԡ7>+?ٯN? I`?vB\?_l\Jx?q~e\?[ U0?lQ?# ?R#3O?iz ?/.?윥|?͆azٌ?"?*Dn?/D?kI6?5|dc@?k ?r9p?I?_D?Ye?֚^S?H$?~3?֨}ݔd?kK޴?q@?q= ?쉟?"{o?6?(?dԗ|?B^V?Ԇ?Dh?#iDb?JT=G?뒊Z0?0NY?GkgW?ꋳХ?SkWI?wd?ٰ' ?ÛM/l?*F1?݁[(?`ύP^?-7l?wo\? _?".:?=QZ?3.h2_?z?{69h?qKw?4?A ?ӆDR?Q1$?.^"?3? ?ѤPI?Tj?m? -Zp|?ح'F~?"uK?:0ژ?׉D'?υw`?9??du?8$?ݹE?vuq?b{d?ܡ??k?HP?.K?U?5 L?(&A$?c25?RvL? }?ϼ_~?nVa_ ?\?y(}?֮B?B#S>?.im?^1??r5?D(?>H}Ԡ?tS0?nǃ?#R+?e 1N?d?A8^@?Ïkk?N(6?NWD?҈?s!8~??dh? [?idv?0p7?a'Uʐ?t?nj?LH?=?ɵ:?=T%??c)[?ެxd?ݰUO?ٻDy ?X47|?ֻ}ۧ?̋P2d?s5%>?ڇ΂-?\-?*ˁ]?wSIx?ӎy2?ʻ$ʿ?0Bt?ǎ ?L&D?望:?lP?븙qKq? !W"?+ ?cb?l?T&:?g2 ?E ?YqX?@ӝ?(?4?@?̀?)?³0?,$?*?O??![tz?/"? h?͸?Ï,W?tɝ4?mp?خ#ve?u,?_N?.Z?) 8??"bUL?(HC?/\?nig?Ig}h?Xۮ~?:xǴa?8m?*܀T0?b)ݔ?U`?@e??Xs? ?׸5r?$b_?PQf?oc%?Zve?\H?,`+#?D6(?2m;?M? F? ?]nKl?6 8?-p?4*U?sU?t?Gh5?y eX"?N?V?|ûr?{?̇4l?ݘ?0g?[ M?;¹?XIA?3?ݒ*\X?OkB?߼ӂ? x?z^P?Vl>?ֿt.?쟻j?&Ku?K/?Ҕc?7W?q&xtI? Qi(?%`?ڙ/0\n?0^0?"3?5W?8Jd?]@?b()?"Ť?ԊK?&!n?N(]?ËWcl?j@?3/y?VI%̰?(d?TY ?p~??P:Lw?|F?cr?FCQ?+s?I=2?_؃L?l/?3'\ ?(:?MR%R??ưt?B4 8? vX?cP?@pA=?1?{X?]? +8?u덚?耧?_?Fy-l?˧g`?j?!)K ?k?kՔ@?މ5?魬iR$?Q q`,? v7?^b_z#?$*?gy]+<?W3@?N?} T?-4?"KJs?gR?wK?ݬ|?Fzk?!_R?-?O?ؙ? ?AcI?͛:? 'y=?9w$?'=}?0W/?Q=?caqF'??aܿ?J ?F}q?׽x]?^(?jՈ~?jd?mM?ӵ\N?[ӥގ?60 ?;?ܲuz?'ax?#=5/?ke"W?iol?E`?9ِ\?&)c?vׄT?p?ڍxD??QF[?YfO ?̃PMB@0?z)?各?-Rj?܇% ?F@qW?zo)? H?j5?鐶?m2?>DxP~?Qw(x?ߴ?@ř?C?6?M?@rR?K:?wwp?/D?ӎ?,rc?Jqd?[ H? hxL?*v?Ƣ?9??7?mJW`?nR ?ϒ$Se?ڱ|?4a>?A38?W'R?HGb0? '00?ڋ?3/H?֏pN$'?؛C ?W#j1??jC?݌zLw?]#,?%+9?A'?~d?ց?TG?R#^?ŏ7L?am?❀S?e܂_?j`?,,w?Ӝ|紼v?d?]]?\?n?-WX?SȤ?w;Ef?Q: ?ՆVKT?Ai>??ua*?ڐ9X?a+?C?,b ?=-p?f>bI???۳?΃>?%f?}?p0?5ݝ?׌aV?E60$?_}?w?Jk[Y?!@%?vDo#?иV?YC ?}j?9 j_ ?$C[??Єos?(?V-*D,?"ۏ]@?5?b#8? ?ێ8t?F.M?}g,?::@?[??*]v??F?/&aB?F?+2W?APA?ϮVk\?Ե }R?r GT?B?‘1?mItR?ֈ}k8?Ed?Pf?F7X@?҅,?#uc[?WJQ?8㍺?<^?u8f?  |?0>S1?pCi#?,$?*2D?,K?C'?,7?wBV?濕-t:?/![?;B?杓l?tn N?!`?b?P]T??W?KЫ_?JUP0?>$E?wJ?!CZ.?L'?`{?iL?j)?а?wQ?~qv?ęT?jݴ??@?^?%0(?iq?v?ROS{?c>bxu?Q8VX?$K#֑?q_yB\?M ?&W?B?"?LIHX?T/?x&? ?ӄ?#aP8?dZi0?a+t?iQi?Ự ?V4` ? `?*??L /8?ޙ8?8͝7P?7/`?c?[*pH?JP@?-4Sl?b)?ީ%ݎ?Z?ci?9,?(^ ?q?[Xi-?q`7?n@|JS?IԒ`?ѣ4¯G?8r??~ N|?'?Hc\?`ǻ ?ڽØ?ɦZ^?hQ1?9?'>4?Jq\5?Ĺכ?ɅSiOJ?M(?X?|W?AƴИ ?M?݋6?kh?" $?Ԉ(`?a`"/?ق?G?b?ufe4?|?*ypL?Z?妰 ?Ă?H?)G?Ⱪcp?ϧ(?Ɖ?z9v?S5?83&'0?$^w?+ܘ?/xNfjp?WI ?^~`!?U RC1?~8f*?s?w߾z~?ž΄h?Cl?d v?#YJ?ə4? Nef$?w*k?M8n@?뫖+?T$ ?3A??+?qӳ ?\A@?2S@?УY#?s<?:K2!?& ?c7k(?&?wO@X?ݭ??r|?5_?d~]?lW?T2?'y.sV?Ձ9^ʰ?B%r?y|=?xZ?<2?{4?>?qD|?0 jn^?(/s+?A2Ll?̼G?B? Y+Ĥ?L= ?XWp?#a)X?WWX?Q#?b}[?'@'SN?n3 ?䛾|?փs/?wC?2.?̸wBڨ?؍77?ɖEl?/ܸj?tI?E"9&C?寬 ?)&S?pq?T K?9^?NL5?\U?T?4B?ty?f$D&?h-?4|v?K?T?ꊼi@? $L?bZ7}L?6=Td??U?u y?Wo?;1B?K\?ŘX?B o?Լ_F?%/nl? 𰅎?kd?\|?j?_N?ߕ.㐉T?s(&?,YL?f?s?mbR?D? Z?F&?Q"?&mYn? b(P?1 ?.ŷ*?1l?R2A+?TK?oH@?G+v$?σ롪?[he$? ^6E?:5?j=\J?Ϲ?c`?#?ɚ>rN?2p[?'@?-bd?Ӂ?;h?LUL|?F?Ia? SLYV?ϐlP0??D@x?ᆏ%s?L36aȓ?Bn.?ɠ@D?3g$@??ID?̱*?ydCV?Rl|?)"?9?h ? ybH?p`?`Me?;޼?1f!C?r]/?ЍB?O?اp?!?T<8?că ?03-?^4k?Z؁v?r83D?B_|?L q?F-ա? ?؄Av?꠫i6??x{?5?S]?7?6E4L?O?}7R?$L?I=*?TR]?ds}?uQ |?$E^?jDNzx?+M ?"NRQ?hB ?fe?͢^ڀ?t8?oa#?l 7?? t?d$-/?"ǒb?Z?[?n] eD?mJ۶?:w_?bF^?'{ ?͏eU?ץ0`Y ?ߗFō?%n\d?dSL %F?ס?A*X`)?|?`?'qdYv?j?|?WT3?,8TϚ?KW?RF.?$z?]3#?1MN"?].-Ƒ?3{ٌ-?i[&?Pu?tDZ?A? A|#?~6?c•?/73?p?Q8?0?`@?㣯lT?ҼK ?aN3*?3^?ơz)?nLp?-ӈ?L?1R?婳?`68?⪯}?NP ?l4a?l;3?tM'?әT5V??,?pf?{ 3? ?a ;p?\]|@?PG0?qS??3?@?7>#N?9?ʋX?0#c!?]?GsG`?obpj?kH^D?W;U?J_N?Ih?|˕`?d8|?|@4f?+9´?;4Uwl? &~?kZ?(?ʬ>tO?ù/?3?t?əDCW?,?nH?E%}?\*?4':?ê?x?gH?ljCP?smj?☆rO?GE?nr'?}b?ԯ y͸?r5?pyt?Tx7Q?[grB"?;0?Ё- j?3N(?,nX?Ƙ~n?e?+r ? ~t;?MclSC?3?X͝# ?a??޹ j?=?ɸ?ǿpq ?Z}*?뱖) ?HՎ*?D1)?up҂?"Q?: r?Gvr?}n?Okp?Fh?޼nO@?T^F?UztH?;4=we?mXA?n Ь??X@?GzL?ʙA?$C],?Ϻ<?HsI?Ύ@?$)O?;t?뎏R?Ͷm?6A`8?y{?fC^?DږG?sfr- ?È (?r)a`? Zd?b4?SzA?.%-r?î^p?SŰa4?Va?ZZP?6p?c2?ְ=o?ҁ=?SY?z]?*_"?*m?L' ?ڎCj?)|?R ?1y B?ߖ wV?ټƘS ?(Ϧw? 9?2#? t?bh'@? ?m ?]X1?6P?rb?.l?%ڰ?~[q? yH?|MP?ՀX?1%??T d?h d ?ɷ?@|x.?3$(,?w~p?K?xn?A ?:x3U?m.*?4Upz? YE,?B5$?ղ? RP?Ճt ?vNp?J6?u?&8?te?srΠ?bh ^?qav?լ? b?J-:?N.&T??k?|!’t?ݽ?jkq0?'X?lOj?f&)?~Fh!4?y-?ᖊ̈́?Rj$@d?.:?%?8?Y?0|O ?Zh5A?ߌ*?+?8?M+?oW;?kP?ݿ6?_HHH?1g ?3Tvn?ЛK?uc? ?n?"Q?w@"?(S)E?BI,?Æ?,3OG?VS?t?M?(?gN*? hJ)H? t?][@?Ev?ꇮx?`Ǝw?Q?l~?en?֩;|???Sr?T{d?ӋG?AE|?u`P?w?ҐPK]?M??y$g?Ќ8PN?TP^p?䲧qh?T?G s%?7 ?'[M?mt >?g aR?&T5?(NNjLv?B @?B14J?X?Q4F~??ޮ Mt?0u?I;?=?$,e?$0 ?ݫq?а^t?CfH?T)T ?A?{K?ͬ=V??i%??԰/?+1?U,?… PDH?RJ=?tQ)P ?F??AfX? ?=:sb?('H#K?u#u3?YB:zI?5dn?a#?&n7d?'M?~izX`?<9?l0Mt?dzX,?ʾk?O@?ӺC?cqB,?FN$?ޓ@?۩?wr?WcW?4Dt?m9j?ٞ?ZݡwB?c6C?z?B:[`?YB?՗5j4N?w],?N?p,LN?{8a?$-?u[?F?x']?bi?F0?9c{j?k<¼?,%? Kk?rU?У {e?1At}?NHK2?ɜ%y?'b?8C?yL?f*\?¥2?fE?3X;p?h9?BV?"ķP?fCvX@?|TJ0?"?ۢb?6?^>$?W4j%?Qy?"Ն? D?iz?`T씀?Ұ?z{?rvp.e?0Bk?Rv?cS.?:z?WLk?_G[N?J`1?n?)2D?&}gq?VbPA;?誹v?# ?Ƀ?Gld?Qy?n ? [os@?Yŏ*?DJ=?oꮽ?լZ?ú ?Ӵ!} ? ,l?)]`? ,?CBd ?Ώ}6|?ʸnT?XfJ]?kp`?ӥ\?TqD_?F-Q%?+GI8S ?Ҝѹ7?$q?ơe?т|,"?OFj?[hz?ؿSR?wF?^?Gj&]?F21b?!I?\OP??|<_?isI?7c(??To?;B?б][?!_5?66,V?ͻuچ?ÆP?Kš+}?g}xs?⏿x?%q?ʁRc(?ᆻ~dq?dI~?zz ?/?D? ?ӽ( ?`?*34?=n?Ġw?Zk~?E]r@`?& ??o?42?W,s?Ž"o?Hm:@p?I$?P?Iiܤ?b;eL?O.?|귤?wWq?RgFe?LZ]f??_̉?_?[ ?ryL??.ߢY?+h[?,""NJ?>£?Mz_?R3?*$ 7?[9|?a-J+z?A?쫸?3+n "?K$?ݤzr?Ӹ˟B?lj#?IV?15?D?d?eg?οH?׈t;I2?̥C?LJv?ʖ??Kz?ښ?4?P*?RYlm?v?38`h?>0 "?C뛵?nT?a:?J??5W?m?d&P?DA-?rOvj?omn?Jo?C"eg?C?Ě^ d?L?1"W`?#ܐ?53`?m?X?;~{<,? ,Zp? ((?!ͦ?Ʈ H?ҍ(?]?ۿ>lb?O.T?ld&?>"?`4:?kyU?\Q |?4T ?:Vq-?"rxB3?a!%8?hVj?](?Xap?jeq?`9?j?뼷 ?IEM?'m?;f?8P?,?6i? 8?9JA[W?p?]?uGt`?8/e ?Χr?@ 0?V<[b>?IW?dnaT?y$?V ?ݚ/‡'?e&$Ty?L3F?m(?ɼH?'@?`7`?Hxf?[7L?pc"?!o?$?_?)s3?$6?//?ͣ?zS?`?0L?DOu4?SYP? I?Kǒb:?Nj?å?{i$3T|?#f?IP?<\?&o?We2?҉r:Q^?/$X?N ?@;dq?-< F?qLI~?[i?|Ǥo?%} ?i?g3 4? ?[q-?4?^,@?cN:?h TP?iH&Ka?Җ ?byF?p6}\?ӂz? 7P?? B?m2@?r*?5 ?9@?mM?Z?w*й?jth?&?W7?V?@?~?r-?60[?0fp?dB?ZE?b?x!s"?yaj1rP?}d{?q?@rx?OKJr?^j?JR?5⸾?a?VYj?30`?똪^ܔ?rC?֏R?S=E ?ĬF.L??˳|P?R(?~'L?ř$l?ЭGJ?W;H?;_X?7/u:,?ݟK?5?׉hs"?ط(T?.bb?V^m>?RM?!\?߻.f?P?րS?03?~í?>@H6??a?ܦ3?Ê*?M?t?SN]?%>O?LX?ot?ꄀ?K#*??[?K0?i_&d?'?T,?D.[?]@?38I??* ?譜_?Z?Su5#?薚Y?")$p?[(@?~0Px?@?9?d; ?Q^W ? ن?ZM[?ݏ֒x?C?g?]@ѣ?ߟZd?ZF(? 6? ?GVhL?8?~h?&H?ڇ ` ݘ?Qq?Lt?% ?KP?X z?4F?RW??1v?ѝ))&|?3AhN?J x?]??ن6?< ?ݷ`< ?PIm,?ZGd?7ЃŲ@?pm?t?s8?.??؄7\?*Gv?=Uc?x2Y ?>MAA??1?yZ?ecR?^4º?dרf ?(?b?{t?Ox?rѡ/? ǭ?NÎv?ћ!?)d?:":?!k?M?>Xe?%Mލ?zH?-$? c{v? Njx?껡?ɚӯ?pƇ;s? \??PZ;?&?Ă?P^?SE?LEܢ j?gg,?= 3?+<\W4?$,`?z?ӭ;?^?PU}0?;V?s?.|?C&?P-s@?W?>8?X.Q`? F\N?yi3b?hMѤ6?傕<?סo("?g'?ܦ[>?+e ?ױ? zz?8 ?⼆;?=)?Icp翸?νFE$?V?:+l3?•(*?׍}?"OKA?Wz{?O?]d%?nn?OK;?{^E:?&TH?^?p7$?7sy-?nK?n Z?J}?Mr?錔ձq?BC?I?gAu?en$?ܕh\? ԡ? ?Z)=??:ak?̲8?wM&?/?-0B?fd?B8x$?Y>(?h?f\>N? M|[?Ӧp[?X'[5?_B˗?M^n?1r~D?1'g?:ǘ?Cg( ?<|v#^?ݲy?.u? B}c? X?V7?ɫ+!?2?C/?ݐ!Ln?c$F?0VNp?Ѵ}PL?@r1?{0۔4?ĦOo(?֔?9B?чimI2?WBI?Ň{m?0?״?)L?|?l?JQIc?€?ɐ',?eҨ?M.?m~p?tR@{?`I޸?=b)?⎵b?r?;d ? ?D'?,wCP8?>sn?~Ĵ?t?OӦJ?CWs?=^a?<_1??1Z?q!U?TVn?蓨6v?t,?wH?!:? Ww,4? ?$1(p?ܘm^?st?g!? ?=b?4hZ5?4?WXM?"y N?b忻$"??v?sV?0?tGF?O :?˩?Zb?|p,?)??܄LS?6Ŏ? A?E|DA,?ԖT?ng? ?vG?k?]8m?t婴?X ? v7P?@_Š?GYjXF?:?!h.;?,e?B{?X/n?fO?iޟ?Q,t5f?smgZ?ɚ`m?X/2?4=p`?oȿ?65 ?b? Բ?zMp?bU0?>wa}?SVd5?~Qm?;Bz? F?ېj%s/?*?7N:?`yֿ?X^?'oL?ț?Дe?sЦ?! ??@c?ړdk?\C?2LAp?hZenK?Hoޚ?2\6/?{s,:?Oܫ.?|c暱??%%0?y?|Rr?ŋl?D7$?k3?*0x?ngl?F?šc?ڙ1$ p?ԓBeo?Ќn2?E??r8&?\’?\4?'3y?e<x? Xd?I7_?IG_d6?1QH`?!Wc?? *h? {զ~L?J?΁?N*R?-L?.k?&ˀy)?l"wI?w?ԝbF(?'ʟo?X {?EA?$J-?4?xXt?֍?p!?̨??KLp?j2p?Q6?L@e,?ٝQ[4?u?y ?{?~u3?7j8?QD;?W@Z|?X?NSzB ?ح]_ܪ?_?,?^~g?n7"E?)}zl?C⿨??8!~?٤פ^?&Wy?XKC?rX{?gG ?Q*?e0?ڲ ?V;?*ϟ?"47?-fp?ΰ8b?f?e4?Yax?Y)h?ɒ. ?ISз?BX?'X~??0?'g?? L|0?\~f}?n?ыq?QTkŠ~?ՎA5?耞yH?kB?X /s?穁c?Q:?85?IPdrT?MF? ?ˑ$?{0?u7%(? a~?}0)?$jDT?L?J~?Xf??z?ĄH?1~qk`?#R?̮@? sC8?ܡ ǂ?"?oܠ?e&??x?G/ft?ٱ?]I¿??W?OA\?pj?꒿?r? ?ޱ?N^z:? b6?϶ܥ?n-i?&$n?cK% ?[O cl?-/y\? +1?V<_Ξ?R?杮5x?[({Xb?w(=?2A[C?Ifq!?iK_.? ph?^<0?Zn?:V?@u7?–ƍ?ӨѡFB?О{9e?؞n?r?e?}x}?šK`?[3q?#?܊:?yOLHh?xT?K̿Ɩ?<)z?ܨ[?0 `9?`?w?~x\?q ??Z@F ?洛kϙ?d?P?D--A?F8?|1)-?ߠ Q?G?܇^~?GjzP?- Ċ?^b ?Ԥ~ Z?Ww?璜\?_$p?O?6/t? (dx?L8 ?>?,CRŽ??h؏?y?Qn?mLP?'?̷yE%P?ѡ7g_?&P?VH?5%?VX?$9?lp#?K?L@?hTG?Ъ+(?ΥoV?i?}B?1[?/?FIs?)#?`S|^j?cK?/%?S?c?ldz?'tq?CD?$O?#2?Y^?6CQ$?y?9D0?աrfH&?my"H?I&`?Ml0?0xCq?!?lD;G(6?W?8E? "M?5/?&rQ?9Y?i4?< wP?v̨a>?pfM?ܑ>?ꈶG@N?(ϹC?YQ??҂?|[t ?%'?IU&?I'_%?LK? 8?`(Wh?`P.k3"?6?ؘ?WS ? ?yF5?άl*?(}?ιժ? [?4?㭦?M_?*#ʃd?ぬb?]gqc?v?Gf?\p?ۮ,?px?xs?:Hg3?z? 1W[?ԂE?s? Id,?sᲝh?-S'?CwfL? B? ?%as?&2j?c ?kW!?mОof?.LV?Ԫ1nv?E?Ј{?Tt=+? o ?ݗLSGH? =?&H?e?X-\?0j2?ʄ?J*.?/r>|?AA?! ?FD)?A;99r?;) ?7<@|?f*w?̎kF?Þ?o͟?߭Dfnh?ҭJ?x=g/`?M=(]?5Q?ǩ(4?#Ax?^o\?ݶw?ŠR?,j[?x?]mߺ? CU? G@?ޠ ?㯵h%j?Ҝv??)tZ ?){E?;nI?^\ ?L?Dc?~8vC?)?ԛKq ?Cb?G#$?3bF\:?̖|?m?v?wf>?õ4?H@r?kDuJ?崇a?Z΅?ߕ?*2?@?ORUP?A;k .?0fsP?tt?\%6D?F5?j%^}?׮(\?_@~d?@78?V,@?n??:?Zjl?Ju1?G e?ϫ{ ??XsT?ӭҿ ?mdZ,?1H?>?pW?Ó?K]?͓{8?]˼? R^>i:?Ӝ* ?>?Q0Z?Q,n? 9?| ?몘h6u?촡?k^?V"%?|ww?~/?qEG?|{?ra1a`?;a>U?׍-V?9ɶ ?k30?Ppi.? q;?,d?׈P?籽?9A"?ۀ-9? ?h*?4l*?|<,?EyH? A?$?+ ?C0W?OO1?}Bo???d!?4_?쒄X_?\qXGx?N?&J?e?#ȸb?*t??0|?k ?^K`?Ӡ,n?8N.r?Ӌ?_?#?8O?ܾ}NH?w#?͡ ?KVy?E6?ߢ,G?N& ? i"?^L?܅YN?wT7(?}?+b?pO?K?Eѝ0?Mul?з)VŠ?`]ڰ?m?Oߞ?Ͱ?v~D?Kg~?%42?!w?p&(?AE??ZA_0???\?/ϑu0?=q?^7?q7k?bv?&}l?ά% t?`?Ⱥ~?zQ??mY'I?o* ? \?OZ?s6?㣬b?kPT~?Y?/kK ?A13ʄ?Q ? $?.i?,|t?ߢmf? x?x ?f6@?0K$?y]?;?ᬠQ?)p>D? ?߁c2&?š8l?Ƣrh|?杶lX#7?K$H ?֣Bl?<9?/gg?b\?3x?$O$?r)?״?Dd?bA?Ql?uA?¸v?@YsI?쐿?U䘉?>5F?Ea?"LK?N%?lc?:?74?{]rθ?VǾn? z?a7p?ౣq'?ס7/?sf-{?Ю>*V? _JE?vvTBp?}UnD?TF?vMH?btN?Y?ґRʾ? 8>?AFZ?П0\?oH?ܾmt?NR8Y?ͨP+?- ?⏪X?`B=X?Zv?' ?ڴQ?)kjc?@?XU8l?HʡQ?_J}? ̤"?p?*&?ְ/??'VnB?7Ƴe?|?S#`?՜dv?I?#w|ӹ?Hq ??X&h?"Ph?)s}?/b?K^e^?/I?}"0?ڧ[?լw?z@?+b+?{OaW?g@ox?K~?>Km1G?&O-? b^C?$os?~c?Mh?~U -?p?Ô]8?4?a4`??~^U?4?b"?"~!?tY?1o?ܧh?U5x?ʵ= ?띕1B;?h'?JT̄?!mV?؀#?̫?Ø`?Bl:h? ?afd1?\Vb?e?f'?iS?m?nt$X?BL=`?HŌ;??YW?t?ƗĩJ"T?v?+(?"ؽ?4?Xg?@9\?+u܋? ?r&O`?B?-~?늛:?K8Q?*?aJ?)GD?C%na?s?8(?ѧ?cd?I ?w?ܷ/ϗT?@x ?Fv"?KK*?ydN_c?ux?\8?!g4?p-7?A?ɟ8Yv? ݖΒ?9?B_?t;?|Cu`?~_<4?1yj?w|?'X?́h?Ѵ ?ӹ?q H?{D?7?)z3P?4?l?܏@?h@Q^?&#?{?{[?`XO?P ?[?8Ur?˳KI?~D4?t^~?Ee}?ڱ}P?l?LYe?㗓Yʂf?gF 0?Ł2?c(s'G?눃8?d ?͈?#m?6?02?*I ,?2E?O'?8Ut;?)@?B|& z?~0q?nè~?][ i? Wl?Rw(`?1M?rkL?~??P^"?ɷEj?>yy?:M?箾Y?㭽ul? ?f?T$?١S?ԏ?N^?VE3 r?Ҥ\߈?c?ͶP5Z ?5Kԥ=?[J)A|?em*? ?ؘpR?8BG3?Q?n?zNV? (?ͽ@?fh=?ePzl?̜,Č?`Gp?巭5Lbb?_Vq?'i5̼?E90fj?|?/rW?W ?/X#?ࢗ&?]fÍ?њa@?xS?ܮP]n0? #}?ԧ?ׂNT?×m4?ͽ%1?!e?DA?^?J?'9?sn8?7|BHͲ?(?K„?%GN?ɭup??pſ=?҅nf\Z?ط,?C ?̪1?$c?逰8w? ?ϭ8?֔36?ƞ-d1?W?+~? Dp?߇kfD ?z$iH?jPV?޲Q?W?;ϳ?k?$E?En3n?\ "?/d?jenl0?托Ц^???o"?R? ?Ӓ??ym&`?X||?m|Ņ?뚍G:?ml?%e{6?Vk4/?౱?U?o"?¤N?K#0?XA॒?0C5`?ܧv?Vh?_Nr?TOQ?<ߡ>?͆?An>?袙? ?ǼX?%;?_kah?Ρo?/QP?:X?4P?䃗 j?144w?6 i?Seo?huR?(J?d=?rX?U'?MO? V?gs?iIz?9=k?@{D?굆'A?#?C@?]Ջϰ?j=fg?RHM)?ꮿQ?-טJ?.Ar?بU{{?ڽ!_?)6?b_?.50 ?Ã\!QT?d6E.?gn*?beC?|?ThT?}]Ҝ?$;?Ezy?3[?rle}?nP?2 P? %?J')n?ʦc?Q?ӲG?O 7R?uʭ?CB#8t?؟(3?v?;qtד??]ʀ?]h?Ϸ+P? +?p̐G?b|x?ZI?tAW]?YFLl?<Z?er?d27,?!͊?I &?oDDN?|7?蘍= ?‰0?h _?-PF?I'I$t?\ ? L?ϖ?Fdm?wn+?:E|?R-?ȌU?ch/?Q1?L*?a?79b?/t?1f@?!_R?d?P4?WKw?=d ?|?)V\?r3t?p?^@ۦd?ÛeD?KM?~?ژӳaͰ?վ?U'd?.?0{???*.?-?,l:? zᐧ?ϼLYq@?δ  ?ɚA?=)?GY?$t?_?0?Er?/^vc?[l黏?ϩ?z5:?h?Tz?GXpz??#/$TV?Qp5+?룋?֫P_%>?3?z?jj?۵E?ٍ0ؖ?;e!8?I?w?Ֆh]_h?\޲?I??\`+?%D ?c! ?V9?%bv?5iML?mD?0V?&.`0?y!?U=ߛ?w[9?UX'?ǁݜ?t:?:?ot?Bۋu?LYt?2$? ?M6 ?Hf ס?ܨU8?ٜ}?\7]{?D?>&!~?v?>=è4?>Iw?eE?д^ q?:.?Slo@?('8K?㹒.P?X?ob\q?9a?{?߲ض^?I2?=^?N^V?Q|! ?B:Q?H{w|?ZđP?O?rP9?ʀ{L?MT?R?J{O?Kkq?c?5Z3*?$/k?ڏ#ZN?+j574?4j_g?+bf?A?QU?C!?ق?l3F ?ZCO&?oD?PLr? ,?c|4?06?P?) .?鿇*Γ7?:?4Vъ?֨+Al?/Pp?TtF?̏x?륂???3\Ʌ?fɟ?w ?ko?΂ ?>j^ZO?I(?Q|b?99d?n+?5ߡ8?, c?O# ?Jn?k?ğ &?%O ?* +r?u?dk?Yp?J`?ˉrɺ?ΓE?ܢ U?Т?Cؗ?J\d?ڛX[=?? dE؋?L7?X?˨xP?<x? 'P?`ga?ү~,?E_??h?Z7?iOS?ހG0?+%G?K?!3??R~0??'?O8?ȩ?{Tu.H(?gz\?ګt?>g,ɠ?U? Ѫ@?km=5?=J?2zfJ?J?Dy?ZBp?r]?3?hR?ww?麇P2? mќ?ゟd?aG ?"!?Cr9?K}?#ܑ?&p?$f?Om N?XYNt?,j?s5?T/?W7'9X?Q4?Q"?2`TR2?~5?eh>?ZV?z˭T4$?ഷlcq?˞km?c5j?E\`+? eW?Tr) ?8?,,?ƌ]h?.Gs?>kYl?竴7?;B?gƛӿ?&t?)>I|?琯(?"-%9?~s?ۍq/D?f?1 ?4/?96H?.?'^d?ݔ!#u ?LT?7ѣ?Ȇ֢?#]dk?ۡJ[(?3,Y.?ۣ͸g?帺6Qg?Q ?ȻM?}CZ?˭v2?zXy? 7?.?ySBP?+{?ϖS22?N+o?w8w%?m=B@?دzX?\Gy?٦qHt?]):?Hn r ?¢e?퐃W[?Tj?%?lf[(?: ?qi)?G|?sd?UƜ(?3Q{h?⼻gȍ?ڬ2?F6?u`?şY?u;&? ].?,?ɬ/@?_0?]]_?MP?Li:?o{i?Ȗ??:Rx?Bg0??7qL?K%??ݤPl?+Y5?鶹ǭ:?7]:?=?r$?͐I?}}l`??e݌?ȴ &T?gi? nX?,?mA?ق/??ݮHJT?A9 ?aH?ğw~?^T ?kS ?)cZR?T'<?Ŀk?gƉ7?+&?`Sr?^2?#@l*?a(nl0?݇S?hB?ޥT?$MȎ?64:U?l'Q?+Va?hF?9f?8[n1?ϒ?ǀԥL?PU?春:f|?Ȕ;?'{@?Z&X?# ?6@?p,?3>?M?ݷ*n3?nAZ?(2?햐?ڎqM?^Br?FJI|L?,bv?=P~@?Q1?_D?Ԓ;#?8֦5?ݮE#?*x?븧U?L%\}?YK?󁷥?Br ?S"`x?o?!)˟?Վy?9LV?[?6'?\U?*E?ɸ!r?Cј?Tܹ?L?|?E??Յ#X?_'1?M|?6$?DScb?W53?m?V&i?:U(?ט?"%he?W?E?< (P?Vl'? LI?V4?O\u2?qe?<_]?Z#? ,?"ڢ$?0޴?"?/m8]?yt?׷V?9n?YҨop?|4uQ?!p ?ѥX=[?;Cw?hId?KQ? 'DӴB?7I? 1V?CWb/?tF?ȹ>?;ZW5?߸ M?ɂb#8?4Wp?^)?엡?S?-Dx?`?0Z? ?yi2?'|?t*I?yNH?2fɅ?G2܁?b?э[93Ƣ?VmD?#]B?ɁL?1.?L?.H~?gS[?`? M?ڟPz?׀,ָ?Dt??K?3aD? HE+?8뚲 .?P&?ƺX?M>?oo?CG6z`?`Ȥ+?_j?ŽSI?/~@?QjӞ?]?큦J7? ?0F,Р?;{6g?$9Ej?"CaH?wY?q&?mt?-Δd?R ?zp~.?*?0bi?ʫw?O͖s?둋'?΍E?Ҷ&R?ê ?RUEX?FW?FB?ã?'C?'hd ?oU?]i'?8><&l?4r?gE?Hʔ?͜'W|???4pZ?'E {R?I?߷&?;0?7nO??ԗIcp?Bӊm?7aS?_`?U_Y?>qN?Cn?\sks?w?j>+^?sd;?W4?7/ ?0Ѓ?հod?ߧ?|?؎#9?-?Ƥ3?ȕ^"?V/?a ?*nn8?ﻺv,?`??ΐ}?Љvj?&p?ȨS׽?B3$?砰=? ?ϡg)?Ӡd5nGJ??u\?Dq?cqĜ?R+?ߑq?I?}ϙ}?c??F7z?Ic?1d?ކ'*?݉rJ?特~?O?QY?vL?/U"Ke,?l*t?Jsϡ2?H?%fD?q\Z?拾 {?_.l?чI?֗1?c&=?gP%?p#si?r} ?gcsd?^?`jSw?Ll S?XZ?ݚVLN?ۍ 7?ӽI5?[c)5k?Ѱ\״N?D_(?L24?ޭ-?+p?0(?9K}(?_?ܦSmz?"?ѱ\b?ʙފ?헽@# (?+E~?kKi?'Ϙ??Kw?E^??eA??G@@?RS*?䒉I,?v?(?pD=?⁉ƭ??ijrB?FO?ru?_  ?ɛPy4?L=p?G(?0?r[;^ ?ps?]%qŶ?~? ě? yM?5s?v?Ă*h?KvP?gY?ዲ"?X?=`n?, 0k?+G5?:86?E10y?CV6B?H?W?+E6?1 ?9r~?Sr?%H?,̜bK?v??AҴ?>5y?+?{?;s?~X?s %!`???ˆ[J??0%?)?O;q6?aD٩?Zw?45m?%q?%yFa?DpXP?Xy?2ج?F? ?ヰ$ؖt?ˀ蹎?"A?uFե?_1?y?ЬKfp? ,+? w?U?BDZ?ɯ~?;qH?8l}0?r(?c*?d! ?, M?|]?kR?WbA?)j.Р?c ?EB? AWq?1@Ev\?Ώ"?=5`?݇P?}S ?;}?M?D[!ǰ?Ux?z_N?#?|?wД?RwD?[;u?Ӌ?x?살q?XM?ɹuJ.??I|>?סH6?ߨf?Qqc?"#?^j?ݒO?r%l;0?GsT ??d?Қof?A-8?ȰN?֘?A?h~02?Jݢ?G"?E@?ዅbn&?T>F"?ď9Wd?.?%N&?B?4|>?u?¹('?H?ۉ4?yC{?:y\k?߄W&?uxӴ?ӥL?0s8?4A ?3&7v?` (?;?gn2?1?ڻ r?NfN?X?ѹ>&]?wl?wP?\?e1È? r[m-?3G ?6?L- Z?h?T?&h_??QQ\?6?2%?QlPR?]e$?9%N?G%?t7?l3xS?ՔMFI?>q?tAME?3_#?5?i? ;)?CLb5?2^]?Ҫ'?ܰ?䁤O#?|?톕z?}򕄦?*I?NT]?-7?Y:Tn?2sz?~k??ސAH&?%ֶ&?z;_Š?`1?Kei?Vl.?ar@?Ѕ]p?.v?NwF-t?;Aq?B?Ir>r"?`?dH;.?ͫmTP? ?4?/?pS?*_EGi?dM\h?fWú?@$??N?BA3`?[̌?4?1D&2Kl?4Ï ?|#w?*KT?lѐ?:#?\EC?(??YR?+& ?o/?O@8?ϯ3j? ?@d[F?R?>Z7E?MR?9o0?2s]? a?[9?\?}Y3]\?]X?ˆs+?☫F?搧V?n?{׿?娳-x?1;?y9?cP?6Ao?'Mh?~@?!!?魟x?XݠE??fyԜ?Җaǀ?>&zG?`?҃ ?tێs?k?)9?Ϯ5?o4?e6C?:?ԭ ???5?b =^?L 1^?8߾?hj?5 8@?_cD?<?9?V?iK?Hiq?ҤR4?Շ$?x]?1.?&2?M֡;Od?:#?fݴ?fg&6?n.t?N&?K y?ǚ~~?\e@?``0?gJ3?"jg?@Tj\?Q7> ?1X?c?t0?Xaʽ?~=:Z?ԯq{N?ā L?8(:?ѽ2?fj?о@;?S?"L? `W`?.?yZ?QT?%Dg?Sq?Ѥm-4?ՐRf#\6?0dL? ?C?ڰӒ?ݹ0Z? 0?\wH?#= ?t?3?P\N?tu?px?dt)? rP?D޿ީ?D^?+.?34_E?Hi,D?ҙU’y^?JƇ? R?*]6]i?M8?*3?!?n&?FLf;?mT??W(W?qX??-u?`e?}-?#*?[rt?Mt?r߈?yJH?e.=?[g?D]-G?0Z1?xz??}:?̠x?~|?707>?k}ۡ=??ѭZv?NG?Ԍ[2?CE?+2m?N?brݳ?Tѯ) ??ܱD?+"?/l?Sg:?McZַ?%\D4?Z}A?yr? 2H?P?l?ZzMG?\s@?PD 1!?תND?οֲ(t?ݛj?]p ?5Tq?3ꓒ?o}g?P?.D?ȼΛ\|?1awKn?g2h?ю^M.?X]?ɇE?ߞ]S?A6v?+;l8?CaF:@??M7_v?!'??g? ",?=t?PT?n>Kw?Ţ`wx?{o?3V1?Ȁ4?S@?iH???]?oh_?"?] ~?c}?Ωm&Kd?[P?PҶ"p?C?а^X? ޻R?A5?<'?e?lia?C ?6RF?t h?⽶,v?-Lޟ??$?=?ԈHy?,(V V? #?tpC?H"xB?GV?`rfU?'~?h;?-\ꂝ?D A8??I>?,F?EO? FQ ?/t~?/9)?`?2֭#a?ڞ?.{`?־?U؊?&XD?P|>~?o62Y6?E'? ?V_4?(o?xb?K?ˮ:0?ݤ#:?bհ ?~ ?Cb `?^?Az3?{Y)wMZ?P}ؠ?dޏ??ɇYT?Ю?@d ?X?ߩ Pl>h?碁?;B?(?Un`?F̮?h?5v?\? >N?푒P5??R?B??>~X.?4 Q?M_`m?6g?|k?Cbzb{?SiE(?ܥ_بb?H]@?'?f͑?Ͷ:dP?ܾBX@?@?(&{?|~r?V-?ۻs~M?F?Bq ?s V ?ԛ ?g,?2Sxh?U|b? 1? ?9xT?&H)w?&T`?ӸS8a$?3t?Z\$?@!a?$}G?u?3'?jN?6?ur X?J;"n?$$h?wѾT?Lݲ?ד ?J֡?⍹C@?n&?F?%O5?&?F 4j?{%l?LM?Fb?5b?He1J?uA?QO?#?E?_a?V풠?xb?ȍ ?0gC?VqP?֝t Ȃ?i`M&?CPdA?g`?<7o?i9K?-"{?^4? :?]K?˶b?类'!K?=ެ-O?/P?k#`?tb?G?$?+3?L>,?ْ{?ҫ-e0 ?SS?Ƚ%?\]J? P?%Usբ? lU:?v?lX?^z??[?3_-?1V{?@ ?æ#X?ɾb ?Vة?抓Zx?#WB?ӭ ? ?F?$?&^?^ʲ?v$?ҋN?]h3?KHZ s?ۊlpB?p7?;baF? R ^ƨ?VK?#غ{x?V-?l'2?߉?Йsʶ?U`U?HBŠ?i1Ow? ?ce,Ek? Y?=x?ڝ?Q3?f:?h??K*?jr3j?=?5׳?䃁Y0?|tt?ᥢK.?xd?1x?!L9?̻z2$?d?Ą@?֋'?TT6x??@?x?Ե(!?_,C[?1)G?? έK?9#[u?j N?Rk0n?cgN?l^ ?&Se?ã?>?(?5ml?I{tL?!{?]?'X4ST?ͤh?jbd;?[P ?#?(m?BZV?/Z&?D??!U?f1?֥l?`F"?ΎW?WOf3?2K5f?X2?a+?nm?B ?[h?䋻w? iP?̦d:?hR?@VѨ?ؗ.a?,M5X?jUp?Zt ?sU{?9OZ? \V? 0&?vd?¨eB?ߢ A ?5v9O?GU,?в^cu?ߏ?&?&Q9?; MP?{?aE,?K&?"k?((?!y?j?>+0饆?~CX?FO?x??Z)?@!.?m8?%?Ml(?ܷiAoN?|?oI`?=U_?xMݛ?Ժޮ?U./l?z.? ?_5?c?[Ί&?@G?It?1!y?}?3?4(?RO}~?潕Q??6 ?8;2?0? ? q?*!J?vmp?_ t?֩?? ?PT?sF?Ӈtmp?봖Q?ם5t(?O?\ݿ ?G?˝q?ڰ*?Faᘳ?+?6;?km ?m.L? kr?ߧ?D6s`?x6?v*D?F?r8 x?h?'Wli?Ro}?J(,??x?DҀ?Һ)B ?J?l?Ou?aL*?ɥ;/x?Yp?n?kM?р~Df?,`^]?n?TdH?@f3?;co?̙f뒥?rpL?XUp??o h?R8FV?ƅq?Nx?Pl?:1_?|?%?Jd?*KÕ?˔R˅?÷X?sJ%?~sKrG?O Y?|*>?N]X?O?g?eh?Ў?~y?Ut?g?u?1!P?Ŷ|?V`?@OL?>Dž?rN%?[Mί?C l?S(O(?s?^ ?Kr?àð? %?zɄ`?3.u?&9??&?f r?vY0? I?KtO?RT9?y9D?Q2? H ?ިx@?Sd?{?` Wt?ڝ`[Y?,4? +Gþ?:Hw+?d~?ή???5{_z?j[ ?ʚELH?a?zݿH?h-6?5rn?S ?^+?NB?ף8?/R9?}?4?s?㢯?6, ?6Jܠ?o4?gd? O?ɒP?f`?PZ?.H?Iq?~r?βɀ?&dϸ{?6?.Z?RCh??I(r޸J?Nj3?옥?İ9 ?ȹ?#bT?TŠYJ(?ce^(?пexh?f UT?&ag??ç\hZ?.R?)M٠??WJ?9? rط+?A%?ThLh?I??U?l#?}i#Sx?CؼW?/htS?ɯh??4] ?@@Lh?ʕf >2d?h. ? NK)?ޑ T?<(VO8?܌).?%!?{?sW?#?֤C?Ap?+bq)??J"+?,9?8d /?ĮY`?D?t&?z*A? m?AL}{?s?Ь1>h?دXB?6q?>_~?*?02?_D6P?U}?\?Ѭ4?%1?#^?2<5? ņ ?@T?P?0?N`?$!?Ec4?#9? y?T^\#?X?ڼ}٢b?m g?q4Zz?ڂ.8?O?|?LZpK?K?ȉz?;?%e?RD?leܠ?톃)_ ?`B?.J?hUX>?:U? ?Ј`i@?Y$8?{@@?i;h?j -(?_ڹu?Жz?Rr?ǰ?ӳѳ?2?F? ԡ?OJ?8?$W??noqT?c9n?@]T?9 2?YӎRG?Ӽ$?"֩Z?FTq?ʚ쉫?~??\?8P?:E2?ϺF?*EX ?p?'?( ?ڵfs?HW5;?H?Ϧv(?Y]Q&?پL?%/?)?㭅?(C?׌F?Ueѓr?&?^?B>?q5?Yj}?s}?$&?"Xsi?Z?Sp?ꙜB7?< ? ߋM?H ?ޠg?tJ?}pHuT? @n?M6? l?;+O?ic&?7rF]?剺c?*Uvy?;B?rN?8fr?s -? Xb?dtխ?E?&d?5L ?q?fȚ?.\'? 5Y?q}?9$\?Kq?i2[?yw^O?X*y?Y_?Ѧ^$2?F{m>0?7(?7R9 ?РdI?ޕc-v?ՓF^6?nW?`?琞Ej?äO?Lj ??w-v?&}?IĻ?Vk h?5UC?6nn?Ú?$6C?t6?ȒSm?|'䳶?znS?Vl?zX?Μ?D?Ea?B?ج5?ЫV/6?߳F&?2??l_bd ?nk?២27?y?ߞc?.)(?k?7?&K?ޮD?ۑТ"&?~ e\A?`xOmX?߸,?EW?VW?l. ?gH;h?;?A8~?,z?ʳ%(? >~?K^?>vJ? B2?Q5?f( ?~y?~ט?Ѕ<``?TUTZ? /X?{`?7DY?؅t?m;ex?RXJ?N?? 2ߏ?[N ?y4ތ?fgsl?I?!+e?ĠK?r*iv?&?NgL3?>F'?Y4C?vd?Lj$?Ҙ'@??]84\?BlWz,?kWH?nķ?Er޴|?u ?讣19uR??@XZ!?f?Dw?QoX8?֯_T?7]ձ?I9t?#?ʙt?ŻM_?ZDp?Ά9?6RN?IQ7֩?&@?C6Z?Ze-]?{?꜕3vo?қby8?ݥJF?L2?p?ߍX?f?ǼΟ?CW%gZ?1[p?1?3dKˆ?nn?;X?T?߰h ?8Á?%?Zg?`6vx?gr4^?&J?܌?O?I88??FH5??ΠA: ?e?'b?C?g?}?1>? H1?_E@?g ?Z=NR?_dd?#ڈ??:?Lp?ݯQϊ?3VJ?2dxt?n?>?ّ7K0Fb?\V5??مyc ?xޥϬY?\(Z?@m?r-k? W?ʟ|F ?́Zx,?r?c)J/Qt?$\? ]?!#?s`yS?Ė?PU_?լ ?1?$L?o ^?N?ݷ(?Wb?چ0i?u܁?It v?zA?3?ўa?q"Ԉ?݄ ?2?"2?ƊPU? -X? 55?!?⡑?X?^$q$?B i"?Y&h?ĥU?M~=L?;CI?{!ބ?P?э ?ŧ_R?xlq?xVFP?QJ?56?*C ?8 " u?9yKp?:?͸ u?^D}vp? a՘?A?Ǔ?ξ?h?Q}P?D~O?mv2?K&6^]??ERl?ai?p?m#Ԧ?9h?T3p?X{qp4?Ùx[ ?^]L?cY?oު$?ޑd"?/%5?~{%?? K8?U:?j2: ?겒[?c7Ɖ?B?V0?M\?Tո?/Dl?) m?Xb"Ү??=?Cؔ?Am?C~.1?yz'?+{ !?ڦ?m) ?i(o?ćBX?Az2?ᗜ x?V??N2=? I|Lt?;i?S?r ?ݛ>?%+v ?^b?{?Њi6?6~x)T?+ۧa?uܒ/?l(?βٜ?0%?恑JBr?NHZim?v@?ꭘY?,; ?t (7?"g.?0B@E?p?6T?8?'?ܿ?L?͍n'?Ѻe? af?XM'?^ˈ?) vH^7?:a?z%EBe?Y(?7[?97,>h?! KBn??\?9? ?&:Y?\[?o'0?c?8s.?҈?iJ]a?O?@X/?n ~_8?1q?bْ9?|@bj?痞z?ޚ?7~?[0? f?̣x3 9?뷇ݏ>? o< ?ȉs?QY$?W(?eǩk?PX?[C–?U,? ? |4?u'pP?D ?Ѕv02? ?иx? C qV?h?Աj)Z?.X?Ҙ^e?Ѳ 8?VL{0?N\P??&f?ݿX$x?y?ır&?a`]? fP?s^X?"DQY?R++?6?Wa?gRm?[?*H?Zd\?a 4?S?L#?a,!_?5y6 ?.?9U?'h: U? ?=nxJZ?^[?xV?~ ?i?DTF?Ӆv>?u:l?q ?E?ol?l?ɼ<?[y@$?=]?޵iu04?$0?۪;~?@.|ͤ?*#?\^?s?چ5A?问 o ?LR?b?UX?ݴ3L?狣W?̾]?Yb@?sF?&9~?J?[ ? ?s8?|ƩB?[d)?w9Nh?䈊0?۪[?8q&?NjN,??5Ȫo?(1F$?ʗ]0?t2? ?h?Ў\9?] nP?Ye?;Y?t&Q ?!;#y?8?0P ?JP?Mo?ξ%D?d2]?}f?s?3~?qOX4g?x22?Y6A?ލ?T?!.R?dvn6?}#P˒?rG?⬬tT~?ԁTԆ?6+?ؘz ?¹WZQ ?]%?X?ތC+?>oT?Ӓ*?BP|?28N?8%vM?C-?fn$h0?Z+j?s=yX&?COP?tn|_?nyB?q?^Oe/ݐ?GY8? ?㠕Ew ?m;?Ǵw[?v%s?Ns>?~b+?<?`[^??(E?aZ{?8?1ޭ?`?r0׬?\?zP?A ?w?( ?&?գ?Ẍ?~ X?,y?ڲ?f?,b[?P?=W!\?!0?x`6?n *W?c ?^4 ?(? ?溮zE?쇏$!?=h?Ч(?Si˲h?- N? As?C\F?,@2?T??5-V?*Ot?8+`?G}@?א/?\7??|? ($?~p?|? ?0kk4?|,}D?/ՑK?hu>?ٸ?*4L%? |?HO?%Y+ ?HReP?c[.W?p%?F{Co?A?$p?骊وIo?4~?a9g?Ķ2?ҏƂb^?{|v|p?`w?2>%?N-?OO5?dz,?aⱁ?Һ?_#ߒw??,?2?#b?~}^?ؕo?ݐwvkKT?h\?9jd?@ c?huC0?)UV?xƊ b?f ?,?v&G? 3?xft?hhBh?tyw5G?]: |?xĄ?;zt?ȏ?ۤZ?ތ4?H?kdH?A,D??IOZ?*!-h?dl-|*?U{P?烮?;X?GT2)?t?-gvw?9PmKQ?L.?3 `?9m?`w?JtN?r{?"`ʨ?C&?zU?~sa?~lU?i?#Y=>?P ?݌?꿌$%?Ͳ!*??\'?ӵ?Nw ?K>?Ҙ`@?놢?\6?h3?ch>? PZ?ܳz?ߴTj?wȀ?h??R_n?~]Ll?C ?z"?ŵ(?)c.?t͉?ܙ=?ޏxA?\Z$?웓!Q?,ǑF?S2?sW?~P?q5?Td?؞Cx:?,|?k{j?EP?l~l@?Uʃ?a?۞8R?[?3nB?t ?`^?Z{q?о?*_?v  ?kb?ʥ7(?)AD?TM@?JX0?3Z;&?m5?j55?f;K?R 4?ģr\h? H?ä 8~T?}la06@?DqҐ?l:a6?לofx? %?ȋoa?g#z&T?p=vz?繰@?θg&?W@Q?!?Ƴ}n?W?{~\?7Zj?ұM 4J?ٓ?Z4P ?(Px?B̐?ЉHc\?TMCR?02%h?&M&?4 ~?h|S?=|?zE ?M)?w ?٭ńvX?qD?j)?X D$?ǙSx?n޹P?4LG?\x*g?d9(?ѼM?$?h#p?A?e}x?s'?C91Y?ԤN?n\?~(l?2S5>?eBS?1F]J?儬[?[ ?J?ʟ5??*Kr]?,qG^cM?caX?”?}GF@?Uv*?MZ\`?v?w~?g?|m?(?'*?U!?Ȍ?=g@`?留?b/?S='?_+l?W]6B?鐨?`,$?ĕYx?YU9,?4Y/6?IB?Jv-?&?Ih?ӌ]'`?Z?Et?ygu?݀>y?۫D]?إ?Y ?+?p?jȤa?} ?7@??VQ?ݥH{lx? : B?;x`?sҮ?ɰs?r垝$?\|5f\?4SY?g ?yx?2#b?Vg??|NL?߫x?o؛w?e?PP 4?G?]r?jg?j[e?A?n2W?Դ0?U,?ѝ{5?z/?[`??q9=?c`?ԝI?Fᜒ?DeL?ãL=%?L`T9?0?ܗԩ}?+I?Sz%~?իcGMd?ؚ ? E?\v??3?SxeQ?/0 zx?$/a?ʩ*s?a~ǒ?̇W?Jiյ`@?}1a ?F^?er?ףۗyN?4?ڛe}_?!/?}?ò]@?9lp4?R??X?vA?,$?0N ?se2>?uo? fV?w|??c?ՀAL?ٖ?Pk?؅! ?j?E#B-?L<?W1g?n/W?䫬)4?a}>?elb,?Ԁq(m.?cSYٟ? Ohx?-㶷?P!#.?VO&k?7+,l?G{|?V>A?R0?$`?'{ ?7ް?Zk?\_XH?4}}?`Q??y?微.??X?UUUM?HM^0? Ft?;wR*?9FV?#?uc?G캂?7ˤP?gS? {O?16s?x~}S?!ت9 ?T c{m?q|t?QE^?nW??Gq1?76?0[c_?˔,?;_h?h~ut4?Ӌ?ޓD!>??S>X?VKE?ü2-?A(q? $?+k?R 6?^]e¿?3?kn?>K?= ?vgJ? ?3?sk2?Y X?Mj?! k;?e Q?16\?ly&?-(?8h?հ@+J?j?]9y?! ?"J&?K?R߸?se8?㚬aQ?ѹI1?h)?k)+o?b?vL-?h?QYKb?@_M?2~?A3?9Oc?$צ?.=̢?Š4#%?{( ?5W? f?|?,?8?dG܋>?c4t'_?/h?q) ??d?K6Ap?M? VN?G?zS]?¤_?0V?<?Ce4?< @)p?d?`?BkzW?C\@V?>[?(?F0:p?<%l?^U?9? ?ނ ?,J? ]\?ઈ *?S 5l?>L?cċA? sP?ڡ2L?4K?9pI2?/?$ڨ8?nVm?ťM?P?i?o>8\?Ʃ,F?gm{.*?:F?.]F9W?a_S?4l,? 0?鞯Ǵ?1?E?޾CL}6?g֊z? 5?;?&:?5T4?hY?Ȏ?7s0?fY?^nL0?{-t ?O ?ޚaq? ??J^/?/ ?4lu?}b?ZM?Tp?D|?I1~E?n!?mZy)?j?,?c,?|i\?|#L?L_p?„y+?PEB\?ю:?ȼ?9"k?ܳT*?N?ƚ,e ?F2?)J`@?̫?u/?ax?gKSQ?qWs??aw?ԅaYl? $? DA #?^< 2?Зy0?z(Ѣp?,T?{F̓S?N) ?@:h?c;)?ȕls?i)?(PX? Q?^ Cn?Qi*??+&? %`?Ȏ!? l_t?B?B)0?FhX?4?ޛBZ3A?k?۸?k}?. ?&ʹ?F? 9?ݝ?f?^?Kz] '?]al>?ں8?՘=?ٲl?C?3?I_՘c?Hb?v?.HX?B~ i4?wVU?h$l?xk?–'?{l.`84?Qp?@LȦP?N9?ӄG N$??́lUl?r{kM?\|\?셸J?t)'?%v9?,]?3Rl?O{+?踹? ?׆^?6w7#?˩Uʆ?қCO?1?{?VbC?0?ۮud?UWLi ?;HdR?, KJ?cW?ՠ m?hTv}?.x? h ?dǃrp?H9?}W?^?'X?Uc+?W\o?[$_?kP?WWP???f=-~?p?֦W?(Q?1@?ؿ>?ơ@`?ʼn1J0?/ ?˨??:7^fqn?۳b?]h?޶ t?Ꚛ?ü?؋Ft$?#?v)?(AP.?pR䓔?DzA?刃ݽ?P=U?pE?C4?␁L: ?_Yu?fWn?`g?*V?O$$v??{$ P?伊?{q?2ej?W*֑?YZ? ?y(?]Z;? ?h|?rؗ ?Tx?ӂJ 3f?FNb?lUN?t`? 4OUy,?DLnEaY?w@P?}#?o8?(mt`?@R?(R?Sl?)"@? d^V?':3 ?1)?֥Z4? ?p.ctJ?tLix`?V)??p$?{ M; ??? g?tn.?cB@?;M;?җ?DZYv?Y:+'?=Q$?p?3/S $?d?Ѥc?eVp?6`%?5$?N?w.o??I^^?XN8?.e?,;?]W?ksi.j? z?p}[?gx`?6%u?wr u?d??Ԕt?Nfʃy? ?۵j?ܚ'Y?LV?ayL?AF??j?9k/?ٱ?lSYEm?䓷f?'9i?8,T=4?ص?{xy?漯*<?|1?,w0? @c?0Ϩ(?|2?ꍝi?a?4|/?ٿe|?^?*??A5?(y? %eT?ֵI?[9?D?%sX?}*wd@?PyȰ?#G[bk?^#?'h@?d"~7? e{GP?e:ƭ`?u$^??]P?\xf0 ?MKX?}?ۚ?/?`}sTx? p? ?p; ?^M?De1?㳐5z(?G3\?ȠxN?г[?P7 ?A!?J?Ĕc}?&D@?Ly'L?IU?4x?ɡo;?C?$0?ذF?Yn|?_#?g?໊\*?Ň?9r!?J?쾟^&?3+?/W?b}l8?-?;?|B?xu#jv?[ܸ??̥%4?HL?li?7Bx?K͈"?6N$8?P*E?thW??!`- ?6?xP ?*?KҲ^?-Yo¼?7%?4?Y3 $?ٴ:J?FrC?=5G? D]?S84??rӘp?ջnh/r?)b5?Ir?@.Ĩ?!Pb?݂?`8T?؛oTH?ưf%?梲E*?Nvc?E>Z{?c̈?[>@?${?:18?7 ?!^)?i~To?hJ ?H1:?XP0??e?ɮt?Ϟ4 ?:S?a?Mvf?OZ?HV??"KS?ԇ?ӑF a?X yz?)xi?@N=?r Dz;? '?2B|D?S,?n;x?j;`?W<?KN%?0a?- ؾ?䛝+c|?LU/?˃[44?z@?稰7v?ݸ` ?7?{܆T?O(?ӑy?٣ @?S?p&Y?!Ow?lx?7?䥹~?l?)?!s!?ݒ?X?ZJ?C?.?(?e*'?֗CP?͇?ئD$$Y~?ϥ偓?JL?ߓ`gN#?hzo?p?A?N]?cDB?щؒ??⢋F\?xJ?J?ïU?Xb!BG?c3~ ?xFz?"NT+?=%?.*6?Φ/?،Q?r$.?+6l?>e?Uđ?PC8p?`=&?5W$QP?;]l?*t?]*^?M,?yiO?ɒa?*{_?aѝ&?T ?fmb? >?h;?2b?9`TU??M9h?ܰڱ ?]9?߳'[?Y?N?ή`{?ë6l?rHL? -?蜭~U?;U?"L2zd?B:5/4?C )?*Қ?-kbzX?}Eܞ?T"\B?}#x ?U"?n.,?Dݛ]?Ԉ 쳈?E;?Ԏ;XZ?QfO?5ϖ?5 |p?ƨy?*Zv#?8! H?#L?|?AY(?ɨ 3?#?NO8?6B? /Z?0j p? _ahi? =&!? 8?05 8?B\X?a(:?tS!?皼[Tg?bZ#C?Hzh?X8?41?mEkG'T? s_? SF?Ȫ 1?$?j?ؐ?>T?-(@??İ?đȥP?C|?\Ƕp?L? ]?mE??҆?ɿXFʣ?5j?*?<^?Af n?M^J?h0?Mhx?oJHt?ⷢ5?:Bf?P?zlf ?p8?/;?S?UR?kpߤ??_k?5lO?48?ѫЂx? *h?`m9?h0C?>M?7?Ԍ(?K_?/Ho?8dH?k$l?x \?wh7? vU?C)¼?>} :?P'?OX?: {7?@?<1>?_?novbf??@3?GR? R?ܼmW?겖`?Ӳ?Ҙ%ن?ωU?ՁX?.2 ?IA&D?^v^A?ȺW?!7?_{?]Q?و?t?WB?eil?ˉG?7-?`?KR_?FI$?9 ?Hک5,? ?Nm""?ק[j ?W8?@,T?ʩX$? ?\%qH?ʸ_?}:?Ļ?98?I;a*?u{ ?yq:L?a*tjh?gd-P?J?`Mx?뭧+?H?k ؑ$?? ?#T0? ?Aڋ?TX`^4?fv1d?]G?؃?Pkx?ͭ[Qd?x) ?m p? ՠ? iʒ?a?V䙴?g+y ?v?Xќ?~b?67_?ٝoc"?J9"ň?Nx?db?O2B?gB?Z?9?B?3 3?Xț?8"?FZ _o?I`H?&X?m`?S^|_?s|_?N?㿁N[? >]?,cr?I?[?r6Sr?̕:?PZN?b?ې(?E!?SqH?l!?#!^?dE ?v /?**?Ǫ{!?߃); ?YL?Bi4:?Y?@'M?(W;?wPO?{MT0?:NAw@?&z?ots?~?7? 0y?I=d? 6Wf??ԽB>?Ө:?V.?܉D ?}\P?+#?8F?ؒA\?5_F?@?ѯqm)?>iY?v29?y?`?eHݟ?/hE?jFC?|]Z{$?ԽC ? %є?uTW?&Zʒ?쌁h?#~Kp?Ҋ29E?Ґz#r?bb?H0eIS?N?2 )?X?{y?"??ҍ")?Mo?<:D?*OX?z!?Wk?v?bS?H1T:?\0R?R/[?LDSs`?'n?*ΈP?63: ?糌u??,m?Ey?H?_t:%?GVd?Pt"? ڒ?)V{x?GM0?絒ob?`O M?iIzb?w8IP??gB!l?F5?#aH?@%:QP?\_?GV?GS?X;p?V}c7?%8?VNx?a_,?;V?^J?]n?/fm? `?IQLP@?!N?@m?¾?c.F2@?øm2p?*X?߶}?LUF?,ぐ?lߤ?w[R_?< ?2}]?x/^v?6[/@?-"R?l3?C0??^hu|?' ?jn ?f?UT?״d?zF?đAD= (?/kk- ??UR)c?:Hv?on?a ?V? ??(o^ `?ˀeA?z?1A?7?H뢶r?z< ?뎜4>j?Ob̠a?b??@`?V ?ƚ=?><)?0Xd?G-}?(*?[10?NN,F0?3s3?߄R Ȗ?mʦ?py?,,EŐ?׬Z?ʈy{?B4?Χt?^ |X?+ ?;-`Px?P?W< K?CEd?$k?$ Vx?s|?ǫ?O[ux?ߴVYۜ?4M?WiTV ?%G,0? Zd?sm?Vl?a?AzM=?S3?o???Zi^?{$vsp?|?s?2y?rٞLe?&?nm.?Z&so?S?%??ӡߊ)?Ie?fez2?uZ.ݨ?(m~?퍇{?wy?UU?O} ?'l:?wy&?ݥD%\?d8k?Q$?ҽCd?I•?Ii?,"?K?ȔDa?f@`Y?|.Ԛy2?jc?!pu?#/?҄{Á?v>=??zt?bzب?S29?R?X N?ZRb?!ш{v?IL6?T޸?Fqb&X?|*~@?ѧe ,~?2pi?JOd?odl??:)lA?^5?|_Q?gx?N!?Մ?wF?Y%9?څOߢ?&??df?8?ي5?z/4 ?_5?Q+??Uf?埬/>4?GbAR?ҭJ ?H?˕ ?nP)0#?ۛh?ш?Wӣ?;"%?̭IJ?H2U*?-?{?w,[?tH?K9?щW2%`?񙙔??;oh?t2?,#L?E5?Jf?8o?3P?ʷ ?ɭ ?zN? ,?ZoI?߈I4?\ ?ep??Y?DD?3J8?%?3Z?O.? ?/hi?ɝJG?`O@?اQņ??R4?H/?җli?p6a?7?.?} %?e/. ?%@~?as߭?၏Y?pTz?ΰ?2`?W7@[?g{.&n?լ,nzH?#{?~XT??OwO?*TP'?9U?⡅W?ƺk۩?jx?U?V?nGx?#37??ZK?tY*?WP0?Ԇ3W6?K!?P}6? ? `k0?C?*',? W?YPp?c@? ?]&M?4mb+?j|?:?\a?:?hMY)?L%k@?\['g?K?!xH?48?|u?h6 ?l>S?P;,?EEU?oYn?֫?Txi? tm?ָZ]? ^?<-0}?]??_c5&?}`V?*~.u;?JBW`?Q?3 ?oXߟ?~+wx?E?q?W#?7;b?Ei?D1:H?e`!@?/(!h?\k̺ۀ?!Њ?|t?[V @?ȉHR?ɚ?éL?)Sw?>?RE?3P?ice?̒` \?~?\^ ? s?J2?^o?X2?)Y2?ywN?W>K7?#X?=twN?= E0???y?ez?z{t?vQ2M?SzR?YN??" ?炆_ޜ?}Ǝ?G ?5A&8? u?U(?Z'F?W DR?mK'D.?w?>?.d?ǛD- ?hS8!]?}X?I?\2s?iZ??Bq?|?yci2?m_?Ƈ̴? iMKx?t8?c??t? \j?n@?u;동?wi(/?8 .?`D"y?{=?ۣ7~T?u+Z?sb?bv?FO~?!vzܸ?~``?;?1/\?T?N?ot,]uf?dRN?Ę??i*?ųD`?\kB?.k0j?a!p?U D?*5;?GB?cW?؜I?^oGh?~?ghm?9?a ?;^8?犬?SW5? ֹ?Rjv?)¶>?P?wXb?Z"U+??#!CD?߄?ÌTYs?B5I?2Xp?Љp?5?wyb=?ὯJ ??⟪,?JMg?zoǖ? -7D?^?4B訃?ڰJ?ɭ<:?u?snR?)̱@?`@#ڏ?iA@p?Ee?_:j?`n?u?3p?yg?HS?SZ?N?Lw? 5X?!8@?C ?Ҋ?=vw?J_?P^?ؠ\x?dc?ض]?޵᪜?)^?ɉ>?#d#=?j ?ꗷz`2?نi%2?3n? ?`F?67G/?k-Xz? <??ւKt?)xL?%??է"r?ԈW@"?|L?}Q/@?&x?G?}E;?u5.?$7?(n7;? Шqx?c#?5N?Є`1?v˄?"6??#1?ٚC;?Ҵd?WJ₂?RӲ?o)?O?xh?!؏,?;*? c?)KKތ?C֡?\j6I?(0b?GG?У5? ,=7?(.o?ȋ̮t?5[ak?f%?2?u-u? 9?Q?5?K]e$n?6tq?%f?O?&2?{r>??I?WW?%׀?Oeb??إ N?e$?~f?>me?ܣy?h]^?v5$?Zh? "- ? h"?鄙6,?UY d? ?լMP?ԢȐ:?8͊u?eҒ4?.Z'?W? ??g?+?}/?шQ?\?ѓo?矿?*e?;HֆD?-Y"/??c?`r?d|?V?Oڊ?ۡWyy?ھ_V8?DTW8?k"w?r#?)LA?Ō:Np?83? QJY??ؕ'?e?A-<6?獏:"6?ar?D+&}?53,b+? -/~?=T?㔇r?<L?t*?Οj?y+?è`V?ӅD ?uZ?2?ۢ?Ԑ?{o{?E?9q? ?ho?$?{d?9?<*5?p?!o"8? |f3? ?ѷV.B?(&?f @[`?ڞ1D? ~j?_Y?5E?&?ҫcW>?Тy$en?*?]!~Q?n-? rZ ?+l?Q$?ڷ x?~K6?@ah?uJB7?P? ?歷L?:ȽGp?[>r?.#gI?c-M?@?[?A,ۀ?5)?#i1K?ۋ#?ib?ҦMD?牜L ?R܀-1?ᜀ[?$j0E$?T#[?Ly?% f?ITl`?,H?~D?ҕ)?Cjh?e ?0HGt$?FH/?鲫I?n?8M?#!4?㦗*?`H?O0? &fp?ߧ;?H ?1#K?Yspv?0·]?ҿp?K1?{z`?]p%?F?`7?m|)2?,Pu:?1?P3_?ZwG?1$~? `?$փ>_?7,?#$Wm?Tf~?|hz? =?F1R?łqv?CI?^rx?+jil?䷈DJ.?˳D ?׆+?c ? Y?WD??;pK? ?$+??ī?akf?V$]?~(t?{)? Mo?Ƀ?P?|n?L9>?EE.?2Y?}(`z`?JR??80?a{W?E9 g?T? Cbf??~vL?#QEZ,?;n:?E7??,xH?@@?lE\?Wc?^Y[K?j@x ?/1Kx? (?8Y*?䑣?(4 aV?#JIi,?"U (?t6}?׊q9P?* ?E?G g?IISnư?vh?P?S?S`?n?sp:2Y?Psz?J|?%cH??C1?Ea`??-+?B+{?үꘀ?_?zW ?-#?33 ?/J?PBw&~?vPp?뫠 ?/T"{?k_+_?} m}?߸>?y{~?s׫? P ?p`(W0?٥"?`?CYV?ۭi(??"R?WjZ? &|?7A?-8 ?ȚFx?Η?՗[?V?jAX?.Ƀx?!EV?o?'-M?? ;??вK? @?E=$Y]4?*"F_?ҡ_:?`T?ܲV?K?YXs0Z?b??3C?Jfx|?օZ5?8*;*?Rx?,O?Nu$j? Xu$?}^u?&}?%wMy ?O 1L?Ҿ!.?r}Ө?9+?CNR?Ç`?@ ?gf?N6:F? ?ʊ?;r?CĄ`X? ?۶9҄<G?+ ?"d{?ht@?pzу?:>BJo?f~x?pʕr?}y?»K?۞tD?젒k?w{?HUNq?UYp?p3y?NQ?t?Ag? 4?4?+?5?.x? g^O?!I ?~?Jk?댍)c;>?hbb? ? X?DA]4?Vך?x?:c(k?NDŽb?L(M?ͥLh?+B)U.?߷kH?7vL ?#?I ?Y;9?2)TH?`MJi?t~ ^?x*?-?D ?ϧt?Vέ>?۰F=(?.?(Ir?ثǨ?f2?[3L?Ӭa}@? 1O?ZRp?,?#`?? T?n ?95y?g?O*?3Yi?ۈ W?vŠu,?7:t?aA?{.c?:Ŵwq?o1Dɣ?S_H?5?``q?ʾi?ձ'?KA? >?+'}?|\P?H? b ?ڒT?ЩgT?1۫?| B?p5%?ϣ`?7 ?|u3ڻ?(B+?Ȁ?z?&:s?`o҈?ꂧ5j?k[?78?n&?gF?ߣ>LN?\w?^t ?v?4So?W)t?; ?) ?ޑddU?UYϣ`?k?O8?R0?Ҟ]ݨ&P?)@@?`w?ދ}?s ?Ł%?j`d?ѳh?)y?Gi(`?b,Č=?Խw-?խYH?:sí?9 ?ᜟ`]? {^? ?a?̜?їC?š@E?Q9N?@?р?ë\?ѤR?r.b?dޞ?H"?r.?J^?G?HӪ?sPYsX?l]?/j?z@?9Hd?1Ơ> ?eEw;?_$?ۛ?,&k?)??d݇Hr?rˠ=?x.B ?,]L?I?|ae?Ė7?t?!ٶ? #gB?506?nݖh?7?ݘ_1 ?x?ɀ?M35?ؤL?h@?z4?þO?["?C?e ?PU?绳H~?t2b&?'l\?vH?ӑ)?c.2?ʁ6,-?8J?;8' ?҄K?X,4YP?Ug(?ܕ72 ?M?яZW?V훕?PL?f1k???Ep?^?Э9e *?F?Q?G ۉX?hFGw^?!?KڋԢ?!4t?&#mK?̩Nd??D?i?/ x ? %?$}h-?ࡱ2?e-˝?t??=㧜?|u 6?)bV?P?<@-L?>;Z?@1?H]?yT?Ce&?ЫSJj?鲓0q?d?nQ?{Q4?5ܫ?5Y~^Z ?G ?Db?hP?~0ҍe?}H?l)?f~? ՘? ?ٹ6 ?6P??܀²m ?ӿ x?^H?Ay?CS?D?/NSB?1-X? D<3?և65?۱`?K.&v??kS8r?Wp?ގO ?(j9PF?%4-I?K?&H.,?>H ?U!d? ֚X?Iۥ?і:n?wӰC ?ZSq?q%P?Ҕ?^r?[1?$9?࠻'.s?خ:dh?vx?{+F?҃r6?Ҡ =}%?v-?ڸ?{?{ Q?~V!?텻+)Q?uy/?5 ?P (?XXW?l}?nu`??e??adB?X?2-I?P ?'^Z?"~T?`1$?"u?1d]?~bIz?5`?N+?۟+T?륂M??䅄yS?5ݿ?Ø?|?ܚYz!?GOԮ?ڤ'N?}}: G?@?naM?wR?̥?~#?HV?W?ӔIfX?Us?y~ك?մ?ʯ{?-d?⤎?丞/?;l3?z?Qqj? ?WɻO?EQ ?1?rEa?jWr(?E:`?hߠ+?rv+?$΍?jPx?Z?Пh?Ͳ! d?u ?[H?V? p?`8?*_?nb??Y??? ?azi?i?b5?#QK4? <~?m ?d)0?y^?V&:?=?͒F?l?㻊|?y*?z?vRQ?ևtH?R?t ɴ? L^?5uh?NCq?Ez۟f?bF8?FK v?!]]F?r*?{bpy?u'?l?ݎ80?"m?cm?С<p?h?!??wy[?ⴓu V?_T?2?2?}q"=V? *8?LX?ÉW?aC?A'O?e0.?xS&?%T@?2Q?"B?݇cH?Kx2f? b!? cgkb,?!e?/R"?u?<x?[2g??-\#>?h??#~[(?_En.? ?4@?qSm?1?28W ?#?K?`K?{Nnp$?# ?ֶKyD?"'HD(?̒Hi#`?e ?IҢDx?ʓH?m_1~?}]b|?ԕ?y?(]Jw ?Q6?c??_-?L'?l?@L J?}SY"G?y#?ᆪ?d1|?MR?>?V&?*dΑ?Vym? z`?bQV>?h?Y=3@?&'D@?rOJh?隇?XP?_Y?^? /-p?dJz>?R[J`?V?p.?־Qߪ?` U?>S)<? v? ?"?Y]e?u)v?g@ 1?ӗ:G ?Gx8?5~6'?zIFX?Ut?i?v:?ۍ|x?}?ּ؀ x?.]?K2?3?Y*???Ա jn?_CM ?J}#,?༫?2FJf??aI?6h+?쐱Ի?Q"?͕0T^u?'|J7?‡$`?/?Mq?Ѭ$?Ľ#nY?rD Ԧ?Nk&?ᥣI{ ?X_Uj?ۇz?e?/J4$?~y?2V28?]{o}|?';?.a?q)?⶧?얹?pD+&?`LV?ܬ'G?Wbd?1u?KT,?:?9=?浱?Iz0?dt#?%\J ?]{b?eմDB?뵰i@U? bq?#?Ar?4@@?:?І?{?1Rc?١F?ف*?mel?}P?c$?,?rչjm`?LZ?_b-?E J?ȡ ?l=?O?_Zi@?ĵ(?ہU(?DJʧ?ý/RX?Nh?ߤ(:?ZRZQ>? QP?㫗`5?{? ;d?^R?V? (0?9s:?ՠK?ˆ?)?wY?#<:x?:?ZKr ?=*Uh?2[Ɩ?r)?$ C0?L${r?J:?3)?^?엻d?% ѐ?Ǘ0n?Cꋤ?*]c?@+Xl?濗C3?᜜oF?'{?蒽T?h&?ߩ*??C?Af}6x?}R?s'%Gx?ڤ5`?SWG?Q?ux?_Lew?攍fT? Tz9?dr?蟂? *a?^҅? Z?+M?vmG?=Zf)?.DI?Fk?Y(?1?ܢ.) ?t ?D?{+!?5J?zhW?ǘ?>?˛2b?4s?望3a?[|?D[؎?\G!?-3?A _q?ft ?o&6m?U?E]*?҂ftbT?9)?ԍ] ?u2?ߺkiN?vWo?}ɶ?Nqp?Ȋ mB"?Gk?ې۹:?Tx`?Қ?ۛ[.?܅Bmx ?^=JFk?̰?̢l?-Y?FSp,?H ?M[Zrh?{8?ٌJ8>n?1 ;?TL}?l,l?ؚ?Cp4?_6?|c^?G9nH?DaC ?0D?ٕ ?_nn?0II$?=6?f?ѽF:? G;"?!$VC? <6?L?#?b4Tl?)RD?v?xV"?Uq/? ,?Dگ?q.?:<]lp?؈3?ţR;?# ?v *4?YT!?t?1c?lz??u?"?I?:?!2U?R`?YZ 0N?hC+#?ӏҟ?n(? Vy?$aN3?ǜVJ?*?سУ#2*?ۛ!J?i?yipP?ιj8?.*?ѧgd?#?" x?LT?Rv?Wo3?w?ـH?"QZr?XS^?`??YƈF?]g{w?EIt?DKAN?/IMx?dF?ߒ¸S?Mqb?CNcڊ?~?ݵ?rØ\?ƖoL?m* ?fw?3zl8?NOV?[nYy2?G\%?Hj?ܢϚ?>G?1M ?ώ4~0?m&K?<?ޮP-?ݢ?ӎyy`?Ɂo?ܐ7d?*Tx?] ?Z?;?RId?,C?Q?q?ꦤ?<@%?#r@`?A-?4wp?ؘ}q? ?C?N?de?_A?x%T?b?6,2?7?ɬ-0?܃m%&?΄}X?3p"?Nr.?W?WJD?+Y+?He/D?|tcX?7Ф?AH,?i?YM`?Nb~[? u?`?) ?㟍?N?{< ?5`||?9?,{\? ?]L0?ř?V'(q2?^JN?OoA?,֌?ע6 ?ߑvMce?gzq?O f?)bi?_0E?~[?{y?0?ρ?hyL?v!΂(?w(?~,He ?b'm?7?[5x?$<_ Yv?UTCj?fe?+{>;L?} svP?=T L??x?V &?ǬYOP?ffo?މQ?Sn?sq}? F/Z8?1?4?Ď?U-'E?愳@S?fX/!?;?M ɍ?{;?]=߶?\?Ld?%WM?*'L?5HǺ?\8?Cu{0?P NL? 5n?xz?蜋M}?sD,?Ď?Y,a?b~?N:&?:=J?WxSk?^xn`?O??qkCB?G4.?:oJ?ˌlҳ>?AJ?О*'?ch`?x'`@? ?{О?[\ 9'?TF\?7N#0?Ӣ ?rߒ?p?D1?d]?(I?j<+?ZY`t8?& ?.Q:?"?^Ml\?Li?ևr)?>?1H? la? {na?}Ip?a;Sy+?D/? 7?u?D3%?U~\?%Qqr?5#1x?յa۬?n@Ӡ??lF;?I͡.?uA? /? &/J?ѯ 0?/[?CL?5@M?kG2L?\z @?NJhe?J9?8ٺP?oޛ?ĊSG?? 9 h?K?MR>?z ?TFϯ?(,?l?5hE?z`?"$?`?f,QP?,ʵ(?ˌ ?KF?=?|f? ?W?-? $L?=~U'?ғjP?̺tg?+Y팔?VM׉?٨bʪ?*~wK?uT[!?s/(?(Ֆ?ݤ\ȼz?ʅ]9?Se2o`?9U0M?Ll?,,M?rh?ȵ}|?ϐkHx?;z?^?W0?[c0?{dLT?U$G?jQ?Q?-70?4H?@n? ! T?#t?cx?DZ'?߽?6"\?8yVU ??㠎qO?Ꞽ?3H?d'?Vӂ?i٫?C`??;C?Ìy?AH"?' :?"J,S{?"'?3aL~h?>>3"?;?xQ@G1?Ŀm?`?I\Z?; U?X}0?FdZd?Dc!gn?FT?7\Ԯ@??1Oau?*b\^?HN4?? X.&?ڃN?8xx?2sr ?B6?9{P?䂥ZI?4.VW?\N?@?<@2x?Ϙ =qx?Hwu?xt?ً^0?ceP??*3iP?YSQ??ڔU?svLժ?#BIB?Ms?݅˷?Sv)?HU< ?3P/?} Om?hZw3X?CPJ0?nܠe?L q?i?n"BZ?'?u}5? ?ws?G'(?uR?ɝо?@O@ 8?NJm$?-#?;?ߦZ1wU?6U??[] ?Rd?butv?غbF?s8?+c ?Wtp?D?w(?෡4P?bX%?ԣsX?ݙF#n?"?2׋l?%8? bb?nF?i$H?Ԫx?_{7 ?xL?zػ?s?׬? ?"0?dP?Tᴴ?[TE?U.7S?Ֆ8Ě?=ZNhI?Ol?S9g?戎?N ?p#?lN?w06?"?j ?w3??*q?!H?D`?B ?u? UOD?$?)?Q6c}?lp8?l  ?!*M?j?c,?!`:?؍?x/3?RV(?橝}2?%&?DQ?GnvнA?傽{ "?K,?›9x?핀YW?|+i ?^'A?ca&?31[-?b?Ƣ?D+ qw?\ ?? `?A?]<0?)K?+`?/l^?V?ڦ3l?@[Ҭ?g*?2q ?T[6?B6?Ud?aM?Sv'vL?'"ƾ?bnrn?f@?v?$1`?_jڹ?JVG?N sL?B/F?u?Ň=-?/~K9?(G w?Ӈ4?Б?G?e]X?eS.?nxA6?$mFE|?O2׈&?uS0h?Ƚ?ɘNW?V ? ??D7F?ہPd?6.9?nB?-)CM?k1?h^T[?'?nV-h?҂H??ࠀٌ1?%Q ?Ml?H?. >?q "?f:t?Ωኜc?ΡBI0?߄Q?sH?︧_;?%kx?^_?Ph?RF ? +>n?~XM\??ނ?n).?>>ow?Ǎ?I/?_Y? G+?[ ]?%ʩEm?itWd?&jķHS?ݖ@?EJ?8qL?u5?Zuf?ێ?nF1?0B?FN?S$?d[s?GLt@?.?A2H]>?!2v\?T?Ònsh?py?La?zS?&n? Sv9?z?"P?cq?Z8?f? 61?Јhw?ߝg0?R>D? v?[@?Xo?䏵nL?E ?M>?zGP?v_غ?w6j?U{vu>?KL@?P9$?!:?xoߗ?ݶ;Ɩ8?v?3"?rN0?JFƗ?|kp8G?gj?Ҙ{?ΙY?K8? ?`R?s .o?V?F zj?6U6B?"ck?Ιw|?ݠuOV?4-δ?Ⱥ ?z/Q/?!1?R2b?2p?,]?QcN@|?Bc?Џe?p?9'?%xk?27\ױL?}?d?Hv?4>J?w ?8%?ڱ@ 6?}lKW?>Z(z?㋚7?NX<?^lR?ԿTR?Xb8?Ya*?c`r?>S? t?S(?AB?z?ƱI?F?8u ?<'-(?fy?n?8׭?dt?7/{?oSA$?\V?lǼp?ބB?ئp?(&{?)+ ?c?@0?e.?;m.w8j?ߙ8뿸?A$V?Ҁ ?W_?zᯌ?Tp:?"!I{?hv6?~KL?rO1?0 (?ʃY/?% =?[)'6 ?]Ǯ?鬒?4 K?ж ODp?o ?я\?&"?KIF?:| H?{7u?Fwc?@_D? _???P?GT?Pm?&n?/Y?xD?n ?Zbn(?5g? | ?ٳKu4?@IR? S?彪Bt?K?:_?Ƌyop?zxH?Kk?jĢ^?Vsҵ?,=̽?Az5? J9?@ |`?z_;?) ?uJl?g N?τ? 6sF?מ|?]/?A6?Է.1?=4`??{?3?Gx?~ @?lY?Цc>?;9L ?ѭ;[ݲ?NKŴ?k?̢L? v?aԛ?̋p ?+? ?9??bU?-9$/=?DL/?ׁ/N?zA.?$?xS?hG?Z?bQ?} u0?x?d?=VF0?dX?H5Tq??(?Bmmt??`?YJ?? ?S?01?Fx?X2N??S|?&iX?s?M&_p?ݔ 4,Z?HB%?% ?}7ݡE?2Y?ܷߣF?vk?.;8 ?8Y:?^?-ΞI?U*? E?YnƲ?(?ӝ c? ;H?*g?F:?6lJ?ƣ?߱ғG?Ll?2!ic?c ދ?M?C?B[`)?NUP?ᚉm?^?藸!!/?sBt?ԨIA z?N m?űo&T?t?n Bl?y8d?<1lS?ڻKZ?( 4?HP>?@?7D?Fq?cmh*?;Y#?٘G?ۏ h?}XR?V@:?暴ž?| i?$؊?=K%?y>4?)̪X? P~`?o$?W+p+?O;?)?1`k}?ϡ? ?{,z?dh?uX?H?jV8?Fs?ʊ?捯:j?\F=?eBտ?&C,:?؄,*8l?ـJ?bU? l?ɥ'D?XD26?G?N:^@?RCs?7 ?޴ aP??Tlܾb? ,?ZB?<_b?ka?fU`?T?ORX?լ?`?e׉uz?ܕ\?̕'B?곢?WA?鋝}u/? ?uZ%?鴝?dS\?Za$?T3?m3 `?tNP?+JȐ?? bc?MuH? ?KHBҀ?P k?ʶqG?o;[?%z?7B6Px?ڟ?sj觀i?Q2Фx?|?Е&?q t?Ҙ5#I?w??xlgU8? )?1j?鄷Ć?Y?iQ7_?"?א x?l'wJ?9k^?[H@?Ԯ޹4?M٦?>,9?KTx@?,%?A#P?I?&?%6?ΌO"?\?˿GdP??d7M?H@',>?p$j?z?irY>r?|u%?Hr?`-4h ?~BKP?`? ?hjc6?:I?tHVX?#@?D?2s0 ?mU@?JK'\? #@? f?G0?ۺzP7?\:? sh? mx?uJ|?eL?lV?ݩEn?ML2*?ކR?x-?! ?vh$?܇崥?J?Rm ?P0x?,?N?ヌA?^d1J?mUB?ղQ|?l\?cit?0j? 0?q7vUx?&C?mk?1-?E;C1?BeVe?>?8ɶ?:Ȗ@?x8?-?:?嚁?8a?#?߸Q6K?A?M:?DZ?ǁw?0Jh?^A ?m\b?擃 ?M=}= ?-}5P,?`k?1z @?+# ?Ă?ˋh1,?& r?%Qi?׀O5p?}(U?M~Go8$?B?FmB? v?WK@?Ӕ.n?&rX?#ob?np?تh?geŝ?vMY-?̻q~u?掐Oik ?Η:e}&?ѥG;!?š3o?Ș%?kM?3?ՙS?I쮤?Mg?H0D? `F?ft?R?&#?U&?֊V?ձb?ĎgO?1vT)?+!$?fe?ℕ6"$?K?ԫE[@?sgS?=RD`d?F!A?Qk'? ?փe?_?JwW ?ko r?m*?-?ɿ(l}d?%;?p*P?+$S?g"??jI;?|?›?Ǩ|l?p??ܑ;zB?چSi?6Ȳ?ۏ$?Z?ky{u?7D?8ŽE??оM? ?\/}h?|Ba???ℨ@?Ǔ4?Xp ?mWw?=K?mh ?RfvT??#M^?? ?>]?~5?0?̡Lqx?.hI?ޡ?5"/?;36?;Ǒ5?T^,?KlWr?}ja?T84@?䶄`D?5 CV?J??5{A Y?L:?Ҕ?!z ?줼*c~?]N ?تNm&n?m2? "?gx?O?{icġ?B8i?(Gx(?X5 ?"FKg?gEoK?g9?:?:H.N?h>\B?? R?="v??" _3?۲@G $r?֒#H]?ES?o?i^?WM??Nlo?%`R ?Ӧ?La,?ê /?޷P?<;?AJ?<Π?U7?k{hV?tì?Z\`?ؒȃ9B?Q!Τ?ճg@f?#P B?Up0?ga0?T$`?޲#8?ܼ6, 7?ڴ8?]S?|v(?ir?1Ә?8y?46LR,?hg}\?t&p?}6Sf?4!`?,?ͼ,j?^ }(?꟡a? ?`7b?1 ?i p?Ef?I0?蟞Q?X8jM?d?&@?t?l2?LY՝?> < ?!?+T?4?kق(?CS^?A&?"dV4? W?"h<?3zH?A?Ͷ?q'8?/IR?$LO ?IM?K8?̄bw~t?T? /?['<,?ˈ$?+x?p[??%??>?dRo#?Q!+h ??E? NX?W{?ô;#|?0*?踄W(?ˉ?& `?|Ϟh? 4} ?DT?<?f?r =G?An?)%?R`,/?[?'ח?,w臐?Wu?*l=?=}M?ʔ?OC(??1 د? (S@?&8)?:?X?k ?XX?˃ak{p?/HJ>?"gXU??@_.R?T 0?i?*Eو?CŁ?{m?ע}iH?Be?r ?9+*?,Q ?6bx?b/?E)O?@Oh?-cC8?T`?"ם@?^ `?б?;?.?DgՆC?؊>G?ިu?O??zqu?;Rz?nco?iz?o?}B?smSM?ׂVx??T῎&?P ? m,8?ɽ?vЧBx?̀E(?!'x.?Ӥ \?3|1?ڳ}2*?vѩ?}"9?OS?ʅ< ?wB?7{w?p ?w?a]6?_e2?r%\?wo?I),ݶ?ˎ?̱cܠ?4%t]?4Űߖ? ה?э|+??< "?9?̦_?kG8?=Ȁ+?}])?ގe2r?_Nd?JPq7?\/?AQ?W\d;4? eD\?:!X?:̖ ?PLA?ZP ?e4>?A_T?|,1?T9ʂ?1 ?11 ?팋! ?dW$?cB>?CEF)?lZt?2f?Ϟ@?(?0b?$?$,h?ѧC?ђbH?/7?Y\!V?HV~=?rBG?{; ?)?(?.^?Ocpk? "#?)A?ҹD? @3?hC0?FK{\?ޮ]?7C?A0?9@>?xj?Y?7whY?5Oi+?Q<?z?]f]??w3?X?ȇG?#;?q?`f?Γ;ȟ?є?ՙ|v.?_mX?ݩr?۱QM^? $v?pA?ރՊʹ?`a?rC?¹?E0?X(?ٵ?@u ?BܨyN?1r?ۘZd?x?n (?Ki ?EL?[{O[?;P|?E?<= \?új?;$`C?6 ?t8#?hs۩c?нz?'v-$?pUX9?vJj?S)i7?M?{ q?$]?yA6?ocCjX?c0YA?pU?[EN?,[?_Zn?תSŤ>?߮],?ds?\Nsv?-?R]?[)*q?_0=y?tRn?9v?oq^d?~ s\?`4T?hrG[ ??5e?S4?wtt?wS<#H?0/?S=h?O?ԈN%?ړ:?ԟ]Ɲ?3i?~swW? /,?d? Ft?і#S@?xoD,?jSsn?dS?& N/?ꞡ ?c I?t9?GV?LFn?]\?f?VCw?Ĝ~?M}N?VxϨ?̙?@Pɢ??c)l{?E0 `?ݮ,w|??|Y&?څ [?! h?nI^?<?mB˛v?Tr?d1?5y|?Õ:_vp?QG?٭>*?2`h?^q?P}h?j۲f?*˻?v&ʝ?tn?k? ?=(?ԃN?0ȶP?5p5?}N7S?~+D?wO?ܘaj?Ӣ_D ?QJ$8?>?xs?G?(מ6?Q)?"?|?R?)( ?ʬ`Cg?]=V?'Oz?7d~^?oP ?c[O?O ?ʿҀK?J0?` ?m?U?l` ?c&?݁_? ?*`#?q〢fB?uD ?F? ?ǔwnp?l>?kIɓ?l!?짖?[ j?'6?n P?ZWu?P9?^G?(9?ZIT?^?Y?NN]??\џX?Ě% ?@p?H;d",?V>Z?OQ?K?e qe?݋ȷ?9@?̓h?}G(?sg0?hhU?}"?V*#??ى [?cC3i?Zl ?笍 &?̣)y8t?ϸ MK? I? ҈>?̧i0`?u.??wP¬?KX??~xs?@(Sn?qZMA?<ۅ?O7?'#?i>?FUD?'1?BRM[?S]O$?ҳM[R? L*?Xv2?Xes?FSY?KvZD?gMd d?&&?-?^" ?!V0?م++5?)< V?#k?BX?]"?N i??sV{'?r!* ?֛ȯDE?E@F?<?: i??Jk30?ĥE?GJP?6[d?,?J}??ZP?+{W?ˌ>m?e?ǎ1;? qc?-Fb?qa#rp?ե2n?s:?U6 y?^[?JZ ?΍p?`ld?߸> Q?=K?~2?훱?r?5. ?|BEh?@, V? ? y?z6?H-?E,O?$~?z<4G0?'k?e4?鶽\?T]l??65?ɖ6M?ߑ4^?iTĬ?nFg?”4?R vF?l?ά~?Խ"5?wBw?̍?S~??ށn6w?v0ew?FQkv?Eawp?b|@?K:5Jͬ?Y?%l?6c?m̾?Nh*?Bߚ?c)V?ٕ?ʽD?z?v%?zVb?˽88r?슂Bk?#?8S?׼b ߫?ȃGe?,.??Uog?e1o?,"?U{d?npF`?۵z ?쨶TZS?/?H_=?FMa^?MB?䁎@M?Չ#yf? F=?+* ?O|?5>?a8}s?zx?Y(Z?Ա9rc?Zr+T?#VD?Z̋?r;?P ?"?-.*?h?qc? `p?m*c3?5u[Mn?P6aD?eF?z??晴ng?%sh?ںĠ?r ? l*ͬ?-?upv(?63?5AǴ?y z?IZb?ߎcH?O?1Mcd(?̳?aF"?܁Lb?aԊ? `[?X ?W h?0\?fج?xŎt?ǐ,?XN?<6?r,?ܖ S?␌1J?6?c?4'M?v @?xkD ?ʙs?ȁ^D|?깷?ѻH;c?ZS ?.ao?߄{M3F? p%@?QT/?Av?J8P?Ai1F?Gh$?閧L?v?ѻ念?R6|??E?ˇ?DJ ?cb?7Eњ?-{C?_D?k2??D?y?qw?nxE?⍘ ?¡A8?{(N??Db,?˙˚?ё?1QY?WI0=?F]?9?c%K|-?Gu@ '?Y ?6$?бĀ?ɶBz\??صr?ۭ\?XN~?4iƼ?A?QPF;?g6x?!D"?AX ?DQ"?5j?O/J? `،?!x( ?Ov?z>nK?By?#f?Az?f 0?t 8???\c?[6?_aW?>6?r)?9,?qt?_5 ?REH?㦐?iI2? ;?|k?՟0?@D]??gp?䭂5"?'c|6z?{%:$?z^? ?͞i?̬*-?'?YN?E?\k0?Z,??܂J#` ?̶]D?Ȥ]H?L\۾?B`OL?jn??=Ћ?Fq?T"q?:8%r?n)?ҥq}?MXF4x?zQ?c`?ܬS6?I9?S ?%? 6=??g??ۖeO?=?nJoRa?sT?t@-z?W?${?׮+P!?8~f?Mm?6?"|?w逶?VZE?8ky?jd|2?\l?DlG?'?Z}.?Lr?ݵk5?NiPP?ޤ?|X) H?>lҞ8??p"?{V?Б\?6\0C4?Ը?(|?8?hwᲬI?惦(?C\? "?qo?Ѝ*G?N?s?#?Կ쭐? "?Ы:? qrR?>7?k?ʪ?*?YG#l?m%0? F?TҜ.??d!?X ;?[ǰ?Ul?㠠?Vn4?zt?#tً~??W?ۅdj?c5A?5H>?$2N6?h?gp?e?P@?D6?N"p?/ r?%E?IS?<׋p?zO?~@r??0e6o?sZ?%N[+*?[Pb?񇩜|?گiHd?`?bq)s?񜍩N?{z???c.o?1V?{$#?ܧy ?cnz#`?p$?,?C|?h8b ? ?t=?z-6bg9? o?+8-_?2,?Ỹ.?y$d? ?yk?ˆG?x|?MԎ$?n? UC??g?E?ÌX?MT?^w?8.? W;o?c[}?s k`?SD?]h?VPc'?VKܠp?쟊7I=?)iG?(S?ބ׵? z«?ߓbJ?(2n?j?o?ћ oJ?DLv?ث=h?zx@?.g'ְ?0iƣa?Ya=?6%?z?J~Ђ?8F;?[|(P?k٤? ^?k?[f œ?|j?V>,?ЄC?oS L??(ݩ0?ƒ!?T"g&:?ة?ퟗmE?eu?TYg?1760?5uda>?'k?xZcU?I(? I'?m`?U'o??c(?/ևw?~n%$Q?RU֍?fǺ@?04@?gnP?sl?Ɲ% ?@R?$$>?/ ?ܾ?? ?^-4?*?99A?്7Tj?O|J?3?edoV? M?<ȱ?X3?|_?016?\xԦ?Ϛ:2@?ś}շ?9zG[%?1?xoX?ȄAF?5ڠ3z?㪒.?|U!?E|?Eix @?`Y$?شL;?D~?DgEL?ӿ vx? fi?D ?l&C@?8lʦ?u ?,h?_T?Qڻ\>?Yn8???oj?%0L?g][?e~u?+0E?YX?{4?[VES0?Jp?XC?qD?$|)n?eIdw?J̰h?~[!%?(~bּ?? @B?yA!(?J?u ?ԨGLY?R"&)?zaG'?.(yD?a6U?aeʹ?<?,?uNQ=?lSK?!M>8?|$H?om?׉GJ?˵l=I?pwba?o΃?b?*????uK0?s?? >?R?l0+]?? `?y-?ݚ03?0L5?ޅ1?ߩ^?NQ?sWk?k?$H?c+]?̇ 8?0.?pZ4n?.Uy?F0TW?5Ȕ??"H?n=?r@?,ߪ?g-?kDB?Ӕ(?g`I?w]?" ?ab:Ш? ??4QR#?eӨ?9_ ?s?&??Gq|?.E? =CK?>Mj?]D?pB%?<= $_?ZsM?rς?ޖ֎\?sv?jI?K}X?5?Y͈? ?!jPV?lq~?7r ?㘗GLW?218Z?uJz?̅]?#3`?SkB^?;?F'X8м??o ?G?o"?F١?) ?\kW?F`?3l?1g??沠=l?;؛?E%?WKΦ? P ?ڶXV?\85A>? BW`?H$)d?%Y:?1?,a >?$$?ڰEX?l/?ϏH?Un(| D?om&_?Y$ ?߂27h?;b?c'?2&??|٧?HaW?U!?aQ\?(,0?|t6?٦?2+?j}?SR&?Rx?vEN?͜_r}(?:@?X!?dX?՗'?"WeA?3l5W?5?ظl? ȯ?ИO?ZBn?mU?Pvn?{J2?*8?p@ 0[?|-(dV?0`/P?y ?f2?w?éq܈?!,?O/[??0fi?Wv?f0d?q "?'~??>*T79?b`?0R?s䝠?}z=(?6b?=/?0?؞V}$&?erʧ?s0s?eJ?3]F(?8k?%?Վm?P$$?*`?ot`y!n?y\?s]P?bAu ?RQLj?U2`?Tϐ?贆 ?_?Ӽ6?ɦxx?|S[z?N?ʵ?]Q?U?ĖN?ltX?陫)V?I ?1`>W?i>?BY?Uv?̅B v ?8:ߕ?8`?⏓f ?U!?E?js!B?}8?S?% /2|??9.?ԐEÐ?9>'?`Ì^?\^?1)C?\!r ? \{?֎ᶚ?̃$?㟯Yw?4|?@L?Ȥ.)?b?ФK_?HIdV?Dw?맦>?bA+??EV?kR*?:t?pou?筲!?.PdP?in?41[?.YLx?1:J?Π!?r#%k?VDr?~lghp?؀" ?֤_)?Э~&?᫣Љr?WVȺ2?$?S2?ƽPdNP?g?؀e= ?r6? F?R?t:yQ?j<?%9??ZA|w{`?c;\?I8?HL?ZjT?2)7V?ӤTX:?ه=?jT!?;)ȋ?y/x?I]?aE ?BĴ^?ED/?rn ?Ks?P?jt ?VO?>Z0?p o4?ԇ D6?^?ΏH|[?hԂ?Pt?1 ?өf9p?v$7?Fm|!?LX?/V?r?੣ϡq?Sp?E|P~?tZ?fp?z?M쌴?rl?o?Ic?'^vh?NqW?Ghc?:,Y+a?z f<)?Z6dG?HH7sĦ?!It?_9?e8*?)?Ux;T?e?ӊHjT?;?;P|?7?,?/?S f?iԜ?؛&?G"?6?ܤ̂?^?kt;?͈Yel?&97^?Ǖǫ+h?0[P?ٙL8?B5:ek?s}Va?SGE?ۑ`a?Tp|}?*ElE?^? ?e:X?;Y eq?or?P:?>;.& ? x?N?՞;z?ۺQR?@;iVR?6Z?[J?#B?Hl?㕮?!{@!?ԣ8J?Ș?ܧ?͐X?ح=?qp?龔,1 ?DZB? =x&l?ᾰ9RU?@?|>,?k=o?ʭYl?3?.%B?!+?"?D?|ƪ"?E'd4?݀ vl06?-?V?xwO?p1>? /[?Q) ?KoWZ?(?plL?M? r?|??5&?- ?- K)?15 b?>^]?$-t?՗^" ?GP(?7lMV?13?,g?†Ad??h"K.? ߸?M:H? #\?HSt?׹vl?jp?l5F\4?p 6?~ {"?۶ g?q I?2 @?lLt?^$?CK?թR9?u ?b%v?_lG(?/[ ? 04p?Y?"?灰eM?mW,x?nyX?l2?B/!?묾Cʩp?v c?}^?ӌZYDB?\M?\kI?;WsX?z7U3?;z " ?4O?a?#HU,?T=ω?jKB?箋Q]??7D?_w?0ϭ5?뛺?g%M$?W?D 4?? ̄?y1e?1?ZBS?fO/?GjۀH?4 ?T=?3%9I?6=?fJ <?Z"fdI??mhÎ?M?-5?f8?He2T?pj?4h}?f#l?JWa7?R̛O?OU ?P;fZ?l?;?i ?ͪ[h4?7?oy2-?>J8?4 qy ?f0?=L?3 ,?ᩝq ?Zr?eڰ~V?M]]Q?=y&$?:&x4?G@9t:?bI K? =}l?Wj3||&?ӏ!?Ԗ<.???0!e?рI)?38eǠ?̵i,?0qWh?k؄?")]!?L%f ?‡mF?Ol;?"?@g)~?zы?֜8?H}?Iז˔?(Kf??DSsj ?bw ?? ?6T?7.,6?w| `\?n?\;I&??k_??Һ?R?ɒ EL(?7?Pph?`hxIS?#Sy?Z^&_P?DP?[~/?:JZV?]?}S?ጕ4?6.NP?$*g~?FJ*]X?34+FD?|b/?p=Ox?I)?Ż)?lp?5yV3?G ?ӗi}^.?A 5x?prV.?h+?Q?ʛο?2f?b;P?6Д?3,??ߍBYn?T_?*?ElJpv?{oI?6x&L?DJxN?w& ?$fŽR?91?c ?"?eCG?͏fA4??nu?rX?x2Έ?F$?û?CK O?~{?duA?Ÿd?H؛L?E=?o)m ?Uޒ?( Ȕ?m9B?&:J9?h?LUh?Y?rh?\+~?Ѿe?a8?I?m7?Ɨ?˖ߦH?Io?34򵖇?6-?P{?_u?~&QX?Ҵ@\/P?ajRo?ŗZ?( O??[7>a|?l?fG?LIQϽ?>e?=*?{$?P &@?]ut?07? ./? y?O_x?8ܜ?\BP?>n*r?̂?]?d-,@?˜H'?*? u?hCu? ?خBOOJ?P?Kxm|?bHR ?'`?fv~?S啜??>7\?*:?zz?\fiP?tG??P ݫ?0־į?+l4?ܽrhZ?܈@N?;?1D?eG#`? |y ?䟚?Aҟ?dSv?݁q0?xt;?ieA>X?b?e``@?XB^8?8q?x5?Z4|?G?G?^"y?FY ?8n?b8?s?bNɿE? ?} 1?͒ߏZx?ce]?- W?eo^j?>,?j|?0Τ? ?ד?+؁?68?8A?=ݿpt?d`?ϐul?)$;?/I?ۧ@֮?/'?* ݐ?&jW?eL@?T?^`?/h&;V?x֓p?>@?'[?]z?;g-6?x$?9m?C?;3-? ?E6?ܚ?P5\30?V,x?S-s!?+bW?%6Q7xX?|{e?s F)1:?tlp͵\?!&v?T+#?L?%dXx?GJe%??yya?X\?ۜj?ֈ??C*?g?2Өp?׽y??ڗQ^ ?h?vi]?i?Py??|lw?6Ƕ?Έ=?Vf?沰Z?%;?C":?!&OTt?Ҟ7+P? Mk? ?Yj/?`q?M?$^?^E?ם ~~?oq!r\?'ys?ïCD?k?L£?@?" `?GR>?vL?#N?Y ?3kWg?'Q?qs?( ?Ϡ?c&uL?՘@v?Η(2?i+?[?ʻ ?/K?*?{y?πBk4?:2`E?Q u?&QV6?Ptj??P?'i]?ӵ?:, ?Չ>!?7?Fx[p?pT'[?HBq?9a?|uI~?3k:?EDB6?~] ?}1OQ?D|?z'?Km!?Igs?:nH??԰! ?tg8?4b?!-?K\*8?C>m?DӀ?ԝj?_]?ܰ^_y2? qG?9s?<֮?0?hS+[?+ӫG?۱ p?aH0?^es?x?ߚ :?m_\?భ]???FqF?61"Ʈ?\zF?>C#?)7y?=dh)"?RQ1t?x;p?AS0?팄jl;?ᎢaB?eS?? ?:mɰ?+ ?x\]=?}?)Cl?Ŭ6@ ?-}?׿j4?ڢ`Z?B璝?c?O?Zk?⡀-u?H?ɸܠ?h^]?1?yz?|?'W%S?& ?Pz?7?ÃZ?¦?Ӭ9i?@fU?α?P?G](d?EgOT`?Zŷy?Cx`?nf?uI!A?7/X?wQ?|#Ie?t s?:4?K;co?ojI?48%tQ?%3P ?ՊZ?`"i/?g5?7[?r?.?j 5?HLn6?$^+?~&.?~?oCX1?*7 ?~?F?bk>V?~x ?..$?ܡ+?Ӄm[yx?I`G?F?|5F?/m?!7?|Yc?3yU?ƄYh,?g ?"G0?=rw?p+G%?s S\?ت?g!vpX?C3?Q?O~B?.ۣ?d?w?ƌ?Fa?wqm ?I84`?BD@?}rC|?Ԋq?ffZ?Iq.?wx=?d؉?#C?_YY?4Fƣ5? ~n?ƀ:/t?YL[?@?rGB?yN?~~(?H+? "?͇js5?E Ʀ?i+?jI??x?}7?v ?J?Դʝt?9F_)?;/t!?蟽?4?&c8]?J?d61?T$6m ?D~%ZQ?]?4ۥ?[E?6?]x$?hp?йOo?`%?++4 ??$^؇?(p\?& F?,!"?*K($?ԭd?h{&?^0?,L1oo?^m?$d俢? g?hΗ?_?܆l?Ϙ?N?辳hB7?\̕?V `?k(W?vM?? `U?]_CH?ڜ:n?n(df&?/K?k?!c?Jaw?׀ǔ? ~H?8p?aSr?4?Y{P?Dz,s?mm?M*?Uq A?L$ @?ˠI?Fut?d} #?ts?ƃ)ܘ?ΈE4?xÌ-?~Oux?qZU?݉I? t?Hf?fcm^΅?E-Z5`?D߶)?΂8N&?S`?.]Ƣ?f?o'e?K?9)O\??8O,?IE?x˸?9?*YX^?ؐZ|?òl0?$P^P?xSlE?OH?ǽM Kh?u?tb?u?ᱏlJ?߰^?AlG\?nE<,?Jq? ?lX?E?ԧ/>6?▥e?;?5'?/X?_~Bs?ռH?Z?ߍpf+?%Q? yI?߳)8>?aD8X?Cn?84`?vў?!?b?2fG?ԁث؋D?H=?d?pfjvj??縎Z%?Ԙ"?+MH?}id?XJt?ׂ!J?E0h?0)sz?}?4" ?ӧ\r|?!Ӱ?E5S!S?RSq?ꥳnt?i兮?ԍ?/?ʿt?4?~G?BFg??UC%x?ڒ-Ŋ?0?T4n?팈s3?%C$Af?[[?vA?`?;8R?A?8?ոCD?=lrt?H;P#??q&?띍f?-ł?d:V?Kͮ?6$?,)|j? D?lU\?R?y#v?Ԕ&g?ځm?F6`?R,\.?[?ាR"?/|?7?]|?v~;?Y,(>?,AY>?қ '?g.??؟&mG?P?吁g?\?̝ /?Ƹ?g ?d( ?D/?P w_?47uE?p ?h?;`Yr?1c5?6mN/??-?,cb?h:U?H ?Ma?ஆtt?j ?V\4?p?ҜLCGh?$?Qٞv?V?S?+֙T?$0?ϱ@R?}Q:SRP?^~O?y$s?枷?s?`&a ?t=B?%W?26 ?o_Nu? 8?nN?H?ݵT? ]ya/?E ?ۢ Of?ڄ?=$X?szT8?euKD?<]X?dთ?~?@D`?C8g?>.?~l2!K?ߣ|}ݶ?MpÉH?7mr?露? !?Iɷ?յS?;c?`?⡚?ҎCKj? ?i*? 8?1/?ҸS? >[v?vAkz ?p Ml?ʄ{5?06X&?y4(? :H?Z 2=P?";$?\$?s&3?ҳ ~?토ݬ?к9w?qz%#2?ߐb???5WVC??p1Y3?Lވy?a{;ݠ?$6?B ?G?K?艁?+\W?E?[Q^?5V?v?M2\?o :?ze@]?12{V?Ӗ3i?_, ?~ ?_[Dg?|?o2r6?I+ZT?‚u?ppI}?t /?{B?q?4eX?”^T?ƩU^L?P2nF?u?`e^?}*TA?ӹ-?pCx?X@O?$ދ?=R C? -hL?˞?? MRa?L%?#5@?&;㹧? L?}@w?ҁ[?g[JB?C; ?_v??+6?4Qo?,L\?Z?~d?[;=?]l? 7?"?."J?c>?ڬ].?&%W?#[D?0æOH?=z$?>dl?FxH?M`u?eDO{?9H݊?sd[o?Dr?L?,?gS?Ub?1?gL?쨔FH?KHɭ?w˺?u0?̨h?삠?ԗP?V2?ێ(d?ª80?xd?q?>$#?IUa?ҥ$2R?jP?] 8?TK,Y?K@ذ?س? AX?Ș5r?oK\?Ϳ?!,*D?eC?V7?O)BS? VL?\ Y?ȚX?ŹٓE?&9ی?S?+?o?)j?XĜծ?)9KM?~M?Zr?F?6P?"p\m?w n?!']K?W@?Mr_3܅?ͨp?zx?E?6? ?5Y?Kdx?t&?({?Pb`?m?l?u&?]G}"?LO?ܮAk?5h/@?%ܻc?Jh [??~0?U9V ?Ŵ5aH?'?Iӕ?ѕPDh?l|? ߦ4?%o?Ld1@2? e??n[T?ԱlQ?̴}?׸dBH?O?FQ?IPCb?pP,?u9#?֍gm?ߡ`?ٲt@?#1nn?F:?J4X? U?LɎ?Qc2?ڔ>|?j+?yw?QL2?h\?0& j? |b?,?'`?9?fmP?00?qD0?5M?gn݂?S'wŌ?Ll٭?}o?f[$p?  ?Ɇ)*?~?2ŒZ??ؼ*?#?MQ? 4?ʱ?#?cdD?53ּ?ߺA":P?#س?t}w?^;?ؽjm?Ц?8?}?2?#1?]?TX?g_?$G? $ ?ܒ?u p?|bD?QUvʥ)?ᑺӰ?m?2?{=ѿ?`?'l{?#H ?ӿFoT?,*?hè?Ul?PSp ?)|R ?35Υ#? 8_?3?* ?R_N?E''y?x^P,?V8rya?r Zŀm?^?^z?d.} ?:?ծ7?lbz?S?nysi?9+i?98 4j?3H?P!4-+?ᇴF?(_|?:?gº?- 'n?&*?Bz?晥1'?6t?{j?NmsD?l1?Hݴ4?O‰?qK'?ЇSB,p?ލ+?o*?fG? ?ޢGȽ?в*gwB?N:;?SZ?2z?뽖˩?ϰ? `8?NRD?ٞ*9?̤.3x?<(?ᄋ8q?xW[?$56?՟Q?Sl?쵄|F?@ڈ(?u[?V8N ?g^A?d;9p?+9?Cß?Yr ?]?$4?gCR?7L?xj??NM*?<,Ou?L?VNX?դ*r?;Z=?>V ?ޤ$?:;d?欱?=lB?Ր#?CK?l(D?&ʟx? ?6L@? V?{_?&@Ao?vFC?c9?'C途?ϵ$7?ފ?;fCF?t?}X?͹ ?t͌?<ZA?Tc ?c+?f?ʹ-8 ?[lK&@?5,9h?]S?Q?B cv?d?\`?pӲ? fF?$a|?C??LtP?vX?Ͱ?sk[?ή ?e?؈t?~q@Ư?53H?ql?Z@?*>z0?옉=?8Lu?c=ĺ0?ԙK ?b&8J,p?&F}?[?(?Mq6Ū?!APϠ?cFEB?ꫬ(*?I_?/Ba?j??!?nX? Ƹ?`OR5u?7?y0~8N?',ȡ?/1?ኩv)i?I'?Q~U?B@?3XN֑?n ?λ:?s'?srAK?8Т? j?\?%?xQ ?S?E?S<"?ȓ=?:|fq?|dų?{|?M*?&ҹQ?ŭ7?ԝÀL??h??ʜ)?ٌ?gr%n?+)?_J?~"?gZ?{٬4? (9~?.c#?SJԆ?$^?HҰ?6?h̞)??%|r?Pw =?%gC?ЕkB?(0l2?㦉?KmT t?굃>.>?ӂ:Y \?`Kt/?ّ?2(?qȲ?yp:?Fx?F?X h ?Zk}?Ѭ\,?kB >q?Mn?ue?"v?Ե ?Ӌ&j֦?S\͛?U?n>B?ۇW}?rHr7?m?E@g$?n m?VyR?ޒ?(9~?v=So^?0J?W:RH?ŝC?nz%?ǃWV?hob?ٮ[U?2^?4rd?e. ?d<1? +9?մn5?'M|?㯢?j?]w ?>oL;?cٹ ?RC?[Q9B?PgQ?Ҋgݸd?W?@h貙?H?U^?㤺>L?3!߷?naX/?D?j|8?R?/2?]W?ec&?B *?/ ?A -S?w04_?BW?U ?K?zk(c?_pI_?,mt7?1R?ط+T?ޢ8?&_A?[»p?at%? Є?ȨM?"祭`?)T?ʚP#?~Nr?k2FnN?/b?;ޱW?̱|%͂?Z3i?pZ?f}?Sz*)?e7.ܥy?(%c?` ,\?w$?oM` ?!ՠ?1:1?g"?'c ?tZ?яh?ʮYeU?x?ʿ_82$? ?Ѓ"s?̀]1?yA ?Y'03?̈́?GY??۟?֨E?E \?_%HK?Ϲ?Kƹ?G#?P<].9C?|Y?7^?rzш?aJ?i`[#?^G?\tAH? ܼ'?,u?nlp?˖l?sw?Uy?mn8?ӄ%b@?먻)A0?v,&?߲?ıl"?3?sHz??{u? =F?.?6?:`v^?ǺB?Ь\x?Ķ=*?Ig?Uw?4!Cz?1Bȯ?2?t)5?7j?դٸ|?sa\\?8Ftޔ&?0y?}?RP?_S`?ǿg *X?,<^g?DM?P^™?|`,?Kz?شω3?O~?-? &.?Г|o0? ?sX?Iˆe?Y{ֶm? ί?_?ٷ{5*?Uy?TQ8?{ų?Iu?l 3?F?3􇉘??U?i8?߮~?d?"YUF?)b?i /?NcP?O?Q|^?kd1?1?¦ ?3,2?UCD?J6}?p?ɬA??c?@\!?췦?"?i)"?= ?_?9?0 }?#xQ?Q.4?E`?}RR?w9? ?*|?[c ?? "?#XpE?&??c܊P?%/ |?o#?`H?i_3?qj*h?Zi?_zEZ?>TC?8?u?Rł?Ƣ0?o|?D??f/?J?Ah`?D?;?JPC?B-g?U?hX]@? B3+?S?㒉w?†[?r$?f^?ʧX?ۊۨ?S`.?1!}I?Ef,?u9آV!?b9?EV?Ϯ?? e? f?=x.DP?vT9p?Ѡ:?&7k3?n@A?ЛA#kj?ߒ`?~o?-JHh?Bty3?K%g ?|[dLW? 2)#??]?W ?? }?db2?к3bX??X/:?mWyU?鐮*S?, ?;f?Վ?ޢwΞ`?6qkk??^s?S½w?|`\]?H-f?2 K&T? |q?͂O?iG7S?>$?՘U?6?>xZ ?s ?"hoX?K?a̬ ?ʊ'n9`?*y;K?Z?Jm?V1)?'ޙ~?Y8*>D?0b^6?Ϙ@v\?o}8?;!V? R?/;?`l?CƔ?0~n??oRD??Wm|???eE?-K{#?]p?n?u#:?hv-?qȸ?챀? 4?gp?O;]\??YZHt?D\,?E4O\?y?j KG?,:gP?.q_?Sv?tQM@?Mua?l1?gG,?#pה?H^?!LZ) ?I=*?~v Q=?׉|?[> 0`?ԁ9u?W%k#?3k)?$i?ޝ D?LIE?ڰ6?;wj?a@?(W@?3t(?nn*F?H5Q?M x?۟D?7r?愓)m?$:?+%+/? #1?8]?߃%5?gl?`T2?&~?;?>?:WD?%PL 8?:X h??p2V?l% ?b=9/?e'48?d;?ӌiw'?c$3?yv5V?ϱ.-Z?*\^?x?Rm^r(?j?mA?^.?R?[{?ח??9ON?|? ?w?ꦤڮ?޼?}?.3?@?#b?L?J[Ա?.?I)? ͇\ ?b48?Ɉ6?9w:?촜O?"(:8??3ڊ+?lbި?ٯ_?XͶ?,`?s|8? N4?*D?u ?᝞?Fa-m?pm`M?V"`?d?:l?A@7?ݍZ?R@??ѝZuL?OH?C/?dD o!?EM?I1?=Թ?*|?X?o P?J4Ȇ?{?ȒUH?ٯJ8? 10?Έ?>?_mP?^W?BΛI?LC?"q?WBC?pXvY?_?)4?utcM?R?@v6? !R^?{?sK??) ?!1(ܣ?os?I? v?ڋo4?W<3(?ԼezY?i9`?)?A#4?O?efM$?bS?? ꊭ#H?cC@8Z ?zZP?"0}F??.?0{?O&/?ጣS?jk ?[p)?ɕv0?!{͟?lcY`?Ac%?)?L[s?Ͻ_k?8`{ ?D6 ?p]?wQ?Gncr ?*\,?vO'q?,R,?{?ׁPq?a[?qԜ?zs?*wME?3CXY?gl?}ݐ?UVK?[!?O?ū?_Di?Ka0?@?9蓕9??Jۢ? ?ij?ֱA_?8Uǰ?_sp?m^PX?!V5aP?8PX?U 곜T?эt `"?L>L? l?#P?m ?lLZ]?(?GͰ?D)L?*?z?B?ġ)?۸Ɯ?F?器_?%9i?ܥ7N?*/?˃7y?Y|?!;?Fٴ?q?B='Q?[80Pp?佞?h ?z3|?g߈?O)^? 5p?˭?K ?/^6?촑JK?ٲל?t+8l?u̧?g@??lcݦ?:?_n1?~?6?ߟ,?䙩?ǠJ ?Fn?o r?aҝ9??x:?Ρ?Uk? VWf?Һ?M?7~?瑽lK?5?G?@c?cJ?n?SNt?H+?7l?+ ?s*-|?z0I?s?Jأ?+$?Px?}o d?J%h?ӓ?6?"mGQ?꯻TGe?Op\?ᡈ1a?^YW ?e.C`?w7d!?*i nr?`Qr?)?8z?8He?%yU?:ךa#?,?I8&?{؆?UN,?.K?&ۗf?ŷZp?Zv̸?\)i%?B?Eԑ?ʉ s,?;eϗT??+yU#?s97?)**?5|!?ap&H?f۬6?ozq?R|?\ڎ~? [?#?Ӷ:D܈?2(Jp?g0O?<VЎ?&'E?3/p$?ߟ?G|r`"e?kX?*r@NC?:?Yq?C,lbHh?" 0?&E?F?35?qxzu?B#8?iND?IJZ?䓙R?y/P?w?ק[$@?sjk?֖Zp[?=u%9?, ?E[O?&B"?U(!?yxF?,Ҭ)?3?cf ?6<.x? fD?gH?б<&O&?%֬[?C=`?ٸQZ?ޓl-IV?BZ\'?㎘7^$??Wb z?%i?0.GB?]9?jL?l(!v?:?~?>2?綾%*?7?غ\:?ō?' ??o[wz?ׅ2f?2)??'6?^OF?s`??Yuf?8?˥7 &?Ux_?'_ɾ? ?*"? \?qm? X?+? Að?ﮧvl?W)J*?cc?(ng?l3D?w X?dS:? B?C2M?M?;!?<9 ?|(?(,7?K.X?A5 ?{!qX? ?b@?cdt?,[R?qU?M%Z?77?yqq?龵؟<?JVܾ?T?$?՘sHh?/'?vH?aɵ?ض? .?椢)6?̔lf?kY>H?¾P9`?ွG ??R? 9?ִ,? ?WF4?&m ?~+v?44.?틴y?U?,+;tm?ڈ!?K?J6? D?sP?Ti?E.?:T?U"?8V?䐕}?!X*P?.В?F0x?Qq?qq}T?U?ZI S?ńZ?@3?=o?e[r~ ?G(ӑ?Q"?wjʵ?Ѝ$:T?GvJh?׆c??-onH?8ր?t;\+??:~7?ԕP?&W]?R1?n?U2?=?Տ?Ev?F!?gPQ?CS5[?.?D?NY̧@x?2F?`آ?sx{vv?he=?ӢB ?(c ?-Ի?aN@?Ao U?S|? ϋs?ٌ?辳?)S?ڕNxZ?J%=b??P^?,.?hכ?ֱ b???ʎ4?5?P]y?ݜ)K?&k\?; L? u[C?ᕛru?Tw~ ?D^Nߠ? #v;C ??ɐB?T`?9Hm?TQ?y]? G?0]\?{`OsP?Z< ?aM@9?`H?EL%?iz?&F#?mn?&d?9D`?B?jB?/8y?Y-L<@??履,p?ӿ-u? qOu?j#`?$rYo?XX;8|?9?Cj-?Ш?5?ɛҚ?Dz?(ES20? d??ﰾ飬?Yۆ{?[M??E?% dH?߾?O??@۽?+4?y?OG%??S?̃8y,?Œ:!?Rh?( D*\?Įe?ԡ?X?_ ?kBWke?d;a?=?'ig!v?;f?ԥO?愖f?}b@F_?Â"@?/h?ү?wK?S̫z?1??f2=?)W/b?*|W?(?JG?x;d? X? o?^+?A$h?~? ?@c?g*?ƨ@0?}1o2?n4?ӷ`?HWpx?bSb?76 ?YӽI?wE$TAH?8:,?TXȡ?ަKk@?-IM?@?4юG|?qkk?~(Ĵ?ٔ{?_ ?Vj?0 h?y.0P?!pn?DK\?GE?1 ?:yH?wcZ.X?w 暉?+X3?6{?qfJF?z?mt&(?ჰc ?]T?Č,#?ҮD?XT?Zsk0?]˕?q> ' ?fd?+^N?퍑6?|t?F3)[2?Ƨ?훶,Q?`]N?ePV??$b?K'?mH?ﴗ ?B7Dt? z?HOtz?פ>?,?w?v(nP?&>?[D?&@ ?Jo?pb'C?xa?z=k? B?`?_~µ?A?!S30?<?kk?ӝ2U\?ګ/?' ms?m?q?~»?`lVG?ᬐ"K?O2Ј?Y?ԉj4?uo?DC?:`?S? d(?ΗK??"p?hg.?Twٓ?Oh?6?-|?@$b?E4?"C ?b5:?]?<э[N?=5r?ݜa??#|n?dc%.?+L^?L@d?02?Xӹk?~hU?1ib?W~[?n!*?~M@?lX!?Qf?j8qs:? 2?Ӄ]?ߔ_ ?gtbF?ȪYVP?bdE?8v7?䁯*??Y `?1hr??;9$?v {?U#??mP?g8]?Ț/?Ϭh?ѣw,?z/"?T ?bD}?>ؚg ?éJ?:b?2rf}?;h%|0?hCb?&enx? VE?,C)?ظf$?3$(?@Vm&?ݴ?zhZ-?hyr?fR]:?}c`?6>?⮋<Ǐt?s?]"d ?BKy?OCTB?K?˵IV?m?b?bs?%?FK?єm`L?A(??O3?S?p_?<H?j~?u?1 ,WQK?c묛?ӡ.(? ?;?Ƴ$8?ʍJ|{8?Xب?>?k?dO ?~?䕾+O?y7$?aF?gu?w ,?Vl?Y ?豉t?׿mEc\?ܯ?]CM?/?("ӹ ?XO ~?{?<.T??^U?6R??CwBT?͂Q?ԃ@??׹9?HK? @?Xw?H?ӾȂ?U??hp?k:x?(Oo?AgX?%(p?/ay?೸fbl,?Mh?TP?=o?DE8?i֐p?Q?鈷Ş?{e?|1 ?^I?nڎ?)oG?!?뚇pD?3O?b??۰uY4?]o?h5?gb?RFWR? ڝ?PQK?sD+:?1ȺC?vۧ?' ??KP??)dz(R8?b#Mn|?f+P?־D[wL?u'uD?>T?Ii,/j?XӇ?+Fx?ɹ%D?Ǫ?5B?ޑV8?,?HA]@?HA{?<E5 ?V#sT?(.?xy?"s?C ['~?ڎ[T?"F?ѵҊ?i4|r?UB?7#nn?֬x??"{?9 ?v!?N.w?hG@?{?ؒa$?]?G+-?t.^-^?rNт?ПdD?[1/??ĺ\6?ɯ?,$p@?Ⱥg^?IY9\?%⋻F?ӤMA|?0,B?kz`?}2?h?:!k?ķ)4??K?Td)?z? >w?.ƽaIN?7o?>?mOR t?,7 ?ٜy?Ƣj?g4?/?T?J3>)?!?ŷ(?V?ֻ1X{཈? >?ԫy.?|"LB?[#?KT ?'?ƫ9!T?וxe/?C&H?z9j?9YY?^*Tu?J?B}?p?8|?p?p1]Qx?A>M?\L?ӿP? 鐖F?"Qߖ'?&bo?+?[Dx?_*Hm?Vt?֞b~b?◐al?k(8l?Qb? R?PhlX?-{dm??㋖g~?3h?*O ?)%?Ss?~Z?UC?.+PD?;?]e?D0q?1{V?dcTk?aX?DZ ?$Sk?" ?(?V s?۶~I0&?D}b 4?y:M?=G?kb0ł?J>t??RĔ"?j?g.?莇?f+@?@?\=??<(?P4?+Uo?7?``?7Mh?cmű?~ K?#ϙb?Ή3>D?ͱ?H"ٳy,?Sbb?m?D?*@?\?Y(!?Ӻ 8` ?݄h?ߍUC? 1h??~>t??S?U6{?ب{ ?~D?!å9G?٧9s?Q?uE?`?& ?wh?顃vY:?֡VN?CUz? Il?(E?fѼ?ܤB?.ߜD?Tm?Ꮼ{N?踙P??7PL?ɸ\?ĝH?:C?e{(? ? cn?BZt?eY?ͤxF?VR@?ұp?˷A @?եK:?ؼ,,X?_\?d?3?\?[?mj ?T> }? "?BufL?|?XT,?3Д?Π^ ?7?K82?w"?~?1W?֍6?8?ĥ?} ??Q??vCF?.?]bih?o?륝 ?QHM?崎h?K~?+`?Mj?Xjz?jK?ǃ牏?8޹|?q"`?=,O?3m"3`?c IQ?/˰6?_x6?sWV?e+VN?zp???N;0qX?#\jB?hQ-Ol?1Y?V+?Ϛ8D?ИP5qh?KK ?3]"*?>,(dAh?nҵ ?_L?ըE;*?TSp?E3 ?5z???~^~?2>c?,ƔW?*)Y'$?ВB 2?#b&?o4^?+3F*Z? ??a?޿?A(? ֓?w1 ,?L0?? a?p,6W?ٲ7O?>ڛ/{?ṧ+G6?ْjMd?k+?>FS?seW?'ƫ? E. ??;!?zֽP?U]Su?y ?)"$?zb? i?y -?֠p؀?R.?eE?c?y)Z?װ =]Bt?z<?;`g? :<b? MH?10R?QȍX?WOk?RB&$?c?1A?|?s?ޚO??g`\?)c}?s ??p`h?;y:?Ѳ\-4?^oUH?Lki(?g @J?J=ډ?r?ۢ(?vht?kWB?o?B ?C?V?̚jɴ~H?ʵ*?`?xl?bS(?ރQ?5 BB?_N.?XMSk?8\|?RY&?VqU0?S?z?ϺF?wj!??%z?RʪQ?i^h?*f?*n)?7]Lq`?BDnp?,c?Aw?LuP?E~n?ZN?֋^ְ?߭ a?Aο x?ۗ͘e?$?殡  ?utQ?Gs?efװm?&9?"u?_a?0LP?CX?\?΁I`?l%h?[?tYhV?Ry?뷋2?ؠs? ?˚2d?젥s|?=Y?֋:?=2W?LoP?vG2 ?>;a?]c?`5O?SJ?[.PVG?5O?:HL? ?Io˿?8x?19?33x;?ĤX?nZ?p3C`??Pn?4,G??;*?B?PF?x8?;~?Ƒ$L\?*b@?+BX?By=?~)0|h?ޖVW?"- ?m ?'ĉ?:4?R?Q ?Y !?֦Qu[x?Qf?Й?З]H?0mT?7?IQ?š ?ޏ) nx?P&>?E`?M 8?B֙.?K?U?9G?& ̩?>ܡ]?:9??[Y'?%;?? ?đ:d?\V ?*.#*?T26T_?⠃P?jR8?+6Nj5?O?> ?ڳW \? ?K< ?ٲIY?I!:?S䊆?Ŕs ?o덈8?عEJ?,:+K??"Q;?a+Q ?ps?6%? ]|`?X5?Nsh?/!}?I?|5 ?D?ڬt_?Hɠp?A>.?޻S\?lh?/5@?x?UGd?%=va?J?픣?zY?'),?6f)?љy?ub?rS?U@?\ۅ?,?ވih?9r?šn?Aٝ?X??DzuT?N8=X?wcf?t'|#q?:M@6?qC0?xt?|S7??M^X?ٹEZ^?f?ˍ[Zx?],x?FUZ)3?+ ??ߘ?JM U??-:x?̲?o#? Ҙߝ?Мn?۷0?fy0h?xov?޲c?p0?}m??p~?G~;"?0?/?釥K?=?h?p?y!T>?={j?, `? :?K?٫#|?F$?A(F?]7?7?-)?)?c%ۇ?͌ m}?o4&?9?u+?ؕyV?Ŧ?*M0?#<~?Vz?5X]?.?ad`?uhW?EM=G@?}qGjy?3`F2?؅o“?㞲M?H]O4?c6Z?%*?PTz{?87y?&-JD0?\H0?7v?_ ?V?.,:?eWS..?r#?- ?N?RW'-? 4?{$p)?뼃E?bۦs@?]?0? ?ĝR?WSl/?;&?HJ%~?+\?P_?3q;Z?T?m1 ?مќxX?Th?]V?lT?翵 ?^?ފ:?˿y- ?Ҏ?xu?z?4$?J;?pdm?ϫ >?:?j2?T44#?sNz?B=?5v|?㹀a?꺹`?O,b?? ?f5>?UCx?ٶ:S%?'s?xϸ???̗Mt?ީvst?.EZ?Q&?n4ef?J?) ?M_:?;͘?NwG6?>c륒?쮝? 0??\1?u?lr|?_BS?+sҒ?A§?*ٵ?fp"s?IGn?Uu?kK~7?ľd?вlD?IJ_?FJ?Mf$?LȟS?j?p?h3W?N=o?Йcܚ?'?~G?o|d? ?* E_?I R?6Φ?˼0?QwS?n4?ڤx?O ?ȑ*?8@T?5kB?≷WWo?S? p2R?HIe?/p?G ?a ?b?d$i?簅2 Y?zaw?5a?mh&p?fR?h?욁c??]iĶ?תװp?vb?&j4u?I\#ع?'ӧ?VP?0#+?DwU?Ƨ??ң/?hE ?נvk?65ZB?<)s?ڡ|?j?ʶ ?B?ƈz '?U?|v?55?ײt?…:N?%<#?if?ۀG-:?, ƀ??C'jK?7@?iv?Q0?V$?:DM6?C ?獶d?Ai o?( c?UtV? n:?`ҏ?ͦ?]?>?xvMW?Z ?bL?Ӑ0?롇R^g?O3vB?\?Te?? h?*ʊD? |Z4?TС ?a2H?MO?䄹?譾`f?v(?ָ'u4?艸/?[?óU?v=?̣m` ?oԸ?2[K?~p8?~U ?q?耕?E@?QV?)5?C"?ƥ6/x?IϹ/|?gSX&?˰/D?Il$S?g+?ѝ*?⁐vJ~?\g]?sK< ?爥|o?g -T?1$??&?د`?c?Wb?aJP?dCu?fQ6?(7?@Nr? B?ѶXVz?ʭF?g?'F?6?9.v?叱h?tc?ë+w?W-!`?|),?àM?pH?7?tDuC?ߡ]T?.lSE?H_>^?1H?MX?uI?QPJ?Di|~?6qDs[?r,F?-#p(?|)?#9(|W?EYC?U;|a?S?˟S?һtq?ᶚ6?7xĎ?e\j?҇?'e$?tr? -V(?1ڵj?S5?|~?'/fv?H4XI?=6`?If?)r?O7?”wG?h n??o(gE@?ƞOd?Ӽ]?e&'A?~,?Zl_2}?g3I ?d ~?r.Lu?G6?> ?ٕ`?vH?pz?(~?~?}Е?:x2.?z??m ?Ze?>|f*?Ҏ";_?fbb?V?̾D8?lF?N&?_ȃ?xŶH?66Z?KR{??jk?7?H8?]Ab?$#sb?.:?^ ?]#,5?fQ[?w?nNC?u(!?(2x՞?pu?Zl=?sx(?syG2~?1Un?~(Y?"?MYP?Ex*??wh@v?顯?.XKi?U? ?-7?xCx?ʊrq'H?{1&?Ͱ?Of?(a\? Z?ZIx?j7ҩ?t;?f))x?rQR?Iى? eG?\?5Q??8 W?G0?; ?B?ۭƅ?,#a8D?V]?hL?Mol#?N ?d4f?w?V? ƇBf?"\M'?ݻJ`?k(?O?? g?\?֍? N?Y-?G#?z{q/?`&S?1-?T?k^?8?s ? Ch@?`/}2D?c?љ8yw?nPtX?#{'f?w B?OM?V?P?;[`?a?5~ө[w?ҤY?"'EJ? U^??m?jj0n?7?MbO?(y¢?͏@?.s? 8L0?@M;T?Ap?HRi?q 4?&??$c?Ǩ?, ɴ?}7e?޲mͺ?c ?1]??rtX?&Q!?A{?멐I?] s? mt?7M?=7 ?M'?}?:)?tVeF?*h?᭣b'?궔Aa?ث,?ݼ x? Bq?гmf?6`N?Հ?NN9+?Ovݙb?2XhL?})q?_v?$?ߣ@ ?e3P,?qWC?ؚ>?EC?$\?*r|\?5-}?vHO?[ZD,^? 1k?ROz?n.d?~%IbV?L?4?V ?t ?咎i`?LJNv:x?d@?s?{m/??&lhc?]'h?OEC ?d?n6?V ?pYc?m}?+ 5?rU?(Y?:rB?a ?NJ?ƙU?)\?'lia?Y7'&u?qj#?ʴqY p?ҕ;?8?fQL?٫8P?&xT? 9?rgF?(u_?l'%z?4?2?!')=?ra?O:?/?kk '?`K?[|K7?k}R]?ļ?˧}]?iyh?Td ?#DQ`?0=&@?L}b??R?d?k&?Qw?*ݪ Y?o7F?l\ГP?G]l?Vn?cJ?(?}?6j[?_@?:?<ΡF?%z?l? w N??i)?W16??\փ??d?&D?G^A6\?桏*?c`H!? B?耗/?Ґٜ?.?~?V`??R&? ?{l0?@ྯŢ??`h?^Lt@?濍ygi?NZx?푋A?,)d1?ڐ*?Tx?xy8R?JA?.q?hCM'7?쀵{W?">JS?\-A+ ?Oc?sp'S?7+i0E?" -?V|ч?7z j?1P?QĵX?ءTl?&:k???ls?8u?ϐg?p?2:?Ќ쇂?x\?CL?R?1l?srB?a??a?Xrt?Y},?s*"?N0?PV?|9&?-xN?p]*?Y".@?#}0H?seAP?YEɭb?_hf&?˛F=l? ?ę ?/ؚ?WE/I&? ?T?'ȋ?Ž.\?羳;&?^m?7 hz?@lg?숋f?Ưp$t?_pp@s|?<'%?ļ ?sfg̚??4٘?@cɲ?+|?.q ?/B?`ĵ:?կK 0??p?X $?hT<%?WTx?aN;J?0D?ӪMf?a~m*?ɺq?@>k?Vuu6L?_jC?T9>?%C.?@Ʃ?Ao?1m?ү-4?#WVx?$`.?~b?@?턤?5@)-?ttJ?m?]j\j?йbE?FڔE?֪e{v?͑ ?yw?޻JyF?H??:6D?吙}`?|%=?z?IYY?@ux`?ίHy?3k2@?㇙-@?a[ g?̮7?Iɪ?%3?h?5}*?PUKϡ?@R@?܉5?m?VyU?)*?j9rF?]hF/? +*?Q?ϪI5?NF(10H?w ZST?f?9p?rGS?5d?!|p?@6:?**Xz?ξ'l?KFW ?i,-?Pq?4``?N?e?4lu?C?կ6?]0ӎ|W?a뻸?I?4??mx? mG:?x,0?f}ϒ?a̘?caIЄ4?Ÿ?s?Հ:(U?Жޙ `?b7Er?fR?ˬ ?To?0 `7_? 0Ԩ?_pP?::??.n?ο*|O,?Oz?F}r?i|t%? >? f?S,?c?t… ?KңL?#>n?bLT?4( &;??/ b? Ӭ? s?t(?ͼ݄k?s$?B}x\? ּ?Z?-!?y?&7?¾-??w?뷧al?spf?9Oda?Xc!?_^?=pxI?l` ?g?w ?)6o?v)? `?=W*? ?ȏl|x?4?v^&9?=@?+ҩ`?q0?ɫ4-5?쐙|5?`b?߬A?⵹fd?7qyc ?q >.?92 ? *?3?ln?=a?`m?ws? )C^?|7?'?;?H+~|?i`m`?ik—? !?P?⃽AG?ێ??w\ެ?`KO?Fޗ?̑ h?^KY??v(?/X?#l?«w?U?Oce~?F1;?P|)C?>x?h?D>,?4D:?R$4P?j˕,?eiG?2Y?䰭)?<^A?XӋPH?ĥMP?!xl?[j??ѼPx?ܢ¯?=%? sf?!,o?,cNp?ep[P?>?NaB?`?N_5Y0?dGBg8?G?u') l?./ۇ?鿃?sT?ٳ_D?0?i 6?2}?ܐ &?5z1?C0?لr@?K6?뗆[?߮YC͋(?Aݫ+?SoX?ـ'v?uz?f7-l?Le b ?p? C׭?+??,VJ?N?*?"`ͳ? ot?ǰ38?5h$ ?(%;?67-??ϝf?}J?{~?d*9??$?K|et?ʪ8&~?K(Nn??'l??.HgD?eT#C?:V3?Cg@?ɝ0?(3.?׼S*mH?,>?PJӮ#x??Nr%?ity?7;g?ӳs(?0?ɐ8t(?Џ?;'U;?z  ?ՍjN?k~|6?i?m0IX?RZ@?ȗ5?9DpxP?3B`?Gm?m%?Na?}?ZpG5S?`?۰7ڼBd?UO,?Ep?֝܂?)? ?r*ijvt?%‡?G?Ki?} s?aEK?}fh_?r64-?G1}?6I )t?lt/?ҬqT?|;%?S[h?p]d?p(??݀?{w;?F?lC?d ?ӌF?H (?{<:B%t?(%9T?’%?K&@?kYf?UNV?9)?D4/?Lhm*? J"q?*8{u?YU"?{@F?D@q?>9x?ky{̓?7?Zuw3?qh?ƛ ?>!;-?xr?ٟ,7G?=z?&(?דY7$?Yˢg?cyb?#+~?߻*R?*bU?碒%T?UY?a?:g^T?ۆIԯ?ȟ?VBh?ru0gA?${t??H!?6[?ݨ+D?_ ?;c.?gkӃ?be?9( 4?똝ҷj??1S.?U2L?K=&?jl?60??J"skZ?Pd-?a?|IcR? D?¨h?QJ)]*?KA5~>?O3ƀ?%0?Et0?r?ӫs`?Xg?\?F4!oj?.攘x?l(?Z|Af? y?us8?3';?\?.Ewf?e?zQ_?ˇ9*/?105T??q}?5#4?B.!?d ?{0?m1O?V$?_h?%e?`;?՛ο\8?Dٕ8?P*x?毸?巔o?i&?YH?aG q>?;? ?C F%?s4ڏq?wǥ:?t#?S +>?|]a?{LU?IV?kA?bv?*hgfM?`?Le xA?Ң?ӏL?U?6f`B?ckN? v,x?q!b?R?n4?r:`?Ex?۪?۳AL?Òr? 3?B ??)_vh?f | ?Igm$K?D$?`50?v.gw&?l ?y?\8?BEh1?rvm@?=2$?T???d^?k?[E?;׌?~ ?܉`gj?hwi?:y۸?C9?瞖V; ? #?ѣ?#ea?v,?t_-?N°į?ۥG6p?o9H?MT?+?4?S??҅1?*M_?{?(@?PP0n?/T}Ȏ?>s?aUL? (`?"?ob?^$?ԥ6l&?֡Vv?㴖?P6`?D6!?zr8ߞ?^4?QT8?@ O?yP|?d'?Ϗ'?N|+?2@|]?+ݠ?Ú?".L?G ?t0=?[&la?z?X?4#(Kd?P3/0?%Ib?䢬5m?Ϭ1>?T?яtq?jB?`?ԿI ??T t?Қm݄?}v?ə%7K?<@[?YT?ױ?!:?J? ͭX ?.\׭?瑋1?k\?۸ ݖl?X,?|jn??+NҌ?zFNN?8I ?u?S,@?O""?*!?|5?oD$m@?D$P?3k ?z-?o5?OH?컧?c?HZP?O_V?s 5S&?/iD?ǔ5D,?A/y?ѵiS`?~ h?P8u?]?1#~?{2]? r3?⒯;1t?"{4t?S@?LL?0Dhh?R^0?2WvwP?*hն?d?ٹ?92-?6#?-T?²h?v?dxh?nv1 $?@ A?qa9`?<"?՝ ?NfP.?z?ɼ-Xi?b~4?.gc?6d7p?&M?~v=?@ ?٤vm?m }?(h?B*? E?8RǤ\?@nvP?ݽ֑I?}WӲ?r>xL?bV?nge?Ǜ??ق?ෂ&?H(o$i?yZ\?mIe?ڛ9n?t(?@?N ??6LJe? k? }? ?`oȥT?}?fR?ɕRM?O|z H?\?M?j}a?JfJ?pin#?v#L???K>S?iq?pZ ?'Q?B2?ha% ?aA?Ad?8Qp?i悄`?z?z{"? l֜?ڶ>'?_K[?~?njv?hGIi?Տ0Ԁ?2d?ͼq?u垔?堻"?!?t~dH?~TK?nX?N?8j? G)A?j[fHp? }>X? 2&?s_?w?.?F«V=?i\M?DžKߤ@?+x.?t:?mH}M?r#?W? 5?ܨO&?1?#5*O?Yp?c{I[?WI?M?비t?>u?qL,?s?,Ɉ?˰^?˱8[ ?"L?Mw?*#?]ΐi0?=VXwv?5?:&%X?Br?ȪSOx?#eX?-G ?psG? D]i?#?Rpht?KOۥ7?ᎋ#?N/pK?#黛?>8?<?&Zi?E0&ġ?}5[^?d k?%""?wKMD?Jؘ8?C?U`:~?ޢI?ג{jW5?(^"Ie?S(?6 ?㽿q?—oH?*[&݃?ä?X`U?T=?HQ ?煐`??L?_<9?;{FN?[ {?]nD|?Α; ?sy"aa?'wQ?ğM׸?0$`=?(.N@?㑠?,G?焽Fԥ?20Dd?bu0qZ?7#?Rؘ?bY?U{Y`?Hn?pcJ?scO&?ᙉWh?<?R@?=Gݨ?p ?N(?fmG?+?+c]?푄v!?רPKN?f}~?CBfܳ?x)ث:?"{? .?pJT?3F` p?.Cj?kF@?_Rx?O?]% P?끟x{?֥?Get?CDJ?=U?Ww2?4l?Ӗb#?A.H@?h#?C.?]6x?=/ ?ԧz?=?"@ ?/Lb$?`ֈ?ώƹ?,@?:s?Y>?%?"?ލX["?]N?\jZ(?({?s ?2>X?Pl?Zgl?cy#?ޟ?P/?]3 $?jP??Vw>?58?4͓@?4?;Ԗ?^sM?՞\®H?m5K? 5D?Չ t?v7X?ܺ4Bgn?ϟW,p?w:L%?tqQ?=*r?Ϭ7`?QN 2bY?|.<^?mӠ?kP?7PB? W?sj81?"W?;??y?N(?&K?p?4S\Ѱ?%?ĥ P?D.7?e?|F@?4?F8 ?ҧh'?H?.y+?- 2|?VyRI`?[,.?ķ?V|^c'`??|`? Oa?8!+*?e?<Č? uE?冟[I:?Sc?נO?Yʵw?̼e?2 0 ?_0?(j?m` ?eiA?0_1|2? >EF?ȴb?2iʳ`?'?ѠPf? M ?#, n?9_/?a}?y.£"?p ?fW?,L3?@<}?j߉MS?3?b{?ǗT?ǒ4$?$J?X`?f[?gPÈܠ?u?am_?*3d ?0lWzh?Vu?@D?۩?:$@?Џ:kx?^(?7ɠ;??-LN?䂼@?HO?x,?  ?~E?uh݀?Ƹ?p0gK?kVE-?%+K?7ѣXE?m/E?igr?ҵ^?ˍ$?۬e{?dn`-Y?]bk?GSU?f(p?ވ5T?Q?,d?嬦 ?Z _?גO?ĩ?Dz?21:?O{?ы`?+&c,?sjHf?Q?վ`DZ? f$?뀶Q??W)ټ?뜍s?oz&?K?t4' ?M-m?nB?0# ??0:??r4L?9E?)qS?Ǹt̀?l]2 ?5?M@%.?\D?DJk?,8m=?wtv?Ήg3??pL)O ?r?Ʊ? 1?ܸR0?ݕF?oq?wWh?u?_?ך)?{JB?`:e?y?8h\l?tZ&?#@z?AP9ӑ-?ަmy? %|???,6x2x?0x?ԻΌQr?i+?CiE?OU#?vQ?ʷ;T?G?҂??ϰ?]Է ?䐞/?l?Wt<8~?g'|?q?4?߯c)vT?q&?X?~c7a?䳆B?['??,|?DeeKe?iːET@?t8?/}? B,?xrtf?ʳ'?ƿȨ?pе?g\?ۨ2*?<NR?Ý){??+lv?yo|:?iB?Wzd?Fyi ?1;n?&ڢ?$!\?ǃTr?u2i?9F ?/De?|?h{?~TwN?cHwt?ךhi[??5?) sn?X@??O#k?ܫ-pf?F?R?$$?|5?Ϸte,b?ˢ!?|Ver?Ư^(?Q?0B?@)?-7c`?xZ?Ʉ:t?暷?tih?ԧ%i?{h?u'@PЖ?ё_R?ja?? N?oݟ0?cT?Sٻ$? ?mWT?NĈ ? :D%?:? fs'L?:4?&H?W-W0?W٬? }P?P?.?ĊPLM%?hx&.?ْg ?%|%?ӑ6W?"̯?jsYb?p2Y}r?*L?]&?iҍ;4?jh{)~?b1?I0"?Rh?)LN?Ht4 ?CR?A+/?{]?෣٭+?)mu?AL? ?4?X?ߐQDք?6ȆD?l8?;D{?ۻ/b?Ż ?kK??_&R?OJ?O0? tz8?t6V?h\K?H/z?'cA?<@t"?x `?L^*?#?]e$:?퓅?&?? m?Xs~ڐ ?SZ?ȪOT?:Z%q?̵H2?`\\=q?Ó?C?#=:?my?E?<.d?u^?ӀB?+r?3XVY?u@#϶?֒/=?ٰYM?cb??+l?пS?[90@@?T 8?੏o?dQU7?`8'I??pi@?j&1? G= ?Ѧ >?U?#?ǷK?tLt?_x ?;SV{b?SrT,?vB9L?b:u?楯RH?h?X?#?I?"D0?Lu?^ y?#ҕ?ﭫ(:?9|?Ҟ5ں9?˞9bS?U?b ?(?e"T!d?kUS?R5cD? 6+5,?-Ro?⬟[i?;<0?)0?H[?ز?FE ?kDj?h;qn?nl?H `?z_?H @?Q`$? Pj'?_&?  ?/S91?fSY?v?'"@)?f?"I;?cB~ z?\;,?B'?E?'?I7ӥ?~bEm?dWuZ?ߔ] ?'xw}?hy?;̒D?PEV?]^Ҹ?ՙs8?9?j?t^(? :t?^}?]?I\B?eѣ1?}ね?":pU?ڊ#K?$8Fx?b\?q(?|2*'?٦w?.ef?$R=?w; oY?t*?L*k?+ו?vF=?+3?Af?ڀW_P?(54@?C[I5?ӃZ*?䏊%/r ?T ,P?ղ?:F?Pq\?~?ލAp?|mbY?z&8t? ?mJc?Snu.?<p?A?'~?ضE??G?Z?RWlY?pٴ?06S?қx ^?\ h?lkmm?s?*1?CpYT?VjԮ钮@?9~c?ww0?쑴|?j?BG?1O?T5?\@|T?Y1? _?KS?ݾW5? d?Vװ1u9?Cg?Mv?ޠ0? CW?tL@?HUAP?਎{?9?#p`?فNV0?梈1;??X?s?Ӡ1 ?Щ.?ޯnu?t?ء{M$v?cq%?J?||d?*?ϯAN&?z40?EFe ?q?CKQ,K? 8o?8<?Pl?S?C4?hx?cY?mpn?Fg?f}?SNtl?C\$?1!d̻Z?{d?؃lkt?ƌ*D?4-H7?֔WGEb?8ݓ@?uB??-kI? /,?9$j ?B&?qb#?hfpt4?oW?܄ͺ?ֲÀv? I.?ಈ#z?A[Oz?@#۰?;tb??t#?{q?x*0=?a$?Z'?yMZ@?Lp2t?mԝ9?@z0? ^z?ڟEDŽ?KV&?d?c4%J?uҷ?]_?Da?-ݑ(?a ?wuBѰ?IJJ?(?᪲i0?>M64?h擟?ʏfrc?DFiM??!?퀂!?B HBi?[s6?? @?j`e?%d?+?M+ ? iULc?Ƕ$?ԕ8{?`g_%4?S&G @?Ö?K?/aS?eu^j?u;m? i'?SW|j?Zvq??Qh?r3][?Ś6?2Gib?:KMF?|ٖ/?;q1?L?,?*_\8?:[#?80?ީ*?$?@6f?<8?ꊘ~X?8?dk(0?ׁdn.̬?p_!?OB?Bb?t 6B?80?ǀ;U?%پ?/#X?]?]T  ?-F_?Gj?MR? IHM?Y70?|?η?f%_?农7?Q.*?H?}I;z?0C?Q?F B??~?V=p?[g2p?GtH?b?(>?+@߻oH??o̩F?!ؗ?ޅٿ[H?]0?jf?49?(Td@?Ul?]AQ ?쩉C?a)?̒Nl\?*?'oU?@?ȓtK|?Ϥ*4?َ#2?_yw?$? ?@Kw?ҠS?;?ꘁ!'?~?y7.?ՈT?G;o?A5z9?U'r?IXN?i)?$pR?l2^h?ԄC?nu@?|b?U-1qX?˩4r?x3?rd?Ol'?W/W?o})?AC??GR2?kx(?g v?ϼBST?4(B?%??Ӓo8?%ȩ$?'--@?W;?;v?量%?%|C?D9?ӫL?L[ ?ʨ0S?{&?(=#(?oj?(C?I`? [7?8?vq ?AT<?g0?aQ|? ?Zhp?_N!?k?j?IE?X?Z޾~?bXm?ްOT?n^a ?y ?뢦?k A?˲H?1rU?x׾!?\?XN?f?0y?s<?>?7ﮏs?iGd?%n9?$ p-Y?+k悼?M=?gpEV?dw]?.**L? 1+??ؒ0j??#fsX?j}h?H}$w,?80?L(?(I8?5A2?;r5cP?_٠?ͦ>9X?ɡl7?^^V >?]?AOx?6?ǝ*?Ȁ:?b]bT?Cdl? ?l6gV?tyZ](?ح yR?4gk?ҙۤ˕?-v?2,?# ? ~f=?ߟf?.?;@?@%? U? nN?w?->r?Z?{8Mz?ͮ7?\p?T b?-r?(®?,,?? 6?N?C8? [/?@?f * p?&oF0?¤f;L?Qp`?Ai?(?<(Ȋ?/1T{?*?W??C#c?Ȭ`?۠C??@FrsP?-"?fRx?P*]?U?_ ?Cy@?0?萴 ¦?3d?؁?߼z?u?`Ry?Oe?B?Ű?'iJ@? FKr?0JF?)0?CQ3AB?V?REh?!EX?l?tw>?-A ?^i5?> ?_L-?P`?X?"g?K6?6*5?|?]o|?2?LJ?祐( ?iJ(t?ʖ|?f0j"?K#?[ ?nI\?BX?о'2?󦮹?xq?aN?*@?Z@dߑ?gY*? f?\߈?&hQ?V?F?`3-t?Rnh? Jʏ?8g?Is~?vT'?KSLT?2?^>c?ު@? ?,?]m ?[?\?Ĺ3H?|B?]WX?X?YO?cfy_?i<8??ɤٳ(?"?v? .?ܿ?Uý?m?Φ z?qh0?g?g@cd??Q B0?-)48s?NV4? H`?KTI?a"aʨ?T\- ?o4iP?J?[^?&h?E?!xo?$f?b`?ڏB?O<?ݾ ?w?vQ|??uBw'? ?θM.T?3 %f@??KoFV?EP9?E?7d[jT?ҞtY?RG?A+?bN|uw?ԭ>?խۜL?JhD?7o?"א4?`>fJ?})M?X5n??ԪcT{?/r=?KYzW?T^b!?ZCQ?8p#x?앙u%?t*?oJaQ?&?|4F?RV1eX?(e?ǟj0?e 30?ȼS%P?4 Dx2?mk?y ?'k?գG$?z?GM?{9?&BTRF?bm{?G"??) ???]u>?Z?!?&:?׭?Q㳡?ȟ?%>Y@?iB ?oX?1H$~?Ѭ*|?Q ն??.|?3@?Zx?U?A)?g}{?f?\HzW?RX?eG?[U?B6-V?6?n?SNB?Sf?Ʉ?릉\ ?ή?W2?d?U?.= ?4rZN?5d?ċt?!Ƈ? r ?ߕ3v?ҁN)?1NZ?i^??1?BxU?&T?u(? `?q6? t ?{|-D?>κ2?فJD?Jat?&S ?<- ?B?Ck[?&Ⱥ?~Lb?G?μJ*t?4պ?C?b%i(?k?ݑ+x?AXZ?*\,?S.RI? ?cB?աp?3Hy^?F^`z?Pj ?M?ڄ#X?ƴS? ?96*^?Qg??V$?kR:?H\?dd?qWM?滾@?Ҟh?œ0}E?'&?꜋dK?A*?|?L?Tr'?Z@v?fp?i!T?NsO?f@?G\_F?0uϤ?vV.{?~+At?^LW?bp;?h)>J0?O7ڧ?i?*?|ެ?kw?H]?+Vt?u:^ ?dVN?y؁;i? f?ç{?Eǡ?rR3^?a8?b0M?ۈ?q#?ti?؇h d?>?]I^'Ѭ?AWo?ËzBh?R8$_?#1t?7vA?Џچ?.g?O|i?qݜ?|.?d?FB?Z?kߙ ?pEy`?s߁?&oXv⛼?Hj`R? Qj?|-W ?jO?p?7Z?JI?lӵT?RP7xC?d z?:P?B-?o ?%)H?è=$?W$?m?pel?vڒ?ǟ4?[u?ן;?&y^d??62?јF?Vuy?yuH?YJ?$&?X,e ?>*}?zT?ko?C?Ar?@?ދ?u ?xV?ћ]H?MUD?M}?uԪ?FN& ?nArU?@I?E?i?J?I8?䗵֏? ! ?Ya.?^,x? L?4D?=? YH??g ?TT?2>=?nBʉ?O=?jG?,'t?csU ?R/?8^ f?*T|?1j/]? ]?ᏺq?@l?)'?Ε?a?D)?U[&?1Z@?1ْ?n?F:_?wĵ@?]RS"?rZ?wb?bqY}?§8?ƚG??3U,?˚*p?6X ?rC? 7RR,?1?)uA?蝔fyG?ں&?=?!ͯ?ǽZ1?P?q{,?'M?RPߏZ?ݫ?dx? ߣ? ed?]Ul?|&?=?݄ ?x9|?}N.?\?*}R? 0u? ̶bd??6+?q? i4z?؀\?穂N?h?"p?sϣS?čH?Bi0?D@ ??nѨ?4dPb?op?8:>?Vn_?m=?Z?ھg?ʛn?͂d ?!W?{_ ?D?&W]k?m?4?=_X?ۮ Tr?]Yj?<8?C\=?;]?섭zx?g.K\?tN?ɲ#T?EZ?Y#8?>?)W?@C.?\xy5?\$I?꾳?zB׉$?ȱ.?_֭?Ҫ( ?Ič?|Z?Ha= R?LLH6?#-?oJ?V]@?o?6>Q?x6M?챓 t?)?S"D? !IE?^v?ȁV?! {?}`? ? Kj?]5?ʠ/x?ߖuE?ނFy?e ?= ?{7?[?;A<@?r!,$?@̭b@? r8?_y?ҏ{?5?耏](?bt"c?w`?lq|y0?൸ӽ|?ӗ/?Á? 4J?[? >2?Ϭ$ ?Ap ?3H?s0;?ݸY.? iv?L"o?öTH?coh,?W w?u?ּz(?(-$?J'?W{s\?1ЄaZ?м`̒?m?H@ ?L78J?oF?ڊl?Փ~?J/??ڧ)g?ޱFL?OZ?ށ e?锠&?+v?`?R{?jf/?_?i;I?Jc?ֈ!]?u悈?氮Fa?H?0?~ب?o=?glP ?KchH?&I}x?ި9?[a*?:?W?dVָ?.v4?F?س?s}hf??C|?Љ*?eI?Gz?)r?鯲=4?+ ?YD)`?4U{?툟ޓo?u1z?1F?ר33U@?YSa?կI?գ5梐?낎]R?}?ԵED?0=??a媁?8;?2? ?jo&?u? `?g?,m?(aQh?l/J?G\?Ba@؎?wͣ8?ǹ?T[?X:?u7H?lj)?q b?[~j?y ?$9?L@ ;?4}Z ?q]{6?̌Ge?.I?ۀo?#oQ? 6.8?Փj-n?hl?fD?K``߁i?L?܄ /?߁DP>?deb?IA?{Uw?{y?hn\E?1qt.?xj? ?љi$?I ?g{M?ŸCBǬh? O }?5 (???">ݨ?q?: J?op?ʳr?V=k?hؾ\?B݄!?ܦH=D?c3?X?i<6o? VwN?Ӈ؎&?ᔛ+6?7K?`ӜN?އ?052?n?ӞG?ͫAD?8?!T4?e7oU?챵)Vj?؛?g3+?˞?S"('@?QB?QKyQ?_R7?Ɩ ?⁔c ?Y{m?ߠ\7?aCuG?̰e0h?܀6E*?L?v0?Їq7?6m?}?FfqȐ?2R*?O0k ?5?hd?BH\?#\?sUj:? ϩ8??寮T?LX]?}ᣣ?ɖMx?KPY?UOp?-Oz?0^$K?ބ *?i?E4@?چX\?u?ˑٰ?Ƿ*h ?mKY4?o0h? I\?Ât\ ? ?Ќꕐ?T?c&&?CS?v ?~j\z?6??4??9/?|t?O2? `a?)g?mڵ`?5fn?տ!(?I?'t1?ve?ҘF ?ڂK:n?Yty?Ȃ? 1%?\? ?¨WҴ?v\贜?4?᧸(?һK?B[/?ʋx̐?Pzf?6Nu/?iu?K!?'?q?"b^?&9C?>;?l_5?KpP(??ˊ^iX?;y|`?%?n[f? ?Aܸ=?{ ?%J2H?Kɭ^-?K*<2?;??E[ ?W#?R?/8?v@?}ȥ1x?^"ԉ?(Sϩ? @?ͻ?RY?ߪWW7 ?&@!?N&?o[t*? ?e?|@?| ?>?߶(?McJ?4F`?%3Bu?܏R|? PGi,?(kS}$?ԠcY?;DB?+u?Xhj8?g֪ ?$" ?U?or>SP?h~ۈ?AVn?}+朜?Ԑٿ܉?Ck?9J?m%{ ?ejn) ?vaʮ? w+x?끜=?yJ!?s\?&\U?ډ?'l,?p6?i?ﮩ8[?*4?&J?ZtW?d5?dќ?mJ ,?4̶?}鐊?⼍Z?I{ɨp?hܡ?2h ?C?v?ȠX(?-w~t*?-[?#LSs|?-9?K$nx?-M?k|\.?t_H?S[?~?4"? L-?fA?)LO?m3)0?DGҼJ ?K?u&[14?皴=5?x.?$!?oIN8?"??5 68?߄l4?هcw?,2(?7י?@.V?㳽u8?8T?g$)?&b?z?aw?ˬ7?.s?csA?! y?U8m<4?٠lu*?2 ?c*~?ゐwzqu?r$?>`o{?3?i_@f?`8&H8?M?wx?-?$94_?Mҋx?]YLM? ?{qش?w1?!ba_?“\Lؠ? ?j-?ñ,?K?ɰ[X?n4I?A[$F? 5Sx?̊X*?c! ?3U?ڡ;?>?ɱ{Rl?C|a?vp ?͓?@Og?_C?O`H?e.Bq?K٘? a&?*8s1?$O?fN?$uh?&ow\c~?hJ?;6L?ZTt!?w 8? f_?[?s?C?/`?ܔpK?*?cW?e }}?JW#$?0j?toH?ͣ"?} KL?ShG8?bڣ0P?Y-^]?f?عnW? BF?:?8 0?f?)$:?ڜ d?\Fb?ɣK?Z$8?@4L;4?@?G?i߅f ?\ >|?E;/g?05?Xq?о%(j?芠Q?؃TȜ?u?9Xxr?uߙ)??7?M")b?1;t?: ?+~?向i?7lsl?!?Us`?~}WXX?QJp3?$?Z?䶩ԘC?.VP??7S?.?q蚋??p?އZ'?҈HCޫ ?1 ?"LX?U7P>?.?2$C?϶ы?;p?/?胥`? h?PT?0$ ??T0?p?޼8?x)v?/L0V? {?:A?ti4?Ry?,D>9?fS? ^?˰}l?߷)a?Vv?E?7K?A#f?ڽ>?e?O?Ǫc ?5m?9`?􀨌?n?K?wr?#?SWp?›D.?9?X 9x?hY[E?j?b|X?m+Tpp?j`-?T~?x=?@? |N᪵?< !0? PپX?ť-,p?dSNC?ah?ڢD1& t?Y ?3'#m?ǝJA\?#Ǡ?7 `?՛y\?Zctd?g4aX?ԅ~?)a?_K^?)<8?%t)?~8(?*W?fh?"?}#?7m ?O$2?(HD?j ?᩺t?%#̄?˷-?Ɋ鿿d?6.?KԪ,? .0?,8{)? ML?*?莢9Rc?I!?0s?{ۢm??ȊQk?w~?l4lt;\? R'?P8$?-?/pD?q?׸0i?yփ?FuP?Ӿ\?8U4?Ggat?~ox,?Ÿb?$?7ΌrT?ݍ a?-L?}8 ? TEmH?,쇧?CU?°#] ?ƞϳZ?۰9?⋟e3??J?pH ?Kޔ??[ ?- I@?,RxZ?p0?;e??ts?0? 靕?aLc:?:?f?2 l?`+?㈹ `?m?Sl{?(S "N ?JoB?\?F` Z?ʶ2??V8?v? ?f9ۇ?Z콄·?փt?/e'?{/i?,_ (Z?)ܽ?;5?֦h?C@?2pk.?а^2RB?E?n^(?6qE?Ke?ӏS?~ef@?䜠R(?[Ь?)?_{f?a0?荅qD?:~B(?>0k??/g?$/ӫ?J"s; ?ҷ/??Tc?쥟cA?D?-u?G7qF?O{{??j? j?n&,>9?0yNP?{Vo?܋󻌝n?nj?!mAz?w*+p?᠑(0?ՒÔEj?sbX?n*J?J2?^bl?qYW ?7@?pF5?? ,?l ?_(?P.@&?L?z;?9L?hDžb6?4B?E_?Žv%?ܺ-. ?Ѽ$?BgP?z V?ɦg7٬?kvP?Nh?Ą#C?ҦBF?WfO?ڭa?y?$A?D@?͙2c,?oi k?Q-]?yz?/?,w?תּ.@I?`ר?l"Z ??>F< ?0?v(p?`!B?v?a&?%\e ?{؜?b?L,@?gO?݀@¼P?ᷛ ?[9H?}l>R?&.P M?o71?52?Fh?!|?fs& ?fh >?4FA5n?J 0?zA6??Iǥ?|[!V?st?hk=?-=?*syj?ɸ.l?Xǝ?Pu8??/cPd?C,}H?87?7qx =?T{`?j9?|c$?z"?e<?ұ0 ?Ӝ+?ޑ P4?L1?zr?1>=?F8~?ǂ?-%Q?⦱m?>-w?hF1~?G~?L8%@=?̱$?+=8U?-rS ?4>6?С?B?ۏlu?8C3? :U?R?Ա 0,?pN?bA?ؑ>?Ye?}A^L?^M? t?HMK?qJ?j@?]?6Zh?ׁZ?'Uzx?ѯ}?X(?\\?Ɩ;}#?7A? l?ΙL?L!?e9?ޟiM?→g?t?2{H?Ćv9iڜ?|U?鱱VaH?NuF9?1?qYɧ?jC.?ғW ?ϔd?k?俓+j?a)t16?t|?$9T̗?2.?L\,-?2%4p?8JT?jN?MLJM?21%m?t93>?u?-K?"j,??5?ddg!?+z2?V7~"?v?hD?ﵖ??v?.ʌT?C@?(Ԫf?q ?̇ y? ?DDj?d;i?1k͛f?>1?(n6X?4g :?@?U:C?` ?4$?Zk$ÀD?}àI?i^Vd?n?Ԩ|%?L?}?Bh?؛l3?  ZD?;V? Xz? }H8?g?)P?:H?Й\?aA^?n? ?!y&? 7O|?I?pR?ٲIg'f?/p?pOI?nc?XsHu?ٶHZhQ*?apSZ?8S*?ŠT?畵'?%%6?UgK?唐?:ʎr?MKX?g?n>?ȁVRX?p'? ~_?~l?e0 ?>*IŠ?mPa?Q)?.O$-?PA(?a?}R?R4? A? V[?_1M?], :?S/K?6{?U̒0?? _"d?{ey?_?/ 7U?~>?WT ?u_b?cD_P?s>?X ?DpA?g1.?x 9?.H?ؽsb?]5"Qv?xmI?Pפ?Sr?Ⱦ?޳(1Ѥ?H~?è4?'uC?zx?YLU? ?+]@!pz?Ҷ;nR?.4rL?rQ?X+?`$B?٥Od?Րw?پ@:=?y?c,?`!P?/@i0?A ?I>s>? 2t?_rF?mgZ?݂<?0U?? fĊ?Rp ?NO?KT;?ޝ,? ?m%T?v*e~m?-? j?[2?-{@?%}l4?Л)?׹`&j ?糏?:P?FH?e?y+??9[ ?ϓ?b?W~d?jh9?盖Fx?T}.\?}IH~?ѩD?fP?|+?\}5d-?ա|`=?0ܖf?D} ??$a?ʰ$p?Ҍ?6aKXU?.?ch?[!{?΃w;q?ﲝ?2?VkѾ?wQ?W?V?l1̚?uk?U9w?.9d?]$9?m,?䊖{H~?:?₅C?֊V? n?BT?Kb?yr?|~?O2?#i?{A+$?se=H?:m*b?{C?pm?ٍ4K?ҫM? 2B?@?鉏L?cEtd?E{ ?u? 3?˵+%?1;Z?m??hӇ$?G恳?FI1?q4AQ?+f?Z?j?Y?0?owI?۲?"6?\δ?΅? (2*?;]A?{f(?ОD;n?^g?ں?≥cx?4`G?RޞH?*ܝX?@t,?ӹ7 ?m9(g?( =0?]F8?+?*W:r?]Y?i`=?& ?Ե'{z?ўkN?h??NV?2?N N{?He?ߒa?9<'#3?P?xCx ?%zϬ?(o|sm?.?:z&?Mp?oPg?`[?t?V'j?~?畋x?Ko6?F]3?J?q^t?2q1?՛FT?}ߋZ|?7+?3~橃n?秠f` ?" _?/B5*?FR?YT?z}{?0N?h?Le?0^}v?ikFY?y\@?j?9y?n2Luq?j?$+M`?^:3?%3rH?+ePƄ?R4b?ڰ?ImF?K9?eʜ?ጁk?a >?hВ{?׮+?ľf ?eϲ?S. ?{e?є?L!K?u:? ?|0R?uwj8?,;E(?ר~-;?CKf?뢗Ӿ?8Ëso?>t}e+??]>.Q?Ӆz\?G?+T9?G?nt`?qq?Nma?d?ƛ_x$?կ+.ό6?˔*J=t?8)? y?j䉔?yr?#WL?Eb?U~z?եQ,?ҽ?;Mru? ?ذUZ? T?%?Ɗy!?-p?9 ?>o?8PDž?SH?v0?J2?9΋W?XK? I?q]? t=?ǿ6g?|uլM?qM[?,7;?x?vm?=~tpb?tx?6$%E@?˦+w?@=>i?쁗?Q ?_-X?0?Q7 (N?L?O[E4??kr?ਸQC?rp?y3o?ڏ$?׳j?7F~?( ?˞J4?XxI#?V ?:w:e6?A??I8H?ޑ&Q~?Z5^?(Tk?)3?r<4?i^??)2?'"k?݅Q2B?*!?i^?*=?-;?O*?9Ƶ?K?=ɫ ?T?Kgҧ?tE\?y9J?t"?Q?p?"?<_?Fg~?KE?سw&?A$?W?,;?vQT?ҕo?;~wP? 8|?N@?-_>?Q;w? ?\??{;i?ĽK?J^?*V3?IL?Qx ??𮻴??L?ꪇ{?i+RCP?&E1G?롹8?g?҅=?m*w ?Bns?A?!3?xBN?Żd?ѯY{Tz?&?$?Y?U./?đ2P?s:?: (ST?f0? &?Á(?>gg?e*5:6?z$̍?Nkt?p:R?ʽ?k$;B? t?%>?E ? ?hnDs?Du?j"~W?Ҿi?B?JF?lD?^2G?Й\P?/=?֨"p?̒B?F7P?^o?mp1 ??=S?g ?ߵi.?]A ?)?2dH?Mh?o?-v?/4A?P.?wa˩%?>?IG?Z?сYB(?㝁e)?M ?ؼR?ѯh?ѽ.IR?v:M|~?+L#r?G2?Q'?ih??Z-۔?Mɧ.?ڰ?]H?mJޒ8?\G??š?kF԰?}MWj?̊A?uu*|A?㹣/?IZp?zf%,?L ?!0?H|YS?2$?@к?z}?Šۍ4H?la?C!?0*l?y ?a?BH?zN0?H?נw?mQ`?W tte?؈ ;?_ ?LǓl?;U]+? кU?iN?^WT?k?ӳaR?Z29ҋ?ɷ\h?R6?#|_?AA?n1? 4k?4+F?; ?$c2?ZU?_P??ŊZE?dћ1?СRz?HG?elN?8FD?J&?k??Nj!?9Zg?9?? K.g?d&Y?ڦ~8Z?Ұ?blzq?ҵ?S ?ۨc0 ?}s?4\??GM?\[gZ?[Ȉ&h?2Юμ?%X%?ɻ?"V?J??T$? HL?P?핵L?kS3?r?Iֿ?uWI6?̄T;j?soO8!?Nx?4ba5?0ƍES?ط?+@?9Q?%os8?VK?P䰀?ҏ D ?c[=f ?Ȗ?} z1?۝c?m?yVC?Zv?Aȡ`?hG>?r8f ?9?e$,?pe#`?e'? J$?7a&?6?b[Q?3?, ?o?˜?G*b?U{?.7K?Ѝ3T?Dh{p?1-?]2>?‡t?݁x? Y?r'r?Op? +?ekB?v2Py?- ?4(?쟻͠?τL ??ղq$?@??P}>?tv߁?8oǦ?ӳ?a?f̀7??jD??#M)?!7?7%? 0(?3?Z* ?+p?ׄ22UI?૶$X?;-^u?13pt?2.,t?$ VH?W{0b?t`?7sr ?93??%qhz?[?YQV#?[*JH?*1 J??-qխ?9?_? ?q?yak3?=wha?V FS?9S#?~_;N_?ŗ ?*9+I? ؛?/&?hL 0?$`0?.eS?9?ׯݼB?m@?xGڡ?|}@?wbt?YS^?B&P?L9+)?4'?/qE?Ҽlir?dsF?*r_?KY7W?j.?2l`?_?닞C?V&R? -? Bb?ܲ7 W ?SF?ݡW@?U_:7?ڨ4{s?J?A G?BK8?H1 ?5 ?]^e,?wbP?6hw?tNH;?-?ċœ?gM}?ʮjt?ԧ#?\~?W?>'? ?\3k?}?vԂř?kf-?vv*?ټ|B&?͒|?s7V?ϴ6?f}?~?%?w\?JzL?x?%$?X G?b2A?7L?P? uϞ?*1?`Jr?2Z?ҵu9ts? d~ ?JLN?阂JY?iN?J5+H?U2?F;?Jv ?L I?̶_㈈?t? R;?@e?HL'?־oXd?RYWb?+g?t? FIV?#"?M2?łͷD?p/V?|? |?$J?#屹?ՓFaϒ0?lY?,?8whpt?8Cu?B  ?6?o?бVv?h5s?:[ ?v'0?xL?폞 ?ъo?Jz6?U M?n2J ?Hê?9O/C?J?i?%`x?cE0N?84=U?׶<~?枺?ٿ%^?5?'#?ݦ?k?Z?!V?SV*>p?l=f?@2 ?,x?,h=?)ltP?峞?PY>(?# =µ?A P}?uGG?T:?.S(?౗#o?Xjh?/?ZJ?o~t?eѦ?M5? s?:۳? ?ۃخ43t?^??.?7LZ-?h78??'t?;XP?@7b)?΅h?{ 1&?2O8?Bf)?Բ?kYoh?BP?CztY?_UV@?>}h?bB?0R?Օ.4?-|{?ڣVl@?=;MRz?[SP?Uv?zB?e}?/oM?`Q?Q ?+a? @+f?K'?Y&??wE?} ?㺩Ohl? SV?0!\E?t_`??ᨒ?s?H ?R@uj?(nT{? ??V˛?\ ʸ?HLp? 6+?hd?hgw?JTmx?A?tF?*?ї7eVx?L?^r??|wR?8o+?qA?$}a?]Jx?n.,?mt?[]殕?ԵH?ͤKdKd?էҏ2R?VV?(TL?Y?)R2?tR?wJ?ܓt@?Q?-NF?ȾgdH??S?Mk?тǭ?Mc?۱rJ? #JM8c?\B?wZH/?¹P?q==&V?̼&[? 5|?Q}?_ܭ@?_ƺ?g#Xw?SF׵B?q4?Q,^H? M8 ?a88?j|9?تW2? ,A?Q.g?E䦀?b@;}?ی6?g!?!ǩ?CV?‚Ķ?߻#MǺ? pR?C0>QO?!Ur?捏S?tڲ>?r'0x?q??zĠ?P3P-?!'h?ÿ 8?)WS?͈q(9?)&\0?WUw?; ?z](?Ujr? s;$?5 ?g&i1? ?rI?56aZ?ZGP?T.Y\?^/z?.%?쉉 ?(XF?j&y?&? ?f9>?ԣ@J??,|Y?p?}5v?+z98?XCnI?k?  ?º7?ϛ-?]u?NnR?SɄ?]%;(?$_D‘?%x:jH?g0?UW?h%?,0lA?kOki?d%?7,?K?鹁p!?Ln?o? ~h6?뗘2j?.W/ k?U?@*AC?ޞf?F|??)?GM?R>~*?j#?e?};Y{t?UX? T<^x?1??-xb?Yq?>?2%r?^?Ȣ7?d]?3P?۞Ccu?xP?tT?Qd?_4iK?[N/g?hvs\?jΎ\?偬?ߚ|U? 6?kZ?ʀde? (>?(?ҹU?EKSj?N@(?W:?W4?');Z?==0?$KOM?DqU?+# ?ig}g?ӿSߘ?QLx?`\fZ?$^vhK?¥?Lm"h?@a?\0?瀬J?Ifm_? Ms?Ӷ(? sZ?)kAW?燉x?Jf?@zDĐ?Ό|?}H U? \'q?T#8z_w?J#=?FruȀ??냚? _(V,?Io3L?.p2?e4?Cj#?E5?B?,>P? ?Ғ?#=p?⡣+u?J'Ȁ?ܴ":?i??K?ӄrrO6?1%?܊F9?=?ޡn?eAdљv?V@h?:?ٮ8$?yPI?j9a?ڮ3?vf?֞ok?㦏E?z(P?Wɟ{g? RO?0?엔?l1:?z?Ŏƀ+?z:+rp@?Y2-?ͿQbN!(?e?`7v?GUq%?A?^8!?Jw?G?d b?씉?PL'?貇l1m$?Ȝh?jK?ߘáČ?{!? gX.?,?|\?nF-?.7?Qȝ?[ ?Xo' ?.Z?`א_?gH;ݗ? ?x$5?2/? 6Z2:?ؚ*E)?"a0?ZiD?A̳?WȲH??`Ѱ?U+r?[١h2?Ӛ?śf ?Õk?6Ap?J8\?(M*v66?':?˔x>?%1D?,`?͹Ƈ\?P?]?ތW?r?(7'E?]Y}X?ʬ8?7do?z2O?rwI?kɰ? r[?M{?ɌN`?jx?\~4?\\Ei?䏈t,?ԐfC@\? ]ґ?*rN?~mD?'?e$v?  \?b5?}8?R?\?9bš?:6?Vr 0?˕@i?ݞ5$?5?Mq?ߙ{wX?&n6 ?a0.J?>B? ?\-?Q~ ?%,?(2?ooiԼ? O^8?~5? Ez ?*z?+8^t? ?pX”?ʙ)?0LJ?sc?޺Z??oT? ||*F?$0?6ُ{?&b?/?O1? )Ç?ш@?zo?ץeӻf? Yה6?5f4?L@?&Б9??ڜK?׉-wj?f0?g?;3dD?}^u? ? ԧ?g6]?}?/?Ԛ?R?u ?b?v?N?W@L?x?Ɉr!?N`?px?ޥp•??OR?䕨=f?꒕?P!p?Dr5?9;=M? ,R:?r8?=f?aO(?&?*?Bp?G(@?ho?+?3?W?Yx?V?aZ6?=gW?M?ԮV?xQ?w(y?⃚zT?`4?G(GE?n? %+,?嚠)?yX?b 0?o.?lTRl?Q_0 ?X?-ŎDP?螈ft?c**?A]ݯ?=4?ԣo:?K?ӚIf?¯h@?&?8I3??h?33X?ΜWL?Rt?K>?>H?۶i9?mM?2ji3?wǀ?6?KA~\*?㬉JO? 78?n^?޸^G?롢:?#&c?}?}y$?ݫ Ҧ?ӄ{5K\?}sf^i?V L?i 5}#?ҒM?ژ?įČ?0[eT?_,?B$?M?ݚC+?57?FM?WT?嫛?@'c/?l?t*? V+?y??gjl?Yִ?œ.+ե?8Q?6??9?؂ (?%'?zfO?Ѳ/.?xB92?ʚ*?$l ?$ C?ٱ\kѯ? ݍ ?I?܆RJ?lEh?Qj?֛?&A?['>?b0r?"f?!H?06?nJ؅?}4?KX?gÆ0?3eJ?iEt`?^H?H4%?ӱ{x?4?s0? \uG?POh?Ŭs ?̳O ?޶?U?˫xNN@?eQ̹?Ġ?́J?ųUgt?3i"?F0?ᐏV?Ĵ7G,?!0?*̪?]S$O;?\)?C/w?:?Hp?Gn+en?te6?ӈh ?5 ?֥ycv?І4?E;c?(ͮ?ZVZ?ʚ?ỽe"?ܪ>X?[o?z?)?"szX?ǡ2)?ӄG?f"+? ww?$ln:?|쯤?ߧ ^?H]d?,L(?TV,?̪憐? k(o?{lO}&?̜/b6F?0B?/ݯQ?%r#?v8?¥,?{\҄?^b?\(?(a?nCLP?(S?;?L,Lv?(C_?*??ꁣXr??8@}?,26o?>bP?͓p?&g1?ዒR?/Oj?بܤ?֛&?W]?)9?λn?'J@?<$?+G?^ud{O?N+'n?iL&? *~?1o?0x%?  P?̄MG?RM?P}? vB?1V?=h?0?>?i͕?_I?X?n?e'k??I ?|>@?j/7?몷i?0wl:?@"-)?5S(?~7??\Hx?I_?ܦtQ.@?ؙo?.J?[xY?xž?H!h?^~?bϦo\?56s?/W}?ZIm?ӢZ,6?o0d?嚉b??|R? 3+?2~? ٍ?w7?(F~af?G5xy?߃?RF&?> !?~F?ޣOXb?(dg?Л5_-Z?ݞ*?6w-?ə[e]x?b^?c?纔\??|Ly/?֌!;?θj?,?Zݾ(?V`s:?W{"?Ł`f ?? n?Ҽ#~?ۚ̔c?܌D?݅k?÷N?Ö?5?ɴ?A:Fn ?ҩ7M? N?͠2?tl*?߹f *?[Us?ȂsTP?)?ѱ5?A b$?MpuX?|?c?ܶ?m*s6,? ?{\?'V?o^D?g`?Ky;?\r5?Qӝ?0f?L*?rh?Yͅ?żUoa?Nw???z'?ئN?g?`ry#?H x$?]?QկE q?[^C?gj:?ֈ?7 ?爡?*ŸHhC?#:3[?‚RldP?w\uc?@?|(?4{K V?M.?=?[I?Yg=?|=?ڠ} ?Ý]?'=N?Z7?~>k?Re?š#?Ιi?5i`?G`? zO?谂?$?XƓ ?&?:?P?˧W?5X?{:?f ?$4=?epS?v?!=?&u?Ow?SkR?W ?s"=? %#?{@c$"?u?r8y?e Ŕ?B9B?ϟ?À ?ɸq,?p1??{?kܣ#?V t?B;|quZ?B9t?1?YM^0?T(?_"2?O ?r &?F;*o?$7?Sэ?*?хH?Lp>?}@o?ܪK$p?zC?”?;v"?oP? ?o,DD?j2E?Uq?婂_?ŸQ'?e.?³{?)2?^?vV#?Dǥ?Hw?uVN(S?H?o "?\?jv?ށ4AG??ncf`?ҦP?1p]3??خTS?ĨF?&$_?a]?Y ?زݝ<5?]d?_?Ը;"J?9"?? 1?z?AJF@? 4?î?aEY8?fsy?S]]?ҖE?︕h?=?FdZ? ?wVĘ?懀.?۱Y?[Ra?"~y?U d?nH?ޥ|? ?dc"?mx{y?Vf@(?՜ ?-3hL?⽹?%:}c?t?) ?u:?SYȉ?e?Q,*÷"?鮡?m8M?kWͤ?|?늄Ny8?P4?!y?~?=?Ϫ? &?37 ?gv?jb:?!A?VoKl?yp?${l?]H~?Źp*?sil?k9(?0}Dw??"|f?ֳ^gޓ?'@?[5}?ЈЉw?Ş]I?㎋~?拿J?8y?r pF?O(:?{ӼS?q?2 CU??झ??^$g?~.v?34?8?3*?5Y ]?*?NT?s D?ޖMz?!X?ESX`?wQp#?r۞6?5?>vD??M5?9`?Qzi?WO?=?džf?;~?B/1h?g ?E,?ƓDw?v?{Rl?`H?6z=?(r?UFwx?'.6?V?o wp?܆_^.?d?ނ?n-S?| ?ゟcp?L P?R'6(?a z?ҙ?*C.?đ9 ?8&42?Ы? ?AI1?;L(?}©p?JӠ?rmZ?k?Fs8?p?1!~K?: etk?bnp?Z?5A§c?=?pD?ߌA,?홇1?=?;0<]?ˬL?ز?D^ X?h"j$N?`?Pw?@'M?b*?!]?ܗ;k?L)?ۢgtG?ѹw\?Gc,+?5/?kԵ?3x?}?'?ӕ qD?8Ͼ+?o?rut?ڈb[?sD~B?'? V?B't@?]-?e7yL?^xd?eti??p?\B ?4Ň?#FVP?s&?k5.?#?BV% ?P&f?ƍ|!?tOr??ֵ/S҈(?ŜS^?97WT?*&?ߏNw?ڤ(1@?~-ؤ?ip?]m?؄>m?~v3? F!r?$ƬG?e+ah?ϔƚд?[%5n ?j9H?ҕ6@?ȿKxq?pTh"?9T.?'Z?6?ԘX?Iz?i\z)? &?,o-@?͏g]g?TR?(gh?DŽ?-Ғ_?5$?ﳄsZX?/?2זs?z?Mu|?t@)?@@? ?7G?N|?kK˼h?͵tn?Dq`? ?xF?Z?'.k@ ?,j?dH?4w@?PgK|?d8? t?fOT?Ǩ ?:?bX?ܓ*?6q?.0f?/SI*?l^t?^/?w?忣D?D-?KDӄ?fO?דO`??`?/ ڸ?je)?⬭aL%?9 ?/!?%UƝ?fS9?۩v?f4m?uEVh?7?oF?]2tq?>?Ӷn@D?9ZZ ?. : ?Oi?vAUx?H=?œ;zp?ońvT?oCS?ݛtRVU\? p?tyȇ?GsX?:6?f?!ChW?\7 ?.gbw?X?;C?c0s;?Dg*?d9ú?eņd?Y)?ڃ?[g?~* P??N?⺳?/h`H?R+n=?Թʳ?䥅T?~pmn?s@rAp`?笪?F%+?po~+?Y#t??H8?(c"?#?^B?G s]b?4`?Pxp? ɦF?isTW?Z_"M?alp?ԃ?\?+[?qvl?wv?ꇅ&`?d?Ut\@?ޛ?eD~@?{??" /?0Y?Hx?5H%?f?1p?h%PS?)T"?͑?* %J?PzD?죔+?ad?V?RLF)?CԵ?=.&?e܄?Dn? I?|٩?1,7?y{'b?ˋ?ҙjk?e ?꭪U?@$?iUFZv?d?pd?0xR?`4 P9?P?gF?־#b?B6?x5b?ޅR~?0l'?y^Ƴ?J\R? ?ͪP?0?퇄 %?0?pXEP?9?39,?|t$?b '?!?J?I n&?Bh?qX1?e4?@>?ɇ?׎?ղT4?lr`? ŘQ?눾^? @|?ќ#=?/spV ?ݘ?'4Ga?:v͘t?פO?HN?U?Q4B?EWZ?x+5%?ߍ:?X?@z?{LU3?%a*?'{k? {Ӆ?LuD?z'?wË]?n_?Y qL? 9? %e?i?/ܡ[G? [?j?;6b?od?ԀBoA?Jy^?v?&@?Sgov?{6(;?Do?Δ?#TwH?D\˼?ȉڸ;?tjc?iq6?fP܎?(.^?X{?b1?,HeD?yJe?SN?m?@G?<@?Y&x?d?´sD?_?nwdf?}}j;?N ?@@?c?X!H?>t?R ?hق?7@?(9j?-?g?$ "?{o?u|?]@?od5?kߺ?In?8 /'@?&3gc?)?⽀?J%U?:I:6?gcA?ӑTn?/_X?s?=?=ϼP?[E?Ђ9?L;?8j4%D5?hbX?X@?#쫋x?IO?ޡ8M]??Zbߴ?>4e2_?eFq?+f??և+?Ƽft? NeK?.iU]i?n ?\'S?.n?mK?xӂ?P#?;c??r?P?'?,DP?L?+S8=?Ӽ?|3?Е?Y.\9?@U]"?+T?SCr??1bH?F ? 3H?q)jT?,G?cVg?۰_s2?I 7̭???)R?`?W-?A\'1?%s<?yE\N?D!?ݾ ѩ?^??| ?թ~?}Y!Q?i{?`R>U?OK~?4Q?1z9bp?Cg} ?QH?I-&?i?sM? 6P?/1?8e?{3?{b oR?0疱^?%?5=<? M_9p?O?ҢO? ?&M@?r6—?¢?|.??|kc?Ԭt?=G?̗P}(?߱P?检^X?ph?7V?4=P9?zfVL?ډ,N?F<?O?BOk?'db7?Ǵa/?J=*??j{?n?vcUz? ?\ΐ?||e?fi?T\$? S"ڠ?܊M/ y(?J#?* ?r44@?k}8?K5pyvo-1.5.2/pyvo/dal/tests/data/scs/000077500000000000000000000000001462331236700171235ustar00rootroot00000000000000pyvo-1.5.2/pyvo/dal/tests/data/scs/result.xml000066400000000000000000003463251462331236700212000ustar00rootroot00000000000000 ROSAT was an orbiting x-ray observatory active in the 1990s. We provide a table of all photons observed during ROSAT's all-sky survey (RASS) as well as images of both survey and pointed observations. For ROSAT data products, see http://www.xray.mpe.mpg.de/cgi-bin/rosat/rosat-survey {'SR0': 0.5, 'DEC0': 2.0, 'RA0': 78.0} Written by DaCHS 1.0.1 SCSRenderer This resource contains data associated with the publication 2000IAUC.7432R...1V. For advice on how to cite the resource(s) that contributed to this result, see http://dc.zah.uni-heidelberg.de/tableinfo/rosat.photons A table of x-ray photons detected by ROSAT Right Ascension, equinox 2000.0 Declination, equinox 2000.0 Time the photon was detected, in Julian years. Energy of incoming photon (corrected) [keV] Total positional error, 1{sigma} arcsec Galactic longitude Galactic latitude ROSAT cluster survey exposure time Photon identifier (a combination of observation date and detector coordinates) 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 MzAwOTc4MzkwOTM=
pyvo-1.5.2/pyvo/dal/tests/data/sia/000077500000000000000000000000001462331236700171075ustar00rootroot00000000000000pyvo-1.5.2/pyvo/dal/tests/data/sia/dataset.xml000066400000000000000000000071121462331236700212570ustar00rootroot00000000000000 OK Not the access key for the data Access key for the data MIME type of the file served Size of the data in bytes Epoch at midpoint of observation Object being observed, Simbad-resolvable form Approximate center of image, RA Approximate center of image, Dec Synthetic name of the image Lower limit of the bandpass (in BandPass_Unit units) Upper limit of the bandpass (in BandPass_Unit units) The pixel scale on each image axis
This should not be the dataurl http://example.com/querydata/image.fits image/fits 153280 18885.9416782409 Test 288.95078924817 15.0322239971381 Test Observation 3.8e-07 5.2e-07 0.000403806 0.000406123
This should not be the dataurl http://example.com/querydata/image.fits image/fits 153280 Test 288.95078924817 15.0322239971381 Test Observation 3.8e-07 5.2e-07 0.000403806 0.000406123
pyvo-1.5.2/pyvo/dal/tests/data/sia2/000077500000000000000000000000001462331236700171715ustar00rootroot00000000000000pyvo-1.5.2/pyvo/dal/tests/data/sia2/capabilities-basicauth.xml000066400000000000000000000020121462331236700243000ustar00rootroot00000000000000 https://example.com/sia/capabilities https://example.com/sia/availability https://example.com/sia/auth-v2query pyvo-1.5.2/pyvo/dal/tests/data/sia2/capabilities-newformat.xml000066400000000000000000000022571462331236700243520ustar00rootroot00000000000000 https://example.com/sia/capabilities https://example.com/sia/availability https://example.com/sia/v2query pyvo-1.5.2/pyvo/dal/tests/data/sia2/capabilities-priv.xml000066400000000000000000000027241462331236700233270ustar00rootroot00000000000000 https://example.com/sia/capabilities https://example.com/sia/availability https://example.com/sia/auth-v2query https://example.com/sia/v2query https://example.com/sia/v2query pyvo-1.5.2/pyvo/dal/tests/data/sia2/capabilities.xml000066400000000000000000000031611462331236700223450ustar00rootroot00000000000000 https://example.com/sia/capabilities https://example.com/sia/availability https://example.com/sia/v2query https://example.com/sia/auth-v2query https://example.com/sia/v2query https://example.com/sia/v2query pyvo-1.5.2/pyvo/dal/tests/data/sia2/dataset.xml000066400000000000000000000236761462331236700213560ustar00rootroot00000000000000 calibration level (0,1,2,3) publisher dataset identifier short name for the data colection telescope name instrument name internal dataset identifier type of product timestamp of date the data becomes publicly available RA of central coordinates DEC of central coordinates size of the region covered (~diameter of minimum bounding circle) region bounded by observation typical spatial resolution dimensions (number of pixels) along one spatial axis name of intended target dimensions (number of pixels) along the other spatial axis start time of observation (MJD) end time of observation (MJD) exposure time of observation typical temporal resolution dimensions (number of pixels) along the time axis start spectral coordinate value stop spectral coordinate value typical spectral resolution dimensions (number of pixels) along the energy axis dimensions (number of pixels) along the polarization axis UCD describing the spectral axis polarization states present in the data URL to download the data estimated size of the download UCD describing the observable axis (pixel values) format of the data file(s) primary key timestamp of last modification of the metadata
1 ivo://cadc.nrc.ca/TEST?C190508_1442_SCI/C190508_1442_SCI TEST TEST-1.6m TEST-INSTR TEST-DATASET image 2000-01-01T00:00:00.000 253.0637009871805 69.94853437563255 0.7176383627131244 polygon 252.33774699999995 70.20847500000002 253.83481099999992 70.19265699999998 253.77173199999996 69.68570399999999 252.31050299999995 69.70114700000002 2048 BYW1651+6953 2048 58612.30703703704 58612.30726851852 20.295 0.1 1 1.1699999999999998E-6 1.33E-6 1 https://example.com/datalink?runid=i4oh43a0oky3dyvp&ID=ivo%3A%2F%2Fexample.com%2FTEST%3FC190508_1442_SCI%2FC190508_1442_SCI phot.count application/x-votable+xml;content=datalink f0a8f0ad-be05-4adf-ab3d-8b13c45b79b1 2019-09-19T22:17:07.594
pyvo-1.5.2/pyvo/dal/tests/data/sla/000077500000000000000000000000001462331236700171125ustar00rootroot00000000000000pyvo-1.5.2/pyvo/dal/tests/data/sla/dataset.xml000066400000000000000000000261431462331236700212670ustar00rootroot00000000000000 Splatalogue SLAP Service small description identifying the line the name of the line catalog this entry was drawn from the wavelength in the vacuum of the transition originating the line The molecular formula which may include notation of the common quantum state of the transition The molecular formula which may include notation of the common quantum state of the transition Actual chemical name of the species The type of molecular given as an integer identifier sijmu2. Einstein coefficient Lower state energy Upper state energy Lower state energy in Kelvin Upper state energy in Kelvin an integer flag indicating whether this record provides the recommended frequency for this line. The initial and final quantum number states that produces this line Known interstellar species a description of the spectral line
JPL: CH2OHCOCH2OH v29=1 65(10,55)-65( 9,56)JPL0.0026007993198247656115269.3542CH2OHCOCH2OH v29=1Dihydroxyacetone0131.249668558294-4.74809778436645442.7856446.63057178378637.066044526917642.59806942928565(10,55)-65( 9,56)0
JPL: NH2CO2CH3 v=1 9( 4, 6)- 8( 3, 6) EJPL0.00260079801795189115269.4119NH2CO2CH3 v=1Methyl Carbamate017.1236909189353-4.83754596637333130.4111134.256073708445187.631403639604193.16343131112 9( 4, 6)- 8( 3, 6) E0
JPL: NH2CO2CH3 v=1 9( 4, 6)- 8( 3, 6) EJPL0.002600797618590801115269.4296NH2CO2CH3 v=1Methyl Carbamate013.6709044372638-4.84357552571736130.4111134.256074298853187.631403639604193.163432160581 9( 4, 6)- 8( 3, 6) E0
JPL: (CH3)2CO v=0 54(33,21)-54(32,22) EEJPL0.0026007950374124845115269.544(CH3)2CO v=0Acetone15230.27724514961-3.02603304293211835.60529839.4502681148261202.242703660791207.77473767207154(33,21)-54(32,22) EE1
JPL: cis-CH2OHCHO v=1 14( 2,13)-14( 1,14)JPL0.0026007933722844356115269.6178cis-CH2OHCHO v=1Glycolaldehyde021.9273878887541-4.87033282331431231.969235.813980576529333.749727368876339.28176492197714( 2,13)-14( 1,14)0
JPL: C3H8 N/AJPL0.0026007911904735396115269.7145C3H8Propane00.635151099856094-6.7094715707246130.94892134.793903802094188.405202200504193.937244394447N/A0
JPL: C3H8 N/AJPL0.0026007911882172737115269.7146C3H8Propane00.423328037153043-6.97957609404102130.94891134.79389380543188.405187812818193.93723001156N/A0
JPL: NH2CO2CH3 v=1 23(20, 3)-24(19, 5) EJPL0.002600789356130706115269.7958NH2CO2CH3 v=1Methyl Carbamate05.01323226119246-5.72089974485859279.3062283.15118651397401.85700719681407.38905329251523(20, 3)-24(19, 5) E0
JPL: C3H8 N/AJPL0.0026007859830224104115269.9453C3H8Propane01.69346180808879-6.4646252884359130.94896134.793951500753188.405259751251193.937313021783N/A0
JPL: NH2CO2CH3 v=1 23(20, 3)-24(19, 5) EJPL0.0026007822579477637115270.1104NH2CO2CH3 v=1Methyl Carbamate05.2265567985742-5.72089656247709279.3063283.151297007897401.857151073676407.38921226771423(20, 3)-24(19, 5) E0
JPL: NH2CO2CH3 v=1 23(20, 3)-24(19, 5) EJPL0.002600781919510257115270.1254NH2CO2CH3 v=1Methyl Carbamate04.80745869862592-5.72021293919008279.3063283.151297508243401.857151073676407.38921298759623(20, 3)-24(19, 5) E0
JPL: C3H8 N/AJPL0.002600780777848383115270.176C3H8Propane01.05845801182539-6.20136303548129130.94894134.793939196077188.405230975878193.937295318201N/A0
JPL: C2H5OOCH-trans 21( 9,12)-20( 9,11)JPL0.0026007731630344477115270.5135C2H5OOCH-transEthyl formate058.6767590539827-4.6139139381576178.957782.8027104538654113.601864252006119.13394479168221( 9,12)-20( 9,11)0
JPL: C2H5OOCH-trans 21( 9,13)-20( 9,12)JPL0.0026007731630344477115270.5135C2H5OOCH-transEthyl formate058.6767590539827-4.6139139381576178.957782.8027104538654113.601864252006119.13394479168221( 9,13)-20( 9,12)0
JPL: NH2CH2CH2OH v26=1 18( 4,14)-17( 5,13)JPL0.002600759664053141115271.1118NH2CH2CH2OH v26=1Aminoethanol02.00560091486674-6.01486204317833267.4658271.310830411005384.821410750999390.35352000438218( 4,14)-17( 5,13)0
CDMS: CO v=0 1-0CDMS0.0026007576334647012115271.2018CO v=0Carbon Monoxide10.0121216495404374-7.1424633520481903.8450334130820605.53211357267721-01
JPL: CO v=0 1-0JPL0.0026007576334647012115271.2018CO v=0Carbon Monoxide10.0121216495404374-7.1424633520481903.8450334130820605.53211357267721-01
Lovas/NIST: CO v=0 1-0Lovas/NIST0.002600757628952286115271.202CO v=0Carbon Monoxide11-01
SLAIM: CO v=0 1- 0SLAIM0.002600757628952286115271.202CO v=0Carbon Monoxide10.0121242121-7.142360865160203.8450334197533405.532113582275631 1- 01
CDMS: FeCO N=14-13, J=13-12CDMS0.0026007539084712634115271.3669FeCOIron Monocarbonyl0131.500969428903-4.061336764522968.408872.253838920225398.4244375310149103.956559027197N=14-13, J=13-120
CDMS: CH3CHNH2COOH - I 29(11,18)-29( 8,21)CDMS0.002600753536198126115271.3834CH3CHNH2COOH - I&alpha;-Alanine02.70687445003096-6.2029243806737686.4611190.306149470606124.397535405638129.92965769369129(11,18)-29( 8,21)0
pyvo-1.5.2/pyvo/dal/tests/data/ssa/000077500000000000000000000000001462331236700171215ustar00rootroot00000000000000pyvo-1.5.2/pyvo/dal/tests/data/ssa/result.xml000066400000000000000000001212031462331236700211600ustar00rootroot00000000000000 DALServer proxy service for JHU spectrum services Degree of match to query parameters URL used to access dataset Content or MIME type of dataset Datamodel name and version Dataset or segment type Number of points SI factor and dimensions SI factor and dimensions SI factor and dimensions Dataset Title Dataset creator Data collection to which dataset belongs Creator's ID for the dataset Data processing/creation date Version of dataset IVOA Dataset ID Instrument name Band as in RSM Coverage.Spectral Original source of the data Dataset creation type Dataset publisher Publisher's ID for the dataset ID Restrictions on data access Target RA and Dec Target name Object class of observed target Target redshift Target variability amplitude (typical) Spatial coordinate frame name Equinox Timescale Spatial Position Aperture angular size Query Metadata Access Metadata Estimated dataset size General Dataset Metadata Dataset Identification Metadata Curation Metadata Target Metadata Coordinate System Metadata Spatial Axis Characterization
1.0http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261170552832application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115923.80+005905.16 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422611705528322000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261170552832SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261170552832PUBLIC179.849160 0.984768SDSS J115923.80+005905.16GALAXY0.4519410FK52000TAI179.849160 0.984768
1.0http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443407863906304application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115923.80+005905.16 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434078639063042001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443407863906304SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443407863906304PUBLIC179.849160 0.984768SDSS J115923.80+005905.16GALAXY0.4517070FK52000TAI179.849160 0.984768
0.2182701723871967http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261472542720application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115955.84+010937.46 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614725427202000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261472542720SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261472542720PUBLIC179.982660 1.160404SDSS J115955.84+010937.46GALAXY0.07839570FK52000TAI179.982660 1.160404
0.2182701723871967http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408233005056application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115955.84+010937.46 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082330050562001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408233005056SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408233005056PUBLIC179.982660 1.160404SDSS J115955.84+010937.46GALAXY0.07828230FK52000TAI179.982660 1.160404
0.2152962973906383http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261447376896application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120029.86+005857.51 SKYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614473768962000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261447376896SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261447376896PUBLIC180.124420 0.982641SDSS J120029.86+005857.51SKY-99990FK52000TAI180.124420 0.982641
0.2152962973906383http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408224616448application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120029.86+005857.51 SKYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082246164482001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408224616448SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408224616448PUBLIC180.124420 0.982641SDSS J120029.86+005857.51SKY-99990FK52000TAI180.124420 0.982641
8.695981252317388E-7http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408262365184application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120008.29+010848.68 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082623651842001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408262365184SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408262365184PUBLIC180.034560 1.146855SDSS J120008.29+010848.68GALAXY3.19604E-050FK52000TAI180.034560 1.146855
8.695981252317388E-7http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261464154112application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120008.29+010848.68 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614641541122000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261464154112SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261464154112PUBLIC180.034560 1.146855SDSS J120008.29+010848.68GALAXY3.05799E-050FK52000TAI180.034560 1.146855
8.497366575010945E-7http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408249782272application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115943.32+010204.53 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082497822722001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408249782272SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408249782272PUBLIC179.930500 1.034592SDSS J115943.32+010204.53GALAXY0.1740460FK52000TAI179.930500 1.034592
8.497366575010945E-7http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261510291456application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115943.32+010204.53 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422615102914562000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261510291456SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261510291456PUBLIC179.930500 1.034592SDSS J115943.32+010204.53GALAXY0.1740790FK52000TAI179.930500 1.034592
4.841872020928674E-7http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408237199360application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120020.57+010207.33 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082371993602001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408237199360SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408237199360PUBLIC180.085720 1.035371SDSS J120020.57+010207.33GALAXY0.1985670FK52000TAI180.085720 1.035371
4.841872020928674E-7http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261455765504application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120020.57+010207.33 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614557655042000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261455765504SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261455765504PUBLIC180.085720 1.035371SDSS J120020.57+010207.33GALAXY0.1986180FK52000TAI180.085720 1.035371
2.7872930478268343E-9http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261468348416application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115938.50+005726.95 QSOsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614683484162000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261468348416SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261468348416PUBLIC179.910430 0.957486SDSS J115938.50+005726.95QSO-0.0002556920FK52000TAI179.910430 0.957486
2.7872930478268343E-9http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408270753792application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115938.50+005726.95 QSOsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082707537922001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408270753792SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408270753792PUBLIC179.910430 0.957486SDSS J115938.50+005726.95QSO-0.000136880FK52000TAI179.910430 0.957486
2.367640751371186E-9http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261459959808application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115949.74+010446.07 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614599598082000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261459959808SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261459959808PUBLIC179.957260 1.079465SDSS J115949.74+010446.07GALAXY0.2004570FK52000TAI179.957260 1.079465
2.367640751371186E-9http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408207839232application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115949.74+010446.07 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082078392322001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408207839232SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408207839232PUBLIC179.957260 1.079465SDSS J115949.74+010446.07GALAXY0.2003520FK52000TAI179.957260 1.079465
6.785416172617944E-12http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261162164224application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115904.60+005656.94 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422611621642242000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261162164224SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261162164224PUBLIC179.769180 0.949151SDSS J115904.60+005656.94GALAXY0.3960870FK52000TAI179.769180 0.949151
6.785416172617944E-12http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443407893266432application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115904.60+005656.94 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434078932664322001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443407893266432SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443407893266432PUBLIC179.769180 0.949151SDSS J115904.60+005656.94GALAXY0.396050FK52000TAI179.769180 0.949151
2.341439286296514E-14http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261501902848application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115944.85+005628.47 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422615019028482000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261501902848SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261501902848PUBLIC179.936880 0.941241SDSS J115944.85+005628.47GALAXY0.1009130FK52000TAI179.936880 0.941241
2.341439286296514E-14http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408212033536application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115944.85+005628.47 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082120335362001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408212033536SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408212033536PUBLIC179.936880 0.941241SDSS J115944.85+005628.47GALAXY0.1008910FK52000TAI179.936880 0.941241
4.041185654538269E-15http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261187330048application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115928.97+005619.92 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422611873300482000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261187330048SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261187330048PUBLIC179.870720 0.938866SDSS J115928.97+005619.92GALAXY0.1638470FK52000TAI179.870720 0.938866
4.041185654538269E-15http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443407914237952application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115928.97+005619.92 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434079142379522001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443407914237952SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443407914237952PUBLIC179.870720 0.938866SDSS J115928.97+005619.92GALAXY0.163890FK52000TAI179.870720 0.938866
8.581980694287659E-16http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408279142400application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115944.81+011207.06 QSOsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082791424002001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408279142400SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408279142400PUBLIC179.936720 1.201961SDSS J115944.81+011207.06QSO2.000140FK52000TAI179.936720 1.201961
8.581980694287659E-16http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261480931328application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115944.81+011207.06 QSOsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614809313282000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261480931328SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261480931328PUBLIC179.936720 1.201961SDSS J115944.81+011207.06QSO1.030260FK52000TAI179.936720 1.201961
2.0502337296517507E-17http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408258170880application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120044.06+005553.57 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082581708802001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408258170880SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408258170880PUBLIC180.183580 0.931548SDSS J120044.06+005553.57GALAXY0.1255930FK52000TAI180.183580 0.931548
2.0502337296517507E-17http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261506097152application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120044.06+005553.57 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422615060971522000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261506097152SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261506097152PUBLIC180.183580 0.931548SDSS J120044.06+005553.57GALAXY0.125550FK52000TAI180.183580 0.931548
1.4806720665249466E-20http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261489319936application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120034.98+005517.51 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614893199362000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261489319936SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261489319936PUBLIC180.145770 0.921530SDSS J120034.98+005517.51GALAXY0.08604780FK52000TAI180.145770 0.921530
1.4806720665249466E-20http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408216227840application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120034.98+005517.51 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082162278402001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408216227840SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408216227840PUBLIC180.145770 0.921530SDSS J120034.98+005517.51GALAXY0.08602110FK52000TAI180.145770 0.921530
1.6300048361147802E-21http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261174747136application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115922.31+010453.49 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422611747471362000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261174747136SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261174747136PUBLIC179.842950 1.081526SDSS J115922.31+010453.49GALAXY0.1035140FK52000TAI179.842950 1.081526
1.6300048361147802E-21http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443407901655040application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115922.31+010453.49 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434079016550402001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443407901655040SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443407901655040PUBLIC179.842950 1.081526SDSS J115922.31+010453.49GALAXY0.1035230FK52000TAI179.842950 1.081526
1.33654114566609E-22http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380725178694238208application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120020.40+004752.35 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#807251786942382082001-03-31 05:48:15Z6.2.5ivo://sdss/dr6/spec/2_5/#80725178694238208SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80725178694238208PUBLIC180.084990 0.797876SDSS J120020.40+004752.35GALAXY0.139860FK52000TAI180.084990 0.797876
1.0976232720625123E-25http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261485125632application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120022.76+004741.46 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422614851256322000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261485125632SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261485125632PUBLIC180.094830 0.794849SDSS J120022.76+004741.46GALAXY0.1398420FK52000TAI180.094830 0.794849
1.0976232720625123E-25http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443408220422144application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J120022.76+004741.46 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434082204221442001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443408220422144SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443408220422144PUBLIC180.094830 0.794849SDSS J120022.76+004741.46GALAXY0.1397970FK52000TAI180.094830 0.794849
8.441184510333563E-42http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380443407868100608application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115918.12+005113.61 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804434078681006082001-01-21 11:23:50Z6.2.5ivo://sdss/dr6/spec/2_5/#80443407868100608SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80443407868100608PUBLIC179.825520 0.853781SDSS J115918.12+005113.61GALAXY0.07704420FK52000TAI179.825520 0.853781
8.441184510333563E-42http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261136998400application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115918.12+005113.61 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422611369984002000-04-29 03:22:00Z6.2.5ivo://sdss/dr6/spec/2_5/#80442261136998400SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261136998400PUBLIC179.825520 0.853781SDSS J115918.12+005113.61GALAXY0.07709410FK52000TAI179.825520 0.853781
8.441184510333563E-42http://vaosa-vm1.aoc.nrao.edu/ivoa-dal/JhuSsapServlet?REQUEST=getData&FORMAT=votable&PubDID=ivo%3A%2F%2Fjhu%2Fsdss%2Fdr6%2Fspec%2F2.5%2380442261136998400application/x-votable+xmlSPECTRUM-1.0SPECTRUM4000sA10**(-17) erg s**(-1) cm**(-2) A**(-1)SDSS J115918.12+005113.61 GALAXYsdssSDSS DR6ivo://sdss/dr6/spec/2_5/#804422611369984006.2.5ivo://sdss/dr6/spec/2_5/#80442261136998400SDSS 2.5-M SPEC2 v4_5OPTICALSURVEYARCHIVALELTE VOivo://jhu/sdss/dr6/spec/2.5#80442261136998400PUBLIC179.825520 0.853781SDSS J115918.12+005113.61GALAXY0.07709410FK52000TAI179.825520 0.853781
pyvo-1.5.2/pyvo/dal/tests/data/tap/000077500000000000000000000000001462331236700171175ustar00rootroot00000000000000pyvo-1.5.2/pyvo/dal/tests/data/tap/capabilities.xml000066400000000000000000000273251462331236700223030ustar00rootroot00000000000000 http://dc.zah.uni-heidelberg.de/__system__/tap/run/availability http://dc.zah.uni-heidelberg.de/__system__/tap/run/capabilities http://dc.zah.uni-heidelberg.de/__system__/tap/run/tableMetadata http://dc.zah.uni-heidelberg.de/tap Registry 1.1 Registry 1.0 Obscore-1.1 GloTS 1.0 ADQL 2.0 ADQL 2.0
gavo_simbadpoint(identifier TEXT) -> POINT
gavo_simbadpoint queries simbad for an identifier and returns the corresponding point. Note that identifier can only be a literal, i.e., as simple string rather than a column name. This is because our database cannot query simbad, and we probably wouldn't want to fire off millions of simbad queries anyway; use simbad's own TAP service for this kind of applications.
gavo_match(pattern TEXT, string TEXT) -> INTEGER
gavo_match returns 1 if the POSIX regular expression pattern matches anything in string, 0 otherwise.
ivo_healpix_center(hpxOrder INTEGER, hpxIndex BIGINT) -> POINT
returns a POINT corresponding to the center of the healpix with the given index at the given order.
ivo_string_agg(expression TEXT, delimiter TEXT) -> TEXT
An aggregate function returning all values of expression within a GROUP contcatenated with delimiter
gavo_to_jd(d TIMESTAMP) -> DOUBLE PRECISION
The function converts a postgres timestamp to julian date. This is naive; no corrections for timezones, let alone time scales or the like are done; you can thus not expect this to be good to second-precision unless you are careful in the construction of the timestamp.
ivo_interval_overlapsivo_interval_overlaps(l1 NUMERIC, h1 NUMERIC, l2 NUMERIC, h2 NUMERIC) -> INTEGER
The function returns 1 if the interval [l1...h1] overlaps with the interval [l2...h2]. For the purposes of this function, the case l1=h2 or l2=h1 is treated as overlap. The function returns 0 for non-overlapping intervals.
ivo_healpix_index(order INTEGER, ra DOUBLE PRECISION, dec DOUBLE PRECISION) -> BIGINT
Returns the index of the (nest) healpix with order containing the spherical point (ra, dec). An alternative, 2-argument form ivo_healpix_index(order INTEGER, p POINT) -> BIGINT is also available.
ivo_hashlist_has(hashlist TEXT, item TEXT) -> INTEGER
The function takes two strings; the first is a list of words not containing the hash sign (#), concatenated by hash signs, the second is a word not containing the hash sign. It returns 1 if, compared case-insensitively, the second argument is in the list of words coded in the first argument. The behaviour in case the the second argument contains a hash sign is unspecified.
gavo_to_mjd(d TIMESTAMP) -> DOUBLE PRECISION
The function converts a postgres timestamp to modified julian date. This is naive; no corrections for timezones, let alone time scales or the like are done; you can thus not expect this to be good to second-precision unless you are careful in the construction of the timestamp.
ivo_nocasematch(value TEXT, pattern TEXT) -> INTEGER
ivo_nocasematch returns 1 if pattern matches value, 0 otherwise. pattern is defined as for the SQL LIKE operator, but the match is performed case-insensitively. This function in effect provides a surrogate for the ILIKE SQL operator that is missing from ADQL. On this site, this is actually implemented using python's and SQL's LOWER, so for everything except ASCII, your milage will vary.
ivo_hasword(haystack TEXT, needle TEXT) -> INTEGER
gavo_hasword returns 1 if needle shows up in haystack, 0 otherwise. This is for "google-like"-searches in text-like fields. In word, you can actually employ a fairly complex query language; see http://www.postgresql.org/docs/8.3/static/textsearch.html for details.
ivo_apply_pm(ra DOUBLE PRECISION, dec DOUBLE PRECISION, pmra DOUBLE PRECISION, pmde DOUBLE PRECISON, epdist DOUBLE PRECISION) -> POINT
Returns a POINT (in the UNDEFINED reference frame) for the position an object at ra/dec with proper motion pmra/pmde has after epdist years. positions must be in degrees, PMs in should be in julian years (i.e., proper motions are expected in degrees/year). pmra is assumed to contain cos(delta). NOTE: This currently is a crappy approximation that does *not* go through the tangential plane. If you use it, let the operators know so we replace it with something real.
BOX
POINT
CIRCLE
POLYGON
REGION
CENTROID
COORD1
COORD2
DISTANCE
CONTAINS
INTERSECTS
AREA
LOWER
OFFSET
CAST
IN_UNIT
UNION
EXCEPT
INTERSECT
text/xml application/x-votable+xml;version=1.4 vodml text/html html application/x-votable+xml;serialization=binary2 votable/b2 application/geo-json geojson application/fits fits text/csv text/csv;header=present csv application/x-votable+xml;serialization=tabledata votable/td application/json json application/x-votable+xml votable text/plain text/tab-separated-values tsv 172800 3600 20000 10000000 100000000
pyvo-1.5.2/pyvo/dal/tests/data/tap/examples.htm000066400000000000000000000105671462331236700214600ustar00rootroot00000000000000 Examples queries for HEASARC's TAP service

Examples for the TAP service at HEASARC

Simple geometric query on rosmaster with circle and point

The Table Access Protocol Service at HEASARC allow for simple geometric queries. For example, this query searches for observations in the rostmaster catalog within a circle of radius 1 degree of the coordinates (ra,dec)=(50,-85) -- i.e., basically a cone search -- and an exposure longer than 10000 seconds:

            SELECT * FROM rosmaster                      WHERE exposure > 10000 and                            1=CONTAINS(POINT('ICRS', ra, dec),CIRCLE('ICRS', 50, -85, 1))          

Simple geometric query on rosmaster with circle and point

The Table Access Protocol Service at HEASARC allow for simple geometric and cross-match queries. For example, this query searches for observations common to the rostmaster catalog AND the chanmaster catalog with a minimum exposure of 10ks.

	   SELECT * FROM rosmaster as ros	            INNER JOIN chanmaster as chan	                ON ros.name = chan.name	            WHERE ros.exposure > 10000 and chan.exposure > 10000	            ORDER by ros.exposure	             

Simple geometric query on rosmaster with intersects, circle, point

The Table Access Protocol Service at HEASARC allow for simple geometric queries. For example, this (slow) query searches for observations in the rostmaster catalog where a circle of radius 1 degree of the coordinates (ra,dec)=(50,-85) intersects with a circle of 1 degree radius around the center of the pointing:

            SELECT * FROM rosmaster                      WHERE 1=INTERSECTS(CIRCLE('ICRS', ra, dec,1),CIRCLE('ICRS', 50, -85, 1))          

Simple geometric query on rosmaster with polygon

The Table Access Protocol Service at HEASARC allow for simple geometric queries. For example, this query searches for observations in the rostmaster catalog where a pointing lies with a user-defined polygon, in this case a triangle with vertices (-5,-5), (5,-5), and (0,5)):

            SELECT * FROM rosmaster                      WHERE  exposure > 10000 AND                             1=CONTAINS(POINT('ICRS', ra, dec),POLYGON('ICRS', -5, -5, 5, -5, 0, 5))          

Simple geometric query on rosmaster, chanmaster with join on distance

The Table Access Protocol Service at HEASARC allow for simple geometric queries. For example, this query computes the distance between rostmaster observations and a given point, selecting those observations near it, and ordering the result by that distance:

          SELECT DISTANCE(	      POINT('ICRS', ra, dec),              POINT('ICRS', 266.41683, -29.00781)) AS dist, *	  FROM rosmaster	  WHERE 1=CONTAINS(	      POINT('ICRS', ra, dec),	      CIRCLE('ICRS', 266.41683, -29.00781, 1))	  ORDER BY dist ASC          
ObsCore queries
pyvo-1.5.2/pyvo/dal/tests/data/tap/lazy-table1.xml000066400000000000000000000013331462331236700217660ustar00rootroot00000000000000 test.table1Test table 1Lazy Test Table 1utype id Primary key unit meta.id;meta.main utype VARCHAR indexed primary test.foreigntable testkey testkey Test foreigner utype
pyvo-1.5.2/pyvo/dal/tests/data/tap/lazy-table2.xml000066400000000000000000000026231462331236700217720ustar00rootroot00000000000000 test This is a unittest schema test.table2Test table 2Lazy Test Table 2utype id Primary key unit meta.id;meta.main utype VARCHAR indexed primary test.foreigntable testkey testkey Test foreigner utype
pyvo-1.5.2/pyvo/dal/tests/data/tap/obscore-examples.html000066400000000000000000000034741462331236700232650ustar00rootroot00000000000000

ObsCore Examples from Heidelberg

These are examples for ADQL you can run in TAP services carrying an ivoa.obscore table. See ObsCore for the underlying data model.

Finding images by time and place

Suppose you read in an old amateur observer's log there was an unexpected object on the night sky in the cold winter nights of the week between January 12th and 18th, 1903 – and now you would like to see whether there could be an observation of such a thing.

SELECT s_ra, s_dec, t_min FROM ivoa.obscore
  WHERE t_min BETWEEN gavo_to_mjd('1903-01-12')
      AND gavo_to_mjd('1903-01-19')

There is also a shortcut via user defined functions. As an extension to regular ADQL, DaCHS lets you write gavo_simbadpoint('object') and replaces the result with a position obtained from simbad, like this:

SELECT access_url, t_exptime, t_min FROM ivoa.obscore
  WHERE
    t_min BETWEEN gavo_to_mjd('J2416128.5')
      AND gavo_to_mjd('J2416133.5') AND
    1=CONTAINS(gavo_simbadpoint('Aldebaran'),
      CIRCLE('ICRS', s_ra, s_dec, 15))
pyvo-1.5.2/pyvo/dal/tests/data/tap/obscore-image.xml000066400000000000000000000632171462331236700223660ustar00rootroot00000000000000 Definition and support code for the ObsCore data model and table. Definition and support code for the ObsCore data model and table. The IVOA-defined obscore table, containing generic metadata for datasets within this datacenter. For advice on how to cite the resource(s) that contributed to this result, see http://dc.zah.uni-heidelberg.de/tableinfo/ivoa.ObsCore The IVOA-defined obscore table, containing generic metadata for datasets within this datacenter. The calib_level flag takes the following values: === =========================================================== 0 Raw Instrumental data requiring instrument-specific tools 1 Instrumental data processable with standard tools 2 Calibrated, science-ready data without instrument signature 3 Enhanced data products (e.g., mosaics) === =========================================================== High level scientific classification of the data product, taken from an enumeration Data product specific type Amount of data processing that has been applied to the data Name of a data collection (e.g., project name) this data belongs to Unique identifier for an observation Free-from title of the data set Dataset identifier assigned by the publisher. Dataset identifier assigned by the creator. The URL at which to obtain the data set. MIME type of the resource at access_url Estimated size of data product Object a targeted observation targeted Class of the target object (star, QSO, ...) RA of (center of) observation, ICRS Dec of (center of) observation, ICRS Approximate spatial extent for the region covered by the observation Region covered by the observation, as a polygon Best spatial resolution within the data set Lower bound of times represented in the data set, as MJD Upper bound of times represented in the data set, as MJD Total exporure time Minimal significant time interval along the time axis Minimal wavelength represented within the data set Maximal wavelength represented within the data set Spectral resolving power delta lambda/lamda UCD for the product's observable List of polarization states in the data set Name of the facility at which data was taken Name of the instrument that produced the data Number of elements (typically pixels) along the first spatial axis. Number of elements (typically pixels) along the second spatial axis. Number of elements (typically pixels) along the time axis. Number of elements (typically pixels) along the spectral axis. Number of elements (typically pixels) along the polarization axis. Sampling period in world coordinate units along the spatial axis Nature of the product's spectral axis
image 0 Carte du Ciel potsdam/data/fits/POT032_000002E.fits POT032 000002E 1913-08-26 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000002E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000002E.fits image/fits 435344 3.50857212362378 32.5520448642212 2.62789597705705 Polygon ICRS 5.0880992953 33.8657594349 5.0355038987 31.2453170186 1.9726562093 31.2287678614 1.9153724039 33.8626908007 0.633652077522129 20005.0 20005.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam AO Potsdam, CdC 32-cm refractor 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000002F.fits POT032 000002F 1913-09-27 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000002F.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000002F.fits image/fits 435344 3.53452561567638 32.5695561136923 2.63052286885795 Polygon ICRS 5.103047601 33.886686541 5.0917765511 31.2555119167 2.0160910239 31.2347269366 1.9365250319 33.8649731746 0.634285487467423 20037.0 20037.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam AO Potsdam, CdC 32-cm refractor 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000016E.fits POT032 000016E 1913-11-24 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000016E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000016E.fits image/fits 435344 35.1868032690563 32.4808618261433 2.63817216226016 Polygon ICRS 36.7551716874 33.7680816314 36.6569941298 31.1022837509 33.6625182329 31.1788382583 33.6735378399 33.8410154025 0.63612992526032 20095.0 20095.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam, POT032C 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000019E.fits POT032 000019E 1914-01-10 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000019E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000019E.fits image/fits 435344 41.9674003552431 32.3991901793566 2.63199545777752 Polygon ICRS 43.548354499 33.6930243809 43.505828469 31.0660423298 40.4307731342 31.0878159639 40.3821684028 33.7082053158 0.634640565840527 20142.0 20142.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam, POT032C 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000023E.fits POT032 000023E 1913-11-24 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000023E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000023E.fits image/fits 435344 51.013271521456 32.3592083837928 2.63320798636414 Polygon ICRS 52.5854005932 33.6698086892 52.51655884 31.0228597137 49.4658779108 31.0489341391 49.4582217525 33.6839678682 0.63493293710053 20095.0 20095.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam, POT032C 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000024E.fits POT032 000024E 1914-01-14 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000024E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000024E.fits image/fits 435344 53.2710128701656 32.3315125241693 2.63161394905183 Polygon ICRS 54.8696536533 33.6223099336 54.8100487496 31.0070068952 51.7281712497 31.0126204922 51.6981281808 33.6237338407 0.63454857445322 20146.0 20146.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam, POT032C 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000037E.fits POT032 000037E 1914-01-23 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000037E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000037E.fits image/fits 435344 82.5782914160801 32.0756686823358 2.63224704496679 Polygon ICRS 84.1734494156 33.3485377418 84.0849626303 30.7322269305 81.0277353071 30.7779028646 81.0221467846 33.3910659111 0.634701229864731 20155.0 20155.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam, POT032C 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000041E.fits POT032 000041E 1914-01-28 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000041E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000041E.fits image/fits 435344 91.6034594938728 31.9863773920912 2.63157744935597 Polygon ICRS 93.1854668029 33.2901563606 93.1207735598 30.6519541351 90.0617359171 30.6701913547 90.0404624178 33.3040260012 0.63453977345489 20160.0 20160.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam T-berg, POT032 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000042E.fits POT032 000042E 1914-01-23 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000042E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000042E.fits image/fits 435344 93.8580667241656 31.9665743279601 2.63161025563022 Polygon ICRS 95.4495587754 33.2578595127 95.3683729818 30.6303045167 92.309936229 30.6592222838 92.3026865321 33.2851196812 0.634547683876008 20155.0 20155.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam T-berg, POT032 14930 14929 -1 -1 -1 NaN
image 0 Carte du Ciel potsdam/data/fits/POT032_000043E.fits POT032 000043E 1914-01-28 ivo://org.gavo.dc/~?potsdam/data/fits/POT032_000043E.fits http://dc.zah.uni-heidelberg.de/getproduct/potsdam/data/fits/POT032_000043E.fits image/fits 435344 96.1095474818101 31.929532175206 2.63162090137484 Polygon ICRS 97.6853052447 33.2333654587 97.6308154 30.6046273426 94.5763673352 30.6070206617 94.5432016023 33.2334262897 0.634550250833854 20160.0 20160.0 NaN NaN NaN NaN NaN em.opt Astrophysikalisches Observatorium Potsdam Potsdam T-berg, POT032 14930 14929 -1 -1 -1 NaN
pyvo-1.5.2/pyvo/dal/tests/data/tap/tables.xml000066400000000000000000000013311462331236700211110ustar00rootroot00000000000000 test This is a unittest schema test.table1
test.table2
pyvo-1.5.2/pyvo/dal/tests/make_testdata.py000066400000000000000000000106021462331236700206010ustar00rootroot00000000000000from pathlib import Path import numpy as np from astropy.table import Table from astropy.io.votable.tree import VOTableFile, Info from astropy.io.fits import ImageHDU def _votablefile(): table = Table([ [23, 42, 1337], [b'Illuminatus', b"Don't panic, and always carry a towel", b'Elite'] ], names=('1', '2')) table['1'].meta['ucd'] = 'foo;bar' table['2'].meta['utype'] = 'foobar' votable_file = VOTableFile.from_table(table) info = Info(name='QUERY_STATUS', value='OK') info.content = 'OK' votable_file.resources[0].infos.append(info) return votable_file def votablefile(): votable_file = _votablefile() return votable_file def votablefile_errorstatus(): votable_file = _votablefile() info = Info(name='QUERY_STATUS', value='ERROR') info.content = 'ERROR' votable_file.resources[0].infos[0] = info return votable_file def votablefile_overflowstatus(): votable_file = _votablefile() info_ok = Info(name='QUERY_STATUS', value='OK') info_overflow = Info(name='QUERY_STATUS', value='OVERFLOW') votable_file.resources[0].infos[0] = info_ok votable_file.resources[0].infos.append(info_overflow) return votable_file def votablefile_missingtable(): votable_file = _votablefile() del votable_file.resources[0].tables[0] return votable_file def votablefile_missingresource(): votable_file = _votablefile() del votable_file.resources[0] return votable_file def votablefile_missingcolumns(): votable_file = _votablefile() del votable_file.resources[0].tables[0].fields[:] return votable_file def votablefile_firstresource(): votable_file = _votablefile() votable_file.resources[0]._type = 'results' return votable_file def votablefile_tableinfo(): votable_file = _votablefile() votable_file.resources[0].tables[0].infos[:] = ( votable_file.resources[0].infos[:]) del votable_file.resources[0].infos[:] return votable_file def votablefile_rootinfo(): votable_file = _votablefile() votable_file.infos[:] = ( votable_file.resources[0].infos[:]) del votable_file.resources[0].infos[:] return votable_file def votablefile_dataset(): table = Table([ [ 'image/fits', 'application/x-votable+xml', 'application/x-votable+xml;content=datalink' ], [ b'http://example.com/querydata/image.fits', b'http://example.com/querydata/votable.xml', b'http://example.com/querydata/votable-datalink.xml' ] ], names=('dataformat', 'dataurl')) table['dataformat'].meta['ucd'] = 'meta.code.mime' table['dataurl'].meta['utype'] = 'Access.Reference' table['dataurl'].meta['ucd'] = 'meta.dataset;meta.ref.url' votable_file = VOTableFile.from_table(table) info = Info(name='QUERY_STATUS', value='OK') info.content = 'OK' votable_file.resources[0].infos.append(info) return votable_file def dataset_fits(): hdu = ImageHDU(np.random.random((256, 256))) return hdu def main(): dirname = Path(__file__).parent / 'data' votablefile().to_xml( str(dirname / 'query/basic.xml'), tabledata_format='tabledata') votablefile_errorstatus().to_xml( str(dirname / 'query/errorstatus.xml'), tabledata_format='tabledata') votablefile_overflowstatus().to_xml( str(dirname / 'query/overflowstatus.xml'), tabledata_format='tabledata') votablefile_missingtable().to_xml( str(dirname / 'query/missingtable.xml'), tabledata_format='tabledata') votablefile_missingresource().to_xml( str(dirname / 'query/missingresource.xml'), tabledata_format='tabledata') votablefile_missingcolumns().to_xml( str(dirname / 'query/missingcolumns.xml'), tabledata_format='tabledata') votablefile_firstresource().to_xml( str(dirname / 'query/firstresource.xml'), tabledata_format='tabledata') votablefile_tableinfo().to_xml( str(dirname / 'query/tableinfo.xml'), tabledata_format='tabledata') votablefile_rootinfo().to_xml( str(dirname / 'query/rootinfo.xml'), tabledata_format='tabledata') votablefile_dataset().to_xml( str(dirname / 'query/dataset.xml'), tabledata_format='tabledata') dataset_fits().writeto( str(dirname / 'querydata/image.fits'), overwrite=True) if __name__ == '__main__': main() pyvo-1.5.2/pyvo/dal/tests/test_adhoc.py000066400000000000000000000202571462331236700201170ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.adhoc """ import datetime from astropy import units as u from astropy.time import Time import pytest from pyvo.dal.adhoc import AxisParamMixin, SodaQuery def test_pos(): class TestClass(dict, AxisParamMixin): pass test_obj = TestClass() test_obj.pos.add((1, 2, 3) * u.deg) assert len(test_obj._pos) == 1 assert test_obj['POS'] == ['CIRCLE 1.0 2.0 3.0'] test_obj.pos.add((1, 2, 3, 4)) assert len(test_obj._pos) == 2 assert test_obj['POS'] == ['CIRCLE 1.0 2.0 3.0', 'RANGE 1.0 2.0 3.0 4.0'] # duplicates are ignored test_obj.pos.add((1, 2, 3)) assert len(test_obj._pos) == 2 assert test_obj['POS'] == ['CIRCLE 1.0 2.0 3.0', 'RANGE 1.0 2.0 3.0 4.0'] # polygon test_obj.pos.add((1, 2, 3, 4, 5, 6)) assert len(test_obj._pos) == 3 assert test_obj['POS'] == ['CIRCLE 1.0 2.0 3.0', 'RANGE 1.0 2.0 3.0 4.0', 'POLYGON 1.0 2.0 3.0 4.0 5.0 6.0'] # deletes test_obj.pos.remove((1, 2, 3, 4)) assert len(test_obj._pos) == 2 assert test_obj['POS'] == ['CIRCLE 1.0 2.0 3.0', 'POLYGON 1.0 2.0 3.0 4.0 5.0 6.0'] # test borders test_obj.pos.discard((1, 2, 3) * u.deg) test_obj.pos.discard((1, 2, 3, 4, 5, 6)) assert (len(test_obj._pos) == 0) test_obj.pos.add((0, 90, 90)) assert len(test_obj._pos) == 1 assert test_obj['POS'] == ['CIRCLE 0.0 90.0 90.0'] test_obj.pos.pop() test_obj.pos.add((360, -90, 1)) assert len(test_obj._pos) == 1 assert test_obj['POS'] == ['CIRCLE 360.0 -90.0 1.0'] test_obj.pos.pop() test_obj.pos.add((0, 360, -90, 90)) assert len(test_obj._pos) == 1 assert test_obj['POS'] == ['RANGE 0.0 360.0 -90.0 90.0'] test_obj.pos.pop() test_obj.pos.add((0, 0, 180, 90, 270, -90)) assert len(test_obj._pos) == 1 assert test_obj['POS'] == ['POLYGON 0.0 0.0 180.0 90.0 270.0 -90.0'] # errors test_obj.pos.pop() with pytest.raises(ValueError): test_obj.pos.add(('A', 2, 3)) with pytest.raises(ValueError): test_obj.pos.add((-2, 7, 3)) with pytest.raises(ValueError): test_obj.pos.add((3, 99, 3)) with pytest.raises(ValueError): test_obj.pos.add((2, 7, 91)) with pytest.raises(ValueError): test_obj.pos.add((-1, 7, 3, 4)) with pytest.raises(ValueError): test_obj.pos.add((2, 1, 3, 4)) with pytest.raises(ValueError): test_obj.pos.add((1, 2, 4, 3)) with pytest.raises(ValueError): test_obj.pos.add((-2, 7, 5, 9, 10, 10)) with pytest.raises(ValueError): test_obj.pos.add((2, 99, 5, 9, 10, 10)) with pytest.raises(ValueError): test_obj.pos.add((1, 2, 3, 4, 5, 6, 7)) def test_band(): class TestClass(dict, AxisParamMixin): pass test_obj = TestClass() assert not hasattr(test_obj, '_band') test_obj.band.add(33) assert 33 in test_obj.band assert test_obj['BAND'] == ['33.0 33.0'] test_obj.band.add((50 * u.meter, 500)) assert 33 in test_obj.band assert (50 * u.meter, 500) in test_obj.band assert test_obj['BAND'] == ['33.0 33.0', '50.0 500.0'] test_obj.band.discard(33) assert (50 * u.meter, 500) in test_obj.band assert test_obj['BAND'] == ['50.0 500.0'] test_obj.band.pop() assert not test_obj.band assert not test_obj['BAND'] test_obj.band.add((float('-inf'), 33)) assert (float('-inf'), 33) in test_obj.band assert test_obj.band.dal == ['-inf 33.0'] test_obj.band.clear() test_obj.band.add((33, float('inf'))) assert (33, float('inf')) in test_obj.band assert test_obj.band.dal == ['33.0 inf'] test_obj.clear() # error cases with pytest.raises(ValueError): test_obj.band.add(()) with pytest.raises(ValueError): test_obj.band.add((1, 2, 3)) with pytest.raises(ValueError): test_obj.band.add(('INVALID', 6)) with pytest.raises(ValueError): test_obj.band.add((3, 1)) def test_time(): class TestClass(dict, AxisParamMixin): pass test_obj = TestClass() assert not hasattr(test_obj, '_time') now = Time(datetime.datetime.now(tz=datetime.timezone.utc)) test_obj.time.add(now) assert now in test_obj.time assert test_obj['TIME'] == ['{now} {now}'.format(now=now.mjd)] min_time = '2010-01-01T00:00:00.000Z' max_time = '2010-01-01T01:00:00.000Z' test_obj.time.add((min_time, max_time)) assert now in test_obj.time assert (min_time, max_time) in test_obj.time assert test_obj['TIME'] == ['{now} {now}'.format(now=now.mjd), '{min} {max}'.format(min=Time(min_time).mjd, max=Time(max_time).mjd)] test_obj.time.discard(now) assert (min_time, max_time) in test_obj.time assert test_obj['TIME'] == ['{min} {max}'.format(min=Time(min_time).mjd, max=Time(max_time).mjd)] test_obj.time.pop() assert not test_obj.time assert not test_obj['TIME'] # error cases with pytest.raises(ValueError): test_obj.time.add([]) with pytest.raises(ValueError): test_obj.time.add([now, min_time, max_time]) with pytest.raises(ValueError): test_obj.time.add(['INVALID']) with pytest.raises(ValueError): test_obj.time.add([max_time, min_time]) def test_pol(): class TestClass(dict, AxisParamMixin): pass test_obj = TestClass() assert not hasattr(test_obj, '_pol') test_obj.pol.add('YY') assert 'YY' in test_obj.pol assert test_obj['POL'] == ['YY'] test_obj.pol.add('POLI') assert 'YY' in test_obj.pol assert 'POLI' in test_obj.pol assert test_obj['POL'] == ['YY', 'POLI'] # test duplicate test_obj.pol.add('POLI') assert 'YY' in test_obj.pol assert 'POLI' in test_obj.pol assert test_obj['POL'] == ['YY', 'POLI'] test_obj.pol.remove('YY') assert 'POLI' in test_obj.pol assert test_obj['POL'] == ['POLI'] test_obj.pol.pop() assert not test_obj._pol assert not test_obj['POL'] # error cases with pytest.raises(ValueError): test_obj.pol.add(None) with pytest.raises(ValueError): test_obj.pol.add(['INVALID']) def test_soda_query(): test_obj = SodaQuery(baseurl='some/url') test_obj.circle = (2, 3, 5) assert test_obj._circle == (2, 3, 5) assert test_obj['CIRCLE'] == '2.0 3.0 5.0' assert test_obj._circle assert not hasattr(test_obj, '_polygon') assert not hasattr(test_obj, '_range') test_obj.range = (8, 9, 3, 4) * u.deg assert test_obj['POS'] == 'RANGE 8.0 9.0 3.0 4.0' assert test_obj._range is not None assert not hasattr(test_obj, '_polygon') assert not hasattr(test_obj, '_circle') test_obj.polygon = (1, 2, 3, 4, 5, 6) assert test_obj['POLYGON'] == '1.0 2.0 3.0 4.0 5.0 6.0' assert test_obj._polygon assert not hasattr(test_obj, '_range') assert not hasattr(test_obj, '_circle') del test_obj.polygon assert not hasattr(test_obj, '_polygon') assert not hasattr(test_obj, '_circle') assert not hasattr(test_obj, '_range') # error cases with pytest.raises(ValueError): test_obj.circle = ('A', 1, 2) with pytest.raises(ValueError): test_obj.circle = (1, 1, 2, 2) with pytest.raises(ValueError): test_obj.circle = (-1, 1, 2) with pytest.raises(ValueError): test_obj.circle = (1, 99, 2) with pytest.raises(ValueError): test_obj.circle = (1, 1, 91) with pytest.raises(ValueError): test_obj.range = (1, 2, 3) with pytest.raises(ValueError): test_obj.range = (2, 1, 3, 4) with pytest.raises(ValueError): test_obj.range = (1, 2, 4, 3) with pytest.raises(ValueError): test_obj.range = (-1, 2, 3, 4) with pytest.raises(ValueError): test_obj.range = (2, 1000, 3, 4) with pytest.raises(ValueError): test_obj.range = (1, 1, -91, 4) with pytest.raises(ValueError): test_obj.range = (1, 1, 3, 92) with pytest.raises(ValueError): test_obj.polygon = (1, 2, 3, 4) with pytest.raises(ValueError): test_obj.polygon = (2, 1, 3, 4, 5, 6, 7) pyvo-1.5.2/pyvo/dal/tests/test_datalink.py000066400000000000000000000155051462331236700206300ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.datalink """ from functools import partial import pytest import pyvo as vo from pyvo.dal.adhoc import DatalinkResults from pyvo.utils import vocabularies from astropy.utils.data import get_pkg_data_contents, get_pkg_data_filename get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') @pytest.fixture() def ssa_datalink(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/datalink-ssa.xml') with mocker.register_uri( 'GET', 'http://example.com/ssa_datalink', content=callback ) as matcher: yield matcher @pytest.fixture() def datalink(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/datalink.xml') with mocker.register_uri( 'POST', 'http://example.com/datalink', content=callback ) as matcher: yield matcher @pytest.fixture() def obscore_datalink(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/datalink-obscore.xml') with mocker.register_uri( 'GET', 'http://example.com/obscore', content=callback ) as matcher: yield matcher @pytest.fixture() def res_datalink(mocker): first_batch = True def callback(request, context): nonlocal first_batch if first_batch: first_batch = False return get_pkg_data_contents('data/datalink/cutout1.xml') else: return get_pkg_data_contents('data/datalink/cutout2.xml') with mocker.register_uri( 'POST', 'https://example.com/obscore-datalink', content=callback ) as matcher: yield matcher @pytest.fixture() def proc(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/proc.xml') with mocker.register_uri( 'GET', 'http://example.com/proc', content=callback ) as matcher: yield matcher @pytest.fixture() def datalink_vocabulary(mocker): # astropy download_file (which get_vocabluary uses) does not use # requests, so we can't mock this as we can mock the others. We # replace the entire function for a while dl_voc_uri = 'http://www.ivoa.net/rdf/datalink/core' def fake_download_file(src_url, *args, **kwargs): assert src_url == dl_voc_uri return get_pkg_data_filename('data/datalink/datalink.desise') real_download_file = vocabularies.download_file try: vocabularies.download_file = fake_download_file yield finally: vocabularies.download_file = real_download_file @pytest.mark.usefixtures('ssa_datalink', 'datalink') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") def test_datalink(): results = vo.spectrumsearch( 'http://example.com/ssa_datalink', (30, 30)) datalinks = next(results.iter_datalinks()) row = datalinks[0] assert row.semantics == "#progenitor" row = datalinks[1] assert row.semantics == "#proc" row = datalinks[2] assert row.semantics == "#this" row = datalinks[3] assert row.semantics == "#preview" @pytest.mark.usefixtures('obscore_datalink', 'res_datalink') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") def test_datalink_batch(): results = vo.dal.imagesearch( 'http://example.com/obscore', (30, 30)) assert len([_ for _ in results.iter_datalinks()]) == 3 @pytest.mark.usefixtures('proc', 'datalink_vocabulary') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") class TestSemanticsRetrieval: def test_access_with_string(self): datalinks = DatalinkResults.from_result_url('http://example.com/proc') res = [r["access_url"] for r in datalinks.bysemantics("#this")] assert len(res) == 1 assert res[0].endswith("eq010000ms/20100927.comb_avg.0001.fits.fz") def test_access_with_list(self): datalinks = DatalinkResults.from_result_url('http://example.com/proc') res = [r["access_url"] for r in datalinks.bysemantics(["#this", "#preview-image"])] assert len(res) == 2 assert res[0].endswith("eq010000ms/20100927.comb_avg.0001.fits.fz") assert res[1].endswith("20100927.comb_avg.0001.fits.fz?preview=True") def test_access_with_expansion(self): datalinks = DatalinkResults.from_result_url('http://example.com/proc') res = [r["access_url"] for r in datalinks.bysemantics(["#this", "#preview"])] assert len(res) == 3 assert res[0].endswith("eq010000ms/20100927.comb_avg.0001.fits.fz") assert res[1].endswith("20100927.comb_avg.0001.fits.fz?preview=True") assert res[2].endswith("http://dc.zah.uni-heidelberg.de/wider.dat") def test_access_without_expansion(self): datalinks = DatalinkResults.from_result_url('http://example.com/proc') res = [r["access_url"] for r in datalinks.bysemantics( ["#this", "#preview"], include_narrower=False)] assert len(res) == 2 assert res[0].endswith("eq010000ms/20100927.comb_avg.0001.fits.fz") assert res[1].endswith("http://dc.zah.uni-heidelberg.de/wider.dat") def test_with_full_url(self): datalinks = DatalinkResults.from_result_url('http://example.com/proc') res = [r["access_url"] for r in datalinks.bysemantics("urn:example:rdf/dlext#oracle")] assert len(res) == 1 assert res[0].endswith("when-will-it-be-back") def test_all_mixed(self): datalinks = DatalinkResults.from_result_url('http://example.com/proc') res = [r["access_url"] for r in datalinks.bysemantics([ "urn:example:rdf/dlext#oracle", 'http://www.ivoa.net/rdf/datalink/core#preview', '#this', 'non-existing-term'])] assert len(res) == 4 assert res[0].endswith("eq010000ms/20100927.comb_avg.0001.fits.fz") assert res[1].endswith("comb_avg.0001.fits.fz?preview=True") assert res[2].endswith("http://dc.zah.uni-heidelberg.de/wider.dat") assert res[3].endswith("when-will-it-be-back") pyvo-1.5.2/pyvo/dal/tests/test_mimetype.py000066400000000000000000000031501462331236700206630ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.mimetype """ from functools import partial import pytest import requests_mock from astropy.utils.data import get_pkg_data_contents from pyvo.dal.mimetype import mime_object_maker mime_url = 'https://someurl.com/' get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') try: from PIL import Image # noqa: F401 HAS_PILLOW = True except ImportError: HAS_PILLOW = False @pytest.fixture() def mime(mocker): def callback(request, context): if 'mime-text' in request.url: return b'Text content' elif 'image' in request.url: return get_pkg_data_contents('data/mimetype/ivoa_logo.jpg') elif 'fits' in request.url: return get_pkg_data_contents('data/mimetype/test.fits') with mocker.register_uri( 'GET', requests_mock.ANY, content=callback ) as matcher: yield matcher @pytest.mark.usefixtures('mime') @pytest.mark.skipif('not HAS_PILLOW') def test_mime_object_maker(): assert 'Text content' == mime_object_maker(mime_url + 'mime-text', 'text/csv') img = mime_object_maker(mime_url + 'image', 'image/jpeg') assert img assert 'JPEG' == img.format fits = mime_object_maker(mime_url + 'fits', 'application/fits') assert 2 == len(fits) # error cases with pytest.raises(ValueError): mime_object_maker(None, "not/a/mime/type") with pytest.raises(ValueError): mime_object_maker(None, None) pyvo-1.5.2/pyvo/dal/tests/test_monkeypatch.py000066400000000000000000000006031462331236700213540ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.datalink """ from astropy.table import Table from astropy.utils.data import get_pkg_data_filename def test_monkeypatch(): Table.read(get_pkg_data_filename("data/monkeypatch.xml")) import pyvo # noqa: F401 Table.read(get_pkg_data_filename("data/monkeypatch.xml")) pyvo-1.5.2/pyvo/dal/tests/test_params.py000066400000000000000000000163371462331236700203300ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.datalink """ from functools import partial from urllib.parse import parse_qsl from pyvo.dal.adhoc import DatalinkResults from pyvo.dal.params import find_param_by_keyword, get_converter, AbstractDalQueryParam, IntervalQueryParam from pyvo.dal.exceptions import DALServiceError import pytest import numpy as np import astropy.units as u from astropy.utils.data import get_pkg_data_contents, get_pkg_data_fileobj get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') get_pkg_data_fileobj = partial( get_pkg_data_fileobj, package=__package__, encoding='binary') @pytest.fixture() def proc(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/proc.xml') with mocker.register_uri( 'GET', 'http://example.com/proc', content=callback ) as matcher: yield matcher @pytest.fixture() def proc_ds(mocker): def callback(request, context): return b'' with mocker.register_uri( 'GET', 'http://example.com/proc', content=callback ) as matcher: yield matcher @pytest.fixture() def proc_units(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/proc_units.xml') with mocker.register_uri( 'GET', 'http://example.com/proc_units', content=callback ) as matcher: yield matcher @pytest.fixture() def proc_units_ds(mocker): def callback(request, context): data = dict(parse_qsl(request.query)) if 'band' in data: assert data['band'] == ( '6.000000000000001e-07 8.000000000000001e-06') return b'' with mocker.register_uri( 'GET', 'http://example.com/proc_units_ds', content=callback ) as matcher: yield matcher @pytest.fixture() def proc_inf(mocker): def callback(request, context): return get_pkg_data_contents('data/datalink/proc_inf.xml') with mocker.register_uri( 'GET', 'http://example.com/proc_inf', content=callback ) as matcher: yield matcher @pytest.fixture() def proc_inf_ds(mocker): def callback(request, context): data = dict(parse_qsl(request.query)) if 'band' in data: assert data['band'] == ( '6.000000000000001e-07 +Inf') return b'' with mocker.register_uri( 'GET', 'http://example.com/proc_inf_ds', content=callback ) as matcher: yield matcher @pytest.mark.usefixtures('proc') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") def test_find_param_by_keyword(): datalink = DatalinkResults.from_result_url('http://example.com/proc') proc_dl = datalink[0] input_params = {param.name: param for param in proc_dl.input_params} polygon_lower = find_param_by_keyword('polygon', input_params) polygon_upper = find_param_by_keyword('POLYGON', input_params) circle_lower = find_param_by_keyword('circle', input_params) circle_upper = find_param_by_keyword('CIRCLE', input_params) assert polygon_lower == polygon_upper assert circle_lower == circle_upper @pytest.mark.usefixtures('proc') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") def test_serialize(): datalink = DatalinkResults.from_result_url('http://example.com/proc') proc_dl = datalink[0] input_params = {param.name: param for param in proc_dl.input_params} polygon_conv = get_converter( find_param_by_keyword('polygon', input_params)) circle_conv = get_converter( find_param_by_keyword('circle', input_params)) scale_conv = get_converter( find_param_by_keyword('scale', input_params)) kind_conv = get_converter( find_param_by_keyword('kind', input_params)) assert polygon_conv.serialize((1, 2, 3)) == "1 2 3" assert polygon_conv.serialize(np.array((1, 2, 3))) == "1 2 3" assert circle_conv.serialize((1.1, 2.2, 3.3)) == "1.1 2.2 3.3" assert circle_conv.serialize(np.array((1.1, 2.2, 3.3))) == "1.1 2.2 3.3" assert scale_conv.serialize(1) == "1" assert kind_conv.serialize("DATA") == "DATA" @pytest.mark.usefixtures('proc') @pytest.mark.usefixtures('proc_ds') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") def test_serialize_exceptions(): datalink = DatalinkResults.from_result_url('http://example.com/proc') proc_dl = datalink[0] input_params = {param.name: param for param in proc_dl.input_params} polygon_conv = get_converter( find_param_by_keyword('polygon', input_params)) circle_conv = get_converter( find_param_by_keyword('circle', input_params)) band_conv = get_converter( find_param_by_keyword('band', input_params)) with pytest.raises(DALServiceError): polygon_conv.serialize((1, 2, 3, 4)) with pytest.raises(DALServiceError): circle_conv.serialize((1, 2, 3, 4)) with pytest.raises(DALServiceError): band_conv.serialize((1, 2, 3)) @pytest.mark.usefixtures('proc_units') @pytest.mark.usefixtures('proc_units_ds') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") def test_units(): datalink = DatalinkResults.from_result_url('http://example.com/proc_units') proc_dl = datalink[0] proc_dl.process(band=(6000 * u.Angstrom, 80000 * u.Angstrom)) @pytest.mark.usefixtures('proc_inf') @pytest.mark.usefixtures('proc_inf_ds') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.E02") def test_inf(): datalink = DatalinkResults.from_result_url('http://example.com/proc_inf') proc_dl = datalink[0] proc_dl.process(band=(6000, +np.inf) * u.Angstrom) def test_dal_query_param(): class Test(AbstractDalQueryParam): def get_dal_format(self, item): return str(item) # check test_obs behaves like a set but also holds the dal representation test_obs = Test() test_obs.add(1) assert 1 in test_obs assert test_obs.dal == ['1'] test_obs.add(2) test_obs.add(3) assert len(test_obs) == 3 assert test_obs.dal == ['1', '2', '3'] assert {2, 3} < test_obs assert {1, 2, 3, 4} > test_obs test_obs.clear() assert len(test_obs) == 0 assert len(test_obs.dal) == 0 def test_dal_format(): iqp = IntervalQueryParam(unit=u.m, equivalencies=u.spectral()) assert '1.0 1.0' == iqp.get_dal_format(1) assert '1.0 2.0' == iqp.get_dal_format((1, 2)) assert '1.0 2.0' == iqp.get_dal_format((100 * u.cm, 200 * u.cm)) assert '1.0 2.0' == iqp.get_dal_format((100, 200) * u.cm) assert '0.14989622900000002 1.0' == iqp.get_dal_format((100 * u.cm, 2 * u.GHz)) assert '14.9896229 29.9792458' == iqp.get_dal_format((0.01, 0.02) * u.GHz) # Quantity intervals are corrected in terms of min and max .. assert '1.0 2.0' == iqp.get_dal_format((2, 1) * u.m) # But unitless intervals are not with pytest.raises(ValueError): iqp.get_dal_format((2, 1)) pyvo-1.5.2/pyvo/dal/tests/test_query.py000066400000000000000000000404471462331236700202110ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.query """ from functools import partial from contextlib import ExitStack from os import listdir import pytest import numpy as np import platform from pyvo.dal.query import DALService, DALQuery, DALResults, Record from pyvo.dal.exceptions import DALServiceError, DALQueryError, DALFormatError, DALOverflowWarning from pyvo.version import version from astropy.table import Table, QTable from astropy.io.votable.tree import VOTableFile try: # Workaround astropy deprecation, remove try/except once >=6.0 is required from astropy.io.votable.tree import TableElement except ImportError: from astropy.io.votable.tree import Table as TableElement from astropy.io.fits import HDUList from astropy.utils.data import get_pkg_data_contents get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') @pytest.fixture() def register_mocks(mocker): with ExitStack() as stack: matchers = [ stack.enter_context(mocker.register_uri( 'GET', '//example.com/query/basic', content=get_pkg_data_contents('data/query/basic.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/missingtable', content=get_pkg_data_contents('data/query/missingtable.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/missingresource', content=get_pkg_data_contents('data/query/missingresource.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/missingcolumns', content=get_pkg_data_contents('data/query/missingcolumns.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/firstresource', content=get_pkg_data_contents('data/query/firstresource.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/rootinfo', content=get_pkg_data_contents('data/query/rootinfo.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/tableinfo', content=get_pkg_data_contents('data/query/tableinfo.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/dataset', content=get_pkg_data_contents('data/query/dataset.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/querydata/image.fits', content=get_pkg_data_contents('data/querydata/image.fits') )), # mocker.register_uri( # 'GET', 'http://example.com/querydata/votable.xml', # content=get_pkg_data_contents('data/querydata/votable.xml') # ), # mocker.register_uri( # 'GET', 'http://example.com/querydata/votable-datalink.xml', # content=get_pkg_data_contents('data/querydata/votable-datalink.xml') # ), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/nonexistant', text='Not Found', status_code=404 )), stack.enter_context(mocker.register_uri( 'GET', '//example.com/query/errornous', text='Error', status_code=500 )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/errorstatus', content=get_pkg_data_contents('data/query/errorstatus.xml') )), stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/overflowstatus', content=get_pkg_data_contents('data/query/overflowstatus.xml') )), ] def verbosetest_callback(request, context): assert 'VERBOSE' in request.qs and '1' in request.qs['VERBOSE'] return get_pkg_data_contents('data/query/basic.xml') matchers.append(stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/verbosetest', content=verbosetest_callback ))) def useragent_callback(request, context): assert 'User-Agent' in request.headers assert request.headers['User-Agent'] == 'pyVO/{} Python/{} ({})'.format( version, platform.python_version(), platform.system()) return get_pkg_data_contents('data/query/basic.xml') matchers.append(stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/query/useragent', content=useragent_callback ))) yield matchers def _test_results(results): """Regression test result columns for correctnes""" assert len(results) == 3 assert results['1', 0] == 23 assert results['1', 1] == 42 assert results['1', 2] == 1337 truth = 'Illuminatus' assert results['2', 0] == truth truth = "Don't panic, and always carry a towel" assert results['2', 1] == truth truth = 'Elite' assert results['2', 2] == truth def _test_records(records): """ Regression test dal records for correctness""" assert len(records) == 3 assert all([isinstance(record, Record) for record in records]) assert records[0]['1'] == 23 truth = 'Illuminatus' assert records[0]['2'] == truth assert records[1]['1'] == 42 truth = "Don't panic, and always carry a towel" assert records[1]['2'] == truth assert records[2]['1'] == 1337 truth = 'Elite' assert records[2]['2'] == truth @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.usefixtures('register_mocks') class TestDALService: def test_init(self): """Test if baseurl if passed correctly""" service = DALService('http://example.com/query/basic') assert service.baseurl == 'http://example.com/query/basic' def test_search(self): """ Test (in conjunction with mocker) that parameters arrive serverside, while also ensuring data consistency """ service = DALService('http://example.com/query/verbosetest') dalresults = service.search(VERBOSE=1) _test_results(dalresults) _test_records(dalresults) def test_useragent(self): service = DALService('http://example.com/query/useragent') service.search() def test_http_exception_404(self): service = DALService('http://example.com/query/nonexistant') try: service.search() except DALServiceError as exc: assert exc.code == 404 else: assert False def test_http_exception_500(self): service = DALService('http://example.com/query/errornous') try: service.search() except DALServiceError as exc: assert exc.code == 500 else: assert False def test_query_exception(self): service = DALService('http://example.com/query/errorstatus') with pytest.raises(DALQueryError): service.search() def test_query_warning(self): service = DALService('http://example.com/query/overflowstatus') with pytest.warns(DALOverflowWarning): service.search() @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W53") def test_format_exception(self): with pytest.raises(DALFormatError): service = DALService('http://example.com/query/missingtable') service.search() with pytest.raises(DALFormatError): service = DALService('http://example.com/query/missingresource') service.search() with pytest.raises(DALFormatError): service = DALService('http://example.com/query/missingcolumns') service.search() @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.usefixtures('register_mocks') class TestDALQuery: def test_url(self): queries = ( DALQuery('http://example.com/query/basic'), DALQuery(b'http://example.com/query/basic'), ) assert all( q.queryurl == 'http://example.com/query/basic' for q in queries ) def test_params(self): query = DALQuery( 'http://example.com/query/basic', verbose=1, foo='BAR') assert query['VERBOSE'] == 1 assert query['FOO'] == 'BAR' def test_execute(self): query = DALQuery('http://example.com/query/basic') dalresults = query.execute() assert dalresults.queryurl == 'http://example.com/query/basic' _test_results(dalresults) _test_records(dalresults) def test_execute_raw(self): query = DALQuery('http://example.com/query/basic') raw = query.execute_raw() assert raw.startswith(b'') @pytest.mark.filterwarnings('ignore::astropy.io.votable.exceptions.W03') @pytest.mark.filterwarnings('ignore::astropy.io.votable.exceptions.W06') @pytest.mark.usefixtures('register_mocks') class TestDALResults: def test_init(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') assert dalresults.queryurl == 'http://example.com/query/basic' assert isinstance(dalresults.votable, VOTableFile) assert isinstance(dalresults.resultstable, TableElement) assert dalresults.fieldnames == ('1', '2') assert ( dalresults.fielddescs[0].name, dalresults.fielddescs[1].name ) == ('1', '2') assert dalresults.status == ('OK', 'OK') def test_from_result_url(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') assert dalresults.status == ('OK', 'OK') def test_init_errorstatus(self): with pytest.raises(DALQueryError): DALResults.from_result_url('http://example.com/query/errorstatus') def test_init_overflowstatus(self): with pytest.warns(DALOverflowWarning): DALResults.from_result_url('http://example.com/query/overflowstatus') def test_init_missingtable(self): with pytest.raises(DALFormatError): DALResults.from_result_url('http://example.com/query/missingtable') @pytest.mark.filterwarnings('ignore::astropy.io.votable.exceptions.W53') def test_init_missingresource(self): with pytest.raises(DALFormatError): DALResults.from_result_url( 'http://example.com/query/missingresource') def test_init_missingcolumns(self): with pytest.raises(DALFormatError): DALResults.from_result_url( 'http://example.com/query/missingcolumns') def test_init_firstresource(self): dalresults = DALResults.from_result_url( 'http://example.com/query/firstresource') assert dalresults.status == ('OK', 'OK') def test_init_tableinfo(self): dalresults = DALResults.from_result_url( 'http://example.com/query/tableinfo') assert dalresults.status == ('OK', 'OK') def test_init_rootinfo(self): dalresults = DALResults.from_result_url( 'http://example.com/query/rootinfo') assert dalresults.status == ('OK', 'OK') def test_repr(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') assert repr(dalresults)[0:26] == "" def test_iter(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') records = list(iter(dalresults)) _test_results(dalresults) _test_records(records) def test_dataconsistency(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') assert isinstance(dalresults['1'], np.ndarray) assert isinstance(dalresults['2'], np.ndarray) _test_results(dalresults) _test_records(dalresults) def test_table_conversion(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') assert isinstance(dalresults.to_table(), Table) assert isinstance(dalresults.to_qtable(), QTable) assert len(dalresults) == len(dalresults.to_table()) assert len(dalresults) == len(dalresults.to_qtable()) def test_id_over_name(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') assert isinstance(dalresults['_1'], np.ndarray) assert isinstance(dalresults['_2'], np.ndarray) table = dalresults.to_table() with pytest.raises(KeyError): assert table['_1'] with pytest.raises(KeyError): assert table['_2'] def test_nosuchcolumn(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') with pytest.raises(KeyError): dalresults['nosuchcolumn'] with pytest.raises(KeyError): dalresults.getdesc('nosuchcolumn') def test_columnaliases(self): dalresults = DALResults.from_result_url( 'http://example.com/query/basic') assert dalresults.fieldname_with_ucd('foo') == '1' assert dalresults.fieldname_with_ucd('bar') == '1' assert dalresults.fieldname_with_utype('foobar') == '2' assert dalresults.fieldname_with_ucd('baz') is None assert dalresults.fieldname_with_utype('foobaz') is None @pytest.mark.filterwarnings('ignore::astropy.io.votable.exceptions.W03') @pytest.mark.filterwarnings('ignore::astropy.io.votable.exceptions.W06') @pytest.mark.usefixtures('register_mocks') class TestRecord: def test_itemaccess(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] assert record['1'] == 23 truth = 'Illuminatus' assert record['2'] == truth assert record['_1'] == 23 assert record['_2'] == truth def test_nosuchcolumn(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] with pytest.raises(KeyError): record['nosuchcolumn'] def test_iter(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] record = list(iter(record)) assert record[0] == '1' assert record[1] == '2' def test_len(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] assert len(record) == 2 def test_repr(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] truth = 'Illuminatus' assert repr(record) == repr((23, truth)) def test_get(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] assert record.get('2', decode=True) == 'Illuminatus' def test_columnaliases(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] assert record.getbyucd('foo') == 23 assert record.getbyucd('bar') == 23 truth = 'Illuminatus' assert record.getbyutype('foobar') == truth record.getbyucd('baz') is None record.getbyutype('foobaz') is None def test_datasets(self): records = DALResults.from_result_url( 'http://example.com/query/dataset') record = records[0] assert record.getdataurl() == 'http://example.com/querydata/image.fits' dataset = record.getdataset() HDUList.fromstring(dataset.read()) def test_nodataset(self): record = DALResults.from_result_url( 'http://example.com/query/basic')[0] assert record.getdataurl() is None with pytest.raises(KeyError): record.getdataset().read() def test_cachedataset(self, tmpdir): tmpdir = str(tmpdir) record = DALResults.from_result_url( 'http://example.com/query/dataset')[0] record.cachedataset(dir=tmpdir) assert "dataset.dat" in listdir(tmpdir) class TestUpload: pass pyvo-1.5.2/pyvo/dal/tests/test_scs.py000066400000000000000000000025101462331236700176210ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.scs """ from functools import partial import re import pytest from pyvo.dal.scs import search, SCSService from astropy.utils.data import get_pkg_data_contents get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') scs_re = re.compile('http://example.com/scs.*') @pytest.fixture() def scs(mocker): def callback(request, context): return get_pkg_data_contents('data/scs/result.xml') with mocker.register_uri( 'GET', scs_re, content=callback ) as matcher: yield matcher @pytest.mark.usefixtures('scs') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_search(): results = search('http://example.com/scs', pos=(78, 2), radius=0.5) assert len(results) == 1273 class TestSCSService: def test_init(self): service = SCSService('http://example.com/scs') assert service.baseurl == 'http://example.com/scs' @pytest.mark.usefixtures('scs') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_search(self): service = SCSService('http://example.com/scs') results = service.search(pos=(78, 2), radius=0.5) assert len(results) == 1273 pyvo-1.5.2/pyvo/dal/tests/test_sia.py000066400000000000000000000055621462331236700176170ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.sia """ from functools import partial import re import pytest from pyvo.dal.sia import search, SIAService, SIAQuery from astropy.io.fits import HDUList from astropy.coordinates import SkyCoord from astropy.utils.data import get_pkg_data_contents get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') sia_re = re.compile('http://example.com/sia.*') @pytest.fixture() def register_mocks(mocker): with mocker.register_uri( 'GET', 'http://example.com/querydata/image.fits', content=get_pkg_data_contents('data/querydata/image.fits') ) as matcher: yield matcher @pytest.fixture() def sia(mocker): with mocker.register_uri( 'GET', sia_re, content=get_pkg_data_contents('data/sia/dataset.xml') ) as matcher: yield matcher def _test_result(result): assert result.getdataurl() == 'http://example.com/querydata/image.fits' assert isinstance(result.getdataobj(), HDUList) assert result.filesize == 153280 @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('register_mocks') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.parametrize("position", ((288, 15), SkyCoord(288, 15, unit="deg"))) @pytest.mark.parametrize("format", ("IMAGE/JPEG", "all")) def test_search(position, format): results = search('http://example.com/sia', pos=position, format=format) result = results[0] _test_result(result) class TestSIAService: @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('register_mocks') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W42") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W49") def test_search(self): url = 'http://example.com/sia' service = SIAService(url) assert service.baseurl == url results = service.search(pos=(288, 15)) result = results[0] _test_result(result) assert results[1].dateobs is None @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('register_mocks') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W42") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W49") def test_formatter(self): service = SIAQuery('http://example.com/sia') service.format = "image" assert service["FORMAT"] == "image" service.format = "all" assert service["FORMAT"] == "ALL" service.format = "Graphic-png" assert service["FORMAT"] == "GRAPHIC-png" service.format = "Unsupported" assert service["FORMAT"] == "Unsupported" pyvo-1.5.2/pyvo/dal/tests/test_sia2.py000066400000000000000000000172331462331236700176770ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.sia """ from functools import partial import re import requests_mock import pytest from pyvo.dal.sia2 import search, SIA2Service, SIA2Query, SIAService, SIAQuery import astropy.units as u from astropy.coordinates import SkyCoord from astropy.utils.data import get_pkg_data_contents from astropy.utils.exceptions import AstropyDeprecationWarning get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') sia_re = re.compile('https://example.com/sia/v2query*') capabilities_url = 'https://example.com/sia/capabilities' @pytest.fixture() def sia(mocker): with mocker.register_uri( 'GET', sia_re, content=get_pkg_data_contents('data/sia2/dataset.xml') ) as matcher: yield matcher @pytest.fixture() def capabilities(mocker): with mocker.register_uri( 'GET', capabilities_url, content=get_pkg_data_contents('data/sia2/capabilities.xml') ) as matcher: yield matcher def _test_result(record): assert record.obs_collection == 'TEST' assert record.obs_id == 'TEST-DATASET' assert record.instrument_name == 'TEST-INSTR' assert record.facility_name == 'TEST-1.6m' @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('capabilities') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_search(): results = search('https://example.com/sia', pos=(33.3 * u.deg, 4.2 * u.deg, 0.0166 * u.deg)) result = results[0] _test_result(result) class TestSIA2Service: def test_capabilities(self): # this tests the SIA2 capabilities with various combinations: with requests_mock.Mocker() as cm: cm.get('https://example.com/sia/capabilities', content=get_pkg_data_contents('data/sia2/capabilities.xml')) cm.get('https://example.com/sia-basicauth/capabilities', content=get_pkg_data_contents( 'data/sia2/capabilities-basicauth.xml')) cm.get('https://example.com/sia-newformat/capabilities', content=get_pkg_data_contents( 'data/sia2/capabilities-newformat.xml')) cm.get('https://example.com/sia-priv/capabilities', content=get_pkg_data_contents( 'data/sia2/capabilities-priv.xml')), cm.get('https://example.com/sia/myquery/capabilities', content=get_pkg_data_contents('data/sia2/capabilities.xml')) # multiple interfaces with single security method each and # anonymous access. service = SIA2Service('https://example.com/sia') assert service.query_ep == 'https://example.com/sia/v2query' # one interface with multiple security methods service = SIA2Service('https://example.com/sia-newformat') assert service.query_ep == 'https://example.com/sia/v2query' # multiple interfaces with single security method each (no anon) service = SIA2Service('https://example.com/sia-priv') assert service.query_ep == 'https://example.com/sia/v2query' # any access point will be valid even when it contains query params service = SIA2Service('https://example.com/sia/myquery?param=1') assert service.query_ep == 'https://example.com/sia/v2query' # capabilities checking is bypassed all together with the # check_baseurl=False flag service = SIA2Service('https://example.com/sia/myquery?param=1&', check_baseurl=False) assert service.query_ep == 'https://example.com/sia/myquery?param=1' POSITIONS = [(2, 4, 0.0166 * u.deg), (12, 12.5, 34, 36), (12.0 * u.deg, 34.0 * u.deg, 14.0 * u.deg, 35.0 * u.deg, 14.0 * u.deg, 36.0 * u.deg, 12.0 * u.deg, 35.0 * u.deg), (SkyCoord(2, 4, unit='deg'), 0.166 * u.deg)] @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('capabilities') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W42") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W49") @pytest.mark.parametrize("position", POSITIONS) def test_search_scalar(self, position): service = SIA2Service('https://example.com/sia') results = service.search(pos=position) result = results[0] _test_result(result) @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('capabilities') def test_search_vector(self, pos=POSITIONS): service = SIA2Service('https://example.com/sia') results = service.search(pos=pos) result = results[0] _test_result(result) @pytest.mark.usefixtures('sia') @pytest.mark.usefixtures('capabilities') def test_search_deprecation(self, pos=POSITIONS): # test the deprecation with pytest.warns(AstropyDeprecationWarning): deprecated_service = SIAService('https://example.com/sia') deprecated_results = deprecated_service.search(pos=pos) result = deprecated_results[0] _test_result(result) class TestSIA2Query(): def test_query(self): query = SIA2Query('someurl') query.field_of_view.add((10, 20)) assert query['FOV'] == ['10.0 20.0'] query.field_of_view.add((1 * u.rad, 60)) assert query['FOV'] == ['10.0 20.0', '57.29577951308232 60.0'] query.spatial_resolution.add((1 * u.arcsec, 2)) assert query['SPATRES'] == ['1.0 2.0'] query.spectral_resolving_power.add((3, 5)) assert query['SPECRP'] == ['3 5'] query.exptime.add((25, 50)) assert query['EXPTIME'] == ['25.0 50.0'] query.timeres.add((1, 3)) assert query['TIMERES'] == ['1.0 3.0'] query.publisher_did.add('ID1') query.publisher_did.add('ID2') assert query['ID'] == ['ID1', 'ID2'] query.facility.add('TEL1') assert query['FACILITY'] == ['TEL1'] query.collection.add('ABC') query.collection.add('EFG') assert query['COLLECTION'] == ['ABC', 'EFG'] query.instrument.add('INST1') assert query['INSTRUMENT'] == ['INST1'] query.data_type.add('TYPEA') assert query['DPTYPE'] == ['TYPEA'] query.calib_level.add(0) query.calib_level.add(1) assert query['CALIB'] == ['0', '1'] query.target_name.add('TARGET1') assert query['TARGET'] == ['TARGET1'] query.res_format.add('pdf') assert query['FORMAT'] == ['pdf'] query.maxrec = 1000 assert query['MAXREC'] == '1000' query = SIA2Query('someurl', custom_param=23) assert query['custom_param'] == ['23'] query['custom_param'].append('-Inf 0') assert query['custom_param'] == ['23', '-Inf 0'] query = SIA2Query('someurl', custom_param=[('-Inf', 0), (2, '+Inf')]) assert query['custom_param'] == ['-Inf 0', '2 +Inf'] with pytest.warns(AstropyDeprecationWarning): deprecated_query = SIAQuery('someurl') deprecated_query.field_of_view.add((10, 20)) assert deprecated_query['FOV'] == ['10.0 20.0'] def test_variable_deprecation(): # Test this while we are in the deprecation period, as the variable is durectly # used at least by astroquery.alma with pytest.warns(AstropyDeprecationWarning): from pyvo.dal.sia2 import SIA_PARAMETERS_DESC assert SIA_PARAMETERS_DESC pyvo-1.5.2/pyvo/dal/tests/test_sia2_remote.py000066400000000000000000000220321462331236700212430ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.sia2 against remote services """ import pytest import astropy.units as u from astropy.utils.exceptions import AstropyDeprecationWarning from pyvo.dal.sia2 import search, SIA2Service from pyvo.dal.adhoc import DatalinkResults from pyvo import regsearch CADC_SIA_URL = 'https://ws.cadc-ccda.hia-iha.nrc-cnrc.gc.ca/sia' @pytest.mark.remote_data class TestSIACadc(): # Tests the SIA2 client against the CADC SIA service def test_service(self): cadc = SIA2Service(baseurl=CADC_SIA_URL) with pytest.raises(AstropyDeprecationWarning): assert cadc.availability with pytest.raises(AstropyDeprecationWarning): assert cadc.availability.available with pytest.raises(AstropyDeprecationWarning): assert cadc.availability.notes with pytest.raises(AstropyDeprecationWarning): assert cadc.availability.notes[0] == 'service is accepting queries' assert cadc.capabilities @pytest.mark.xfail(reason="https://github.com/astropy/pyvo/issues/361") @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_datalink_batch(self): # Maximum batch size in CADC SIA is around 25 # Test whether multiple batches can be retrieved results = search(CADC_SIA_URL, pos=(2.8425, 74.4846, 10), maxrec=55) ids = [] for i in results.iter_datalinks(): assert i.to_table()[0]['ID'] not in ids ids.append(i.to_table()[0]['ID']) assert len(ids) == 55 @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_pos(self): results = search(CADC_SIA_URL, pos=(2.8425, 74.4846, 0.001)) assert len(results) > 10 # check that results are datalink assert isinstance(results[0].getdataobj(), DatalinkResults) # limit results to 5 to expedite tests results = search(CADC_SIA_URL, pos=(2.8425, 74.4846, 0.001), maxrec=5) assert len(results) == 5 # check attributes of a record record = results[0] record.dataproduct_type record.dataproduct_subtype record.calib_level # TARGET INFO record.target_name record.target_class # DATA DESCRIPTION record.obs_id record.obs_title record.obs_collection record.obs_create_date record.obs_creator_name record.obs_creator_did # CURATION INFORMATION record.obs_release_date record.obs_publisher_did record.publisher_id record.bib_reference record.data_rights # ACCESS INFORMATION record.access_url record.access_format record.access_estsize # SPATIAL CHARACTERISATION record.s_ra record.s_dec record.s_fov record.s_region record.s_resolution record.s_xel1 record.s_xel2 record.s_ucd record.s_unit record.s_resolution_min record.s_resolution_max record.s_calib_status record.s_stat_error record.s_pixel_scale # TIME CHARACTERISATION record.t_xel record.t_ref_pos record.t_min record.t_max record.t_exptime record.t_resolution record.t_calib_status record.t_stat_error # SPECTRAL CHARACTERISATION record.em_xel record.em_ucd record.em_unit record.em_calib_status record.em_min record.em_max record.em_res_power record.em_res_power_min record.em_res_power_max record.em_resolution record.em_stat_error # OBSERVABLE AXIS record.o_ucd record.o_unit record.o_calib_status record.o_stat_error # POLARIZATION CHARACTERISATION record.pol_xel record.pol_states # PROVENANCE record.instrument_name record.facility_name record.proposal_id @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_band(self): results = search(CADC_SIA_URL, band=(0.0002, 0.0003), maxrec=5) # TODO - correctness assert len(results) == 5 @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_time(self): results = search(CADC_SIA_URL, time=('2002-01-01T00:00:00.00', '2002-01-02T00:00:00.00'), maxrec=5) assert len(results) == 5 @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_pol(self): results = search(CADC_SIA_URL, pol=['YY', 'U'], maxrec=5) assert len(results) == 5 for rr in results: assert 'YY' in rr.pol_states or 'U' in rr.pol_states @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_fov(self): results = search(CADC_SIA_URL, field_of_view=(10, 20), maxrec=5) assert len(results) == 5 # how to test values @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_spatial_res(self): results = search(CADC_SIA_URL, spatial_resolution=(1, 2), maxrec=5) assert len(results) == 5 for rr in results: assert 1 * u.arcsec <= rr.s_resolution <= 2 * u.arcsec @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_spec_resp(self): results = search(CADC_SIA_URL, spectral_resolving_power=(1, 2), maxrec=5) assert len(results) == 5 for rr in results: assert 1 <= rr.em_res_power <= 2 @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_exptime(self): results = search(CADC_SIA_URL, exptime=(1, 2), maxrec=5) assert len(results) == 5 for rr in results: assert 1 * u.second <= rr.t_exptime <= 2 * u.second @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_timeres(self): results = search(CADC_SIA_URL, timeres=(1, 2), maxrec=5) assert len(results) == 5 for rr in results: assert 1 * u.second <= rr.t_resolution <= 2 * u.second def test_publisher_did(self): ids = ['ivo://cadc.nrc.ca/CFHT?447231/447231o', 'ivo://cadc.nrc.ca/CFHT?447232/447232o'] results = search(CADC_SIA_URL, publisher_did=ids) assert len(results) == 2 assert results[0].obs_publisher_did in ids assert results[1].obs_publisher_did in ids @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_facility(self): results = search(CADC_SIA_URL, facility='JCMT', maxrec=5) assert len(results) == 5 for rr in results: assert rr.facility_name == 'JCMT' @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_collection(self): results = search(CADC_SIA_URL, collection='CFHT', maxrec=5) assert len(results) == 5 for rr in results: assert rr.obs_collection == 'CFHT' @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_instrument(self): results = search(CADC_SIA_URL, instrument='SCUBA-2', maxrec=5) assert len(results) == 5 for rr in results: assert rr.instrument_name == 'SCUBA-2' @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_dataproduct_type(self): results = search(CADC_SIA_URL, data_type='image', maxrec=5) assert len(results) == 5 for rr in results: assert rr.dataproduct_type == 'image' @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_target_name(self): results = search(CADC_SIA_URL, target_name='OGF:t028', maxrec=5) assert len(results) == 5 for rr in results: assert rr.target_name == 'OGF:t028' @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_res_format(self): results = search( CADC_SIA_URL, res_format='application/x-votable+xml;content=datalink', maxrec=5) assert len(results) == 5 for rr in results: assert rr.access_format == \ 'application/x-votable+xml;content=datalink' @pytest.mark.filterwarnings("ignore::pyvo.dal.exceptions.DALOverflowWarning") def test_reg_sia2(self): image_services = regsearch(servicetype='sia2') irsa_seip = \ [s for s in image_services if 'irsa' in s.ivoid and 'seip' in s.ivoid][0] result = irsa_seip.search(pos=(31.8425, 77.4846, 0.1), maxrec=1) assert len(result) == 1 pyvo-1.5.2/pyvo/dal/tests/test_sla.py000066400000000000000000000024271462331236700176170ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.sla """ from functools import partial import re import pytest from pyvo.dal.sla import search, SLAService from astropy.utils.data import get_pkg_data_contents get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') sla_re = re.compile('http://example.com/sla.*') @pytest.fixture() def sla(mocker): with mocker.register_uri( 'GET', sla_re, content=get_pkg_data_contents('data/sla/dataset.xml') ) as matcher: yield matcher @pytest.mark.usefixtures('sla') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W42") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W49") def test_search(): results = search('http://example.com/sla', wavelength=(7.6e-6, 1.e-5)) assert len(results) == 21 class TestSLAService: @pytest.mark.usefixtures('sla') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W42") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W49") def test_search(self): service = SLAService('http://example.com/sla') results = service.search(wavelength=(7.6e-6, 1.e-5)) assert len(results) == 21 pyvo-1.5.2/pyvo/dal/tests/test_ssa.py000066400000000000000000000023531462331236700176240ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.ssa """ from functools import partial import re import pytest from pyvo.dal.ssa import search, SSAService from astropy.utils.data import get_pkg_data_contents get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') ssa_re = re.compile('http://example.com/ssa.*') @pytest.fixture() def ssa(mocker): def callback(request, context): return get_pkg_data_contents('data/ssa/result.xml') with mocker.register_uri( 'GET', ssa_re, content=callback ) as matcher: yield matcher @pytest.mark.usefixtures('ssa') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W42") def test_search(): results = search('http://example.com/ssa', pos=(0.0, 0.0), diameter=1.0) assert len(results) == 36 class TestSSAService: @pytest.mark.usefixtures('ssa') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W42") def test_search(self): service = SSAService('http://example.com/ssa') results = service.search(pos=(0.0, 0.0), diameter=1.0) assert len(results) == 36 assert results[35].dateobs is None pyvo-1.5.2/pyvo/dal/tests/test_tap.py000066400000000000000000000732771462331236700176370ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.dal.tap """ from functools import partial from contextlib import ExitStack import datetime import re from io import BytesIO from urllib.parse import parse_qsl import tempfile import pytest import requests_mock from pyvo.dal.tap import escape, search, AsyncTAPJob, TAPService from pyvo.dal import DALQueryError, DALServiceError from pyvo.io.uws import JobFile from pyvo.io.uws.tree import Parameter, Result, ErrorSummary, Message from pyvo.io.vosi.exceptions import VOSIError from pyvo.utils import prototype from astropy.time import Time, TimeDelta from astropy.utils.data import get_pkg_data_contents get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') job_re_path_full = re.compile('^http://example.com/tap/async/([0-9]+)') job_re_path = re.compile('^/tap/async/([0-9]+)') job_re_phase_full = re.compile('^http://example.com/tap/async/([0-9]+)/phase') job_re_parameters_full = re.compile( '^http://example.com/tap/async/([0-9]+)/parameters') job_re_result_full = re.compile( '^http://example.com/tap/async/([0-9]+)/results/result') def _test_image_results(results): assert len(results) == 10 @pytest.fixture() def sync_fixture(mocker): def callback(request, context): return get_pkg_data_contents('data/tap/obscore-image.xml') with mocker.register_uri( 'POST', 'http://example.com/tap/sync', content=callback ) as matcher: yield matcher @pytest.fixture() def create_fixture(mocker): def match_request(request): data = request.text.read() if b'VOSITable' in data: assert request.headers['Content-Type'] == 'text/xml', 'Wrong file format' elif b'VOTable' in data: assert request.headers['Content-Type'] == \ 'application/x-votable+xml', 'Wrong file format' else: assert False, 'BUG' return True with mocker.register_uri( 'PUT', 'https://example.com/tap/tables/abc', additional_matcher=match_request, status_code=201 ) as matcher: yield matcher @pytest.fixture() def delete_fixture(mocker): with mocker.register_uri( 'DELETE', 'https://example.com/tap/tables/abc', status_code=200, ) as matcher: yield matcher @pytest.fixture() def load_fixture(mocker): def match_request(request): data = request.text.read() if b',' in data: assert request.headers['Content-Type'] == 'text/csv', 'Wrong file format' elif b'\t' in data: assert request.headers['Content-Type'] == \ 'text/tab-separated-values', 'Wrong file format' elif b'FITSTable' in data: assert request.headers['Content-Type'] == \ 'application/fits', 'Wrong file format' else: assert False, 'BUG' return True with mocker.register_uri( 'POST', 'https://example.com/tap/load/abc', additional_matcher=match_request, status_code=200, ) as matcher: yield matcher def get_index_job(phase): return """ v3njuz4k1ebpdb5q user {} 2021-10-29T17:34:19.638 2021-10-28T17:34:19.638 14400 2021-11-04T17:34:19.638 article cadcauthtest1.pyvoTestTable true """.format(phase).encode('utf-8') class MockAsyncTAPServer: def __init__(self): self._jobs = dict() def validator(self, request): pass def use(self, mocker): with ExitStack() as stack: matchers = { 'create': stack.enter_context(mocker.register_uri( 'POST', 'http://example.com/tap/async', content=self.create )), 'job': stack.enter_context(mocker.register_uri( requests_mock.ANY, job_re_path_full, content=self.job )), 'phase': stack.enter_context(mocker.register_uri( requests_mock.ANY, job_re_phase_full, content=self.phase )), 'parameters': stack.enter_context(mocker.register_uri( requests_mock.ANY, job_re_parameters_full, content=self.parameters )), 'result': stack.enter_context(mocker.register_uri( 'GET', job_re_result_full, content=self.result )), 'get_job': stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/tap/async/111', content=self.get_job )), 'get_job_list': stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/tap/async', content=self.get_job_list )) } yield matchers def create(self, request, context): if request.method == 'GET': return self.get_job_list(request, context) self.validator(request) newid = max(list(self._jobs.keys()) or [0]) + 1 data = dict(parse_qsl(request.body)) job = JobFile() job.version = "1.1" job.jobid = newid if 'test_erroneus_submit.non_existent' in request.text: job.phase = 'ERROR' job._errorsummary = ErrorSummary() job.errorsummary.message = Message() job.errorsummary.message.content =\ 'test_erroneus_submit.non_existent not found' else: job.phase = 'PENDING' job.quote = Time.now() + TimeDelta(1, format='sec') job.creationtime = Time.now() job.executionduration = TimeDelta(3600, format='sec') job.destruction = Time.now() + TimeDelta(3600, format='sec') for key, value in data.items(): param = Parameter(id=key) param.content = value job.parameters.append(param) context.status_code = 303 context.reason = 'See other' context.headers['Location'] = ( 'http://example.com/tap/async/{}'.format(newid)) self._jobs[newid] = job def job(self, request, context): self.validator(request) jobid = int(job_re_path.match(request.path).group(1)) if request.method == 'GET': job = self._jobs[jobid] io = BytesIO() job.to_xml(io) return io.getvalue() elif request.method == 'POST': data = dict(parse_qsl(request.body)) action = data.get('ACTION') if action == 'DELETE': del self._jobs[jobid] def phase(self, request, context): self.validator(request) jobid = int(job_re_path.match(request.path).group(1)) if request.method == 'GET': phase = self._jobs[jobid].phase return phase elif request.method == 'POST': newphase = request.body.split('=')[-1] job = self._jobs[jobid] result = get_pkg_data_contents('data/tap/obscore-image.xml') if newphase == 'RUN': newphase = 'COMPLETED' result = Result(**{ 'id': 'result', 'size': len(result), 'mime-type': 'application/x-votable+xml', 'xlink:href': ( 'http://example.com/tap/async/{}/results/result' ).format(jobid) }) try: job.results[0] = result except (IndexError, TypeError): job.results.append(result) job.phase = newphase def parameters(self, request, context): self.validator(request) jobid = int(job_re_path.match(request.path).group(1)) job = self._jobs[jobid] if request.method == 'GET': pass elif request.method == 'POST': data = dict(parse_qsl(request.body)) if 'QUERY' in data: assert data['QUERY'] == 'SELECT TOP 42 * FROM ivoa.obsCore' for param in job.parameters: if param.id_.lower() == 'query': param.content = data['QUERY'] if 'UPLOAD' in data: for param in job.parameters: if param.id_.lower() == 'upload': uploads1 = {data[0]: data[1] for data in [ data.split(',') for data in data['UPLOAD'].split(';') ]} uploads2 = {data[0]: data[1] for data in [ data.split(',') for data in param.content.split(';') ]} uploads1.update(uploads2) param.content = ';'.join([ '{}={}'.format(key, value) for key, value in uploads1.items() ]) def result(self, request, context): self.validator(request) return get_pkg_data_contents('data/tap/obscore-image.xml') def get_job(self, request, context): self.validator(request) jobid = int(job_re_path.match(request.path).group(1)) job = JobFile() job.jobid = jobid job.phase = 'EXECUTING' job.ownerid = '222' job.creationtime = Time.now() io = BytesIO() job.to_xml(io) return io.getvalue() def _get_jobref_rep(self, jobid, phase, runid, ownerid, creation_time): doc = (' \n' ' {}\n' ' {}\n' ' {}\n' ' {}\n' ' \n') return doc.format(jobid, phase, runid, ownerid, creation_time) def get_job_list(self, request, context): self.validator(request) fields = parse_qsl(request.query) phases = [] last = None after = None for arg, val in fields: if arg == 'PHASE': phases.append(val) elif arg == 'LAST': last = int(val) elif arg == 'AFTER': after = val doc = '\n' +\ '\n' if phases: doc += self._get_jobref_rep('abc1', 'EXECUTING', 'def1', '21', '2018-12-20T00:23:15.79') doc += self._get_jobref_rep('abc2', 'EXECUTING', 'def2', '21', '2018-12-20T00:23:15.79') if after: doc += self._get_jobref_rep('abc3', 'EXECUTING', 'def3', '21', '2018-12-20T00:23:15.79') if last: doc += self._get_jobref_rep('abc4', 'EXECUTING', 'def4', '21', '2018-12-20T00:23:15.79') doc += self._get_jobref_rep('abc5', 'EXECUTING', 'def5', '21', '2018-12-20T00:23:15.79') doc += self._get_jobref_rep('abc6', 'EXECUTING', 'def6', '21', '2018-12-20T00:23:15.79') doc += '' return doc.encode('UTF-8') @pytest.fixture() def async_fixture(mocker): mock_server = MockAsyncTAPServer() yield from mock_server.use(mocker) @pytest.fixture() def tables(mocker): def callback_tables(request, context): return get_pkg_data_contents('data/tap/tables.xml') def callback_table1(request, context): return get_pkg_data_contents('data/tap/lazy-table1.xml') def callback_table2(request, context): return get_pkg_data_contents('data/tap/lazy-table2.xml') with ExitStack() as stack: matchers = { 'tables': stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/tap/tables', content=callback_tables )), 'table1': stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/tap/tables/test.table1', content=callback_table1 )), 'table2': stack.enter_context(mocker.register_uri( 'GET', 'http://example.com/tap/tables/test.table2', content=callback_table2 )), } yield matchers @pytest.fixture() def examples(mocker): def callback_examplesXHTML(request, context): uri = f"://{request.netloc}{request.path}" if uri == '://example.com/tap/examples': return get_pkg_data_contents('data/tap/examples.htm') elif uri == '://example.org/obscore-examples.html': return get_pkg_data_contents('data/tap/obscore-examples.html') else: assert False, f"Unexpected examples URI: {uri}" with mocker.register_uri( 'GET', requests_mock.ANY, content=callback_examplesXHTML ) as matcher: yield matcher @pytest.fixture() def capabilities(mocker): def callback(request, context): return get_pkg_data_contents('data/tap/capabilities.xml') with mocker.register_uri( 'GET', 'http://example.com/tap/capabilities', content=callback ) as matcher: yield matcher @pytest.fixture() def tapservice(capabilities): """ preferably use this fixture when you need a generic TAP service; it saves a bit of parsing overhead. (but of course make sure you don't modify it). """ return TAPService('http://example.com/tap') def test_escape(): query = 'SELECT * FROM ivoa.obscore WHERE dataproduct_type = {}' query = query.format(escape("'image'")) assert query == ( "SELECT * FROM ivoa.obscore WHERE dataproduct_type = ''image''") @pytest.mark.usefixtures('sync_fixture') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_search(): results = search('http://example.com/tap', "SELECT * FROM ivoa.obscore") _test_image_results(results) class TestTAPService: def test_init(self): service = TAPService('http://example.com/tap') assert service.baseurl == 'http://example.com/tap' def _test_tables(self, table1, table2): assert table1.description == 'Lazy Test Table 1' assert table1.title == 'Test table 1' assert table2.description == 'Lazy Test Table 2' assert table2.title == 'Test table 2' @pytest.mark.usefixtures('tables') def test_tables(self): service = TAPService('http://example.com/tap') vositables = service.tables assert list(vositables.keys()) == ['test.table1', 'test.table2'] assert "test.table1" in vositables assert "any.random.stuff" not in vositables table1, table2 = list(vositables) self._test_tables(table1, table2) def _test_examples(self, parsed_examples): assert len(parsed_examples) == 6 assert "SELECT * FROM rosmaster" in parsed_examples[0]['QUERY'] # the last query is from the continuation assert parsed_examples[-1]['QUERY'].startswith( "\nSELECT access_url, t_exptime, t_min FROM ivoa.obscore") @pytest.mark.usefixtures('examples') def test_examples(self): service = TAPService('http://example.com/tap') service_examples = service.examples self._test_examples(service_examples) @pytest.mark.usefixtures('capabilities') def test_maxrec(self): service = TAPService('http://example.com/tap') assert service.maxrec == 20000 @pytest.mark.usefixtures('capabilities') def test_hardlimit(self): service = TAPService('http://example.com/tap') assert service.hardlimit == 10000000 @pytest.mark.usefixtures('capabilities') def test_upload_methods(self): service = TAPService('http://example.com/tap') upload_methods = service.upload_methods assert upload_methods[0].ivo_id == ( 'ivo://ivoa.net/std/TAPRegExt#upload-https') assert upload_methods[1].ivo_id == ( 'ivo://ivoa.net/std/TAPRegExt#upload-ftp') assert upload_methods[2].ivo_id == ( 'ivo://ivoa.net/std/TAPRegExt#upload-inline') assert upload_methods[3].ivo_id == ( 'ivo://ivoa.net/std/TAPRegExt#upload-http') @pytest.mark.usefixtures('sync_fixture') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_run_sync(self): service = TAPService('http://example.com/tap') results = service.run_sync("SELECT * FROM ivoa.obscore") _test_image_results(results) @pytest.mark.usefixtures('sync_fixture') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_search(self): service = TAPService('http://example.com/tap') results = service.search("SELECT * FROM ivoa.obscore") _test_image_results(results) @pytest.mark.usefixtures('async_fixture') @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W27") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W48") @pytest.mark.filterwarnings("ignore::astropy.io.votable.exceptions.W06") def test_run_async(self): service = TAPService('http://example.com/tap') results = service.run_async("SELECT * FROM ivoa.obscore") _test_image_results(results) @pytest.mark.usefixtures('async_fixture') def test_submit_job(self): service = TAPService('http://example.com/tap') job = service.submit_job("SELECT * FROM ivoa.obscore") assert job.url == 'http://example.com/tap/async/' + job.job_id assert job.phase == 'PENDING' assert job.execution_duration == TimeDelta(3600, format='sec') assert isinstance(job.destruction, Time) assert isinstance(job.quote, Time) assert job.query == "SELECT * FROM ivoa.obscore" job.run() job.wait() job.delete() @pytest.mark.usefixtures('async_fixture') def test_erroneus_submit_job(self): service = TAPService('http://example.com/tap') job = service.submit_job( "SELECT * FROM test_erroneus_submit.non_existent") with pytest.raises(DALQueryError) as e: job.raise_if_error() assert 'test_erroneus_submit.non_existent not found' in str(e) @pytest.mark.usefixtures('async_fixture') def test_submit_job_case(self): """Test using mixed case in the QUERY parameter to a job. DALI requires that query parameter names be case-insensitive, and some TAP servers reflect the input case into the job record, so the TAP client has to be prepared for any case for the QUERY parameter name. """ service = TAPService('http://example.com/tap') # This has to be tested manually, bypassing the normal client layer, # in order to force a mixed-case parameter name. response = service._session.post( "http://example.com/tap/async", data={ "REQUEST": "doQuery", "LANG": "ADQL", "quERy": "SELECT * FROM ivoa.obscore", } ) response.raw.read = partial(response.raw.read, decode_content=True) job = AsyncTAPJob(response.url, session=service._session) assert job.url == 'http://example.com/tap/async/' + job.job_id assert job.query == "SELECT * FROM ivoa.obscore" @pytest.mark.usefixtures('async_fixture') def test_modify_job(self): service = TAPService('http://example.com/tap') job = service.submit_job( "SELECT * FROM ivoa.obscore", uploads={ 'one': 'http://example.com/uploads/one' }) job.query = "SELECT TOP 42 * FROM ivoa.obsCore" job.upload(two='http://example.com/uploads/two') for parameter in job._job.parameters: if parameter.id_ == 'query': assert parameter.content == 'SELECT TOP 42 * FROM ivoa.obsCore' break elif parameter.id_ == 'upload': assert ( 'one=http://example.com/uploads/one' in parameter.content) assert ( 'two=http://example.com/uploads/two' in parameter.content) @pytest.mark.usefixtures('async_fixture') def test_get_job(self): service = TAPService('http://example.com/tap') job = service.get_job('111') assert job.jobid == '111' assert job.phase == 'EXECUTING' assert job.ownerid == '222' @pytest.mark.usefixtures('async_fixture') @pytest.mark.remote_data def test_get_job_list(self): service = TAPService('http://example.com/tap') # server returns: # - 3 jobs for last atribute # - 2 jobs for phase attribute # - 1 job for after attribute # Tests consists in counting the cumulative number of jobs as per # above rules after = datetime.datetime.now(tz=datetime.timezone.utc) assert len(service.get_job_list()) == 0 assert len(service.get_job_list(last=3)) == 3 assert len(service.get_job_list(after='2018-04-25T17:46:01Z')) == 1 assert len(service.get_job_list(phases=['EXECUTING'])) == 2 assert len(service.get_job_list(after=after, phases=['EXECUTING'])) == 3 assert len(service.get_job_list(after='2018-04-25T17:46:01.123Z', last=3)) == 4 assert len(service.get_job_list(phases=['EXECUTING'], last=3)) == 5 assert len(service.get_job_list(phases=['EXECUTING'], last=3, after=datetime.datetime.now(tz=datetime.timezone.utc))) == 6 @pytest.mark.usefixtures('create_fixture') def test_create_table(self): prototype.activate_features('cadc-tb-upload') try: buffer = BytesIO(b'table definition in VOSITable format') service = TAPService('https://example.com/tap') service.create_table(name='abc', definition=buffer) tmpfile = tempfile.NamedTemporaryFile('w+b', delete=False) tmpfile.write(b'table definition in VOTable format here') tmpfile.close() with open(tmpfile.name, 'rb') as f: service.create_table('abc', definition=f, format='VOTable') with pytest.raises(ValueError): service.create_table('abc', definition=buffer, format='Unknown') with pytest.raises(ValueError): service.create_table('abc', definition=None, format='VOSITable') finally: prototype.deactivate_features('cadc-tb-upload') @pytest.mark.usefixtures('delete_fixture') def test_remove_table(self): prototype.activate_features('cadc-tb-upload') try: service = TAPService('https://example.com/tap') service.remove_table(name='abc') finally: prototype.deactivate_features('cadc-tb-upload') @pytest.mark.usefixtures('load_fixture') def test_load_table(self): # csv content in buffer prototype.activate_features('cadc-tb-upload') try: service = TAPService('https://example.com/tap') table_content = BytesIO(b'article,count\nart1,1\nart2,2\nart3,3') service.load_table(name='abc', source=table_content, format='csv') # tsv content in file tmpfile = tempfile.NamedTemporaryFile('w+b', delete=False) tmpfile.write(b'article\tcount\nart1\t1\nart2\t2\nart3\t3') tmpfile.close() with open(tmpfile.name, 'rb') as f: service.load_table('abc', source=f, format='tsv') # FITSTable content in file tmpfile = tempfile.NamedTemporaryFile('w+b', delete=False) tmpfile.write(b'FITSTable content here') tmpfile.close() with open(tmpfile.name, 'rb') as f: service.load_table('abc', source=f, format='FITSTable') with pytest.raises(ValueError): service.load_table('abc', source=table_content, format='Unknown') with pytest.raises(ValueError): service.load_table('abc', source=None, format='tsv') finally: prototype.deactivate_features('cadc-tb-upload') def test_create_index(self): prototype.activate_features('cadc-tb-upload') try: service = TAPService('https://example.com/tap') def match_request_text(request): # check details of index are present return 'table=abc&index=col1&unique=true' in request.text with requests_mock.Mocker() as rm: # mock initial post to table-update and the subsequent calls to # get, run and check status of the job rm.post('https://example.com/tap/table-update', additional_matcher=match_request_text, status_code=303, headers={'Location': 'https://example.com/tap/uws'}) rm.get('https://example.com/tap/uws', [{'content': get_index_job("PENDING")}, {'content': get_index_job("COMPLETED")}]) rm.post('https://example.com/tap/uws/phase', status_code=200) # finally the call service.create_index(table_name='abc', column_name='col1', unique=True) # test wrong return status code with requests_mock.Mocker() as rm: # mock initial post to table-update and the subsequent calls to # get, run and check status of the job rm.post('https://example.com/tap/table-update', additional_matcher=match_request_text, status_code=200, # NOT EXPECTED! headers={'Location': 'https://example.com/tap/uws'}) with pytest.raises(RuntimeError): service.create_index(table_name='abc', column_name='col1', unique=True) finally: prototype.deactivate_features('cadc-tb-upload') @pytest.mark.usefixtures("tapservice") class TestTAPCapabilities: def test_no_tap_cap(self): svc = TAPService('http://example.com/tap') svc.capabilities = [] with pytest.raises(DALServiceError) as excinfo: svc.get_tap_capability() assert str(excinfo.value) == ("Invalid TAP service:" " Does not expose a tr:TableAccess capability") def test_no_adql(self): svc = TAPService('http://example.com/tap') svc.get_tap_capability()._languages = [] with pytest.raises(VOSIError) as excinfo: svc.get_tap_capability().get_adql() assert str(excinfo.value) == ("Invalid TAP service:" " Does not declare an ADQL language") def test_get_adql(self, tapservice): assert tapservice.get_tap_capability().get_adql().name == "ADQL" def test_missing_featurelist(self, tapservice): assert ( tapservice.get_tap_capability().get_adql().get_feature_list("fump") == []) def test_get_featurelist(self, tapservice): features = tapservice.get_tap_capability().get_adql().get_feature_list( "ivo://ivoa.net/std/TAPRegExt#features-adqlgeo") assert set(f.form for f in features) == { 'CENTROID', 'CONTAINS', 'COORD1', 'POLYGON', 'INTERSECTS', 'COORD2', 'BOX', 'AREA', 'DISTANCE', 'REGION', 'CIRCLE', 'POINT'} def test_get_missing_feature(self, tapservice): assert tapservice.get_tap_capability().get_adql().get_feature( "ivo://ivoa.net/std/TAPRegExt#features-adqlgeo", "Garage") is None def test_get_feature(self, tapservice): feature = tapservice.get_tap_capability().get_adql().get_feature( "ivo://ivoa.net/std/TAPRegExt#features-adqlgeo", "AREA") assert feature.form == "AREA" assert feature.description is None def test_missing_udf(self, tapservice): assert tapservice.get_tap_capability().get_adql().get_udf("duff function") is None def test_get_udf(self, tapservice): func = tapservice.get_tap_capability().get_adql().get_udf("IVO_hasword") # case insensitive! assert func.form == "ivo_hasword(haystack TEXT, needle TEXT) -> INTEGER" pyvo-1.5.2/pyvo/dal/vosi.py000066400000000000000000000143771462331236700156260ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ VOSI classes and mixins """ from itertools import chain import requests from urllib.parse import urlparse from astropy.utils.decorators import lazyproperty, deprecated from .exceptions import DALServiceError from ..io import vosi from ..utils.url import url_sibling from ..utils.decorators import stream_decode_content, response_decode_content from ..utils.http import use_session __all__ = ['CapabilityMixin', 'VOSITables'] class EndpointMixin(): def _get_endpoint(self, endpoint): # finds the endpoint relative to the base url or its parent # and returns its content in raw format # do not trust baseurl as it might contain query or fragments urlcomp = urlparse(self.baseurl) curated_baseurl = '{}://{}{}'.format(urlcomp.scheme, urlcomp.hostname, urlcomp.path) if not endpoint: raise AttributeError('endpoint required') ep_urls = [ '{baseurl}/{endpoint}'.format(baseurl=curated_baseurl, endpoint=endpoint), url_sibling(curated_baseurl, endpoint) ] for ep_url in ep_urls: try: response = self._session.get(ep_url, stream=True) response.raise_for_status() break except requests.RequestException: continue else: raise DALServiceError( "No working {endpoint} endpoint provided".format( endpoint=endpoint)) return response.raw @deprecated(since="1.5") class AvailabilityMixin(EndpointMixin): """ Mixing for VOSI availability """ @deprecated(since="1.5") @stream_decode_content def _availability(self): """ Service Availability as a :py:class:`~pyvo.io.vosi.availability.Availability` object """ return self._get_endpoint('availability') @lazyproperty @deprecated(since="1.5") def availability(self): return vosi.parse_availability(self._availability().read) @property @deprecated(since="1.5") def available(self): """ True if the service is available, False otherwise """ return self.availability.available @property @deprecated(since="1.5") def up_since(self): """ datetime the service was started """ return self.availability.upsince class CapabilityMixin(EndpointMixin): """ Mixing for VOSI capability """ @stream_decode_content def _capabilities(self): """ Returns capabilities as a py:class:`~pyvo.io.vosi.availability.Availability` object """ return self._get_endpoint('capabilities') @lazyproperty def capabilities(self): return vosi.parse_capabilities(self._capabilities().read) class TablesMixin(CapabilityMixin): """ Mixin for VOSI tables """ @stream_decode_content def _tables(self): try: interfaces = next( _ for _ in self.capabilities if _.standardid.startswith( 'ivo://ivoa.net/std/VOSI#tables') ).interfaces accessurls = chain.from_iterable(_.accessurls for _ in interfaces) tables_urls = (_.value for _ in accessurls) except StopIteration: tables_urls = [ '{}/tables'.format(self.baseurl), url_sibling(self.baseurl, 'tables') ] for tables_url in tables_urls: try: response = self._session.get(tables_url, stream=True) response.raise_for_status() break except requests.RequestException: continue else: raise DALServiceError("No working tables endpoint provided") return response.raw @lazyproperty def tables(self): return VOSITables(vosi.parse_tables(self._tables().read)) class VOSITables: """ This class encapsulates access to the VOSITables using a given Endpoint. Access to table names is like accessing dictionary keys. using iterator syntax or `keys()` """ def __init__(self, vosi_tables, endpoint_url, session=None): self._vosi_tables = vosi_tables self._endpoint_url = endpoint_url self._cache = {} self._session = use_session(session) def __len__(self): return self._vosi_tables.ntables def __getitem__(self, key): return self._get_table(key) def __iter__(self): for tablename in self.keys(): yield self._get_table(tablename) def __contains__(self, tablename): return tablename in self.keys() def _get_table(self, name): if name in self._cache: return self._cache[name] table = self._vosi_tables.get_table_by_name(name) if not table.columns and not table.foreignkeys: tables_url = '{}/{}'.format(self._endpoint_url, name) response = self._get_table_file(tables_url) try: response.raise_for_status() except requests.RequestException as ex: raise DALServiceError.from_except(ex, tables_url) table = vosi.parse_tables(response.raw.read).get_first_table() self._cache[name] = table return table @response_decode_content def _get_table_file(self, tables_url): return self._session.get(tables_url, stream=True) def keys(self): """ Iterates over the keys (table names). """ for table in self._vosi_tables.iter_tables(): yield table.name def values(self): """ Iterates over the values (tables). Gathers missing values from endpoint if necessary. """ for name in self.keys(): yield self._get_table(name) def items(self): """ Iterates over keys and values (table names and tables). Gathers missing values from endpoint if necessary. """ for name in self.keys(): yield (name, self._get_table(name)) def describe(self): for table in self: table.describe() pyvo-1.5.2/pyvo/dam/000077500000000000000000000000001462331236700142615ustar00rootroot00000000000000pyvo-1.5.2/pyvo/dam/__init__.py000066400000000000000000000001661462331236700163750ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from .obscore import * __all__ = ["ObsCoreMetadata"] pyvo-1.5.2/pyvo/dam/obscore.py000066400000000000000000000056001462331236700162700ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module for representing data in ObsCore format """ __all__ = ['ObsCoreMetadata', 'POLARIZATION_STATES', 'CALIBRATION_LEVELS'] # to be moved to ObsCore POLARIZATION_STATES = ['I', 'Q', 'U', 'V', 'RR', 'LL', 'RL', 'LR', 'XX', 'YY', 'XY', 'YX', 'POLI', 'POLA'] CALIBRATION_LEVELS = [0, 1, 2, 3, 4] class ObsCoreMetadata(): """ Representation of an ObsCore observation TBD setters to do validation and unit check. """ def __init__(self): # OBSERVATION INFO self.dataproduct_type = None self.dataproduct_subtype = None self.calib_level = None # TARGET INFO self.target_name = None self.target_class = None # DATA DESCRIPTION self.obs_id = None self.obs_title = None self.obs_collection = None self.obs_create_date = None self.obs_creator_name = None self.obs_creator_did = None # CURATION INFORMATION self.obs_release_date = None self.obs_publisher_did = None self.publisher_id = None self.bib_reference = None self.data_rights = None # ACCESS INFORMATION self.access_url = None self.access_format = None self.access_estsize = None # SPATIAL CHARACTERISATION self.s_ra = None self.s_dec = None self.s_fov = None self.s_region = None self.s_resolution = None self.s_xel1 = None self.s_xel2 = None self.s_ucd = None self.s_unit = None self.s_resolution_min = None self.s_resolution_max = None self.s_calib_status = None self.s_stat_error = None self.s_pixel_scale = None # TIME CHARACTERISATION self.t_xel = None self.t_ref_pos = None self.t_min = None self.t_max = None self.t_exptime = None self.t_resolution = None self.t_calib_status = None self.t_stat_error = None # SPECTRAL CHARACTERISATION self.em_xel = None self.em_ucd = None self.em_unit = None self.em_calib_status = None self.em_min = None self.em_max = None self.em_res_power = None self.em_res_power_min = None self.em_res_power_max = None self.em_resolution = None self.em_stat_error = None # OBSERVABLE AXIS self.o_ucd = None self.o_unit = None self.o_calib_status = None self.o_stat_error = None # POLARIZATION CHARACTERISATION self.pol_xel = None self.pol_states = None # PROVENANCE self.instrument_name = None self.facility_name = None self.proposal_id = None pyvo-1.5.2/pyvo/io/000077500000000000000000000000001462331236700141275ustar00rootroot00000000000000pyvo-1.5.2/pyvo/io/__init__.py000066400000000000000000000001001462331236700162270ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst pyvo-1.5.2/pyvo/io/uws/000077500000000000000000000000001462331236700147455ustar00rootroot00000000000000pyvo-1.5.2/pyvo/io/uws/__init__.py000066400000000000000000000002421462331236700170540ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst __all__ = ['parse_job', 'parse_job_list', 'JobFile'] from .endpoint import * from .tree import * pyvo-1.5.2/pyvo/io/uws/endpoint.py000066400000000000000000000102031462331236700171330ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This file contains a contains the high-level functions to read the various VOSI Endpoints. """ from astropy.utils.xml.writer import XMLWriter from astropy.io.votable.util import convert_to_writable_filelike from ...utils.xml.elements import xmlattribute, parse_for_object from .tree import JobSummary, Jobs __all__ = ["parse_job", "parse_job_list", "JobFile"] def parse_job_list( source, pedantic=None, filename=None, _debug_python_based_parser=False ): """ Parses a job xml file (or file-like object), and returns a `~pyvo.io.uws.tree.Jobs` object. Parameters ---------- source : str or readable file-like object Path or file object containing a tableset xml file. pedantic : bool, optional When `True`, raise an error when the file violates the spec, otherwise issue a warning. Warnings may be controlled using the standard Python mechanisms. See the `warnings` module in the Python standard library for more information. Defaults to False. filename : str, optional A filename, URL or other identifier to use in error messages. If *filename* is None and *source* is a string (i.e. a path), then *source* will be used as a filename for error messages. Therefore, *filename* is only required when source is a file-like object. Returns ------- `~pyvo.io.uws.tree.Jobs` object See also -------- pyvo.io.vosi.exceptions : The exceptions this function may raise. """ return parse_for_object(source, Jobs, pedantic, filename, _debug_python_based_parser).joblist def parse_job( source, pedantic=None, filename=None, _debug_python_based_parser=False ): """ Parses a job xml file (or file-like object), and returns a `~pyvo.io.uws.endpoint.JobFile` object. Parameters ---------- source : str or readable file-like object Path or file object containing a tableset xml file. pedantic : bool, optional When `True`, raise an error when the file violates the spec, otherwise issue a warning. Warnings may be controlled using the standard Python mechanisms. See the `warnings` module in the Python standard library for more information. Defaults to False. filename : str, optional A filename, URL or other identifier to use in error messages. If *filename* is None and *source* is a string (i.e. a path), then *source* will be used as a filename for error messages. Therefore, *filename* is only required when source is a file-like object. Returns ------- `~pyvo.io.uws.endpoint.JobFile` object See also -------- pyvo.io.vosi.exceptions : The exceptions this function may raise. """ return parse_for_object(source, JobFile, pedantic, filename, _debug_python_based_parser) class JobFile(JobSummary): """ availability element: represents an entire file. The keyword arguments correspond to setting members of the same name, documented below. """ def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) self._version = None @xmlattribute def version(self): return self._version @version.setter def version(self, version): self._version = version def parse(self, iterator, config): for start, tag, data, pos in iterator: if start and tag == 'xml': pass elif start and tag == 'job': # version was not required in v1.0, so default to that. self._version = data.get('version', '1.0') break return super().parse(iterator, config) def to_xml(self, fd): with convert_to_writable_filelike(fd) as _fd: w = XMLWriter(_fd) xml_header = ( '\n' '\n' ) w.write(xml_header) super().to_xml(w) pyvo-1.5.2/pyvo/io/uws/tests/000077500000000000000000000000001462331236700161075ustar00rootroot00000000000000pyvo-1.5.2/pyvo/io/uws/tests/__init__.py000066400000000000000000000000001462331236700202060ustar00rootroot00000000000000pyvo-1.5.2/pyvo/io/uws/tests/data/000077500000000000000000000000001462331236700170205ustar00rootroot00000000000000pyvo-1.5.2/pyvo/io/uws/tests/data/job-error.xml000066400000000000000000000024711462331236700214470ustar00rootroot00000000000000 1337 ERROR 2018-01-01T02:00:00Z 2018-01-01T00:00:00Z 2018-01-01T00:05:00Z 2018-01-01T02:00:00Z 7200 2018-02-01T00:00:00Z ADQL SELECT 'test' doQuery We have problem pyvo-1.5.2/pyvo/io/uws/tests/data/job-implicit-v1.0.xml000066400000000000000000000015301462331236700226050ustar00rootroot00000000000000 1576511540079_32840 COMPLETED 2019-12-16T10:52:20 2019-12-17T10:52:20 600 2019-12-17T10:52:20 ADQL-2.0 tap doQuery SELECT ra, dec FROM table pyvo-1.5.2/pyvo/io/uws/tests/data/job.xml000066400000000000000000000024561462331236700203230ustar00rootroot00000000000000 1337 COMPLETED 2018-01-01T02:00:00Z 2018-01-01T00:00:00Z 2018-01-01T00:05:00Z 2018-01-01T02:00:00Z 7200 2018-02-01T00:00:00Z ADQL SELECT 'test' doQuery pyvo-1.5.2/pyvo/io/uws/tests/test_job.py000066400000000000000000000015561462331236700203010ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.io.vosi """ import pyvo.io.uws as uws from astropy.utils.data import get_pkg_data_filename class TestJob: def test_job(self): job = uws.parse_job(get_pkg_data_filename( "data/job.xml")) assert job.jobid == '1337' assert job.version == '1.1' job = uws.parse_job(get_pkg_data_filename( "data/job-implicit-v1.0.xml")) assert job.version == '1.0' def test_error_job(self): job = uws.parse_job(get_pkg_data_filename( "data/job-error.xml")) assert job.jobid == '1337' assert job.version == '1.1' assert not job.errorsummary.has_detail assert job.errorsummary.type_ == 'fatal' assert job.errorsummary.message.content == 'We have problem' pyvo-1.5.2/pyvo/io/uws/tree.py000066400000000000000000000273341462331236700162670ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This file contains xml element classes as defined in the VOResource standard. """ from functools import partial from astropy.utils.collections import HomogeneousList from astropy.time import Time, TimeDelta from ...utils.xml.elements import ( xmlattribute, xmlelement, Element, ContentMixin) uwselement = partial(xmlelement, ns='uws') def XSInDate(val): if not val: return None try: return Time(val, format='iso') except ValueError: pass try: return Time(val, format='isot') except ValueError: pass raise ValueError('Cannot parse datetime {}'.format(val)) InDuration = partial(TimeDelta, format='sec') XSOutDate = partial(Time, out_subfmt='date') __all__ = [ 'UWSElement', 'Reference', 'JobSummary', 'Parameters', 'Parameter', 'Results', 'Result'] def _convert_boolean(value, default=None): return { 'false': False, '0': False, 'true': True, '1': True }.get(value, default) class UWSElement(Element): def __init__(self, config=None, pos=None, _name='', _ns='uws', **kwargs): super().__init__(config, pos, _name, 'uws', **kwargs) class Reference(UWSElement): """standard xlink references""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.type = kwargs.get('xlink:type') self.href = kwargs.get('xlink:href') @xmlattribute(name='xlink:type') def type(self): """the type of the result""" return self._type @type.setter def type(self, type_): self._type = type_ @xmlattribute(name='xlink:href') def href(self): """the url the result can be retrieved""" return self._href @href.setter def href(self, href): self._href = href class JobSummary(Element): def __init__(self, config=None, pos=None, _name='job', **kwargs): super().__init__(config, pos, _name, **kwargs) self.jobid = kwargs.get('id') self._runid = None self._ownerid = None self._phase = None self._quote = None self._creationtime = None self._starttime = None self._endtime = None self._executionduration = None self._destruction = None self._parameters = Parameters() self._results = Results() self._errorsummary = None self._message = None @uwselement(name='jobId', plain=True) def jobid(self): """ The identifier for the job """ return self._jobid @jobid.setter def jobid(self, jobid): self._jobid = jobid @uwselement(name='runId', plain=True) def runid(self): """client supplied identifier""" return self._runid @runid.setter def runid(self, runid): self._runid = runid @uwselement(name='ownerId', plain=True) def ownerid(self): """the owner (creator) of the job""" return self._ownerid @ownerid.setter def ownerid(self, ownerid): self._ownerid = ownerid @uwselement(plain=True) def phase(self): """the execution phase""" return self._phase @phase.setter def phase(self, phase): self._phase = phase @uwselement(plain=True) def quote(self): """estimated completion time""" return self._quote @quote.setter def quote(self, quote): self._quote = XSInDate(quote) @quote.formatter def quote(self): try: return str(XSOutDate(self._quote)) except ValueError: return None @uwselement(name='creationTime', plain=True) def creationtime(self): """The instant at which the job was created.""" return self._creationtime @creationtime.setter def creationtime(self, creationtime): self._creationtime = XSInDate(creationtime) @creationtime.formatter def creationtime(self): try: return str(XSOutDate(self._creationtime)) except ValueError: return None @uwselement(name='startTime', plain=True) def starttime(self): """The instant at which the job started execution.""" return self._starttime @starttime.setter def starttime(self, starttime): self._starttime = XSInDate(starttime) @starttime.formatter def starttime(self): try: return str(XSOutDate(self._starttime)) except ValueError: return None @uwselement(name='endTime', plain=True) def endtime(self): """The instant at which the job finished execution""" return self._endtime @endtime.setter def endtime(self, endtime): self._endtime = XSInDate(endtime) @endtime.formatter def endtime(self): try: return str(XSOutDate(self._endtime)) except ValueError: return None @uwselement(name='executionDuration', plain=True) def executionduration(self): """ The duration (in seconds) for which the job should be allowed to run - a value of 0 is intended to mean unlimited """ return self._executionduration @executionduration.setter def executionduration(self, executionduration): if not isinstance(executionduration, TimeDelta): executionduration = InDuration(float(executionduration)) self._executionduration = executionduration @executionduration.formatter def executionduration(self): if self.executionduration: return str(int(self._executionduration.value)) @uwselement(plain=True) def destruction(self): """The time at which the whole job will be destroyed""" return self._destruction @destruction.setter def destruction(self, destruction): self._destruction = XSInDate(destruction) @destruction.formatter def destruction(self): try: return str(XSOutDate(self._destruction)) except ValueError: return None @uwselement def parameters(self): """The parameters to the job""" return self._parameters @parameters.adder def parameters(self, iterator, tag, data, config, pos): parameters = Parameters(config, pos, 'parameters', **data) parameters.parse(iterator, config) self._parameters = parameters @uwselement def results(self): """The results for the job""" return self._results @results.adder def results(self, iterator, tag, data, config, pos): results = Results(config, pos, 'results', **data) results.parse(iterator, config) self._results = results @uwselement(name='errorSummary', plain=True) def errorsummary(self): """The error summary of the job.""" return self._errorsummary @errorsummary.adder def errorsummary(self, iterator, tag, data, config, pos): res = ErrorSummary(config, pos, 'errorSummary', **data) res.parse(iterator, config) self._errorsummary = res class Jobs(HomogeneousList, UWSElement): """A parsed representation of the joblist endpoint. """ def __init__(self, config=None, pos=None, _name='jobs', **kwargs): HomogeneousList.__init__(self, JobSummary) UWSElement.__init__(self, config, pos, _name, **kwargs) @uwselement def jobs(self): return self @jobs.adder def jobs(self, iterator, tag, data, config, pos): return @uwselement(name='jobref') def joblist(self): return self @joblist.adder def joblist(self, iterator, tag, data, config, pos): job = JobSummary(config, pos, 'jobref', **data) job.parse(iterator, config) self.append(job) class Parameters(UWSElement, HomogeneousList): """ Parameters element of a job """ def __init__(self, config=None, pos=None, _name='parameters', **kwargs): """ """ # Note: Above is a load-bearing empty comment. # Do not remove, or else the Sphinx build may fail (see PR #193). HomogeneousList.__init__(self, Parameter) UWSElement.__init__(self, config, pos, _name, **kwargs) @uwselement(name='parameter') def parameters(self): return self @parameters.adder def parameters(self, iterator, tag, data, config, pos): parameter = Parameter(config, pos, 'parameter', **data) parameter.parse(iterator, config) self.append(parameter) class Parameter(ContentMixin, UWSElement): def __init__(self, config=None, pos=None, _name='parameter', **kwargs): super().__init__(config, pos, _name, **kwargs) self.byreference = _convert_boolean(kwargs.get('byReference')) self.id_ = kwargs.get('id') @xmlattribute def byreference(self): """ if this attribute is true then the content of the parameter represents a URL to retrieve the actual parameter value. """ return self._byreference @byreference.setter def byreference(self, byreference): self._byreference = byreference @xmlattribute(name='id') def id_(self): """the identifier for the parameter""" return self._id @id_.setter def id_(self, id_): self._id = id_ class Results(UWSElement, HomogeneousList): """ """ def __init__(self, config=None, pos=None, _name='results', **kwargs): HomogeneousList.__init__(self, Result) UWSElement.__init__(self, config, pos, _name, **kwargs) @uwselement(name='result') def results(self): return self @results.adder def results(self, iterator, tag, data, config, pos): result = Result(config, pos, 'result', **data) result.parse(iterator, config) self.append(result) class Result(Reference, UWSElement): """A reference to a UWS result.""" def __init__(self, config=None, pos=None, _name='result', **kwargs): super().__init__(config, pos, _name, **kwargs) self.id_ = kwargs.get('id') self.size = int(kwargs.get('size') or 0) self.mimetype = kwargs.get('mime-type') @xmlattribute(name='id') def id_(self): """the identifier for the result""" return self._id @id_.setter def id_(self, id_): self._id = id_ @xmlattribute def size(self): """the size of the result""" return self._size @size.setter def size(self, size): self._size = size @xmlattribute def mimetype(self): """the mimetype of the result""" return self._mimetype @mimetype.setter def mimetype(self, mimetype): self._mimetype = mimetype class ErrorSummary(UWSElement): """A UWS Error summary.""" def __init__(self, config=None, pos=None, _name='errorSummary', **kwargs): super().__init__(config, pos, _name, **kwargs) self.type_ = kwargs.get('type') self.has_detail = _convert_boolean(kwargs.get('hasDetail')) self.message = None @xmlattribute(name='type') def type_(self): """the type of the error""" return self._type @type_.setter def type_(self, type_): self._type = type_ @xmlattribute def has_detail(self): """whether error has details""" return self._has_detail @has_detail.setter def has_detail(self, has_detail): self._has_detail = has_detail @uwselement(name='message') def message(self): """The error message""" return self._message @message.setter def message(self, message): self._message = message class Message(ContentMixin, UWSElement): """The actual UWS Error message.""" def __init__(self, config=None, pos=None, _name='message', **kwargs): super().__init__(config, pos, _name, **kwargs) pyvo-1.5.2/pyvo/io/vosi/000077500000000000000000000000001462331236700151075ustar00rootroot00000000000000pyvo-1.5.2/pyvo/io/vosi/__init__.py000066400000000000000000000002131462331236700172140ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from .endpoint import parse_tables, parse_capabilities, parse_availability pyvo-1.5.2/pyvo/io/vosi/availability.py000066400000000000000000000034171462331236700201400ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from astropy.utils.collections import HomogeneousList from ...utils.xml.elements import xmlelement, Element from .exceptions import W32, W33, W34, W35 __all__ = ["Availability"] ###################################################################### # FACTORY FUNCTIONS def _convert_boolean(value, default=None): return { 'false': False, '0': False, 'true': True, '1': True }.get(value, default) ###################################################################### # ELEMENT CLASSES class Availability(Element): def __init__(self, config=None, pos=None, _name='availability', **kwargs): super().__init__(config, pos, _name, **kwargs) self._available = None self._upsince = None self._downat = None self._backat = None self._notes = HomogeneousList(str) @xmlelement(plain=True, multiple_exc=W32) def available(self): return self._available @available.setter def available(self, available): self._available = _convert_boolean(available) @xmlelement(name='upSince', plain=True, multiple_exc=W33) def upsince(self): return self._upsince @upsince.setter def upsince(self, upsince): self._upsince = upsince @xmlelement(name='downAt', plain=True, multiple_exc=W34) def downat(self): return self._downat @downat.setter def downat(self, downat): self._downat = downat @xmlelement(name='backAt', plain=True, multiple_exc=W35) def backat(self): return self._backat @backat.setter def backat(self, backat): self._backat = backat @xmlelement(name='note', plain=True) def notes(self): return self._notes pyvo-1.5.2/pyvo/io/vosi/endpoint.py000066400000000000000000000270011462331236700173010ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This file contains a contains the high-level functions to read the various VOSI Endpoints. """ from astropy.utils.xml import iterparser from astropy.utils.collections import HomogeneousList from astropy.io.votable.exceptions import vo_raise, vo_warn from astropy.io.votable.util import version_compare from ...utils.xml.elements import xmlattribute, xmlelement, Element from . import voresource as vr from . import vodataservice as vs from . import availability as av from .exceptions import W15, W16, E07, E10 __all__ = [ "parse_tables", "parse_capabilities", "parse_availability", "TablesFile", "CapabilitiesFile", "AvailabilityFile"] def _pedantic_settings(pedantic): """ Controls the pedantic parser settings. Based on the bool passed in to pedantic, create a config to be passed to astropy parsing to raise exceptions or ignore them on pedantic errors. Parameters ---------- pedantic : bool When `True`, raise an error when the file violates the spec, otherwise issue a warning. Warnings may be controlled using the standard Python mechanisms. See the `warnings` module in the Python standard library for more information. Returns ------- A dict containing 'verify' configuration settings. """ if pedantic: return {'verify': 'exception'} else: return {'verify': 'warn'} def parse_tables(source, pedantic=None, filename=None, _debug_python_based_parser=False): """ Parses a tableset xml file (or file-like object), and returns a `~pyvo.io.vosi.endpoint.TablesFile` object. Parameters ---------- source : str or readable file-like object Path or file object containing a tableset xml file. pedantic : bool, optional When `True`, raise an error when the file violates the spec, otherwise issue a warning. Warnings may be controlled using the standard Python mechanisms. See the `warnings` module in the Python standard library for more information. Defaults to False. filename : str, optional A filename, URL or other identifier to use in error messages. If *filename* is None and *source* is a string (i.e. a path), then *source* will be used as a filename for error messages. Therefore, *filename* is only required when source is a file-like object. Returns ------- tables_file : `~pyvo.io.vosi.endpoint.TablesFile` object See also -------- pyvo.io.vosi.exceptions : The exceptions this function may raise. """ config = _pedantic_settings(pedantic) if filename is None and isinstance(source, str): config['filename'] = source else: config['filename'] = filename with iterparser.get_xml_iterator( source, _debug_python_based_parser=_debug_python_based_parser ) as iterator: return TablesFile( config=config, pos=(1, 1)).parse(iterator, config) def parse_capabilities(source, pedantic=None, filename=None, _debug_python_based_parser=False): """ Parses a capabilities xml file (or file-like object), and returns a `~pyvo.io.vosi.endpoint.CapabilitiesFile` object. Parameters ---------- source : str or readable file-like object Path or file object containing a capabilities xml file. pedantic : bool, optional When `True`, raise an error when the file violates the spec, otherwise issue a warning. Warnings may be controlled using the standard Python mechanisms. See the `warnings` module in the Python standard library for more information. Defaults to False. filename : str, optional A filename, URL or other identifier to use in error messages. If *filename* is None and *source* is a string (i.e. a path), then *source* will be used as a filename for error messages. Therefore, *filename* is only required when source is a file-like object. Returns ------- capabilities_file : `~pyvo.io.vosi.endpoint.CapabilitiesFile` object See also -------- pyvo.io.vosi.exceptions : The exceptions this function may raise. """ config = _pedantic_settings(pedantic) if filename is None and isinstance(source, str): config['filename'] = source else: config['filename'] = filename with iterparser.get_xml_iterator( source, _debug_python_based_parser=_debug_python_based_parser ) as iterator: return CapabilitiesFile( config=config, pos=(1, 1)).parse(iterator, config) def parse_availability(source, pedantic=None, filename=None, _debug_python_based_parser=False): """ Parses a availability xml file (or file-like object), and returns a `~pyvo.io.vosi.endpoint.AvailabilityFile` object. Parameters ---------- source : str or readable file-like object Path or file object containing a availability xml file. pedantic : bool, optional When `True`, raise an error when the file violates the spec, otherwise issue a warning. Warnings may be controlled using the standard Python mechanisms. See the `warnings` module in the Python standard library for more information. Defaults to False. filename : str, optional A filename, URL or other identifier to use in error messages. If *filename* is None and *source* is a string (i.e. a path), then *source* will be used as a filename for error messages. Therefore, *filename* is only required when source is a file-like object. Returns ------- availability_file : `~pyvo.io.vosi.endpoint.AvailabilityFile` object See also -------- pyvo.io.vosi.exceptions : The exceptions this function may raise. """ config = _pedantic_settings(pedantic) if filename is None and isinstance(source, str): config['filename'] = source else: config['filename'] = filename with iterparser.get_xml_iterator( source, _debug_python_based_parser=_debug_python_based_parser ) as iterator: return AvailabilityFile( config=config, pos=(1, 1)).parse(iterator, config) class TablesFile(Element): """ TABLESET/TABLE element: represents an entire file. The keyword arguments correspond to setting members of the same name, documented below. """ def __init__(self, config=None, pos=None, version="1.1"): Element.__init__(self, config, pos) self._tableset = None self._table = None self._ntables = None version = str(version) if version not in ("1.0", "1.1"): raise ValueError("'version' should be one of '1.0' or '1.1'") config['version'] = version self._version = version def __repr__(self): if self.table: return repr(self.table) elif self.tableset: return repr(self.tableset) else: return super().__repr__() @xmlattribute def version(self): """ The version of the TableSet specification that the file uses. """ return self._version @version.setter def version(self, version): version = str(version) if version not in ('1.0', '1.1'): raise ValueError( "pyvo.io.vosi.tables only supports VOSI versions 1.0 and 1.1") self._version = version @xmlelement def tableset(self): """ The tableset. Must be a `TableSet` object. """ return self._tableset @tableset.setter def tableset(self, tableset): self._tableset = tableset @tableset.adder def tableset(self, iterator, tag, data, config, pos): tableset = vs.TableSet(config, pos, 'tableset', **data) tableset.parse(iterator, config) self._tableset = tableset @xmlelement(cls=vs.VODataServiceTable) def table(self): """ The `VODataServiceTable` root element if present. """ return self._table @table.setter def table(self, table): self._table = table @property def ntables(self): """ The number of tables in the file. """ return self._ntables def parse(self, iterator, config): super().parse(iterator, config) if self.tableset is None and self.table is None: vo_raise(E07, config=config, pos=self._pos) self._version = config['version'] if config['version'] not in ('1.0', '1.1'): vo_warn(W15, config=config, pos=self._pos) if self.table: if version_compare(config['version'], '1.1') < 0: vo_warn(W16, config=config, pos=self._pos) self._ntables = 1 else: self._ntables = sum( len(schema.tables) for schema in self.tableset.schemas) return self def iter_tables(self): """ Iterates over all tables in the VOSITables file in a "flat" way, ignoring the schemas. """ if self.table: yield self.table else: for schema in self.tableset.schemas: yield from schema.tables def get_first_table(self): """ When you parse table metadata for a single table here is only one table in the file, and that's all you need. This method returns that first table. """ for table in self.iter_tables(): return table raise IndexError("No table found in VOSITables file.") def get_table_by_name(self, name): """ Looks up a table element by the given name. """ for table in self.iter_tables(): if table.name == name: return table raise KeyError("No table with name {} found".format(name)) class CapabilitiesFile(Element, HomogeneousList): """ capabilities element: represents an entire file. The keyword arguments correspond to setting members of the same name, documented below. """ def __init__(self, config=None, pos=None, _name='capabilities', **kwargs): Element.__init__(self, config=config, pos=pos, **kwargs) HomogeneousList.__init__(self, vr.Capability) @xmlelement(name='capability') def capabilities(self): """List of `~pyvo.io.vosi.voresource.Capability` objects""" return self @capabilities.adder def capabilities(self, iterator, tag, data, config, pos): capability = vr.Capability(config, pos, 'capability', **data) capability.parse(iterator, config) self.append(capability) def parse(self, iterator, config): for start, tag, data, pos in iterator: if start: if tag == "xml": pass elif tag == "capabilities": break else: vo_raise(E10, config=config, pos=pos) super().parse(iterator, config) return self class AvailabilityFile(av.Availability): """ availability element: represents an entire file. The keyword arguments correspond to setting members of the same name, documented below. """ def parse(self, iterator, config): for start, tag, data, pos in iterator: if start: if tag == 'xml': pass elif tag == 'availability': break super().parse(iterator, config) return self pyvo-1.5.2/pyvo/io/vosi/exceptions.py000066400000000000000000000341241462331236700176460ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst # -*- coding: utf-8 -*- """ .. _warnings: Warnings -------- .. note:: Most of the following warnings indicate violations of the VOSI specification. They should be reported to the authors of the tools that produced the VOSI file. To control the warnings emitted, use the standard Python :mod:`warnings` module. Most of these are of the type `VOSISpecWarning`. {warnings} .. _exceptions: Exceptions ---------- .. note:: This is a list of many of the fatal exceptions emitted by vosi.endpoint when the file does not conform to spec. Other exceptions may be raised due to unforeseen cases or bugs in vosi.endpoint itself. {exceptions} """ from astropy.utils.exceptions import AstropyWarning from ...utils.xml.exceptions import XMLWarning __all__ = ["VOSIWarning"] __all__ += ["W{:0>2}".format(i) for i in range(1, 36)] __all__ += ["E{:0>2}".format(i) for i in range(1, 10)] class VOSIWarning(AstropyWarning): """ The base class of all VOSI warnings and exceptions. Handles the formatting of the message with a warning or exception code, filename, line and column number. """ class W01(VOSIWarning, XMLWarning): """ The attribute must be a valid URI as defined in `RFC 2396 `_. """ message_template = "'{}' is not a valid URI" default_args = ('x',) class W02(VOSIWarning, XMLWarning): """ The attribute must be any of the accepted types in the VOSI spec. """ message_template = ( "'{}' is not a valid datatype according to the VOSI spec") default_args = ('x',) class W03(VOSIWarning, XMLWarning): """ The attribute must be an positive integer. """ message_template = "Size must be positive" class W04(VOSIWarning, XMLWarning): """ The attribute must have one of the recognized values 'indexed', 'primary', 'nullable'. """ message_template = "'{}' is not a recognized flag" default_args = ('x',) class W05(VOSIWarning, XMLWarning): """ A ``name`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one name element" default_args = ('x',) class W06(VOSIWarning, XMLWarning): """ A ``description`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one description element" default_args = ('x',) class W07(VOSIWarning, XMLWarning): """ A ``unit`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one unit element" default_args = ('x',) class W08(VOSIWarning, XMLWarning): """ A ``ucd`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one ucd element" default_args = ('x',) class W09(VOSIWarning, XMLWarning): """ A ``utype`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one utype element" default_args = ('x',) class W10(VOSIWarning, XMLWarning): """ A ``fromColumn`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one fromColumn element" default_args = ('x',) class W11(VOSIWarning, XMLWarning): """ A ``targetColumn`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one targetColumn element" default_args = ('x',) class W12(VOSIWarning, XMLWarning): """ A ``targetTable`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one targetTable element" default_args = ('x',) class W13(VOSIWarning, XMLWarning): """ A ``title`` element can only appear once within its parent element. According to the schema, it may only occur once (`1.1 `__, """ message_template = "{} element contains more than one title element" default_args = ('x',) class W14(VOSIWarning, XMLWarning): """ The tableset element must contain at least one schema element. """ message_template = ( "tableset element must contain at least one schema element.") class W15(VOSIWarning, XMLWarning): """ Unknown issues may arise using ``dal`` with VOSITables files from a version other than 1.0 or 1.1 """ message_template = ( 'pyvo.dal is designed for VOSITables version 1.0, and 1.1, but ' + 'this file is {}') default_args = ('x',) class W16(VOSIWarning, XMLWarning): """ The table element is not a valid root element in VOSI before version 1.1 """ message_template = ( "The element table is not a valid root element in VOSI below v1.1") class W17(VOSIWarning, XMLWarning): """ A ``queryType`` element can only appear once within the ParamHTTP element. According to the schema, it may only occur once (`1.1 `__, """ message_template = ( "ParamHTTP element contains more than one ParamHTTP element") class W18(VOSIWarning, XMLWarning): """ The QueryType element must not occur more than two times. """ message_template = ( "The QueryType element must not occur more than two times.") class W19(VOSIWarning, XMLWarning): """ TAP Capabilities must not have an ivo-id other than ivo://ivoa.net/std/TAP """ message_template = ( "TAP Capabilities must not have an ivo-id other than " "ivo://ivoa.net/std/TAP" ) class W20(VOSIWarning, XMLWarning): """ TAP Capabilties must have at least one `language` element. """ message_template = ( "TAP Capabilties must have at least one `language` element.") class W21(VOSIWarning, XMLWarning): """ TAP Capabilties must have at least one outputFormat element. """ message_template = ( "TAP Capabilties must have at least one `outputFormat` element.") class W22(VOSIWarning, XMLWarning): """ The `retentionPeriod` element must not occur more than once. """ message_template = ( "The retentionPeriod element must not occur more than once") class W23(VOSIWarning, XMLWarning): """ The `executionDuration` element must not occur more than once. """ message_template = ( "The executionDuration element must not occur more than once") class W24(VOSIWarning, XMLWarning): """ The `outputLimit` element must not occur more than once. """ message_template = ( "The outputLimit element must not occur more than once") class W25(VOSIWarning, XMLWarning): """ The `uploadLimit` element must not occur more than once. """ message_template = ( "The uploadLimit element must not occur more than once") class W26(VOSIWarning, XMLWarning): """ The ivo-id attribute is mandatory. """ message_template = "The ivo-id attribute is mandatory" class W27(VOSIWarning, XMLWarning): """ The `form` element must not occur more than once. """ message_template = "The form element must not occur more than once" class W28(VOSIWarning, XMLWarning): """ The `mime` element must not occur more than once. """ message_template = "The mime element must not occur more than once" class W29(VOSIWarning, XMLWarning): """ The `default` element must not occur more than once. """ message_template = "The default element must not occur more than once" class W30(VOSIWarning, XMLWarning): """ The `hard` element must not occur more than once. """ message_template = "The hard element must not occur more than once" class W31(VOSIWarning, XMLWarning): """ The content of the `DataLimit` element must be byte or row """ message_template = ( "The content of the DataLimit element must be byte or row") class W32(VOSIWarning, XMLWarning): """ The `available` element must not occur more than once. """ message_template = "The available element must not occur more than once" class W33(VOSIWarning, XMLWarning): """ The `upSince` element must not occur more than once. """ message_template = "The upSince element must not occur more than once" class W34(VOSIWarning, XMLWarning): """ The `downAt` element must not occur more than once. """ message_template = "The downAt element must not occur more than once" class W35(VOSIWarning, XMLWarning): """ The `backAt` element must not occur more than once. """ message_template = "The backAt element must not occur more than once" class W36(VOSIWarning, XMLWarning): """ The `resultType` element must not occur more than once. """ message_template = "The resultType element must not occur more than once" class W37(VOSIWarning, XMLWarning): """ The `dataType` element must not occur more than once. """ message_template = "The dataType element must not occur more than once" class E01(VOSIWarning, XMLWarning, ValueError): r""" The attribute must be a valid arraysize according to the VOTable standard. From the VOTable 1.2 spec: A table cell can contain an array of a given primitive type, with a fixed or variable number of elements; the array may even be multidimensional. For instance, the position of a point in a 3D space can be defined by the following:: and each cell corresponding to that definition must contain exactly 3 numbers. An asterisk (\*) may be appended to indicate a variable number of elements in the array, as in:: where it is specified that each cell corresponding to that definition contains 0 to 100 integer numbers. The number may be omitted to specify an unbounded array (in practice up to =~2×10⁹ elements). A table cell can also contain a multidimensional array of a given primitive type. This is specified by a sequence of dimensions separated by the ``x`` character, with the first dimension changing fastest; as in the case of a simple array, the last dimension may be variable in length. As an example, the following definition declares a table cell which may contain a set of up to 10 images, each of 64×64 bytes:: **References**: `1.1 `__, `1.2 `__ """ message_template = "Invalid arraysize attribute '{}'" default_args = ('x',) class E02(VOSIWarning, XMLWarning, ValueError): """ The `FKColumn` element must have a `fromColumn`. """ message_template = "fkColumn element is missing a fromColumn" class E03(VOSIWarning, XMLWarning, ValueError): """ The element must have a `targetColumn`. """ message_template = "The element is missing a targetColumn" class E04(VOSIWarning, XMLWarning, ValueError): """ The element must have a `targetTable`. """ message_template = "The element is missing a targetTable" class E05(VOSIWarning, XMLWarning, ValueError): """ The element must contain at least one `fkColumn`. """ message_template = "The element contains no `fkColumn`" class E06(VOSIWarning, XMLWarning, ValueError): """ The element must have a ``name`` element. """ message_template = "The {} element must have a name element" default_args = ('x',) class E07(VOSIWarning, XMLWarning, ValueError): """ Raised either when the file doesn't appear to be XML, or the root element is not tableset or table. """ message_template = "File does not appear to be a VOSITables file" class E08(VOSIWarning, XMLWarning, ValueError): """ The element must have a ``version`` element. """ message_template = "The {} element must have a version element" default_args = ('x',) class E09(VOSIWarning, XMLWarning, ValueError): """ The element must have a ``form`` element. """ message_template = "The {} element must have a form element" default_args = ('x',) class E10(VOSIWarning, XMLWarning, ValueError): """ Raised when then file doesn't appear to be valid capabilities xml """ message_template = "File does not appear to be a VOSICapabilities file" class VOSIError(Exception): """ Raised for non-XML VOSI errors """ pass pyvo-1.5.2/pyvo/io/vosi/tapregext.py000066400000000000000000000415711462331236700174740ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from astropy.utils.collections import HomogeneousList from textwrap import indent from astropy.io.votable.exceptions import vo_raise, warn_or_raise from ...utils.xml.elements import ( Element, ContentMixin, xmlelement, xmlattribute) from . import voresource as vr from .exceptions import ( W05, W06, W19, W20, W21, W22, W23, W24, W25, W26, W27, W28, W29, W30, W31, E06, E08, E09, VOSIError) __all__ = [ "TAPCapRestriction", "TableAccess", "DataModelType", "Language", "Version", "LanguageFeatureList", "LanguageFeature", "OutputFormat", "UploadMethod", "TimeLimits", "DataLimits", "DataLimit"] INDENT = 4 * " " ###################################################################### # ELEMENT CLASSES class DataModelType(ContentMixin, Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) ivo_id = kwargs.get('ivo-id', None) if ivo_id is None: warn_or_raise(W26, W26, config=config, pos=pos) self.ivo_id = ivo_id def __repr__(self): return '{}'.format( self.ivo_id, self.content) def describe(self): """ Prints out a human readable description """ print("Datamodel {}".format(self.content)) print(indent(self.ivo_id, INDENT)) print() @xmlattribute(name='ivo-id') def ivo_id(self): """The IVORN of the data model.""" return self._ivo_id @ivo_id.setter def ivo_id(self, ivo_id): self._ivo_id = ivo_id class OutputFormat(Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) ivo_id = kwargs.get('ivo-id') self.mime = None self._aliases = HomogeneousList(str) self.ivo_id = ivo_id def __repr__(self): return '{}'.format( self.ivo_id, self.mime) def describe(self): """ Prints out a human readable description """ print('Output format {}'.format(self.mime)) if self.aliases: print(indent('Also available as {}'.format(', '.join(self.aliases)), INDENT)) print() @xmlelement(plain=True, multiple_exc=W28) def mime(self): return self._mime @mime.setter def mime(self, mime): self._mime = mime @xmlelement(name='alias') def aliases(self): return self._aliases class UploadMethod(Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) ivo_id = kwargs.get('ivo-id') self.ivo_id = ivo_id def __repr__(self): return ''.format(self.ivo_id) def describe(self): """ Prints out a human readable description """ print("Upload method supported") print(indent(self.ivo_id, INDENT)) print() @xmlattribute(name='ivo-id') def ivo_id(self): """The IVORN of the upload model.""" return self._ivo_id @ivo_id.setter def ivo_id(self, ivo_id): self._ivo_id = ivo_id class TimeLimits(Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) self._default = None self._hard = None def __repr__(self): return ''.format( self.default, self.hard) @xmlelement(plain=True, multiple_exc=W29) def default(self): return self._default @default.setter def default(self, default): self._default = int(default) @xmlelement(plain=True, multiple_exc=W30) def hard(self): return self._hard @hard.setter def hard(self, hard): self._hard = int(hard) class LanguageFeature(Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) self.form = None self.description = None @xmlelement(plain=True, multiple_exc=W27) def form(self): return self._form @form.setter def form(self, form): self._form = form @xmlelement(plain=True, multiple_exc=W06) def description(self): return self._description @description.setter def description(self, description): self._description = description def parse(self, iterator, config): super().parse(iterator, config) if not self.form: vo_raise(E09, self._element_name, config=config, pos=self._pos) class LanguageFeatureList(Element, HomogeneousList): def __init__( self, config=None, pos=None, _name='languageFeatures', **kwargs ): Element.__init__(self, config, pos, _name, **kwargs) HomogeneousList.__init__(self, LanguageFeature) self.type = kwargs.get('type') self._features = HomogeneousList(LanguageFeature) @xmlattribute def type(self): return self._type @type.setter def type(self, type_): self._type = type_ @xmlelement(name='feature', cls=LanguageFeature) def features(self): return self class Version(ContentMixin, Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) ivo_id = kwargs.get('ivo-id') self.ivo_id = ivo_id def __repr__(self): return '{}'.format( self.ivo_id, self.content) @xmlattribute(name='ivo-id') def ivo_id(self): """The IVORN of the version.""" return self._ivo_id @ivo_id.setter def ivo_id(self, ivo_id): self._ivo_id = ivo_id class Language(Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) self.name = None self._versions = HomogeneousList(Version) self.description = None self._languagefeaturelists = HomogeneousList(LanguageFeatureList) def __repr__(self): return '{}'.format(self.name) def describe(self): """ Prints out a human readable description """ print("Language {}".format(self.name)) for languagefeaturelist in self.languagefeaturelists: print(indent(languagefeaturelist.type, INDENT)) for feature in languagefeaturelist: print(indent(feature.form, 2 * INDENT)) if feature.description: print(indent(feature.description, 3 * INDENT)) print() print() def get_feature_list(self, ivoid): """ returns a list of features groupd with the features id ivoid. Parameters ---------- ivoid : the ivoid of a TAPRegExt feature list. It is compared case-insensitively against the service's ivoids. Returns ------- A (possibly empty) list of `~pyvo.io.vosi.tapregext.LanguageFeature` elements """ ivoid = ivoid.lower() for features in self.languagefeaturelists: if features.type.lower() == ivoid: return features return [] def get_feature(self, ivoid, form): """ returns the `~pyvo.io.vosi.tapregext.LanguageFeature` with ivoid and form if present. We return None rather than raising an error because we expect the normal pattern of usage here will be "if feature is present", and with None-s this is simpler to write than with exceptions. Since it's hard to predict the form of UDFs, for those rather use the get_udf method. ivoid (regrettably) has to be compared case-insensitively; form is compared case-sensitively. Parameters ---------- ivoid : str The IVOA identifier of the feature group the form is in form : str The form of the feature requested Returns ------- A `~pyvo.io.vosi.tapregext.LanguageFeature` or None. """ for feature in self.get_feature_list(ivoid): if feature.form == form: return feature return None def get_udf(self, function_name): """ returns a `~pyvo.io.vosi.tapregext.LanguageFeature` corresponding to an ADQL user defined function on the server, on None if the UDF is not available. This is a bit heuristic in that it tries to parse the form, which is specified only so-so. Parameters ---------- function_name : str A function name. This is matched against the server's function names case-insensitively, as guided by ADQL's case insensitivity. Returns: A `~pyvo.io.vosi.tapregext.LanguageFeature` instance or None. """ function_name = function_name.lower() for udf in self.get_feature_list( "ivo://ivoa.net/std/TAPRegExt#features-udf"): this_name = udf.form.split("(")[0].strip() if this_name.lower() == function_name: return udf return None @xmlelement(plain=True, multiple_exc=W05) def name(self): return self._name @name.setter def name(self, name): self._name = name @xmlelement(name='version', cls=Version) def versions(self): return self._versions @xmlelement(plain=True, multiple_exc=W06) def description(self): return self._description @description.setter def description(self, description): self._description = description @xmlelement(name='languageFeatures', cls=LanguageFeatureList) def languagefeaturelists(self): return self._languagefeaturelists def parse(self, iterator, config): super().parse(iterator, config) if not self.name: vo_raise(E06, self._element_name, config=config, pos=self._pos) if not self.versions: vo_raise(E08, self._element_name, config=config, pos=self._pos) class DataLimit(ContentMixin, Element): def __init__(self, unit=None, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) self.unit = unit @xmlattribute def unit(self): return self._unit @unit.setter def unit(self, unit): self._unit = unit @property def content(self): return self._content @content.setter def content(self, content): self._content = int(content) def parse(self, iterator, config): super().parse(iterator, config) if self.unit not in ('byte', 'row'): warn_or_raise(W31, W31, config=config, pos=self._pos) class DataLimits(Element): def __init__(self, config=None, pos=None, **kwargs): super().__init__(config=config, pos=pos, **kwargs) self.default = None self.hard = None def __repr__(self): return ''.format( self.default.unit, self.default.content, self.hard.unit, self.hard.content ) @xmlelement(cls=DataLimit, multiple_exc=W29) def default(self): return self._default @default.setter def default(self, default): self._default = default @xmlelement(cls=DataLimit, multiple_exc=W30) def hard(self): return self._hard @hard.setter def hard(self, hard): self._hard = hard class TAPCapRestriction(vr.Capability): def __init__( self, config=None, pos=None, _name='capability', standardID=None, **kwargs ): if standardID != 'ivo://ivoa.net/std/TAP': warn_or_raise(W19, W19, config=config, pos=pos) super().__init__( config, pos, _name, standardID='ivo://ivoa.net/std/TAP', **kwargs) @vr.Capability.register_xsi_type('tr:TableAccess') class TableAccess(TAPCapRestriction): def __init__(self, config=None, pos=None, _name='capability', **kwargs): super().__init__(config, pos, _name, **kwargs) self._datamodels = HomogeneousList(DataModelType) self._languages = HomogeneousList(Language) self._outputformats = HomogeneousList(OutputFormat) self._uploadmethods = HomogeneousList(UploadMethod) self.retentionperiod = None self.executionduration = None self.outputlimit = None self.uploadlimit = None def describe(self): """ Prints out a human readable description """ super().describe() for datamodel in self.datamodels: datamodel.describe() for language in self.languages: language.describe() for outputformat in self.outputformats: outputformat.describe() for uploadmethod in self.uploadmethods: uploadmethod.describe() if self.retentionperiod: print("Time a job is kept (in seconds)") print(indent("Default {}".format(self.retentionperiod.default), INDENT)) if self.retentionperiod.hard: print(indent("Maximum {}".format(self.retentionperiod.hard), INDENT)) print() if self.executionduration: print("Maximal run time of a job") print(indent("Default {}".format(self.executionduration.default), INDENT)) if self.executionduration.hard: print(indent("Maximum {}".format(self.executionduration.hard), INDENT)) print() if self.outputlimit: print("Maximum size of resultsets") print(indent("Default {} {}".format( self.outputlimit.default.content, self.outputlimit.default.unit), INDENT) ) if self.outputlimit.hard: print(indent("Maximum {} {}".format( self.outputlimit.hard.content, self.outputlimit.hard.unit), INDENT) ) print() if self.uploadlimit: print("Maximal size of uploads") print(indent("Maximum {} {}".format( self.uploadlimit.hard.content, self.uploadlimit.hard.unit), INDENT)) print() def get_adql(self): """ returns the (first) ADQL language element on this service. ADQL support is mandatory for IVOA TAP, so in general you can rely on this being present. """ for lang in self.languages: if lang.name == "ADQL": return lang raise VOSIError( "Invalid TAP service: Does not declare an ADQL language") @xmlelement(name='dataModel', cls=DataModelType) def datamodels(self): """Identifier of IVOA-approved data model supported by the service.""" return self._datamodels @xmlelement(name='language', cls=Language) def languages(self): """Languages supported by the service.""" return self._languages @xmlelement(name='outputFormat', cls=OutputFormat) def outputformats(self): """Output formats supported by the service.""" return self._outputformats @xmlelement(name='uploadMethod', cls=UploadMethod) def uploadmethods(self): """ Upload methods supported by the service. The absence of upload methods indicates that the service does not support uploads at all. """ return self._uploadmethods @xmlelement(name='retentionPeriod', cls=TimeLimits, multiple_exc=W22) def retentionperiod(self): """Limits on the time between job creation and destruction time.""" return self._retentionperiod @retentionperiod.setter def retentionperiod(self, retentionperiod): self._retentionperiod = retentionperiod @xmlelement(name='executionDuration', cls=TimeLimits, multiple_exc=W23) def executionduration(self): """Limits on executionDuration.""" return self._executionduration @executionduration.setter def executionduration(self, executionduration): self._executionduration = executionduration @xmlelement(name='outputLimit', cls=DataLimits, multiple_exc=W24) def outputlimit(self): """Limits on the size of data returned.""" return self._outputlimit @outputlimit.setter def outputlimit(self, outputlimit): self._outputlimit = outputlimit @xmlelement(name='uploadLimit', cls=DataLimits, multiple_exc=W25) def uploadlimit(self): return self._uploadlimit @uploadlimit.setter def uploadlimit(self, uploadlimit, cls=DataLimits): self._uploadlimit = uploadlimit def parse(self, iterator, config): super().parse(iterator, config) if not self.languages: warn_or_raise(W20, W20, config=config, pos=self._pos) if not self.outputformats: warn_or_raise(W21, W21, config=config, pos=self._pos) pyvo-1.5.2/pyvo/io/vosi/tests/000077500000000000000000000000001462331236700162515ustar00rootroot00000000000000pyvo-1.5.2/pyvo/io/vosi/tests/__init__.py000066400000000000000000000001001462331236700203510ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst pyvo-1.5.2/pyvo/io/vosi/tests/data/000077500000000000000000000000001462331236700171625ustar00rootroot00000000000000pyvo-1.5.2/pyvo/io/vosi/tests/data/availability.xml000066400000000000000000000011451462331236700223570ustar00rootroot00000000000000 true 2000-00-00T00:00:00Z 2666-00-00T00:00:00Z 2666-23-23T13:37:00Z foo bar pyvo-1.5.2/pyvo/io/vosi/tests/data/capabilities.xml000066400000000000000000000077671462331236700223560ustar00rootroot00000000000000 http://example.org/tap/availability https://example.org/tap/availability http://example.org/tap/capabilities https://example.org/tap/capabilities http://example.org/tap/tables https://example.org/tap/tables http://example.org/tap https://example.org/tap https://paris.example.org/tap QUERY=SELECT%20*%20FROM%20tap_schema.tables&LANG=ADQL Obscore-1.1 Registry 1.0 GloTS 1.0 Obscore-1.0 ADQL 2.0 ADQL 2.0
form 1
description 1
form 2
description 2
BOX
POINT
text/xml text/html html 172800 3600 2000 10000000 100000000
pyvo-1.5.2/pyvo/io/vosi/tests/data/capabilities/000077500000000000000000000000001462331236700216135ustar00rootroot00000000000000pyvo-1.5.2/pyvo/io/vosi/tests/data/capabilities/multiple_capa_descriptions.xml000066400000000000000000000022101462331236700277350ustar00rootroot00000000000000 one two pyvo-1.5.2/pyvo/io/vosi/tests/data/tables.xml000066400000000000000000000026561462331236700211670ustar00rootroot00000000000000 test This is a unittest schema test.allTest tableAll test data in one tableutype30 id Primary key unit meta.id;meta.main utype VARCHAR indexed primary test.foreigntable testkey testkey Test foreigner utype
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/000077500000000000000000000000001462331236700204345ustar00rootroot00000000000000pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/datatypes_tap.xml000066400000000000000000000107421462331236700240240ustar00rootroot00000000000000 test test.tap _boolean BOOLEAN _smallint SMALLINT _integer INTEGER _bigint BIGINT _real REAL _double DOUBLE _timestamp TIMESTAMP _char CHAR _varchar VARCHAR _binary BINARY _varbinary VARBINARY _point POINT _region REGION _clob CLOB _blob BLOB
test.taptype _boolean BOOLEAN _smallint SMALLINT _integer INTEGER _bigint BIGINT _real REAL _double DOUBLE _timestamp TIMESTAMP _char CHAR _varchar VARCHAR _binary BINARY _varbinary VARBINARY _point POINT _region REGION _clob CLOB _blob BLOB
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/datatypes_votable.xml000066400000000000000000000075501462331236700246770ustar00rootroot00000000000000 test test.votable _boolean boolean _bit bit _unsignedBytes unsignedByte _short short _int int _long long _char char _unicodeChar unicodeChar _float float _double double _floatComplex floatComplex _doubleComplex doubleComplex
test.votable _boolean boolean _bit bit _unsignedBytes unsignedByte _short short _int int _long long _char char _unicodeChar unicodeChar _float float _double double _floatComplex floatComplex _doubleComplex doubleComplex
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_column_datatypes.xml000066400000000000000000000014141462331236700264440ustar00rootroot00000000000000 test test datatype int INTEGER
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_column_descriptions.xml000066400000000000000000000013471462331236700271610ustar00rootroot00000000000000 test ucd test one two
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_column_names.xml000066400000000000000000000012621462331236700255520ustar00rootroot00000000000000 test test one two
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_column_ucds.xml000066400000000000000000000013071462331236700254050ustar00rootroot00000000000000 test ucd test one two
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_column_units.xml000066400000000000000000000013141462331236700256070ustar00rootroot00000000000000 test test unit one two
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_column_utypes.xml000066400000000000000000000013211462331236700257740ustar00rootroot00000000000000 test test utype one two
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_foreignkey_descriptions.xml000066400000000000000000000020111462331236700300130ustar00rootroot00000000000000 test test foreigntable fromcolumn targetcolumn desc1 desc2 fromcolumn int
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_foreignkey_utypes.xml000066400000000000000000000017631462331236700266530ustar00rootroot00000000000000 test test foreigntable fromcolumn targetcolumn utype1 utype2 fromcolumn int
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_fromcolumns.xml000066400000000000000000000017451462331236700254440ustar00rootroot00000000000000 test test foreigntable fromcolumn fromcolumn targetcolumn fromcolumn int
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_schema_descriptions.xml000066400000000000000000000012011462331236700271110ustar00rootroot00000000000000 descriptiontest one two pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_schema_names.xml000066400000000000000000000011041462331236700255100ustar00rootroot00000000000000 one two pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_schema_titles.xml000066400000000000000000000011431462331236700257140ustar00rootroot00000000000000 titletest one two pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_schema_utypes.xml000066400000000000000000000011431462331236700257410ustar00rootroot00000000000000 utypetest one two pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_table_descriptions.xml000066400000000000000000000012621462331236700267470ustar00rootroot00000000000000 test descriptiononetwo
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_table_names.xml000066400000000000000000000011671462331236700253500ustar00rootroot00000000000000 test onetwo
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_table_titles.xml000066400000000000000000000012241462331236700255430ustar00rootroot00000000000000 test titleonetwo
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_table_utypes.xml000066400000000000000000000012241462331236700255700ustar00rootroot00000000000000 test utypeonetwo
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_targetcolumns.xml000066400000000000000000000017531462331236700257660ustar00rootroot00000000000000 test test foreigntable fromcolumn targetcolumn targetcolumn fromcolumn int
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/multiple_targettables.xml000066400000000000000000000017471462331236700255630ustar00rootroot00000000000000 test test foreigntable foreigntable fromcolumn targetcolumn fromcolumn int
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/no_fromcolumn.xml000066400000000000000000000015141462331236700240340ustar00rootroot00000000000000 test test foreigntable fromcolumn int
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/no_schema_name.xml000066400000000000000000000010321462331236700241060ustar00rootroot00000000000000 pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/no_schemas.xml000066400000000000000000000010031462331236700232670ustar00rootroot00000000000000 pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/no_table_description.xml000066400000000000000000000011501462331236700253410ustar00rootroot00000000000000 test description
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/no_table_name.xml000066400000000000000000000011041462331236700237350ustar00rootroot00000000000000 test
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/no_targetcolumn.xml000066400000000000000000000016031462331236700243560ustar00rootroot00000000000000 test test foreigntable fromcolumn fromcolumn int
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/single_table_description.xml000066400000000000000000000012601462331236700262100ustar00rootroot00000000000000 test descriptionA test table with a single description
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/sizenegative.xml000066400000000000000000000013621462331236700236550ustar00rootroot00000000000000 test test.sizezero ZERO INTEGER
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/wrong_arraysize.xml000066400000000000000000000014071462331236700244050ustar00rootroot00000000000000 test test.wrong_arraysize wrongarraysize INTEGER
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/wrong_datatypes_tap.xml000066400000000000000000000013501462331236700252330ustar00rootroot00000000000000 test test.wrong_tap WRONG WRONG
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/wrong_datatypes_votable.xml000066400000000000000000000013601462331236700261040ustar00rootroot00000000000000 test test.wrong_votable wrong wrong
pyvo-1.5.2/pyvo/io/vosi/tests/data/tables/wrong_flag.xml000066400000000000000000000014241462331236700233040ustar00rootroot00000000000000 test test.wrong_flag wrongflag INTEGER prmary
pyvo-1.5.2/pyvo/io/vosi/tests/test_availability.py000066400000000000000000000012431462331236700223340ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.io.vosi """ import pyvo.io.vosi as vosi from astropy.utils.data import get_pkg_data_filename class TestAvailability: def test_availability(self): availability = vosi.parse_availability(get_pkg_data_filename( "data/availability.xml")) assert availability.available assert availability.upsince == "2000-00-00T00:00:00Z" assert availability.downat == "2666-00-00T00:00:00Z" assert availability.backat == "2666-23-23T13:37:00Z" assert "foo" in availability.notes assert "bar" in availability.notes pyvo-1.5.2/pyvo/io/vosi/tests/test_capabilities.py000066400000000000000000000200221462331236700223070ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.io.vosi """ import io from operator import eq as equals import pytest import pyvo.io.vosi as vosi import pyvo.io.vosi.vodataservice as vs import pyvo.io.vosi.tapregext as tr from pyvo.io.vosi.exceptions import W06 from astropy.utils.data import get_pkg_data_filename @pytest.fixture(name='parsed_caps') def _parsed_caps(): return vosi.parse_capabilities(get_pkg_data_filename( "data/capabilities.xml")) @pytest.mark.usefixtures("parsed_caps") class TestCapabilities: def test_availability(self, parsed_caps): assert equals( parsed_caps[0].standardid, "ivo://ivoa.net/std/VOSI#availability") assert isinstance(parsed_caps[0].interfaces[0], vs.ParamHTTP) assert parsed_caps[0].interfaces[0].accessurls[0].use == "full" assert equals( parsed_caps[0].interfaces[0].accessurls[0].content, "http://example.org/tap/availability") def test_capendpoint(self, parsed_caps): assert equals( parsed_caps[1].standardid, "ivo://ivoa.net/std/VOSI#capabilities") assert isinstance(parsed_caps[1].interfaces[0], vs.ParamHTTP) assert parsed_caps[1].interfaces[0].accessurls[0].use == "full" assert equals( parsed_caps[1].interfaces[0].accessurls[0].content, "http://example.org/tap/capabilities") def test_tablesendpoint(self, parsed_caps): assert parsed_caps[2].standardid == "ivo://ivoa.net/std/VOSI#tables" assert isinstance(parsed_caps[2].interfaces[0], vs.ParamHTTP) assert parsed_caps[2].interfaces[0].accessurls[0].use == "full" assert equals( parsed_caps[2].interfaces[0].accessurls[0].content, "http://example.org/tap/tables") def test_type_parsed(self, parsed_caps): assert isinstance(parsed_caps[3], tr.TableAccess) def test_stdid_parsed(self, parsed_caps): assert parsed_caps[3].standardid == "ivo://ivoa.net/std/TAP" def test_dm_parsed(self, parsed_caps): assert equals( parsed_caps[3].datamodels[0].ivo_id, "ivo://ivoa.net/std/ObsCore#table-1.1") assert parsed_caps[3].datamodels[0].content == "Obscore-1.1" assert equals( parsed_caps[3].datamodels[1].ivo_id, "ivo://ivoa.net/std/RegTAP#1.0") assert parsed_caps[3].datamodels[1].content == "Registry 1.0" def test_language_parsed(self, parsed_caps): assert parsed_caps[3].languages[0].name == "ADQL" assert equals( parsed_caps[3].languages[0].versions[0].ivo_id, "ivo://ivoa.net/std/ADQL#v2.0") assert parsed_caps[3].languages[0].versions[0].content == "2.0" assert parsed_caps[3].languages[0].description == "ADQL 2.0" def test_udfs(self, parsed_caps): assert equals( parsed_caps[3].languages[0].languagefeaturelists[0].type, "ivo://ivoa.net/std/TAPRegExt#features-udf") assert equals( parsed_caps[3].languages[0].languagefeaturelists[0][0].form, "form 1") assert equals( parsed_caps[3].languages[0].languagefeaturelists[0][ 0].description, "description 1") assert equals( parsed_caps[3].languages[0].languagefeaturelists[0][1].form, "form 2") assert equals( parsed_caps[3].languages[0].languagefeaturelists[0].features[ 1].description, "description 2") def test_adqlgeos(self, parsed_caps): assert equals( parsed_caps[3].languages[0].languagefeaturelists[1].type, "ivo://ivoa.net/std/TAPRegExt#features-adqlgeo") assert equals( parsed_caps[3].languages[0].languagefeaturelists[1].features[ 0].form, "BOX") assert equals( parsed_caps[3].languages[0].languagefeaturelists[1].features[ 1].form, "POINT") def test_outputformats(self, parsed_caps): assert equals( parsed_caps[3].outputformats[0].ivo_id, "ivo://ivoa.net/std/TAPRegExt#output-votable-binary") assert parsed_caps[3].outputformats[0].mime == "text/xml" assert parsed_caps[3].outputformats[1].ivo_id is None assert parsed_caps[3].outputformats[1].mime == "text/html" def test_uploadmethods(self, parsed_caps): assert equals( parsed_caps[3].uploadmethods[0].ivo_id, "ivo://ivoa.net/std/TAPRegExt#upload-https") assert equals( parsed_caps[3].uploadmethods[1].ivo_id, "ivo://ivoa.net/std/TAPRegExt#upload-inline") def test_temporal_limits(self, parsed_caps): assert parsed_caps[3].retentionperiod.default == 172800 assert parsed_caps[3].executionduration.default == 3600 def test_spatial_limits(self, parsed_caps): assert parsed_caps[3].outputlimit.default.unit == "row" assert parsed_caps[3].outputlimit.default.content == 2000 assert parsed_caps[3].outputlimit.hard.unit == "row" assert parsed_caps[3].outputlimit.hard.content == 10000000 assert parsed_caps[3].uploadlimit.hard.unit == "byte" assert parsed_caps[3].uploadlimit.hard.content == 100000000 def test_multiple_capa_descriptions(self): with pytest.warns(W06): vosi.parse_capabilities(get_pkg_data_filename( 'data/capabilities/multiple_capa_descriptions.xml')) with pytest.raises(W06): vosi.parse_capabilities(get_pkg_data_filename( 'data/capabilities/multiple_capa_descriptions.xml'), pedantic=True) @pytest.mark.usefixtures("parsed_caps") class TestInterface: def test_interface_parsed(self, parsed_caps): assert parsed_caps[3].interfaces[0].accessurls[0].use == "base" assert equals( parsed_caps[3].interfaces[0].accessurls[0].content, "http://example.org/tap") def test_mirrors_parsed(self, parsed_caps): assert len(parsed_caps[3].interfaces[0].mirrorurls) == 2 def test_mirrors_have_titles(self, parsed_caps): assert ([m.title for m in parsed_caps[3].interfaces[0].mirrorurls] == ["https version", "Paris mirror"]) def test_mirrors_have_urls(self, parsed_caps): assert ([m.content for m in parsed_caps[3].interfaces[0].mirrorurls] == ['https://example.org/tap', 'https://paris.example.org/tap']) def test_testquerystring_parsed(self, parsed_caps): assert (parsed_caps[3].interfaces[0].testquerystring.content == 'QUERY=SELECT%20*%20FROM%20tap_schema.tables&LANG=ADQL') @pytest.fixture(name='cap_with_free_prefix') def _cap_with_free_prefix(recwarn): caps = vosi.parse_capabilities(io.BytesIO(b""" https://archive.eso.org/tap_obs ObsCore-1.1 ADQL 2.0 ADQL-2.0 """)) return recwarn, caps # this is a test for when people ignore the canonical prefixes for # registry documents. class TestFreePrefixes: def test_parses_without_warning(self, cap_with_free_prefix): warnings, _ = cap_with_free_prefix assert len(warnings) == 0 def test_parses_as_tapregext(self, cap_with_free_prefix): _, cap = cap_with_free_prefix assert isinstance(cap[0], tr.TableAccess) pyvo-1.5.2/pyvo/io/vosi/tests/test_tables.py000066400000000000000000000344551462331236700211470ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.io.vosi """ import contextlib import io import pytest import pyvo.io.vosi as vosi import pyvo.io.vosi.vodataservice as vs from pyvo.io.vosi.exceptions import ( W02, W03, W04, W05, W06, W07, W08, W09, W10, W11, W12, W13, W14, W37) from pyvo.io.vosi.exceptions import E01, E02, E03, E06 from astropy.utils.data import get_pkg_data_filename class TestTables: def test_all(self): tablesfile = vosi.parse_tables( get_pkg_data_filename("data/tables.xml")) table = next(tablesfile.iter_tables()) assert table.name == "test.all" assert table.title == "Test table" assert table.description == "All test data in one table" assert table.utype == "utype" assert table.nrows == 30 col = table.columns[0] fkc = table.foreignkeys[0] assert col.name == "id" assert col.description == "Primary key" assert col.unit == "unit" assert col.ucd == "meta.id;meta.main" assert col.utype == "utype" assert isinstance(col.datatype, vs.TAPType) assert str(col.datatype) == "VARCHAR" assert col.datatype.arraysize == "*" assert col.datatype.delim == ";" assert col.datatype.size == "42" assert col.datatype.content == "VARCHAR" assert "indexed" in col.flags assert "primary" in col.flags assert fkc.targettable == "test.foreigntable" assert fkc.fkcolumns[0].fromcolumn == "testkey" assert fkc.fkcolumns[0].targetcolumn == "testkey" assert fkc.description == "Test foreigner" assert fkc.utype == "utype" def _test_datatypes_votable(self, cols): assert cols[0].datatype.content == 'boolean' assert cols[1].datatype.content == 'bit' assert cols[2].datatype.content == 'unsignedByte' assert cols[3].datatype.content == 'short' assert cols[4].datatype.content == 'int' assert cols[5].datatype.content == 'long' assert cols[6].datatype.content == 'char' assert cols[7].datatype.content == 'unicodeChar' assert cols[8].datatype.content == 'float' assert cols[9].datatype.content == 'double' assert cols[10].datatype.content == 'floatComplex' assert cols[11].datatype.content == 'doubleComplex' def test_datatypes_votable(self): tablesfile = vosi.parse_tables( get_pkg_data_filename("data/tables/datatypes_votable.xml")) votable, votabletype = tuple(tablesfile.iter_tables()) self._test_datatypes_votable(votable.columns) self._test_datatypes_votable(votabletype.columns) def _test_datatypes_tap(self, cols): assert cols[0].datatype.content == 'BOOLEAN' assert cols[1].datatype.content == 'SMALLINT' assert cols[2].datatype.content == 'INTEGER' assert cols[3].datatype.content == 'BIGINT' assert cols[4].datatype.content == 'REAL' assert cols[5].datatype.content == 'DOUBLE' assert cols[6].datatype.content == 'TIMESTAMP' assert cols[7].datatype.content == 'CHAR' assert cols[8].datatype.content == 'VARCHAR' assert cols[9].datatype.content == 'BINARY' assert cols[10].datatype.content == 'VARBINARY' assert cols[11].datatype.content == 'POINT' assert cols[12].datatype.content == 'REGION' assert cols[13].datatype.content == 'CLOB' assert cols[14].datatype.content == 'BLOB' def test_datatypes_tap(self): tablesfile = vosi.parse_tables( get_pkg_data_filename("data/tables/datatypes_tap.xml")) tap, taptype = tuple(tablesfile.iter_tables()) self._test_datatypes_tap(tap.columns) self._test_datatypes_tap(taptype.columns) def test_wrong_datatypes_tap(self): with pytest.warns(W02): vosi.parse_tables( get_pkg_data_filename("data/tables/wrong_datatypes_tap.xml")) def test_wrong_datatypes_votable(self): with pytest.warns(W02): vosi.parse_tables(get_pkg_data_filename( "data/tables/wrong_datatypes_votable.xml")) def test_no_schemas(self): with pytest.warns(W14): vosi.parse_tables( get_pkg_data_filename("data/tables/no_schemas.xml")) with pytest.raises(W14): vosi.parse_tables( get_pkg_data_filename("data/tables/no_schemas.xml"), pedantic=True) def test_no_schema_name(self): with pytest.raises(E06): vosi.parse_tables( get_pkg_data_filename("data/tables/no_schema_name.xml")) def test_multiple_schema_names(self): with pytest.warns(W05): vosi.parse_tables( get_pkg_data_filename("data/tables/multiple_schema_names.xml")) with pytest.raises(W05): vosi.parse_tables( get_pkg_data_filename("data/tables/multiple_schema_names.xml"), pedantic=True) def test_multiple_schema_titles(self): with pytest.warns(W13): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_schema_titles.xml")) with pytest.raises(W13): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_schema_titles.xml"), pedantic=True) def test_multiple_schema_descriptions(self): with pytest.warns(W06): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_schema_descriptions.xml")) with pytest.raises(W06): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_schema_descriptions.xml"), pedantic=True) def test_multiple_schema_utypes(self): with pytest.warns(W09): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_schema_utypes.xml")) with pytest.raises(W09): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_schema_utypes.xml"), pedantic=True) def test_no_table_name(self): with pytest.raises(E06): vosi.parse_tables( get_pkg_data_filename("data/tables/no_table_name.xml")) def test_multiple_table_names(self): with pytest.warns(W05): vosi.parse_tables( get_pkg_data_filename("data/tables/multiple_table_names.xml")) with pytest.raises(W05): vosi.parse_tables( get_pkg_data_filename("data/tables/multiple_table_names.xml"), pedantic=True) def test_multiple_table_titles(self): with pytest.warns(W13): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_table_titles.xml")) with pytest.raises(W13): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_table_titles.xml"), pedantic=True) def test_multiple_table_descriptions(self): with pytest.warns(W06): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_table_descriptions.xml")) with pytest.raises(W06): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_table_descriptions.xml"), pedantic=True) def test_multiple_table_utypes(self): with pytest.warns(W09): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_table_utypes.xml")) with pytest.raises(W09): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_table_utypes.xml"), pedantic=True) def test_multiple_column_names(self): with pytest.warns(W05): vosi.parse_tables( get_pkg_data_filename("data/tables/multiple_column_names.xml")) with pytest.raises(W05): vosi.parse_tables( get_pkg_data_filename("data/tables/multiple_column_names.xml"), pedantic=True) def test_multiple_column_descriptions(self): with pytest.warns(W06): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_column_descriptions.xml")) with pytest.raises(W06): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_column_descriptions.xml"), pedantic=True) def test_multiple_column_units(self): with pytest.warns(W07): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_column_units.xml")) with pytest.raises(W07): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_column_units.xml"), pedantic=True) def test_multiple_column_ucds(self): with pytest.warns(W08): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_column_ucds.xml")) with pytest.raises(W08): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_column_ucds.xml"), pedantic=True) def test_multiple_column_utypes(self): with pytest.warns(W09): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_column_utypes.xml")) with pytest.raises(W09): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_column_utypes.xml"), pedantic=True) def test_multiple_column_datatypes(self): with pytest.warns(W37): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_column_datatypes.xml")) with pytest.raises(W37): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_column_datatypes.xml"), pedantic=True) def test_tap_size(self): with pytest.warns(W03): vosi.parse_tables(get_pkg_data_filename( "data/tables/sizenegative.xml")) with pytest.raises(W03): vosi.parse_tables(get_pkg_data_filename( "data/tables/sizenegative.xml"), pedantic=True) @pytest.mark.xfail def test_wrong_flag(self): with pytest.warns(W04): vosi.parse_tables(get_pkg_data_filename( "data/tables/wrong_flag.xml")) with pytest.raises(W04): vosi.parse_tables(get_pkg_data_filename( "data/tables/wrong_flag.xml"), pedantic=True) def test_multiple_fromcolumns(self): with pytest.warns(W10): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_fromcolumns.xml")) with pytest.raises(W10): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_fromcolumns.xml"), pedantic=True) def test_missing_fromcolumn(self): with pytest.raises(E02): vosi.parse_tables(get_pkg_data_filename( "data/tables/no_fromcolumn.xml")) def test_multiple_targetcolumns(self): with pytest.warns(W11): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_targetcolumns.xml")) with pytest.raises(W11): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_targetcolumns.xml"), pedantic=True) def test_missing_targetcolumn(self): with pytest.raises(E03): vosi.parse_tables(get_pkg_data_filename( "data/tables/no_targetcolumn.xml")) def test_multiple_targettables(self): with pytest.warns(W12): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_targettables.xml")) with pytest.raises(W12): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_targettables.xml"), pedantic=True) def test_multiple_foreignkey_descriptions(self): with pytest.warns(W06): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_foreignkey_descriptions.xml")) with pytest.raises(W06): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_foreignkey_descriptions.xml"), pedantic=True) def test_multiple_foreignkey_utypes(self): with pytest.warns(W09): vosi.parse_tables(get_pkg_data_filename( "data/tables/multiple_foreignkey_utypes.xml")) with pytest.raises(W09): vosi.parse_tables( get_pkg_data_filename( "data/tables/multiple_foreignkey_utypes.xml"), pedantic=True) def test_wrong_arraysize(self): with pytest.raises(E01): vosi.parse_tables( get_pkg_data_filename( "data/tables/wrong_arraysize.xml")) def test_no_table_description(self): """Test handling of describing tables with no description """ tableset = vosi.parse_tables( get_pkg_data_filename( "data/tables/no_table_description.xml")) nodesc_table = tableset.get_first_table() assert nodesc_table.description is None with io.StringIO() as buf, contextlib.redirect_stdout(buf): nodesc_table.describe() output = buf.getvalue() assert 'No description' in output def test_single_table_description(self): """Test describing a table with a single description """ tableset = vosi.parse_tables( get_pkg_data_filename( "data/tables/single_table_description.xml")) onedesc_table = tableset.get_first_table() describe_string = 'A test table with a single description' assert describe_string in onedesc_table.description with io.StringIO() as buf, contextlib.redirect_stdout(buf): onedesc_table.describe() output = buf.getvalue() assert describe_string in output pyvo-1.5.2/pyvo/io/vosi/vodataservice.py000066400000000000000000000752761462331236700203410ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This file contains xml element classes as defined in the VODataService standard There are different ways of handling the various xml tags. * Elements with complex content * Elements with simple content and attributes * Elements with simple content without attributes Elements with complex content are parsed with objects inherited from `~pyvo.utils.xml.elements.Element`. Elements with simple content are parsed with objects inherited from `~pyvo.utils.xml.elements.Element` defining a ``value`` property. """ import re from astropy.utils import deprecated from astropy.utils.collections import HomogeneousList from textwrap import indent from astropy.utils.xml import check as xml_check from astropy.io.votable.exceptions import vo_raise, vo_warn, warn_or_raise from ...utils.xml.elements import ( xmlattribute, xmlelement, Element, ElementWithXSIType, ContentMixin) from . import voresource as vr from .exceptions import ( W01, W02, W03, W04, W05, W06, W07, W08, W09, W10, W11, W12, W13, W14, W17, W18, W36, W37, E01, E02, E03, E04, E05, E06) __all__ = [ "TableSet", "TableSchema", "ParamHTTP", "VODataServiceTable", "BaseParam", "TableParam", "InputParam", "DataType", "SimpleDataType", "TableDataType", "VOTableType", "TAPDataType", "TAPType", "FKColumn", "ForeignKey"] ###################################################################### # FACTORY FUNCTIONS def _convert_boolean(value, default=None): return { 'false': False, '0': False, 'true': True, '1': True }.get(value, default) ###################################################################### # ATTRIBUTE CHECKERS def check_anyuri(uri, config=None, pos=None): """ Raises a `~pyvo.io.vosi.tables.exceptions.VOSITablesWarning` if *uri* is not a valid URI. As defined in RFC 2396. """ if uri is not None and not xml_check.check_anyuri(uri): warn_or_raise(W01, W01, uri, config=config, pos=pos) return False return True def check_datatype_flag(data, config=None, pos=None): """ Checks if the datatype flag is valid """ if data not in ('indexed', 'primary', 'nullable'): warn_or_raise(W04, W04, data, config=config, pos=pos) return False return True ###################################################################### # ELEMENT CLASSES class TableSet(Element, HomogeneousList): """ TableSet element as described in http://www.ivoa.net/xml/VODataService/v1.1 The set of tables hosted by a resource. """ def __init__( self, config=None, pos=None, _name='tableset', version='1.1', **kwargs ): HomogeneousList.__init__(self, TableSchema) Element.__init__(self, config, pos, _name, **kwargs) self._version = version def __repr__(self): return '... {} schemas ...'.format( len(self)) @xmlattribute def version(self): """The version of the standard""" return self._version @version.setter def version(self, version): self._config['version'] = version self._version = version @xmlelement(name='schema') def schemas(self): """ A list of schemas. Must contain only `Schema` objects. A named description of a set of logically related tables. The name given by the "name" child element must be unique within this TableSet instance. If there is only one schema in this set and/or there's no locally appropriate name to provide, the name can be set to "default". This aggregation does not need to map to an actual database, catalog, or schema, though the publisher may choose to aggregate along such designations, or particular service protocol may recommend it. """ return self @schemas.adder def schemas(self, iterator, tag, data, config, pos): schema = TableSchema(config, pos, 'schema', **data) schema.parse(iterator, config) self.append(schema) def parse(self, iterator, config): super().parse(iterator, config) if not self.schemas: warn_or_raise(W14, W14, config=config, pos=self._pos) class TableSchema(Element, HomogeneousList): """ TableSchema element as described in http://www.ivoa.net/xml/VODataService/v1.1 A detailed description of a logically-related set of tables. """ def __init__(self, config=None, pos=None, _name='schema', **kwargs): HomogeneousList.__init__(self, VODataServiceTable) Element.__init__(self, config, pos, _name, **kwargs) self._name = None self._title = None self._description = None self._utype = None def __repr__(self): return '... {} tables ...'.format( self.name, len(self.tables)) @xmlelement(plain=True, multiple_exc=W05) def name(self): """ A name for the set of tables. This is used to uniquely identify the table set among several table sets. If a title is not present, this name can be used for display purposes. If there is no appropriate logical name associated with this set, the name should be explicitly set to "default". """ return self._name @name.setter def name(self, name): self._name = name @xmlelement(plain=True, multiple_exc=W13) def title(self): """ a descriptive, human-interpretable name for the table set. This is used for display purposes. There is no requirement regarding uniqueness. It is useful when there are multiple schemas in the context (e.g. within a tableset; otherwise, the resource title could be used instead). """ return self._title @title.setter def title(self, title): self._title = title @xmlelement(plain=True, multiple_exc=W06) def description(self): """ A free text description of the tableset that should explain in general how all of the tables are related. """ return self._description @description.setter def description(self, description): self._description = description @xmlelement(plain=True, multiple_exc=W09) def utype(self): """ an identifier for a concept in a data model that the data in this schema as a whole represent. The format defined in the VOTable standard is strongly recommended. """ return self._utype @utype.setter def utype(self, utype): self._utype = utype @xmlelement(name='table') def tables(self): """ A list of tables in the schema. Must contain only `Table` objects. A description of one of the tables that makes up the set. The table names for the table should be unique. """ return self @tables.adder def tables(self, iterator, tag, data, config, pos): table = VODataServiceTable(config, pos, 'table', **data) table.parse(iterator, config) self.append(table) def parse(self, iterator, config): super().parse(iterator, config) if not self.name: vo_raise(E06, self._Element__name, config=config, pos=self._pos) @vr.Interface.register_xsi_type('vs:ParamHTTP') class ParamHTTP(vr.Interface): """ ParamHTTP element as described in http://www.ivoa.net/xml/VODataService/v1.1 A service invoked via an HTTP Query (either Get or Post) with a set of arguments consisting of keyword name-value pairs. Note that the URL for help with this service can be put into the Service/ReferenceURL element. """ def __init__(self, config=None, pos=None, _name='', **kwargs): super().__init__( config=config, pos=pos, _name=_name, **kwargs) self._querytypes = HomogeneousList(str) self._resulttype = None @xmlelement(name='queryType', multiple_exc=W17) def querytypes(self): """ The type of HTTP request, either GET or POST. The service may indicate support for both GET and POST by providing 2 queryType elements, one with GET and one with POST. """ return self._querytypes @xmlelement(name='resultType', multiple_exc=W36) def resulttype(self): """The MIME type of a document returned in the HTTP response.""" return self._resulttype @resulttype.setter def resulttype(self, resulttype): self._resulttype = resulttype def parse(self, iterator, config): super().parse(iterator, config) if len(self.querytypes) > 2: warn_or_raise(W18, W18, config=config, pos=self._pos) class VODataServiceTable(Element): """ Table element as described in http://www.ivoa.net/xml/VODataService/v1.1 """ def __init__( self, config=None, pos=None, _name='table', version='1.1', **kwargs ): super().__init__(config, pos, _name, **kwargs) self._name = None self._title = None self._description = None self._utype = None self._type = kwargs.get("type") self._version = version self._nrows = None self._columns = HomogeneousList(TableParam) self._foreignkeys = HomogeneousList(ForeignKey) def __repr__(self): return '... {} columns ...'.format( self.name, len(self.columns)) def describe(self): print(self.name) if self.description is not None: print(indent(self.description, 4 * " ")) else: print('No description') print() @xmlelement(plain=True, multiple_exc=W05) def name(self): """ the fully qualified name of the table. This name should include all catalog or schema prefixes needed to sufficiently uniquely distinguish it in a query. In general, the format of the qualified name may depend on the context; however, when the table is intended to be queryable via ADQL, then the catalog and schema qualifiers are delimited from the table name with dots (.). """ return self._name @name.setter def name(self, name): self._name = name @xmlelement(plain=True, multiple_exc=W13) def title(self): """ a descriptive, human-interpretable name for the table. This is used for display purposes. There is no requirement regarding uniqueness. """ return self._title @title.setter def title(self, title): self._title = title @xmlelement(plain=True, multiple_exc=W06) def description(self): """ a free-text description of the table's contents """ return self._description @description.setter def description(self, description): self._description = description @xmlelement(plain=True, multiple_exc=W09) def utype(self): """ an identifier for a concept in a data model that the data in this table represent. The format defined in the VOTable standard is highly recommended. """ return self._utype @utype.setter def utype(self, utype): self._utype = utype @xmlelement(plain=True, multiple_exc=W09) def nrows(self): """ the approximate number of rows in the table. This is None if the data provider failed to provide this information. """ return self._nrows @nrows.setter def nrows(self, nrows): self._nrows = int(nrows) @xmlattribute def type(self): """ a name for the role this table plays. Recognized values include "output", indicating this table is output from a query; "base_table", indicating a table whose records represent the main subjects of its schema; and "view", indicating that the table represents a useful combination or subset of other tables. Other values are allowed. """ return self._type @type.setter def type(self, type_): self._type = type_ @xmlattribute def version(self): """The version of the standard""" return self._version @version.setter def version(self, version): self._config['version'] = version self._version = version @xmlelement(name='column') def columns(self): """ A list of columns in the table. Must contain only `TableParams` objects. A description of a table column. """ return self._columns @columns.adder def columns(self, iterator, tag, data, config, pos): column = TableParam(config, pos, 'column', **data) column.parse(iterator, config) self.columns.append(column) @xmlelement(name='foreignKey') def foreignkeys(self): """ A list of columns in the table. Must contain only `ForeignKey` objects a description of a foreign keys, one or more columns from the current table that can be used to join with another table. """ return self._foreignkeys @foreignkeys.adder def foreignkeys(self, iterator, tag, data, config, pos): foreignkey = ForeignKey(config, pos, 'foreignKey', **data) foreignkey.parse(iterator, config) self.foreignkeys.append(foreignkey) def parse(self, iterator, config): super().parse(iterator, config) if not self.name: vo_raise(E06, self._Element__name, config=config, pos=self._pos) @deprecated("1.5", alternative="VODataServiceTable") class Table(VODataServiceTable): pass class BaseParam(Element): """ BaseParam element as described in http://www.ivoa.net/xml/VODataService/v1.1 a description of a parameter that places no restriction on the parameter's data type. As the parameter's data type is usually important, schemas normally employ a sub-class of this type (e.g. Param), rather than this type directly. """ def __init__(self, config=None, pos=None, _name='', **kwargs): super().__init__( config=config, pos=pos, _name=_name, **kwargs) self._name = None self._description = None self._unit = None self._ucd = None self._utype = None def __repr__(self): return ''.format(self.name) @xmlelement(plain=True, multiple_exc=W05) def name(self): """the name of the element""" return self._name @name.setter def name(self, name): self._name = name @xmlelement(plain=True, multiple_exc=W06) def description(self): """ a free-text description of the element's contents """ return self._description @description.setter def description(self, description): self._description = description @xmlelement(plain=True, multiple_exc=W07) def unit(self): """the unit associated with all values in the element""" return self._unit @unit.setter def unit(self, unit): self._unit = unit @xmlelement(plain=True, multiple_exc=W08) def ucd(self): """ the name of a unified content descriptor that describes the scientific content of the element. There are no requirements for compliance with any particular UCD standard. The format of the UCD can be used to distinguish between UCD1, UCD1+, and SIA-UCD. See http://www.ivoa.net/Documents/latest/UCDlist.html for the latest IVOA standard set. """ return self._ucd @ucd.setter def ucd(self, ucd): self._ucd = ucd @xmlelement(plain=True, multiple_exc=W09) def utype(self): """ an identifier for a concept in a data model that the data in this element represent. The format defined in the VOTable standard is highly recommended. """ return self._utype @utype.setter def utype(self, utype): self._utype = utype class TableParam(BaseParam): """ TableParam element as described in http://www.ivoa.net/xml/VODataService/v1.1 A description of a table parameter having a fixed data type. The allowed data type names match those supported by VOTable. """ @classmethod def from_field(cls, field): """ Create a instance from a `~astropy.io.votable.tree.Field` instance. """ instance = cls() instance.name = field.name instance.description = field.description instance.unit = field.unit instance.ucd = field.ucd instance.utype = field.utype datatype = VOTableType(arraysize=field.arraysize) datatype.value = field.datatype instance.datatype = datatype return instance def __init__(self, config=None, pos=None, _name='', std=None, **kwargs): super().__init__( config=config, pos=pos, _name=_name, **kwargs) self._datatype = None self._flags = HomogeneousList(str) self._std = _convert_boolean(std) @xmlelement(name='dataType') def datatype(self): """The type of data contained in the element""" return self._datatype @datatype.setter def datatype(self, datatype): if datatype is not None and not isinstance(datatype, TableDataType): raise ValueError("datatype must be an TableDataType object") self._datatype = datatype @datatype.adder def datatype(self, iterator, tag, data, config, pos): datatype = TableDataType(config, pos, 'dataType', **data) datatype.parse(iterator, config) if self.datatype: warn_or_raise( W37, args=self._Element__name, config=config, pos=pos) self.datatype = datatype @xmlelement(name='flag') def flags(self): """ A list of flags. Must contain only `str` objects. a keyword representing traits of the column. Recognized values include "indexed", "primary", and "nullable". """ return self._flags @xmlattribute def std(self): """ If true, the meaning and use of this parameter is reserved and defined by a standard model. If false, it represents a database-specific parameter that effectively extends beyond the standard. If not provided, then the value is unknown. """ return self._std @std.setter def std(self, std): self._std = std def parse(self, iterator, config): super().parse(iterator, config) if not self.name: vo_raise(E06, self._Element__name, config=config, pos=self._pos) class InputParam(BaseParam): """ InputParam element as described in http://www.ivoa.net/xml/VODataService/v1.1 A description of a service or function parameter having a fixed data type. """ def __init__( self, config=None, pos=None, _name='', use="optional", std="1", **kwargs): BaseParam.__init__(self, config, pos, _name, **kwargs) self._datatype = None self._use = use self._std = _convert_boolean(std, True) @xmlelement(name='dataType') def datatype(self): """The type of data contained in the element""" return self._datatype @datatype.setter def datatype(self, datatype): if datatype is not None and not isinstance(datatype, SimpleDataType): raise ValueError("datatype must be an SimpleDataType object") self._datatype = datatype @xmlattribute def use(self): """ An indication of whether this parameter is required to be provided for the application or service to work properly. Allowed values are "required" and "optional". """ return self._use @use.setter def use(self, use): self._use = use @xmlattribute def std(self): """ If true, the meaning and behavior of this parameter is reserved and defined by a standard interface. If false, it represents an implementation-specific parameter that effectively extends the behavior of the service or application. """ return self._std @std.setter def std(self, std): self._std = std def parse(self, iterator, config): super().parse(iterator, config) if not self.name: vo_raise(E06, self._Element__name, config=config, pos=self._pos) class DataType(ContentMixin, ElementWithXSIType): """ DataType element as described in http://www.ivoa.net/xml/VODataService/v1.1 A type (in the computer language sense) associated with a parameter with an arbitrary name. This XML type is used as a parent for defining data types with a restricted set of names. """ def __init__( self, config=None, pos=None, _name='dataType', arraysize=None, delim=None, extendedType=None, extendedSchema=None, **kwargs ): super().__init__( config=config, pos=pos, _name=_name, **kwargs) if arraysize is None: arraysize = "1" if delim is None: delim = " " self.arraysize = arraysize self._delim = delim self._extendedtype = extendedType self.extendedschema = extendedSchema def __repr__(self): return '{}'.format( self.arraysize, self.content) @xmlattribute def arraysize(self): """Specifies the size of the dataType""" return self._arraysize @arraysize.setter def arraysize(self, arraysize): if all(( arraysize is not None, not re.match(r"^([0-9]+x)*[0-9]*[*]?(s\W)?$", arraysize) )): vo_raise(E01, arraysize, self._config, self._pos) self._arraysize = arraysize @xmlattribute def delim(self): """ the string that is used to delimit elements of an array value when arraysize is not "1". Unless specifically disallowed by the context, applications should allow optional spaces to appear in an actual data value before and after the delimiter (e.g. "1, 5" when delim=","). the default is " "; i.e. the values are delimited by spaces. """ return self._delim @delim.setter def delim(self, delim): self._delim = delim @xmlattribute(name='extendedType') def extendedtype(self): """ The data value represented by this type can be interpreted as of a custom type identified by the value of this attribute. If an application does not recognize this extendedType, it should attempt to handle value assuming the type given by the element's value. string is a recommended default type. This element may make use of the extendedSchema attribute and/or any arbitrary (qualified) attribute to refine the identification of the type. """ @extendedtype.setter def extendedtype(self, extendedtype): self._extendedtype = extendedtype @xmlattribute(name='extendedSchema') def extendedschema(self): """ An identifier for the schema that the value given by the extended attribute is drawn from. This attribute is normally ignored if the extendedType attribute is not present. """ return self._extendedschema @extendedschema.setter def extendedschema(self, extendedschema): if extendedschema is not None: check_anyuri(extendedschema, self._config, self._pos) self._extendedschema = extendedschema class SimpleDataType(DataType): """ SimpleDataType element as described in http://www.ivoa.net/xml/VODataService/v1.1 A data type restricted to a small set of names which is imprecise as to the format of the individual values. This set is intended for describing simple input parameters to a service or function. """ def _content_check(self, value): if value is not None: valid_values = { 'integer', 'real', 'complex', 'boolean', 'char', 'string'} if value not in valid_values: vo_warn(W02, value, self._config, self._pos) class TableDataType(DataType): """ TableDataType element as described in http://www.ivoa.net/xml/VODataService/v1.1 an abstract parent for a class of data types that can be used to specify the data type of a table column. Subtypes must be decorated with ``register_xsi_type('ns:name')``. """ @TableDataType.register_xsi_type('vs:VOTable') @TableDataType.register_xsi_type('vs:VOTableType') class VOTableType(TableDataType): """ VOTableType element as described in http://www.ivoa.net/xml/VODataService/v1.1 """ def _content_check(self, value): if value is not None: valid_values = ( 'boolean', 'bit', 'unsignedByte', 'short', 'int', 'long', 'char', 'unicodeChar', 'float', 'double', 'floatComplex', 'doubleComplex') if value not in valid_values: vo_warn(W02, value, self._config, self._pos) class TAPDataType(TableDataType): """ TAPDataType element as described in http://www.ivoa.net/xml/VODataService/v1.1 an abstract parent for the specific data types supported by the Table Access Protocol. """ def __init__( self, config=None, pos=None, _name='dataType', size=None, **kwargs ): super().__init__( config=config, pos=pos, _name=_name, **kwargs) self.size = size @xmlattribute def size(self): """ the length of the fixed-length value. This corresponds to the size Column attribute in the TAP_SCHEMA and can be used with data types that are defined with a length (CHAR, BINARY). """ return self._size @size.setter def size(self, size): if size is not None and int(size) < 0: size = 0 warn_or_raise(W03, W03, config=self._config, pos=self._pos) self._size = size @TableDataType.register_xsi_type('vs:TAP') @TableDataType.register_xsi_type('vs:TAPType') class TAPType(TAPDataType): """ TAPType element as described in http://www.ivoa.net/xml/VODataService/v1.1 a data type supported explicitly by the Table Access Protocol (v1.0). """ def _content_check(self, value): if value is not None: valid_values = ( 'BOOLEAN', 'SMALLINT', 'INTEGER', 'BIGINT', 'REAL', 'DOUBLE', 'TIMESTAMP', 'CHAR', 'VARCHAR', 'BINARY', 'VARBINARY', 'POINT', 'REGION', 'CLOB', 'BLOB') if value not in valid_values: vo_warn(W02, value, self._config, self._pos) class FKColumn(Element): """ FKColumn element as described in http://www.ivoa.net/xml/VODataService/v1.1 """ def __init__(self, config=None, pos=None, _name='fkColumn', **kwargs): super().__init__( config=config, pos=pos, _name=_name, **kwargs) self._fromcolumn = None self._targetcolumn = None def __repr__(self): return '...'.format( self.fromcolumn, self.targetcolumn) @xmlelement(name='fromColumn', plain=True, multiple_exc=W10) def fromcolumn(self): """ The unqualified name of the column from the current table. """ return self._fromcolumn @fromcolumn.setter def fromcolumn(self, fromcolumn): self._fromcolumn = fromcolumn @xmlelement(name='targetColumn', plain=True, multiple_exc=W11) def targetcolumn(self): """ The unqualified name of the column from the target table. """ return self._targetcolumn @targetcolumn.setter def targetcolumn(self, targetcolumn): self._targetcolumn = targetcolumn def parse(self, iterator, config): super().parse(iterator, config) if self.fromcolumn is None: vo_raise(E02, config=config, pos=self._pos) if self.targetcolumn is None: vo_raise(E03, config=config, pos=self._pos) class ForeignKey(Element): """ ForeignKey element as described in http://www.ivoa.net/xml/VODataService/v1.1 """ def __init__(self, config=None, pos=None, _name='foreignKey', **kwargs): Element.__init__(self, config, pos, _name, **kwargs) self._targettable = None self._fkcolumns = HomogeneousList(FKColumn) self._description = None self._utype = None def __repr__(self): return '...'.format( self.targettable) @xmlelement(name='targetTable', plain=True, multiple_exc=W12) def targettable(self): """ the fully-qualified name (including catalog and schema, as applicable) of the table that can be joined with the table containing this foreign key. """ return self._targettable @targettable.setter def targettable(self, targettable): self._targettable = targettable @xmlelement(name='fkColumn') def fkcolumns(self): """ A list of foreign key columns. Must contain only `FKColumn` objects. a pair of column names, one from this table and one from the target table that should be used to join the tables in a query. """ return self._fkcolumns @fkcolumns.adder def fkcolumns(self, iterator, tag, data, config, pos): fkcolumn = FKColumn(config, pos, 'fkColumn', **data) fkcolumn.parse(iterator, config) self.fkcolumns.append(fkcolumn) @xmlelement(plain=True, multiple_exc=W06) def description(self): """ a free-text description of what this key points to and what the relationship means. """ return self._description @description.setter def description(self, description): self._description = description @xmlelement(plain=True, multiple_exc=W09) def utype(self): """ an identifier for a concept in a data model that the association enabled by this key represents. The format defined in the VOTable standard is highly recommended. """ return self._utype @utype.setter def utype(self, utype): self._utype = utype def parse(self, iterator, config): super().parse(iterator, config) if not self.targettable: vo_raise(E04, config=config, pos=self._pos) if not self.fkcolumns: vo_raise(E05, config=config, pos=self._pos) pyvo-1.5.2/pyvo/io/vosi/voresource.py000066400000000000000000000360631462331236700176650ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ This file contains xml element classes as defined in the VOResource standard. There are different ways of handling the various xml tags. * Elements with complex content * Elements with simple content and attributes * Elements with simple content without attributes Elements with complex content are parsed with objects inherited from `~pyvo.utils.xml.elements.Element`. Elements with simple content are parsed with objects inherited from `~pyvo.utils.xml.elements.Element` defining a ``value`` property. """ from astropy.utils.collections import HomogeneousList from textwrap import indent from ...utils.xml.elements import ( Element, ElementWithXSIType, ContentMixin, xmlattribute, xmlelement) from .exceptions import W06 __all__ = [ "ValidationLevel", "Capability", "Interface", "AccessURL", "SecurityMethod", "WebBrowser", "WebService", "MirrorURL"] ###################################################################### # ELEMENT CLASSES class ValidationLevel(ContentMixin, Element): """ ValidationLevel element as described in http://www.ivoa.net/xml/VOResource/v1.0 the allowed values for describing the resource descriptions and interfaces. See the RM (v1.1, section 4) for more guidance on the use of these values. Possible values: 0: The resource has a description that is stored in a registry. This level does not imply a compliant description. 1: In addition to meeting the level 0 definition, the resource description conforms syntactically to this standard and to the encoding scheme used. 2: In addition to meeting the level 1 definition, the resource description refers to an existing resource that has demonstrated to be functionally compliant. When the resource is a service, it is consider to exist and functionally compliant if use of the service accessURL responds without error when used as intended by the resource. If the service is a standard one, it must also demonstrate the response is syntactically compliant with the service standard in order to be considered functionally compliant. If the resource is not a service, then the ReferenceURL must be shown to return a document without error. 3: In addition to meeting the level 2 definition, the resource description has been inspected by a human and judged to comply semantically to this standard as well as meeting any additional minimum quality criteria (e.g., providing values for important but non-required metadata) set by the human inspector. 4: In addition to meeting the level 3 definition, the resource description meets additional quality criteria set by the human inspector and is therefore considered an excellent description of the resource. Consequently, the resource is expected to be operate well as part of a VO application or research study. """ def __init__( self, config=None, pos=None, _name='validationLevel', validatedBy=None, **kwargs ): super().__init__(config, pos, _name, **kwargs) self._validatedby = validatedBy def __repr__(self): return '{}'.format( self.validatedby, self.content) @xmlattribute def validatedby(self): """ The IVOA ID of the registry or organisation that assigned the validation level. """ return self._validatedby @validatedby.setter def validatedby(self, validatedby): self._validatedby = validatedby class AccessURL(ContentMixin, Element): """ AccessURL element as described in http://www.ivoa.net/xml/VOResource/v1.0 The URL (or base URL) that a client uses to access the service. How this URL is to be interpreted and used depends on the specific Interface subclass """ def __init__( self, config=None, pos=None, _name='accessURL', use=None, **kwargs ): super().__init__(config, pos, _name, **kwargs) self._use = use def __repr__(self): return '{}'.format( self.use, self.content) @xmlattribute def use(self): """ A flag indicating whether this should be interpreted as a base URL, a full URL, or a URL to a directory that will produce a listing of files. Possible values: full: Assume a full URL--that is, one that can be invoked directly without alteration. This usually returns a single document or file. base: Assume a base URL--that is, one requiring an extra portion to be appended before being invoked. dir: Assume URL points to a directory that will return a listing of files. """ return self._use @use.setter def use(self, use): self._use = use class MirrorURL(ContentMixin, Element): """ A URL available as a mirror of an access URL. These come with a human-readable title intended to aid in mirror selection. """ def __init__( self, config=None, pos=None, _name='accessURL', title=None, **kwargs ): super().__init__(config, pos, _name, **kwargs) self._title = title @xmlattribute def title(self): """ A human-readable title for the mirror. """ return self._title class SecurityMethod(ContentMixin, Element): """ SecurityMethod element as described in http://www.ivoa.net/xml/VOResource/v1.0 A description of a security mechanism. this type only allows one to refer to the mechanism via a URI. Derived types would allow for more metadata. """ def __init__( self, config=None, pos=None, _name='securityMethod', standardID=None, **kwargs ): super().__init__(config, pos, _name, **kwargs) self._standardid = standardID def __repr__(self): return '{}'.format( self.standardid, self.content) @xmlattribute(name='standardID') def standardid(self): """ A URI identifier for a standard security mechanism. """ return self._standardid @standardid.setter def standardid(self, standardid): self._standardid = standardid class Interface(ElementWithXSIType): """ Interface element as described in http://www.ivoa.net/xml/VOResource/v1.0 A description of a service interface. Since this type is abstract, one must use an Interface subclassto describe an actual interface. Additional interface subtypes (beyond WebService and WebBrowser) are defined in the VODataService schema. """ def __init__( self, config=None, pos=None, _name='interface', version='1.0', role=None, **kwargs ): super().__init__(config, pos, _name, **kwargs) self._xsi_type = kwargs.get('xsi:type') self._version = version self._role = role self._resulttype = None self._testquerystring = None self._accessurls = HomogeneousList(AccessURL) self._securitymethods = HomogeneousList(SecurityMethod) self._mirrorurls = HomogeneousList(MirrorURL) def __repr__(self): return '...'.format( self.role) def describe(self): """ Prints out a human readable description """ print('Interface {}'.format(self._xsi_type)) accessurls = '\n'.join( accessurl.content for accessurl in self.accessurls) print(indent(accessurls, 4 * " ")) print() @xmlattribute def version(self): """ The version of a standard interface specification that this interface complies with. When the interface is provided in the context of a Capability element, then the standard being refered to is the one identified by the Capability's standardID element. If the standardID is not provided, the meaning of this attribute is undefined. """ return self._version @version.setter def version(self, version): self._version = version @xmlattribute def role(self): """ A tag name the identifies the role the interface plays in the particular capability. If the value is equal to "std" or begins with "std:", then the interface refers to a standard interface defined by the standard referred to by the capability's standardID attribute. For an interface complying with some registered standard (i.e. has a legal standardID), the role can be match against interface roles enumerated in standard resource record. The interface descriptions in the standard record can provide default descriptions so that such details need not be repeated here. """ return self._role @role.setter def role(self, role): self._role = role @xmlelement(name='accessURL', cls=AccessURL) def accessurls(self): """ A list of access urls in the interface. Must contain only `AccessURL` objects. """ return self._accessurls @xmlelement(name='mirrorURL', cls=MirrorURL) def mirrorurls(self): """ mirror(s) for this access URL. """ return self._mirrorurls @xmlelement(name='securityMethod', cls=SecurityMethod) def securitymethods(self): """ the mechanism the client must employ to gain secure access to the service. when more than one method is listed, each one must be employed to gain access. """ return self._securitymethods @xmlelement(name='testQueryString') def testquerystring(self): """ a string to be used in an interface-specific way to obtain a non-empty result from the service. """ return self._testquerystring @testquerystring.setter def testquerystring(self, testquerystring): self._testquerystring = testquerystring @xmlelement def resulttype(self): """ The MIME type of a document returned in the HTTP response. """ return self._resulttype @resulttype.setter def resulttype(self, resulttype): self._resulttype = resulttype class Capability(ElementWithXSIType): """ Capability element as described in http://www.ivoa.net/xml/VOResource/v1.0 a description of what the service does (in terms of context-specific behavior), and how to use it (in terms of an interface) """ def __init__( self, config=None, pos=None, _name='capability', standardID=None, **kwargs ): super().__init__(config, pos, _name, **kwargs) self._description = None self._standardid = standardID self._validationlevels = HomogeneousList(ValidationLevel) self._interfaces = HomogeneousList(Interface) def __repr__(self): return ( '' '... {} validationLevels, {} interfaces ...' '' ).format( self.standardid, len(self.validationlevels), len(self.interfaces)) def describe(self): """ Prints out a human readable description """ print("Capability {}".format(self.standardid)) print() if self.description: print(self.description) print() for interface in self.interfaces: interface.describe() @xmlelement(plain=True, multiple_exc=W06) def description(self): """ A human-readable description of what this capability provides as part of the over-all service Use of this optional element is especially encouraged when this capability is non-standard and is one of several capabilities listed. """ return self._description @description.setter def description(self, description): self._description = description @xmlelement(name='validationLevel', cls=ValidationLevel) def validationlevels(self): """ A numeric grade describing the quality of the capability description and interface, when applicable, to be used to indicate the confidence an end-user can put in the resource as part of a VO application or research study. """ return self._validationlevels @xmlelement(name='interface', cls=Interface) def interfaces(self): """ a description of how to call the service to access this capability Since the Interface type is abstract, one must describe the interface using a subclass of Interface, denoting it via xsi:type. Multiple occurances can describe different interfaces to the logically same capability--i.e. data or functionality. That is, the inputs accepted and the output provides should be logically the same. For example, a WebBrowser interface given in addition to a WebService interface would simply provide an interactive, human-targeted interface to the underlying WebService interface. """ return self._interfaces @xmlattribute(name='standardID') def standardid(self): """ A URI identifier for a standard service. This provides a unique way to refer to a service specification standard, such as a Simple Image Access service. The use of an IVOA identifier here implies that a VOResource description of the standard is registered and accessible. """ return self._standardid @standardid.setter def standardid(self, standardid): self._standardid = standardid @Interface.register_xsi_type('vr:WebBrowser') class WebBrowser(Interface): """ WebBrowser element as described in http://www.ivoa.net/xml/VOResource/v1.0 A (form-based) interface intended to be accesed interactively by a user via a web browser. The accessURL represents the URL of the web form itself. """ @Interface.register_xsi_type('vr:WebService') class WebService(Interface): """ WebService element as described in http://www.ivoa.net/xml/VOResource/v1.0 A Web Service that is describable by a WSDL document. The accessURL element gives the Web Service's endpoint URL. """ def __init__(self, config=None, pos=None, _name='interface', **kwargs): super().__init__(config, pos, _name, **kwargs) self._wsdlurls = HomogeneousList(str) @xmlelement(name='wsdlURL') def wsdlurls(self): """ The location of the WSDL that describes this Web Service. If not provided, the location is assumed to be the accessURL with "?wsdl" appended. Multiple occurances should represent mirror copies of the same WSDL file. """ return self._wsdlurls pyvo-1.5.2/pyvo/registry/000077500000000000000000000000001462331236700153705ustar00rootroot00000000000000pyvo-1.5.2/pyvo/registry/__init__.py000066400000000000000000000012731462331236700175040ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ a package for interacting with registries. The regtap module supports access to the IVOA Registries """ from .regtap import search, ivoid2service, get_RegTAP_query, choose_RegTAP_service from .rtcons import (Constraint, Freetext, Author, Servicetype, Waveband, Datamodel, Ivoid, UCD, Spatial, Spectral, Temporal, RegTAPFeatureMissing) __all__ = ["search", "get_RegTAP_query", "Constraint", "Freetext", "Author", "Servicetype", "Waveband", "Datamodel", "Ivoid", "UCD", "Spatial", "Spectral", "Temporal", "choose_RegTAP_service", "RegTAPFeatureMissing"] pyvo-1.5.2/pyvo/registry/regtap.py000066400000000000000000001145641462331236700172370ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ a module for basic VO Registry interactions. A VO registry is a database of VO resources--data collections and services--that are available for VO applications. Typically, it is aware of the resources from all over the world. A registry can find relevent data collections and services through search queries--typically, subject-based. The registry responds with a list of records describing matching resources. With a record in hand, the application can use the information in the record to access the resource directly. Most often, the resource is a data service that can be queried for individual datasets of interest. This module provides basic, low-level access to the RegTAP Registries using standardized TAP-based services. """ import functools import itertools import os import warnings from astropy import table from astropy.utils.decorators import deprecated from astropy.utils.exceptions import AstropyDeprecationWarning import numpy from . import rtcons from ..dal import scs, sia, sia2, ssa, sla, tap, query as dalq from ..io.vosi import vodataservice from ..utils.formatting import para_format_desc __all__ = ["search", "get_RegTAP_query", "RegistryResource", "RegistryResults", "ivoid2service"] REGISTRY_BASEURL = os.environ.get("IVOA_REGISTRY", "http://reg.g-vo.org/tap" ).rstrip("/") # ADQL only has string_agg, where we need string arrays. We fake arrays # by joining elements with a token separator that we think shouldn't # turn up in the things joined. Of course, people could create # resources that break us; let's assume there's nothing be gained # from that ever. TOKEN_SEP = ":::py VO sep:::" def shorten_stdid(s): """removes leading ivo://ivoa.net/std/ from s if present. We're using this to make the display and naming of standard ivoids less ugly in several places. Nones remain Nones. """ if s and s.startswith("ivo://ivoa.net/std/"): return s[19:] return s def expand_stdid(s): """returns s if it already looks like a URI, and it prepends ivo://ivoa.net/std otherwise. This is the (approximate) reverse of shorten_stdid. """ if s is None or "://" in s: return s return "ivo://ivoa.net/std/" + s def regularize_SIA2_id(standard_id): """returns standard_id with SIA2 standard ids modified to what they should have been. Regrettably, SIA2 uses the same standardID as SIA1; sure, they use different fragments, but that doesn't really help with the logic we have here. To make up for that, we replace them with the sia2 ids they should have had on input. This function assumes lowercased ids as they come from RegTAP services. """ if standard_id.startswith("ivo://ivoa.net/std/sia#query-2"): return "ivo://ivoa.net/std/sia2" elif standard_id.startswith("ivo://ivoa.net/std/sia#query-aux-2"): # query-aux-2 is mentioned in discovering data collections, # which isn't really the place to define this. But then # it's endorsed, and SIA2 doesn't say anything about it. return "ivo://ivoa.net/std/sia2#aux" else: return standard_id @functools.lru_cache(1) def get_RegTAP_service(): """ a lazily created TAP service offering the RegTAP services. Always get the TAP service there using this function to avoid re-creating the server and profit from caching of capabilties, tables, etc. To switch to a different RegTAP service, use :py:func:`choose_RegTAP_service`. """ return tap.TAPService(REGISTRY_BASEURL) def choose_RegTAP_service(access_url): """ changes the RegTAP service used by :py:func:`search` to the one at access_url. By default, pyVO uses whatever is given in the environment variable ``IVOA_REGISTRY``, defaulting to GAVO's TAP service. In order to change the service used on the fly, always use this function in order to clear caches that need clearing. Parameters ---------- access_url : str The TAP access URL of the new RegTAP endpoints. To find alternate endpoints, try ``regsearch(datamodel='regtap')`` and look at ``.get_interface("tap").access_url`` of the results. """ global REGISTRY_BASEURL get_RegTAP_service.cache_clear() REGISTRY_BASEURL = access_url def get_RegTAP_query(*constraints: rtcons.Constraint, includeaux=False, service=None, **kwargs): """returns SQL for a RegTAP query for constraints and keywords. This function's parameters are as for search; this is basically a wrapper for rtcons.build_regtap_query maintaining the legacy keyword-based interface. """ # we don't document the service parameter -- it's probably not useful # to users and is just the conscequence of having retrofitted service # sensing into the API. if service is None: service = get_RegTAP_service() constraints = list(constraints) + rtcons.keywords_to_constraints(kwargs) # maintain legacy includeaux by locating any Servicetype constraints # and replacing them with ones that includes auxiliaries. if includeaux: for index, constraint in enumerate(constraints): if isinstance(constraint, rtcons.Servicetype): constraints[index] = constraint.include_auxiliary_services() return rtcons.build_regtap_query(constraints, service) def search(*constraints: rtcons.Constraint, includeaux: bool = False, maxrec: int = None, **kwargs): """ execute a simple query to the RegTAP registry. The function accepts query constraints either as Constraint objects passed in as positional arguments or as their associated keywords. For what constraints are available, see :ref:`registry-basic-interface`. The values of keyword arguments may be tuples or lists when the associated Constraint objects take multiple arguments. All constraints, whether passed in directly or via keywords, are evaluated as a conjunction (i.e., in an AND clause). Parameters ---------- *constraints : `~pyvo.registry.Constraint` instances The constraints (keywords to match, positions to cover, ...) that the returned records need to satisfy. The accepted constraints are: - keywords: one or more freetext words, mached in the title, description or subject of the resource. - servicetype: constrain to one of tap, ssa, sia, conesearch (or full ivoids for other service types). This is the constraint you want to use for service discovery. - ucd: constrain by one or more UCD patterns; resources match when they serve columns having a matching UCD (e.g., phot.mag;em.ir.% for "any infrared magnitude"). - waveband: one or more terms from the vocabulary at http://www.ivoa.net/rdf/messenger giving the rough spectral location of the resource. - author: an author ("creator"). This is a single SQL pattern, and given the sloppy practices in the VO for how to write author names, you should probably generously use wildcards. - datamodel: one of obscore, epntap, or regtap: only return TAP services having tables of this kind. - ivoid: exactly match a single IVOA identifier (that is, in effect, the primary key in the VO). - spatial: match resources covering a certain geometry (point, circle, polygon, or MOC). RegTAP 1.2 Extension. - spectral: match resources covering a certain part of the spectrum (usually, but not limited to, the electromagnetic spectrum). RegTAP 1.2 Extension - temporal: match resources covering a some point or interval in time. RegTAP 1.2 Extension Multiple constraints are combined conjunctively ("AND"). includeaux : bool Flag for whether to include auxiliary capabilities in results. This may result in duplicate capabilities being returned, especially if the servicetype is not specified. maxrec : int Overrides the RegTAP server's default limit on the number of rows to return. You may need to use this if you want to retrieve more than a few thousand matches. The server may also have a hard limit that ``maxrec`` cannot override. Note that truncated search results are not reproducible. **kwargs : strings, mostly shorthands for ``constraints``; see the documentation of a specific constraint for what keyword it uses and what literal it expects. Returns ------- ~pyvo.registry.regtap.RegistryResults` a container holding a table of matching resource (e.g. services) """ service = get_RegTAP_service() query = RegistryQuery( service.baseurl, get_RegTAP_query(*constraints, includeaux=includeaux, service=service, **kwargs), maxrec=maxrec) return query.execute() class RegistryQuery(tap.TAPQuery): def execute(self): """ submit the query and return the results as a RegistryResults instance Raises ------ DALServiceError for errors connecting to or communicating with the service DALQueryError for errors either in the input query syntax or other user errors detected by the service DALFormatError for errors parsing the VOTable response """ return RegistryResults(self.execute_votable(), url=self.queryurl) class RegistryResults(dalq.DALResults): """ an iterable set of results from a registry query. Each record is returned as RegistryResults You can iterate over these, or access them by (numeric) index; note, however, that these indexes will not be stable across different executions and thus should only be used in interactive sessions. Alternatively, you can use short names as indexes; there *might* be clashes for these, as they are not unique VO-wide. Where this matters, you need to use full ivoids as index. """ def getrecord(self, index): """ return all the attributes of a resource record with the given index as SimpleResource instance (a dictionary-like object). Parameters ---------- index : int the zero-based index of the record """ return RegistryResource(self, index) def get_summary(self): """ returns a brief overview of the matched results as an astropy table. This is mainly intended for interactive use, where people would like to inspect the matches in, perhaps, notebooks. """ return table.Table([ list(range(len(self))), [r.short_name for r in self], [r.res_title for r in self], [r.res_description for r in self], [", ".join(sorted(r.access_modes())) for r in self]], names=("index", "short_name", "title", "description", "interfaces"), descriptions=( "Index to access the resource within self", "Short name", "Resource title", "Resource description", "Access modes offered")) @functools.lru_cache(maxsize=None) def _get_ivo_index(self): return dict((r.ivoid, index) for index, r in enumerate(self)) @functools.lru_cache(maxsize=None) def _get_short_name_index(self): return dict((r.short_name, index) for index, r in enumerate(self)) def __getitem__(self, item): """ returns a record by numeric index, short names, or ivoid. This will raise an IndexError or a KeyError when item does not match a record returned. """ if isinstance(item, int): return self.getrecord(item) elif isinstance(item, str): if item.startswith("ivo://"): return self.getrecord(self._get_ivo_index()[item]) else: return self.getrecord(self._get_short_name_index()[item]) else: raise IndexError(f"No resource matching {item}") class _BrowserService: """A pseudo-service class just opening a web browser for browser-based services. """ def __init__(self, access_url): self.access_url = access_url def search(self): import webbrowser webbrowser.open(self.access_url, 2) class Interface: """ a service interface. These consist of an access URL, a standard id for the capability (typically the ivoid of an IVOA standard, or None for free services), an interface type (something like vs:paramhttp or vr:webbrowser) and an indication if the interface is the "standard" interface of the capability. Such interfaces can be turned into services using the ``to_service`` method if pyvo knows how to talk to the interface. Note that the constructor arguments are assumed to be normalised as in regtap (e.g., lowercased for the standardIDs). """ service_for_standardid = { "ivo://ivoa.net/std/conesearch": scs.SCSService, "ivo://ivoa.net/std/sia": sia.SIAService, "ivo://ivoa.net/std/sia2": sia2.SIA2Service, "ivo://ivoa.net/std/ssa": ssa.SSAService, "ivo://ivoa.net/std/sla": sla.SLAService, "ivo://ivoa.net/std/tap": tap.TAPService} def __init__(self, access_url, standard_id, intf_type, intf_role): self.access_url = access_url self.standard_id = standard_id or None self.type = intf_type or None self.role = intf_role or None self.is_standard = self.role == "std" if self.standard_id is not None: self.is_vosi = self.standard_id.startswith("ivo://ivoa.net/std/vosi") else: self.is_vosi = False def __repr__(self): return (f"Interface({self.access_url!r}, standard_id={self.standard_id!r}," f" intf_type={self.type!r}, intf_role={self.role!r})") def to_service(self): if self.type == "vr:webbrowser": return _BrowserService(self.access_url) if self.standard_id is None or not self.is_standard: raise ValueError("This is not a standard interface. PyVO" " cannot speak to it.") service_class = self.service_for_standardid.get( self.standard_id.split("#")[0]) if service_class is None: raise ValueError("PyVO has no support for interfaces with" f" standard id {self.standard_id}.") if service_class == sia2.SIA2Service: return service_class(self.access_url, check_baseurl=False) else: return service_class(self.access_url) def supports(self, standard_id): """returns true if we believe the interface should be able to talk standard_id. At this point, we naively check if the interfaces's standard_id has standard_id as a prefix. At this point, we cut off standard_id fragments for this purpose. This works for all current DAL standards but would, for instance, not work for VOSI. Hence, this may need further logic if we wanted to extend our service generation to VOSI or, perhaps, VOSpace. Parameters ---------- standard_id : str The ivoid of a standard. """ if not self.standard_id: return False standard_id = regularize_SIA2_id(standard_id) return self.standard_id.split("#")[0] == standard_id.split("#")[0] class RegistryResource(dalq.Record): """ a dictionary for the resource metadata returned in one record of a registry query. A SimpleResource acts as a dictionary, so in general, all attributes can be accessed by name via the [] operator, and the attribute names can by returned via the keys() function. For convenience, it also stores key values as properties; these include: """ _service = None # the following attribute is used by datasearch._build_regtap_query # to figure build the select clause; it is maintained here # because this class knows what it expects to get. # # Each item is either a plain string for a column name, or # a 2-tuple for an as clause; all plain strings are used # used in the group by, and so it is assumed they are # 1:1 to ivoid. expected_columns = [ "ivoid", "res_type", "short_name", "res_title", "content_level", "res_description", "reference_url", "creator_seq", "created", "updated", "rights", "content_type", "source_format", "source_value", "region_of_regard", "waveband", (f"\n ivo_string_agg(COALESCE(access_url, ''), '{TOKEN_SEP}')", "access_urls"), (f"\n ivo_string_agg(COALESCE(standard_id, ''), '{TOKEN_SEP}')", "standard_ids"), (f"\n ivo_string_agg(COALESCE(intf_type, ''), '{TOKEN_SEP}')", "intf_types"), (f"\n ivo_string_agg(COALESCE(intf_role, ''), '{TOKEN_SEP}')", "intf_roles")] def __init__(self, results, index, session=None): dalq.Record.__init__(self, results, index, session=session) self._mapping["access_urls" ] = self._parse_pseudo_array(self._mapping["access_urls"]) self._mapping["standard_ids"] = [ regularize_SIA2_id(id) for id in self._parse_pseudo_array(self._mapping["standard_ids"])] self._mapping["intf_types" ] = self._parse_pseudo_array(self._mapping["intf_types"]) self._mapping["intf_roles" ] = self._parse_pseudo_array(self._mapping["intf_roles"]) self.interfaces = [Interface(props[0], standard_id=props[1], intf_type=props[2], intf_role=props[3]) for props in itertools.zip_longest( self["access_urls"], self["standard_ids"], self["intf_types"], self["intf_roles"])] @staticmethod def _parse_pseudo_array(literal): """ parses RegTAP pseudo-arrays into lists. Parameters ---------- literal : str the result of an ivo_string_agg call with TOKEN_SEP Returns ------- A list of strings corresponding to the orginal, database-side aggregate. """ if not literal: # As VOTable, we don't distinguish between None and "" return [] return literal.split(TOKEN_SEP) @property def ivoid(self): """ the IVOA identifier for the resource. """ return self.get("ivoid", decode=True) @property def res_type(self): """ the resource types that characterize this resource. """ return self.get("res_type", decode=True) @property def short_name(self): """ the short name for the resource """ return self.get("short_name", decode=True) @property def res_title(self): """ the title of the resource """ return self.get("res_title", default=None, decode=True) @property def content_levels(self): """ a list of content level labels that describe the intended audience for this resource. """ return self.get("content_level", default="", decode=True).split("#") @property def res_description(self): """ the textual description of the resource. """ return self.get("res_description", decode=True) @property def reference_url(self): """ URL pointing to a human-readable document describing this resource. """ return self.get("reference_url", decode=True) @property def creators(self): """ The creator(s) of the resource in the ordergiven by the resource record author """ return self.get("creator_seq", default="", decode=True).split(";") @property def created(self): """Date of creation of the resource.""" return self.get("created", decode=True) @property def updated(self): """Date of last modification of the resource.""" return self.get("updated", decode=True) @property def rights(self): """A statement of usage conditions for the content of the resource. This information is often incomplete in the registry, you might get more information at the ``reference_url``. """ return self.get("rights", decode=True) @property def content_types(self): """ list of natures or genres of the content of the resource. """ return self.get("content_type", decode=True).split("#") @property def source_format(self): """ The format of source_value. """ return self.get("source_format", decode=True) @property def source_value(self): """ The bibliographic source for this resource (typically a bibcode or a DOI). """ return self.get("source_value", decode=True) @property def region_of_regard(self): """ numeric value representing the angle, given in decimal degrees, by which a positional query against this resource should be "blurred" in order to get an appropriate match. """ # we get NULLs as NaNs here val = self["region_of_regard"] if numpy.isnan(val): return None return val @property def waveband(self): """ a list of names of the wavebands that the resource provides data for """ return self.get("waveband", default="", decode=True).split("#") @property def access_url(self): """ the URL that can be used to access the service resource. """ # some services declare some data models using multiple # identifiers; in this case, we'll get the same access URL # multiple times in here. Be cool about that situation: access_urls = list(sorted(set(self["access_urls"]))) if len(access_urls) == 0: raise dalq.DALQueryError( f"The resource {self.ivoid} has no queriable interfaces.") elif len(access_urls) > 1: warnings.warn(AstropyDeprecationWarning( f"The resource {self.ivoid} has multiple capabilities. " " You should explicitly pick one using get_service. " " Returning some access_url now, but this behaviour " " may change in the future.")) return access_urls[0] @property def standard_id(self): """ the IVOA standard identifier """ standard_ids = list(set(self["standard_ids"])) if len(standard_ids) == 1: return standard_ids[0] else: raise dalq.DALQueryError( "This resource supports several standards ({})." " Use get_service or restrict your query using Servicetype." .format(", ".join(sorted(self.access_modes())))) def access_modes(self): """ returns a set of interface identifiers available on this resource. For standard interfaces, get_service will return a service suitable for querying if you pass in an identifier from this list as the service_type. This will ignore VOSI (infrastructure) services. """ return set(shorten_stdid(intf.standard_id) or "web" for intf in self.interfaces if (intf.standard_id or intf.type == "vr:webbrowser") and not intf.is_vosi) def get_interface(self, service_type: str, lax: bool = True, std_only: bool = False): """returns a regtap.Interface class for service_type. The meaning of the parameters is as for get_service. This method does not return services, though, so you can use it to obtain access URLs and such for interfaces that pyVO does not (directly) support. Parameters ---------- service_type : str If you leave out ``service_type``, this will return a service for "the" standard interface of the resource. If a resource has multiple standard capabilities (e.g., both TAP and SSAP endpoints), this will raise a DALQueryError. Otherwise, a service of the given service type will be returned. Pass in an ivoid of a standard or one of the shorthands from rtcons.SERVICE_TYPE_MAP, or "web" for a web page (the "service" for this will be an object opening a web browser when you call its query method). lax : bool If there are multiple capabilities for service_type, the function choose the first matching capability by default Pass lax=False to instead raise a DALQueryError. std_only : bool Only return interfaces declared as "std". This is what you want when you want to construct pyVO service objects later. This parameter is ignored for the "web" service type. Returns ------- `~pyvo.registry.regtap.Interface` """ if service_type == "web": # this works very much differently in the Registry # than do the proper services candidates = [intf for intf in self.interfaces if intf.type == "vr:webbrowser"] else: service_type = expand_stdid( rtcons.SERVICE_TYPE_MAP.get( service_type, service_type)) candidates = [intf for intf in self.interfaces if ((not std_only) or intf.is_standard) and not intf.is_vosi and ((not service_type) or intf.supports(service_type))] if not candidates: raise ValueError( "No matching interface.") if len(candidates) > 1 and not lax: raise ValueError("Multiple matching interfaces found." " Perhaps pass in service_type or use a Servicetype" " constrain in the registry.search? Or use lax=True?") return candidates[0] def get_service(self, service_type: str = None, lax: bool = True): """ return an appropriate DALService subclass for this resource that can be used to search the resource using service_type. Raise a ValueError if the service_type is not offerend on the resource (or no standard service is offered). With lax=False, also raise a ValueError if multiple interfaces exist for the given service_type. VOSI (infrastructure) services are always ignored here. A magic service_type "web" can be passed in to get non-standard, browser-based interfaces. The service in this case is an object that opens a web browser if its query() method is called. Parameters ---------- service_type : str If you leave out ``service_type``, this will return a service for "the" standard interface of the resource. If a resource has multiple standard capabilities (e.g., both TAP and SSAP endpoints), this will raise a DALQueryError. Otherwise, a service of the given service type will be returned. Pass in an ivoid of a standard or one of the shorthands from rtcons.SERVICE_TYPE_MAP, or "web" for a web page (the "service" for this will be an object opening a web browser when you call its query method). lax : bool If there are multiple capabilities for service_type, the function choose the first matching capability by default Pass lax=False to instead raise a DALQueryError. Returns ------- `pyvo.dal.DALService` For standard service types, a specific DAL service instance (e.g., a `pyvo.dal.tap.TAPService` when requesting ``tap`` services) is returned. For ``web`` services, what is returned is an opaque service object that has a ``search()`` method simply opening a web browser on the access URL. """ return self.get_interface(service_type, lax, std_only=True ).to_service() @property def service(self): """ return a service for this resource. This will in general only work if the registry query has constrained the service type; otherwise, many resources will have multiple capabilities. Use get_service instead in such cases. """ if self._service is not None: return self._service self._service = self.get_service(None, True) return self._service def search(self, *args, **keys): """ assuming this resource refers to a searchable service, execute a search against the resource. This is equivalent to: .. code:: python self.to_service().search(*args, **keys) The arguments provided should be appropriate for the service that the DAL service type would expect. See the documentation for the appropriate service type: ============ ========================================= Service type Use the argument syntax for ============ ========================================= catalog :py:meth:`pyvo.dal.scs.SCSService.search` image :py:meth:`pyvo.dal.sia.SIAService.search` spectrum :py:meth:`pyvo.dal.ssa.SSAService.search` line :py:meth:`pyvo.dal.sla.SLAService.search` database *not yet supported* ============ ========================================= Raises ------ DALServiceError if the resource does not describe a searchable service. """ try: return self.service.search(*args, **keys) except ValueError: # I blindly assume the ValueError comes out of get_interface. # But then that's likely enough. raise dalq.DALServiceError( f"Resource {self.ivoid} is not a searchable service") def describe(self, verbose=False, width=78, file=None): """ Print a summary description of this resource. Parameters ---------- verbose : bool If false (default), only user-oriented information is printed. If true, additional information -- reference url, reference to the related article, and alternative identifier (often a DOI) -- will be printed if available. width : int Format the description with given character-width. out : writable file-like object If provided, write information to this output stream. Otherwise, it is written to standard out. """ print(para_format_desc(self.res_title), file=file) print("Short Name: " + self.short_name, file=file) print("IVOA Identifier: " + self.ivoid, file=file) print("Access modes: " + ", ".join(sorted(self.access_modes())), file=file) if len(self._mapping["access_urls"]) == 1: print("Base URL: " + self.access_url, file=file) else: print("Multi-capability service -- use get_service()", file=file) if self.res_description: print(file=file) print(para_format_desc(self.res_description), file=file) print(file=file) if self.waveband: val = (str(v) for v in self.waveband) print( para_format_desc("Waveband Coverage: " + ", ".join(val)), file=file) if verbose: if self.source_value: print(f"\nSource: {self.source_value}", file=file) if self.creators: # if any creator has a name longer than 70 characters, we # truncate it. creators = [f"{creator[:70]}..." if len(creator) > 70 else creator for creator in self.creators] nmax_authors = 5 if len(creators) <= nmax_authors: print(f"Authors: {', '.join(creators)}", file=file) else: print(f"Authors: {', '.join(creators[:nmax_authors])} et al.\n" "See creators attribute for the complete list of authors.", file=file) alt_identifiers = self.get_alt_identifiers() if alt_identifiers: print( "Alternative identifier(s): {}".format( ", ".join(alt_identifiers)), file=file) if self.reference_url: print("More info: " + self.reference_url, file=file) def get_contact(self): """ return contact information for this resource in a string. Use this to report bugs or unexpected downtime. """ res = get_RegTAP_service().run_sync(""" SELECT role_name, email, telephone FROM rr.res_role WHERE base_role='contact' AND ivoid={}""".format( rtcons.make_sql_literal(self.ivoid))) contacts = [] for row in res: contact = row["role_name"] if row["telephone"]: contact += f" ({row['telephone']})" if row["email"]: contact += f" <{row['email']}>" contacts.append(contact) return "\n".join(contacts) def get_alt_identifiers(self): """return a sequence of non-ivoid identifiers for the resource. This is typically used to provide a DOI for the resource. """ res = get_RegTAP_service().run_sync(""" SELECT alt_identifier FROM rr.alt_identifier WHERE ivoid={}""".format(rtcons.make_sql_literal(self.ivoid))) return [r["alt_identifier"] for r in res] def _build_vosi_column(self, column_row): """ return a io.vosi.vodataservice.Column element for a query result from get_tables. """ res = vodataservice.TableParam() for att_name in ["name", "ucd", "unit", "utype"]: setattr(res, att_name, column_row[att_name]) res.description = column_row["column_description"] # TODO: be more careful with the type; this isn't necessarily a # VOTable type (regrettably) res.datatype = vodataservice.VOTableType( arraysize=column_row["arraysize"], extendedType=column_row["extended_type"]) res.datatype.content = column_row["datatype"] return res def _build_vosi_table(self, table_row, columns): """ return a io.vosi.vodataservice.VODataServiceTable element for a query result from get_tables. """ res = vodataservice.VODataServiceTable() res.name = table_row["table_name"] res.title = table_row["table_title"] res.description = table_row["table_description"] res._columns = [ self._build_vosi_column(row) for row in columns] res.origin = self return res def get_tables(self, table_limit=20): """ return the structure of the tables underlying the service. This returns a dict with table names as keys and vodataservice.VODataServiceTable objects as values (pretty much what tables returns for a TAP service). The table instances will have an ``origin`` attribute pointing back to the registry record. Note that not only TAP services can (and do) define table structures. The meaning of non-TAP tables is not always as clear. Also note that resources do not need to define tables at all. You will receive an empty dictionary if they don't. """ svc = get_RegTAP_service() tables = svc.run_sync( """SELECT table_name, table_description, table_index, table_title FROM rr.res_table WHERE ivoid={}""".format( rtcons.make_sql_literal(self.ivoid))) if len(tables) > table_limit: raise dalq.DALQueryError(f"Resource {self.ivoid} reports" f" {len(tables)} tables. Pass a higher table_limit" " to see them all.") res = {} for table_row in tables: columns = svc.run_sync( """ SELECT name, ucd, unit, utype, datatype, arraysize, extended_type, column_description FROM rr.table_column WHERE ivoid={} AND table_index={}""".format( rtcons.make_sql_literal(self.ivoid), rtcons.make_sql_literal(table_row["table_index"]))) res[table_row["table_name"]] = self._build_vosi_table( table_row, columns) return res @deprecated("1.5", "ivoid2service does not work in the presence of" " multiple capabilities. Use" " registry.search(ivoid=...)[0].get_service('capname') instead.") def ivoid2service(ivoid, servicetype=None): """ return service(s) for a given IVOID. The servicetype option specifies the kind of service requested (conesearch, sia, sia2, ssa, slap, or tap). By default, if none is given, a list of all matching services is returned. """ constraints = [rtcons.Ivoid(ivoid)] if servicetype is not None: constraints.append(rtcons.Servicetype(servicetype)) resources = search(*constraints) if len(resources) == 0: if servicetype: raise dalq.DALQueryError(f"No resource {ivoid} with" f" {servicetype} capability.") else: raise dalq.DALQueryError(f"No resource {ivoid}") # We're grouping by ivoid in search, so if there's a result # there is only one. resource = resources[0] return resource.get_service(servicetype, lax=True) pyvo-1.5.2/pyvo/registry/rtcons.py000066400000000000000000001042641462331236700172610ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Constraints for doing registry searches. The Constraint class encapsulates a query fragment in a RegTAP query, e.g., a keyword, a sky location, an author name, a class of services. They are used either directly as arguments to registry.search, or by passing keyword arguments into registry.search. The mapping from keyword arguments to constraint classes happens through the _keyword attribute in Constraint-derived classes. """ import datetime from astropy import units as u from astropy import constants from astropy.coordinates import SkyCoord import numpy from ..dal import query as dalq from ..utils import vocabularies from .import regtap # Classes from this module are exposed at the higher level namespace, not listing them here __all__ = ["build_regtap_query"] # a mapping of service type shorthands to the ivoids of the # corresponding standards. This is mostly to keep legacy APIs. # In the future, preferably rely on shorten_stdid and expand_stdid # from regtap. SERVICE_TYPE_MAP = dict((k, "ivo://ivoa.net/std/" + v) for k, v in [ ("image", "sia"), ("sia", "sia"), # SIA2 is irregular # funky scheme used by SIA2 without breaking everything else ("spectrum", "ssa"), ("ssap", "ssa"), ("ssa", "ssa"), ("scs", "conesearch"), ("conesearch", "conesearch"), ("line", "slap"), ("slap", "slap"), ("table", "tap"), ("tap", "tap"), ]) class RegTAPFeatureMissing(dalq.DALQueryError): """ Raised when the current RegTAP server does not support a feature needed for a constraint. This could be that it is missing some ADQL feature indispensible to write that constraint, or because it is missing a table or column. To recover, choose another RegTAP service. Search constraining ``datamodel="regtap"``, and then use `pyvo.registry.choose_RegTAP_service` with a TAP access URL discovered in this way. """ class _AsIs(str): """a sentinel class make `make_sql_literal` not escape a string. """ def make_sql_literal(value): """makes a SQL literal from a python value. This is not suitable as a device to ward against SQL injections; in what we produce, callers could produce arbitrary SQL anyway. The point of this function is to minimize surprises when building constraints. Parameters ---------- value : object Conceptually, the function should produces SQL literals for anything that might reasonably add up in a registry query. In reality, a ValueError will be raised for anything we do not know about. Returns ------- str A SQL literal. """ if isinstance(value, _AsIs): return value if isinstance(value, str): return "'{}'".format(value.replace("'", "''")) elif isinstance(value, bytes): return "'{}'".format(value.decode("ascii").replace("'", "''")) elif isinstance(value, (int, numpy.integer)): return "{:d}".format(value) elif isinstance(value, (float, numpy.floating)): return repr(value) elif isinstance(value, datetime.datetime): return "'{}'".format(value.isoformat()) else: raise ValueError("Cannot format {} as a SQL literal" .format(repr(value))) def format_function_call(func_name, args): """make an ADQL literal for a function call with arguments. Parameters ---------- func_name : str the name of the function to call. args : sequence of anything python values for the arguments for the function. Returns ------- str ADQL ready for inclusion into a query. """ return "{}({})".format( func_name, ", ".join(make_sql_literal(a) for a in args)) class Constraint: """an abstract base class for data discovery contraints. These, essentially, are configurable RegTAP query fragments, consisting of a where clause, parameters for filling that, and possibly additional tables. Users construct concrete constraints with whatever they would like to constrain things with. To implement a new constraint, in the constructor set ``_condition`` to a string with {}-type replacement fields (assume all parameters are strings), and define ``fillers`` to be a dictionary with values for the _condition template. Don't worry about SQL-serialising the values, Constraint takes care of that. If you need your Constraint to be "lazy" (cf. Servicetype), it's ok to overrride get_search_condition without an upcall to Constraint. If your constraints need extra tables, give them in a list in _extra_tables. For the legacy x_search with keywords, define a _keyword attribute containing the name of the parameter that should generate such a constraint. When pickung up such keywords, sequence values will in general be unpacked and turned into sequences of constraints. Constraints that want to the all arguments in the constructor can set takes_sequence to True. """ _extra_tables = [] _condition = None _fillers = None _keyword = None takes_sequence = False def get_search_condition(self, service): """ Formats this constraint to an ADQL fragment. This takes the service the constraint is being executed on as an argument because constraints may be written differently depending on the service's features or refuse to run altogether. Parameters ---------- service : `~pyvo.dal.TAPService` The RegTAP service the query is supposed to be run on (that is relevant because we adapt to the features available on given services). Returns ------- str A string ready for inclusion into a WHERE clause. """ if self._condition is None: raise NotImplementedError("{} is an abstract Constraint" .format(self.__class__.__name__)) return self._condition.format(**self._get_sql_literals()) def _get_sql_literals(self): """ returns self._fillers as a dictionary of properly SQL-escaped literals. """ if self._fillers: return {k: make_sql_literal(v) for k, v in self._fillers.items()} return {} class Freetext(Constraint): """ A contraint using plain text to match against title, description, subjects, and person names. """ _keyword = "keywords" def __init__(self, *words: str): """ Parameters ---------- *words : tuple of str It is recommended to pass multiple words in multiple strings arguments. You can pass in phrases (i.e., multiple words separated by space), but behaviour might then vary quite significantly between different registries. """ self.words = words def get_search_condition(self, service): # cross-table ORs kill the query planner. We therefore # write the constraint as an IN condition on a UNION # of subqueries if we can (i.e., the service has UNION); # It may look as if this has to be really slow, but in fact it's almost # always a lot faster than direct ORs. if service.get_tap_capability().get_adql().get_feature( "ivo://ivoa.net/std/TAPRegExt#features-adql-sets", "UNION"): return self._get_union_condition(service) else: self._extra_tables = ["rr.res_subject"] return self._get_or_condition(service) def _get_union_condition(self, service): base_queries = [ "SELECT ivoid FROM rr.resource WHERE" " 1=ivo_hasword(res_description, {{{parname}}})", "SELECT ivoid FROM rr.resource WHERE" " 1=ivo_hasword(res_title, {{{parname}}})", "SELECT ivoid FROM rr.res_subject WHERE" " rr.res_subject.res_subject ILIKE {{{parpatname}}}"] self._fillers, subqueries = {}, [] for index, word in enumerate(self.words): parname = "fulltext{}".format(index) parpatname = "fulltextpar{}".format(index) self._fillers[parname] = word self._fillers[parpatname] = '%' + word + '%' args = locals() subqueries.append(" UNION ".join( q.format(**args) for q in base_queries)) self._condition = " AND ".join( f"ivoid IN ({part})" for part in subqueries) return super().get_search_condition(service) def _get_or_condition(self, service): base_queries = [ " 1=ivo_hasword(res_description, {{{parname}}})", " 1=ivo_hasword(res_title, {{{parname}}})", " rr.res_subject.res_subject ILIKE {{{parpatname}}}"] self._fillers, conditions = {}, [] for index, word in enumerate(self.words): parname = "fulltext{}".format(index) parpatname = "fulltextpar{}".format(index) self._fillers[parname] = word self._fillers[parpatname] = '%' + word + '%' args = locals() conditions.append(" OR ".join( q.format(**args) for q in base_queries)) self._condition = " AND ".join(f"({part})" for part in conditions) return super().get_search_condition(service) class Author(Constraint): """ A constraint for creators (“authors”) of a resource; you can use SQL patterns here. The match is case-sensitive. """ _keyword = "author" def __init__(self, name: str): """ Parameters ---------- name : str Note that regrettably there are no guarantees as to how authors are written in the VO. This means that you will generally have to write things like ``%Hubble%`` (% being “zero or more characters” in SQL) here. """ self._condition = "role_name LIKE {auth} AND base_role='creator'" self._fillers = {"auth": name} self._extra_tables = ["rr.res_role"] class Servicetype(Constraint): """ A constraint for for the availability of a certain kind of service on the result. The constraint normally is a custom keyword, one of: * ``image`` (image services; at this point equivalent to sia, but scheduled to include sia2, too) * ``sia`` (SIAP version 1 services) * ``sia2`` (SIAP version 2 services) * ``spectrum``, ``ssa``, ``ssap`` (all synonymous for spectral services, prefer ``spectrum``) * ``scs``, ``conesearch`` (synonymous for cone search services, prefer ``scs``) * ``line`` (for SLAP services) * ``tap``, ``table`` (synonymous for TAP services, prefer ``tap``) You can also pass in the standards' ivoid (which generally looks like ``ivo://ivoa.net/std/`` (except for SIA2) and have to be URIs with a scheme part in any case); note, however, that for standards pyVO does not know about it will not build service instances for you. Multiple service types can be passed in; a match in that case is for records having any of the service types passed in. The match is literal (i.e., no patterns are allowed); this means that you will not receive records that only have auxiliary services, which is what you want when enumerating all services of a certain type in the VO. In data discovery, you can use ``Servicetype(...).include_auxiliary_services()`` or use registry.search's ``includeaux`` parameter; but, really, there is little point using this constraint in data discovery in the first place. """ _keyword = "servicetype" def __init__(self, *stds): """ Parameters ---------- *stds : tuple of str one or more standards identifiers. The constraint will match records that have any of them. """ self.stdids = set() self.extra_fragments = [] for std in stds: if std in SERVICE_TYPE_MAP: self.stdids.add(SERVICE_TYPE_MAP[std]) elif "://" in std: self.stdids.add(std) elif std == 'sia2': self.extra_fragments.append( "standard_id like 'ivo://ivoa.net/std/sia#query-2.%'") else: raise dalq.DALQueryError("Service type {} is neither a full" " standard URI nor one of the bespoke identifiers" " {}, sia2".format(std, ", ".join(SERVICE_TYPE_MAP))) def clone(self): """returns a copy of this servicetype constraint. """ new_constraint = Servicetype() new_constraint.stdids = set(self.stdids) new_constraint.extra_fragments = self.extra_fragments[:] return new_constraint def get_search_condition(self, service): # we sort the stdids to make it easy for tests (and it's # virtually free for the small sets we have here). fragments = [] std_ids = ", ".join(make_sql_literal(s) for s in sorted(self.stdids)) if std_ids: fragments.append(f"standard_id IN ({std_ids})") return " OR ".join(fragments + self.extra_fragments) def include_auxiliary_services(self): """returns a Servicetype constraint that has self's service types but includes the associated auxiliary services. This is a convenience to maintain registry.search's signature. """ expanded = self.clone() expanded.stdids |= set( std + '#aux' for std in expanded.stdids) if "standard_id like 'ivo://ivoa.net/std/sia#query-2.%'" in expanded.extra_fragments: expanded.extra_fragments.append( "standard_id like 'ivo://ivoa.net/std/sia#query-aux-2.%'") return expanded class Waveband(Constraint): """ A constraint on messenger particles. This builds a constraint against rr.resource.waveband, i.e., a verbal indication of the messenger particle, coming from the IVOA vocabulary http://www.ivoa.net/rdf/messenger. The :py:class:`pyvo.registry.Spectral` constraint enables selections by particle energy, but few resources actually give the necessary metadata (in 2021). Multiple wavebands can be given (and are effectively combined with OR). """ _keyword = "waveband" _legal_terms = None def __init__(self, *bands): """ Parameters ---------- *bands : tuple of strings One or more of the terms given in http://www.ivoa.net/rdf/messenger. The constraint matches when a resource declares at least one of the messengers listed. """ if self.__class__._legal_terms is None: self.__class__._legal_terms = {w.lower() for w in vocabularies.get_vocabulary("messenger")["terms"]} bands = [band.lower() for band in bands] for band in bands: if band not in self._legal_terms: raise dalq.DALQueryError( f"Waveband {band} is not in the IVOA messenger" " vocabulary http://www.ivoa.net/rdf/messenger.") self.bands = list(bands) self._condition = " OR ".join( "1 = ivo_hashlist_has(rr.resource.waveband, {})".format( make_sql_literal(band)) for band in self.bands) class Datamodel(Constraint): """ A constraint on the adherence to a data model. This constraint only lets resources pass that declare support for one of several well-known data models; the SQL produced depends on the data model identifier. Known data models at this point include: * obscore -- generic observational data * epntap -- solar system data * regtap -- the VO registry. DM names are matched case-insensitively here mainly for historical reasons. """ _keyword = "datamodel" # if you add to this list, you have to define a method # _make__constraint. _known_dms = {"obscore", "epntap", "regtap"} def __init__(self, dmname): """ Parameters ---------- dmname : string A well-known name; currently one of obscore, epntap, and regtap. """ dmname = dmname.lower() if dmname not in self._known_dms: raise dalq.DALQueryError("Unknown data model id {}. Known are: {}." .format(dmname, ", ".join(sorted(self._known_dms)))) self._condition = getattr(self, f"_make_{dmname}_constraint")() def _make_obscore_constraint(self): # There was a bit of chaos with the DM ids for Obscore. # Be lenient here self._extra_tables = ["rr.res_detail"] obscore_pat = 'ivo://ivoa.net/std/obscore%' return ("detail_xpath = '/capability/dataModel/@ivo-id'" f" AND 1 = ivo_nocasematch(detail_value, '{obscore_pat}')") def _make_epntap_constraint(self): self._extra_tables = ["rr.res_table"] # we include legacy, pre-IVOA utypes for matches; lowercase # any new identifiers (utypes case-fold). return " OR ".join( f"table_utype LIKE '{pat}'" for pat in ['ivo://vopdc.obspm/std/epncore#schema-2.%', 'ivo://ivoa.net/std/epntap#table-2.%']) def _make_regtap_constraint(self): self._extra_tables = ["rr.res_detail"] regtap_pat = 'ivo://ivoa.net/std/RegTAP#1.%' return ("detail_xpath = '/capability/dataModel/@ivo-id'" f" AND 1 = ivo_nocasematch(detail_value, '{regtap_pat}')") class Ivoid(Constraint): """ A constraint selecting a single resource by its IVOA identifier. """ _keyword = "ivoid" def __init__(self, ivoid): """ Parameters ---------- ivoid : string The IVOA identifier of the resource to match. As RegTAP requires lowercasing ivoids on ingestion, the constraint lowercases the ivoid passed in, too. """ self._condition = "ivoid = {ivoid}" self._fillers = {"ivoid": ivoid.lower()} class UCD(Constraint): """ A constraint selecting resources having tables with columns having UCDs matching a SQL pattern (% as wildcard). """ _keyword = "ucd" def __init__(self, *patterns): """ Parameters ---------- patterns : tuple of strings SQL patterns (i.e., ``%`` is 0 or more characters) for UCDs. The constraint will match when a resource has at least one column matching one of the patterns. """ self._extra_tables = ["rr.table_column"] self._condition = " OR ".join( f"ucd LIKE {{ucd{i}}}" for i in range(len(patterns))) self._fillers = dict((f"ucd{index}", pattern) for index, pattern in enumerate(patterns)) class Spatial(Constraint): """ A RegTAP constraint selecting resources covering a geometry in space. This is a RegTAP 1.2 extension not yet available on all Registries (in 2022). Also note that not all data providers give spatial coverage for their resources. To find resources having data for RA/Dec 347.38/8.6772:: >>> from pyvo import registry >>> resources = registry.Spatial((347.38, 8.6772)) To find resources claiming to have data for a spherical circle 2 degrees around that point:: >>> resources = registry.Spatial((347.38, 8.6772, 2)) To find resources claiming to have data for a polygon described by the vertices (23, -40), (26, -39), (25, -43) in ICRS RA/Dec:: >>> resources = registry.Spatial([23, -40, 26, -39, 25, -43]) To find resources claiming to cover a MOC_, pass an ASCII MOC:: >>> resources = registry.Spatial("0/1-3 3/") .. _MOC: https://www.ivoa.net/documents/MOC/ To find resources which coverage is enclosed in a region, >>> enclosed = registry.Spatial("0/0-11", intersect="enclosed") To find resources which coverage intersects a region, >>> overlaps = registry.Spatial("0/0-11", intersect="overlaps") When you already have an astropy SkyCoord:: >>> from astropy.coordinates import SkyCoord >>> resources = registry.Spatial(SkyCoord("23d +3d")) SkyCoords also work as circle centers (plain floats for the radius are interpreted in degrees):: >>> resources = registry.Spatial((SkyCoord("23d +3d"), 3)) """ _keyword = "spatial" _extra_tables = ["rr.stc_spatial"] takes_sequence = True def __init__(self, geom_spec, order=6, intersect="covers"): """ Parameters ---------- geom_spec : object For now, this is DALI-style: a 2-sequence is interpreted as a DALI point, a 3-sequence as a DALI circle, a 2n sequence as a DALI polygon. Additionally, strings are interpreted as ASCII MOCs, SkyCoords as points, and a pair of a SkyCoord and a float as a circle. Other types (proper geometries or MOCPy objects) might be supported in the future. order : int, optional Non-MOC geometries are converted to MOCs before comparing them to the resource coverage. By default, this constraint uses order 6, which corresponds to about a degree of resolution and is what RegTAP recommends as a sane default for the order actually used for the coverages in the database. intersect : str, optional Allows to specify the connection between the resource coverage and the *geom_spec*. The possible values are 'covers' for services that completely cover the *geom_spec* region, 'enclosed' for services completely enclosed in the region and 'overlaps' for services which coverage intersect the region. """ def tomoc(s): return _AsIs("MOC({}, {})".format(order, s)) if isinstance(geom_spec, str): geom = _AsIs("MOC({})".format( make_sql_literal(geom_spec))) elif isinstance(geom_spec, SkyCoord): geom = tomoc(format_function_call("POINT", (geom_spec.ra.value, geom_spec.dec.value))) elif len(geom_spec) == 2: if isinstance(geom_spec[0], SkyCoord): geom = tomoc(format_function_call("CIRCLE", [geom_spec[0].ra.value, geom_spec[0].dec.value, geom_spec[1]])) else: geom = tomoc(format_function_call("POINT", geom_spec)) elif len(geom_spec) == 3: geom = tomoc(format_function_call("CIRCLE", geom_spec)) elif len(geom_spec) % 2 == 0: geom = tomoc(format_function_call("POLYGON", geom_spec)) else: raise ValueError("This constraint needs DALI-style geometries.") if intersect == "covers": self._condition = f"1 = CONTAINS({geom}, coverage)" elif intersect == "enclosed": self._condition = f"1 = CONTAINS(coverage, {geom})" elif intersect == "overlaps": self._condition = f"1 = INTERSECTS(coverage, {geom})" else: raise ValueError("'intersect' should be one of 'covers', 'enclosed', or 'overlaps' " f"but its current value is '{intersect}'.") def get_search_condition(self, service): # we *could* make this a bit less demanding on the server # if we MOC-ified the geometries locally -- but then we'd # have to depend on pymoc, and that's too high a price for # something as esoteric as a server that understands # MOC-based geometries but does not have a MOC function. if not service.get_tap_capability().get_adql().get_feature( "ivo://org.gavo.dc/std/exts#extra-adql-keywords", "MOC"): raise RegTAPFeatureMissing("Current RegTAP service does not support MOC.") # We should compare case-insensitively here, but then we don't # with delimited identifiers -- in the end, that would have to # be handled in dal.vosi.VOSITables. if "rr.stc_spatial" not in service.tables: raise RegTAPFeatureMissing("stc_spatial missing on current RegTAP service") return super().get_search_condition(service) class Spectral(Constraint): """ A RegTAP constraint on the spectral coverage of resources. This is a RegTAP 1.2 extension not yet available on all Registries (in 2022). Worse, not too many resources bother declaring this at this point. For robustness, it might be preferable to use the `Waveband` constraint for the time being.. This constraint accepts quantities, i.e., values with units, and will convert them to RegTAP's representation (which is Joule of particle energy) if it can. This ought to work for wavelengths, frequencies, and energies. Plain numbers are interpreted as particle energies in Joule. RegTAP uses the observer frame at the solar system barycenter, but it is probably wise to use constraints suitably relaxed such that frame and reference position (within reason) do not matter. To find resources covering the messenger particle energy 5 eV:: >>> from astropy import units as u >>> from pyvo import registry >>> resources = registry.Spectral(5*u.eV) To find resources overlapping the band between 5000 and 6000 Ångström:: >>> resources = registry.Spectral((5000*u.Angstrom, 6000*u.Angstrom)) To find resources having data in the FM band:: >>> resources = registry.Spectral((88*u.MHz, 102*u.MHz)) """ _keyword = "spectral" _extra_tables = ["rr.stc_spectral"] takes_sequence = True def __init__(self, spec): """ Parameters ---------- spec : astropy.Quantity or a 2-tuple of astropy.Quantity-s A spectral point or interval to cover. This must be a wavelength, a frequency, or an energy, or a pair of such quantities, in which case the argument is interpreted as an interval. All resources *overlapping* the interval are returned. Plain floats are interpreted as messenger energy in Joule. """ if isinstance(spec, tuple): self._fillers = { "spec_lo": self._to_joule(spec[0]), "spec_hi": self._to_joule(spec[1])} self._condition = ("1 = ivo_interval_overlaps(" "spectral_start, spectral_end, {spec_lo}, {spec_hi})") else: self._fillers = { "spec": self._to_joule(spec)} self._condition = "{spec} BETWEEN spectral_start AND spectral_end" def _to_joule(self, quant): """returns a spectral quantity as a float in joule. A plain float is returned as-is. """ if isinstance(quant, (float, int)): return quant try: # is it an energy? return quant.to(u.Joule).value except u.UnitConversionError: pass # try next try: # is it a wavelength? return (constants.h * constants.c / quant.to(u.m)).value except u.UnitConversionError: pass # try next try: # is it a frequency? return (constants.h * quant.to(u.Hz)).value except u.UnitConversionError: pass # fall through to give up raise ValueError(f"Cannot make a spectral quantity out of {quant}") def get_search_condition(self, service): if "rr.stc_spectral" not in service.tables: raise RegTAPFeatureMissing("stc_spectral missing on current RegTAP service") return super().get_search_condition(service) class Temporal(Constraint): """ A RegTAP constraint on the temporal coverage of resources. This is a RegTAP 1.2 extension not yet available on all Registries (in 2022). Worse, not too many resources bother declaring this at this point. Until this changes, you will probably have a lot of false negatives (i.e., resources that should match but do not because they are not declaring their time coverage) if you use this constraint. This constraint accepts astropy Time instances or pairs of Times when specifying intervals. Plain numbers will be interpreted as MJD. RegTAP uses TDB times at the solar system barycenter, and it is probably wise to relax constraints such that such details do not matter. This constraint does not attempt any conversions of time scales or reference positions. To find resources claiming to have data for Jan 10, 2022:: >>> from pyvo import registry >>> from astropy.time import Time >>> resources = registry.Temporal(Time('2022-01-10')) To find resources claiming to have data for some time between MJD 54130 and 54200:: >>> resources = registry.Temporal((54130, 54200)) """ _keyword = "temporal" _extra_tables = ["rr.stc_temporal"] takes_sequence = True def __init__(self, times): """ Parameters ---------- spec : astropy.Time or a 2-tuple of astropy.Time-s A point in time or time interval to cover. Plain numbers are interpreted as MJD. All resources *overlapping* the interval are returned. """ if isinstance(times, tuple): self._fillers = { "time_lo": self._to_mjd(times[0]), "time_hi": self._to_mjd(times[1])} self._condition = ("1 = ivo_interval_overlaps(" "time_start, time_end, {time_lo}, {time_hi})") else: self._fillers = { "time": self._to_mjd(times)} self._condition = "{time} BETWEEN time_start AND time_end" def _to_mjd(self, quant): """returns a time specification in MJD. Times not corresponding to a single point in time are rejected. A plain float is returned as-is. """ if isinstance(quant, (float, int)): return quant val = quant.to_value('mjd') if not isinstance(val, numpy.number): raise ValueError("RegTAP time constraints must be made from" " single time instants.") return val def get_search_condition(self, service): if "rr.stc_temporal" not in service.tables: raise RegTAPFeatureMissing("stc_temporal missing on current RegTAP service") return super().get_search_condition(service) # NOTE: If you add new Contraint-s, don't forget to add them in # registry.__init__, in docs/registry/index.rst and in the docstring # of regtap.query. def build_regtap_query(constraints, service): """returns a RegTAP query ready for submission from a list of Constraint instances. Parameters ---------- constraints : sequence of `~pyvo.registry.Constraint`-s A sequence of constraints for a RegTAP query. All of them will become part of a conjunction (i.e., all of them have to be satisfied for a record to match). service : `~pyvo.dal.TAPService` The RegTAP service the query is supposed to be run on (that is relevant because we adapt to the features available on given services). Returns ------- str An ADQL literal ready for submission to a RegTAP service. """ if not constraints: raise dalq.DALQueryError( "No search parameters passed to registry search") serialized, extra_tables = [], set() for constraint in constraints: if isinstance(constraint, str): constraint = Freetext(constraint) serialized.append( "(" + constraint.get_search_condition(service) + ")") extra_tables |= set(constraint._extra_tables) joined_tables = ["rr.resource", "rr.capability", "rr.interface", "rr.alt_identifier" ] + list(extra_tables) # see comment in regtap.RegistryResource for the following # oddity select_clause, plain_columns = [], [] for col_desc in regtap.RegistryResource.expected_columns: if isinstance(col_desc, str): select_clause.append(col_desc) plain_columns.append(col_desc) else: select_clause.append("{} AS {}".format(*col_desc)) fragments = ["SELECT", ", ".join(select_clause), "FROM", "\nNATURAL LEFT OUTER JOIN ".join(joined_tables), "WHERE", "\n AND ".join(serialized), "GROUP BY", ", ".join(plain_columns)] return "\n".join(fragments) def keywords_to_constraints(keywords): """returns constraints expressed as keywords as Constraint instances. Parameters ---------- keywords : dict regsearch arguments as a kwargs-style dictionary. Returns ------- sequence of `Constraint`-s Raises ------ DALQueryError if an unknown keyword is encountered. """ constraints = [] for keyword, value in keywords.items(): if keyword not in _KEYWORD_TO_CONSTRAINT: raise TypeError(f"{keyword} is not a valid registry" " constraint keyword. Use one of {}.".format( ", ".join(sorted(_KEYWORD_TO_CONSTRAINT)))) constraint_class = _KEYWORD_TO_CONSTRAINT[keyword] if (isinstance(value, (tuple, list)) and not constraint_class.takes_sequence): constraints.append(constraint_class(*value)) else: constraints.append(constraint_class(value)) return constraints def _make_constraint_map(): """returns a map of _keyword to constraint classes. This is used in module initialisation. """ keyword_to_constraint = {} for att_name, obj in globals().items(): if (isinstance(obj, type) and issubclass(obj, Constraint) and obj._keyword): keyword_to_constraint[obj._keyword] = obj return keyword_to_constraint _KEYWORD_TO_CONSTRAINT = _make_constraint_map() pyvo-1.5.2/pyvo/registry/tests/000077500000000000000000000000001462331236700165325ustar00rootroot00000000000000pyvo-1.5.2/pyvo/registry/tests/__init__.py000066400000000000000000000001001462331236700206320ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst pyvo-1.5.2/pyvo/registry/tests/commonfixtures.py000066400000000000000000000041171462331236700221710ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Common fixtures for pyVO registry tests """ import pytest from astropy.utils.data import ( get_pkg_data_filename, import_file_to_cache) # We need to populate the vocabulary cache with our test data; # we cannot use requests_mock here because a.u.data uses urllib. from astropy.utils.data import _get_download_cache_loc, _url_to_dirname # noqa: F401 @pytest.fixture() def messenger_vocabulary(mocker): """the IVOA messenger vocabulary in astropy's cache. Should we clean up after ourselves? """ import_file_to_cache( 'http://www.ivoa.net/rdf/messenger', get_pkg_data_filename( 'data/messenger.desise', package=__package__)) # We need an object standing in for TAP services for query generation. # It would perhaps be nice to pull up a real TAPService instance from # capabilities and tables, but that's a non-trivial amount of XML. # Let's see how far we get with faking it. class _FakeLanguage: """ a stand-in for vosi.tapregext.Language for rtcons.Constrants. """ def __init__(self, features): self.features = features def get_feature(self, type, form): return (type, form) in self.features class _FakeTAPService: """ A stand-in for a TAP service intended for rtcons.Constraints. features is a set of (type, form) tuples for now. tables is a dict with table names as keys (let's worry about the values later). """ def __init__(self, features, tables): self.tables = tables adql_lang = _FakeLanguage(features) class _: def get_adql(otherself): return adql_lang self.tap_cap = _() def get_tap_capability(self): return self.tap_cap FAKE_GAVO = _FakeTAPService({ ("ivo://ivoa.net/std/TAPRegExt#features-adql-sets", "UNION"), ("ivo://org.gavo.dc/std/exts#extra-adql-keywords", "MOC"), }, { "rr.stc_spatial": None, "rr.stc_spectral": None, "rr.stc_temporal": None, }) FAKE_PLAIN = _FakeTAPService(frozenset(), {}) pyvo-1.5.2/pyvo/registry/tests/conftest.py000066400000000000000000000012661462331236700207360ustar00rootroot00000000000000from contextlib import contextmanager import pytest import requests_mock class ContextAdapter(requests_mock.Adapter): """ requests_mock adapter where ``register_uri`` returns a context manager """ @contextmanager def register_uri(self, *args, **kwargs): matcher = super().register_uri(*args, **kwargs) yield matcher self.remove_matcher(matcher) def remove_matcher(self, matcher): if matcher in self._matchers: self._matchers.remove(matcher) @pytest.fixture(scope='function') def mocker(): with requests_mock.Mocker( adapter=ContextAdapter(case_sensitive=True) ) as mocker_ins: yield mocker_ins pyvo-1.5.2/pyvo/registry/tests/data/000077500000000000000000000000001462331236700174435ustar00rootroot00000000000000pyvo-1.5.2/pyvo/registry/tests/data/README000066400000000000000000000001421462331236700203200ustar00rootroot00000000000000Instructions for how to update these data items should be found in the fixtures in test_regtap.py pyvo-1.5.2/pyvo/registry/tests/data/capabilities.xml000066400000000000000000000102631462331236700226200ustar00rootroot00000000000000 http://example.org/tap/availability https://example.org/tap/availability http://example.org/tap/capabilities https://example.org/tap/capabilities http://example.org/tap/tables https://example.org/tap/tables http://example.org/tap https://example.org/tap Obscore-1.1 Registry 1.0 GloTS 1.0 Obscore-1.0 ADQL 2.0 ADQL 2.0
form 1
description 1
form 2
description 2
BOX
POINT
TABLESAMPLE
Written after a table reference, ...
MOC
A geometry function creating MOCs...
text/xml text/html html 172800 3600 2000 10000000 100000000
pyvo-1.5.2/pyvo/registry/tests/data/messenger.desise000066400000000000000000000047401462331236700226360ustar00rootroot00000000000000{ "uri": "http://www.ivoa.net/rdf/messenger", "flavour": "RDF Class", "terms": { "Photon": { "label": "Photon", "description": " Carrier particles of the electromagnetic interaction", "preliminary": "", "wider": [], "narrower": [ "Radio", "Millimeter", "Infrared", "Optical", "UV", "X-ray", "Gamma-ray", "EUV" ] }, "Radio": { "label": "Radio", "description": " Photon with a wavelength longer than 10 mm (or \u03bd<30 GHz)", "preliminary": "", "wider": [ "Photon" ], "narrower": [] }, "Millimeter": { "label": "Millimeter", "description": " Photon with a wavelength between 0.1 mm and 10 mm (or 30 GHz<=\u03bd<300 GHz)", "preliminary": "", "wider": [ "Photon" ], "narrower": [] }, "Infrared": { "label": "Infrared", "description": " Photon with a wavelength between 1 \u00b5m and 100 \u00b5m", "preliminary": "", "wider": [ "Photon" ], "narrower": [] }, "Optical": { "label": "Optical", "description": " Photon with a wavelength between 300 nm and 1000 nm", "preliminary": "", "wider": [ "Photon" ], "narrower": [] }, "UV": { "label": "Ultraviolet", "description": " Photon with a wavelength between 100 nm and 300 nm", "preliminary": "", "wider": [ "Photon" ], "narrower": [ "EUV" ] }, "EUV": { "label": "Extreme UV", "description": " Photon with an energy between 12 eV and 120 eV", "preliminary": "", "wider": [ "UV" ], "narrower": [] }, "X-ray": { "label": "X-Ray", "description": " Photon with an energy between 120 eV and 120 keV", "preliminary": "", "wider": [ "Photon" ], "narrower": [] }, "Gamma-ray": { "label": "Gamma Ray", "description": " Photon with an energy above 120 keV", "preliminary": "", "wider": [ "Photon" ], "narrower": [] }, "Neutrino": { "label": "Neutrino", "description": " This term comprises all generations of neutrinos (electron, \u00b5, \u03c4), and particles as well as antiparticles.", "preliminary": "", "wider": [], "narrower": [] } } }pyvo-1.5.2/pyvo/registry/tests/data/multi-interface.xml000066400000000000000000000263451462331236700232670ustar00rootroot00000000000000 ADQL query translated to local SQL (for debugging)Original ADQL queryQuery successfulSoftware that produced this VOTableBase URI of the serverAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceOriginating VO resourceData centre that has delivered the dataContact optionMore information on the data SourceMore information on the data SourceMore information on the data SourceMore information on the data SourceMore information on the data SourceName of a person or entity that produced a contributing resource The terms are taken from the vocabulary http://ivoa.net/rdf/voresource/content_level. The terms are taken from the vocabulary http://ivoa.net/rdf/voresource/content_type. The allowed values for waveband include: Radio, Millimeter, Infrared, Optical, UV, EUV, X-ray, Gamma-ray.Unambiguous reference to the resource conforming to the IVOA standard for identifiers.Resource type (something like vg:authority, vs:catalogservice, etc).A short name or abbreviation given to something, for presentation in space-constrained fields (up to 16 characters).The full name given to the resource.A hash-separated list of content levels specifying the intended audience.An account of the nature of the resource.URL pointing to a human-readable document describing this resource.The creator(s) of the resource in the order given by the resource record author, separated by semicolons.A hash-separated list of natures or genres of the content of the resource.The format of source_value. This, in particular, can be ``bibcode''.A bibliographic reference from which the present resource is derived or extracted.A single numeric value representing the angle, given in decimal degrees, by which a positional query against this resource should be ``blurred'' in order to get an appropriate match.A hash-separated list of regions of the electro-magnetic spectrum that the resource's spectral coverage overlaps with.An identifier for the resource or an entity related to the resource in URI form.AAAAIml2bzovL29yZy5nYXZvLmRjL2ZsYXNoaGVyb3MvcS9zc2EAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAAQRmxhc2gvSGVyb3MgU1NBUAAAABAARgBsAGEAcwBoAC8ASABlAHIAbwBzACAAUwBTAEEAUAAAAAAAAAEVAFMAcABlAGMAdAByAGEAIABmAHIAbwBtACAAdABoAGUAIABGAGwAYQBzAGgAIABhAG4AZAAgAEgAZQByAG8AcwAgAEUAYwBoAGUAbABsAGUAIABzAHAAZQBjAHQAcgBvAGcAcgBhAHAAaABzACAAZABlAHYAZQBsAG8AcABlAGQAIABhAHQACgBMAGEAbgBkAGUAcwBzAHQAZQByAG4AdwBhAHIAdABlACAASABlAGkAZABlAGwAYgBlAHIAZwAgAGEAbgBkACAAbQBvAHUAbgB0AGUAZAAgAGEAdAAgAEwAYQAgAFMAaQBsAGwAYQAgAGEAbgBkACAAdgBhAHIAaQBvAHUAcwAgAG8AdABoAGUAcgAKAG8AYgBzAGUAcgB2AGEAdABvAHIAaQBlAHMALgAgAFQAaABlACAAZABhAHQAYQAgAG0AbwBzAHQAbAB5ACAAYwBvAG4AdABhAGkAbgBzACAAcwBwAGUAYwB0AHIAYQAgAG8AZgAgAE8AQgAgAHMAdABhAHIAcwAuACAASABlAHIAbwBzACAAdwBhAHMACgB0AGgAZQAgAG4AYQBtAGUAIABvAGYAIAB0AGgAZQAgAGkAbgBzAHQAcgB1AG0AZQBuAHQAIABhAGYAdABlAHIAIABGAGwAYQBzAGgAIABnAG8AdAAgAGEAIABzAGUAYwBvAG4AZAAgAGMAaABhAG4AbgBlAGwAIABpAG4AIAAxADkAOQA1AC4AAAA1aHR0cDovL2RjLnphaC51bmktaGVpZGVsYmVyZy5kZS9mbGFzaGhlcm9zL3Evc3NhL2luZm8AAAArAFcAbwBsAGYALAAgAEIALgA7ACAASwBhAHUAZgBlAHIALAAgAEEALgA7ACAATQBhAG4AZABlAGwALAAgAEgALgA7ACAAUwB0AGEAaABsACwAIABPAC4AAAAAAAAAB2JpYmNvZGUAAAATADEAOQA5ADYAQQAmAEEALgAuAC4AMwAxADIALgAuADUAMwA5AFN/wAAAAAAAB29wdGljYWwAAAIMaHR0cDovL2RjLnphaC51bmktaGVpZGVsYmVyZy5kZS9mbGFzaGhlcm9zL3Evc2RsL2RsbWV0YTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly9kYy56YWgudW5pLWhlaWRlbGJlcmcuZGUvZmxhc2hoZXJvcy9xL3NkbC9kbGdldDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly9kYy56YWgudW5pLWhlaWRlbGJlcmcuZGUvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL2RjLnphaC51bmktaGVpZGVsYmVyZy5kZS9mbGFzaGhlcm9zL3Evc3NhL3RhYmxlTWV0YWRhdGE6OjpweSBWTyBzZXA6OjpodHRwOi8vZGMuemFoLnVuaS1oZWlkZWxiZXJnLmRlL2ZsYXNoaGVyb3MvcS9zc2EvY2FwYWJpbGl0aWVzOjo6cHkgVk8gc2VwOjo6aHR0cDovL2RjLnphaC51bmktaGVpZGVsYmVyZy5kZS9mbGFzaGhlcm9zL3Evc3NhL2F2YWlsYWJpbGl0eTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly9kYy56YWgudW5pLWhlaWRlbGJlcmcuZGUvZmxhc2hoZXJvcy9xL3dlYi9mb3JtOjo6cHkgVk8gc2VwOjo6aHR0cDovL2RjLnphaC51bmktaGVpZGVsYmVyZy5kZS9maHNzYT8AAAFEaXZvOi8vaXZvYS5uZXQvc3RkL2RhdGFsaW5rI2xpbmtzLTEuMTo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9zb2RhI3N5bmMtMS4wOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdm9zaSN0YWJsZXM6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdm9zaSNjYXBhYmlsaXRpZXM6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdm9zaSNhdmFpbGFiaWxpdHk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvc3NhAAAAynZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHAAAAB+c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6c3RkAAAAI2RvaToxMC4yMTkzOC9vVXVxYWpuV21vZlAydzc1LkFjYzhn
pyvo-1.5.2/pyvo/registry/tests/data/regtap.xml000066400000000000000000006423251462331236700214630ustar00rootroot00000000000000 ADQL query translated to local SQL (for debugging)Original ADQL queryQuery successfulSoftware that produced this VOTableBase URI of the serverAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceAdvice on citing this resourceOriginating VO resourceData centre that has delivered the dataContact optionMore information on the data SourceMore information on the data SourceMore information on the data SourceMore information on the data SourceMore information on the data SourceMore information on the data SourceMore information on the data SourceName of a person or entity that produced a contributing resource The terms are taken from the vocabulary http://ivoa.net/rdf/voresource/content_level. The terms are taken from the vocabulary http://ivoa.net/rdf/voresource/content_type. The allowed values for waveband include: Radio, Millimeter, Infrared, Optical, UV, EUV, X-ray, Gamma-ray.Unambiguous reference to the resource conforming to the IVOA standard for identifiers.Resource type (something like vg:authority, vs:catalogservice, etc).A short name or abbreviation given to something, for presentation in space-constrained fields (up to 16 characters).The full name given to the resource.A hash-separated list of content levels specifying the intended audience.An account of the nature of the resource.URL pointing to a human-readable document describing this resource.The creator(s) of the resource in the order given by the resource record author, separated by semicolons.The UTC date and time this resource metadata description was created.The UTC date this resource metadata description was last updated.A statement of usage conditions (license, attribution, embargo, etc).A hash-separated list of natures or genres of the content of the resource.The format of source_value. This, in particular, can be ``bibcode''.A bibliographic reference from which the present resource is derived or extracted.A single numeric value representing the angle, given in decimal degrees, by which a positional query against this resource should be ``blurred'' in order to get an appropriate match.A hash-separated list of regions of the electro-magnetic spectrum that the resource's spectral coverage overlaps with.An identifier for the resource or an entity related to the resource in URI form.AAAAHml2bzovL2Nkcy52aXppZXIvai9hK2EvNDkyLzkyMwAAABF2czpjYXRhbG9nc2VydmljZQAAAA1KL0ErQS80OTIvOTIzAAAAMQBQAHUAbABzAGEAcgAgAFQAaQBtAGkAbgBnACAAZgBvAHIAIABGAGUAcgBtAGkAIABHAGEAbQBtAGEALQByAGEAeQAgAFMAcABhAGMAZQAgAFQAZQBsAGUAcwBjAG8AcABlAAAACHJlc2VhcmNoAAADhABXAGUAIABkAGUAcwBjAHIAaQBiAGUAIABhACAAYwBvAG0AcAByAGUAaABlAG4AcwBpAHYAZQAgAHAAdQBsAHMAYQByACAAbQBvAG4AaQB0AG8AcgBpAG4AZwAgAGMAYQBtAHAAYQBpAGcAbgAgAGYAbwByACAAdABoAGUAIABMAGEAcgBnAGUAIABBAHIAZQBhACAAVABlAGwAZQBzAGMAbwBwAGUAIAAoAEwAQQBUACkAIABvAG4AIAB0AGgAZQAgAEYAZQByAG0AaQAgAEcAYQBtAG0AYQAtAHIAYQB5ACAAUwBwAGEAYwBlACAAVABlAGwAZQBzAGMAbwBwAGUAIAAoAGYAbwByAG0AZQByAGwAeQAgAEcATABBAFMAVAApAC4AIABUAGgAZQAgAGQAZQB0AGUAYwB0AGkAbwBuACAAYQBuAGQAIABzAHQAdQBkAHkAIABvAGYAIABwAHUAbABzAGEAcgBzACAAaQBuACAAZwBhAG0AbQBhACAAcgBhAHkAcwAgAGcAaQB2AGUAIABpAG4AcwBpAGcAaAB0AHMAIABpAG4AdABvACAAdABoAGUAIABwAG8AcAB1AGwAYQB0AGkAbwBuAHMAIABvAGYAIABuAGUAdQB0AHIAbwBuACAAcwB0AGEAcgBzACAAYQBuAGQAIABzAHUAcABlAHIAbgBvAHYAYQAgAHIAYQB0AGUAcwAgAGkAbgAgAHQAaABlACAARwBhAGwAYQB4AHkALAAgAGkAbgB0AG8AIABwAGEAcgB0AGkAYwBsAGUAIABhAGMAYwBlAGwAZQByAGEAdABpAG8AbgAgAG0AZQBjAGgAYQBuAGkAcwBtAHMAIABpAG4AIABuAGUAdQB0AHIAbwBuACAAcwB0AGEAcgAgAG0AYQBnAG4AZQB0AG8AcwBwAGgAZQByAGUAcwAsACAAYQBuAGQAIABpAG4AdABvACAAdABoAGUAIAAiAGUAbgBnAGkAbgBlAHMAIgAgAGQAcgBpAHYAaQBuAGcAIABwAHUAbABzAGEAcgAgAHcAaQBuAGQAIABuAGUAYgB1AGwAYQBlAC4AIABMAEEAVAAnAHMAIAB1AG4AcAByAGUAYwBlAGQAZQBuAHQAZQBkACAAcwBlAG4AcwBpAHQAaQB2AGkAdAB5ACAAYgBlAHQAdwBlAGUAbgAgADIAMABNAGUAVgAgAGEAbgBkACAAMwAwADAARwBlAFYAIAB0AG8AZwBlAHQAaABlAHIAIAB3AGkAdABoACAAaQB0AHMAIAAyAC4ANABzAHIAIABmAGkAZQBsAGQALQBvAGYALQB2AGkAZQB3ACAAbQBhAGsAZQBzACAAZABlAHQAZQBjAHQAaQBvAG4AIABvAGYAIABtAGEAbgB5ACAAZwBhAG0AbQBhAC0AcgBhAHkAIABwAHUAbABzAGEAcgBzACAAbABpAGsAZQBsAHkALAAgAGoAdQBzAHQAaQBmAHkAaQBuAGcAIAB0AGgAZQAgAG0AbwBuAGkAdABvAHIAaQBuAGcAIABvAGYAIABvAHYAZQByACAAdAB3AG8AIABoAHUAbgBkAHIAZQBkACAAcAB1AGwAcwBhAHIAcwAgAHcAaQB0AGgAIABsAGEAcgBnAGUAIABzAHAAaQBuAC0AZABvAHcAbgAgAHAAbwB3AGUAcgBzAC4AIABUAG8AIABzAGUAYQByAGMAaAAgAGYAbwByACAAZwBhAG0AbQBhAC0AcgBhAHkAIABwAHUAbABzAGEAdABpAG8AbgBzACAAZgByAG8AbQAgAG0AbwBzAHQAIABvAGYAIAB0AGgAZQBzAGUAIABwAHUAbABzAGEAcgBzACAAcgBlAHEAdQBpAHIAZQBzACAAYQAgAHMAZQB0ACAAbwBmACAAcABoAGEAcwBlAC0AYwBvAG4AbgBlAGMAdABlAGQAIAB0AGkAbQBpAG4AZwAgAHMAbwBsAHUAdABpAG8AbgBzACAAcwBwAGEAbgBuAGkAbgBnACAAYQAgAHkAZQBhAHIAIABvAHIAIABtAG8AcgBlACAAdABvACAAcAByAG8AcABlAHIAbAB5ACAAYQBsAGkAZwBuACAAdABoAGUAIABzAHAAYQByAHMAZQAgAHAAaABvAHQAbwBuACAAYQByAHIAaQB2AGEAbAAgAHQAaQBtAGUAcwAuACAAVwBlACAAZABlAHMAYwByAGkAYgBlACAAdABoAGUAIABjAGgAbwBpAGMAZQAgAG8AZgAgAHAAdQBsAHMAYQByAHMAIABhAG4AZAAgAHQAaABlACAAaQBuAHMAdAByAHUAbQBlAG4AdABzACAAaQBuAHYAbwBsAHYAZQBkACAAaQBuACAAdABoAGUAIABjAGEAbQBwAGEAaQBnAG4ALgAAADdodHRwczovL2Nkc2FyYy5jZHMudW5pc3RyYS5mci92aXotYmluL2NhdC9KL0ErQS80OTIvOTIzAAABeQBTAG0AaQB0AGgAIABEAC4AQQAuADsAIABHAHUAaQBsAGwAZQBtAG8AdAAgAEwALgA7ACAAQwBhAG0AaQBsAG8AIABGAC4AOwAgAEMAbwBnAG4AYQByAGQAIABJAC4AOwAgAEQAdQBtAG8AcgBhACAARAAuADsAIABFAHMAcABpAG4AbwB6AGEAIABDAC4AOwAgAEYAcgBlAGkAcgBlACAAUAAuAEMALgBDAC4AOwAgAEcAbwB0AHQAaABlAGwAZgAgAEUALgBWAC4AOwAgAEgAYQByAGQAaQBuAGcAIABBAC4ASwAuADsAIABIAG8AYgBiAHMAIABHAC4AQgAuADsAIABKAG8AaABuAHMAdABvAG4AIABTAC4AOwAgAEsAYQBzAHAAaQAgAFYALgBNAC4AOwAgAEsAcgBhAG0AZQByACAATQAuADsAIABMAGkAdgBpAG4AZwBzAHQAbwBuAGUAIABNAC4AQQAuADsAIABMAHkAbgBlACAAQQAuAEcALgA7ACAATQBhAG4AYwBoAGUAcwB0AGUAcgAgAFIALgBOAC4AOwAgAE0AYQByAHMAaABhAGwAbAAgAEYALgBFAC4AOwAgAE0AYwBMAGEAdQBnAGgAbABpAG4AIABNAC4AQQAuADsAIABOAG8AdQB0AHMAbwBzACAAQQAuADsAIABSAGEAbgBzAG8AbQAgAFMALgBNAC4AOwAgAFIAbwBiAGUAcgB0AHMAIABNAC4AUwAuAEUALgA7ACAAUgBvAG0AYQBuAGkAIABSAC4AVwAuADsAIABTAHQAYQBwAHAAZQByAHMAIABCAC4AVwAuADsAIABUAGgAZQB1AHIAZQBhAHUAIABHAC4AOwAgAFQAaABvAG0AcABzAG8AbgAgAEQALgBKAC4AOwAgAFQAaABvAHIAcwBlAHQAdAAgAFMALgBFAC4AOwAgAFcAYQBuAGcAIABOAC4AOwAgAFcAZQBsAHQAZQB2AHIAZQBkAGUAIABQAC4AAAATMjAwOS0wMS0xN1QxNzowMzo1OQAAABMyMDIyLTEwLTEwVDAwOjAwOjAwAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAwADgAQQAmAEEALgAuAC4ANAA5ADIALgAuADkAMgAzAFN/wAAAAAAAD2dhbW1hLXJheSNyYWRpbwAAASRodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0ErQS80OTIvOTIzL3RhYmxlMT86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL0ErQS80OTIvOTIzOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL0ErQS80OTIvOTIzOjo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwAAAAZGl2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXgAAABednM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cAAAADNzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQAAAAgZG9pOjEwLjI2MDkzL2Nkcy92aXppZXIuMzQ5MjA5MjMAAAAdaXZvOi8vY2RzLnZpemllci9qL2ErYS82MTIvYTEAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAAMSi9BK0EvNjEyL0ExAAAAHgBIAC4ARQAuAFMALgBTAC4AIABHAGEAbABhAGMAdABpAGMAIABQAGwAYQBuAGUAIABTAHUAcgB2AGUAeQAAAAhyZXNlYXJjaAAACCoAVwBlACAAcAByAGUAcwBlAG4AdAAgAHQAaABlACAAcgBlAHMAdQBsAHQAcwAgAG8AZgAgAHQAaABlACAAbQBvAHMAdAAgAGMAbwBtAHAAcgBlAGgAZQBuAHMAaQB2AGUAIABzAHUAcgB2AGUAeQAgAG8AZgAgAHQAaABlACAARwBhAGwAYQBjAHQAaQBjACAAcABsAGEAbgBlACAAaQBuACAAdgBlAHIAeQAgAGgAaQBnAGgALQBlAG4AZQByAGcAeQAgACgAVgBIAEUAKQAgAGcAYQBtAG0AYQAtAHIAYQB5AHMALAAgAGkAbgBjAGwAdQBkAGkAbgBnACAAYQAgAHAAdQBiAGwAaQBjACAAcgBlAGwAZQBhAHMAZQAgAG8AZgAgAEcAYQBsAGEAYwB0AGkAYwAgAHMAawB5ACAAbQBhAHAAcwAsACAAYQAgAGMAYQB0AGEAbABvAGcAIABvAGYAIABWAEgARQAgAHMAbwB1AHIAYwBlAHMALAAgAGEAbgBkACAAdABoAGUAIABkAGkAcwBjAG8AdgBlAHIAeQAgAG8AZgAgADEANgAgAG4AZQB3ACAAcwBvAHUAcgBjAGUAcwAgAG8AZgAgAFYASABFACAAZwBhAG0AbQBhAC0AcgBhAHkAcwAuACAAVABoAGUAIABIAGkAZwBoACAARQBuAGUAcgBnAHkAIABTAHAAZQBjAHQAcgBvAHMAYwBvAHAAaQBjACAAUwB5AHMAdABlAG0AIAAoAEgALgBFAC4AUwAuAFMALgApACAARwBhAGwAYQBjAHQAaQBjACAAcABsAGEAbgBlACAAcwB1AHIAdgBlAHkAIAAoAEgARwBQAFMAKQAgAHcAYQBzACAAYQAgAGQAZQBjAGEAZABlAC0AbABvAG4AZwAgAG8AYgBzAGUAcgB2AGEAdABpAG8AbgAgAHAAcgBvAGcAcgBhAG0AIABjAGEAcgByAGkAZQBkACAAbwB1AHQAIABiAHkAIAB0AGgAZQAgAEgALgBFAC4AUwAuAFMALgAgAEkAIABhAHIAcgBhAHkAIABvAGYAIABDAGgAZQByAGUAbgBrAG8AdgAgAHQAZQBsAGUAcwBjAG8AcABlAHMAIABpAG4AIABOAGEAbQBpAGIAaQBhACAAZgByAG8AbQAgADIAMAAwADQAIAB0AG8AIAAyADAAMQAzAC4AIABUAGgAZQAgAG8AYgBzAGUAcgB2AGEAdABpAG8AbgBzACAAYQBtAG8AdQBuAHQAIAB0AG8AIABuAGUAYQByAGwAeQAgADIANwAwADAAIABoACAAbwBmACAAcQB1AGEAbABpAHQAeQAtAHMAZQBsAGUAYwB0AGUAZAAgAGQAYQB0AGEALAAgAGMAbwB2AGUAcgBpAG4AZwAgAHQAaABlACAARwBhAGwAYQBjAHQAaQBjACAAcABsAGEAbgBlACAAYQB0ACAAbABvAG4AZwBpAHQAdQBkAGUAcwAgAGYAcgBvAG0AIABsAD0AMgA1ADAAewBkAGUAZwB9ACAAdABvACAANgA1AHsAZABlAGcAfQAgAGEAbgBkACAAbABhAHQAaQB0AHUAZABlAHMAIAB8AGIAfAA8AD0AMwAuACAASQBuACAAYQBkAGQAaQB0AGkAbwBuACAAdABvACAAdABoAGUAIAB1AG4AcAByAGUAYwBlAGQAZQBuAHQAZQBkACAAcwBwAGEAdABpAGEAbAAgAGMAbwB2AGUAcgBhAGcAZQAsACAAdABoAGUAIABIAEcAUABTACAAYQBsAHMAbwAgAGYAZQBhAHQAdQByAGUAcwAgAGEAIAByAGUAbABhAHQAaQB2AGUAbAB5ACAAaABpAGcAaAAgAGEAbgBnAHUAbABhAHIAIAByAGUAcwBvAGwAdQB0AGkAbwBuACAAKAAwAC4AMAA4AHsAZABlAGcAfQB+ADUALQBhAHIAYwBtAGkAbgAgAG0AZQBhAG4AIABwAG8AaQBuAHQAIABzAHAAcgBlAGEAZAAgAGYAdQBuAGMAdABpAG8AbgAgADYAOAAlACAAYwBvAG4AdABhAGkAbgBtAGUAbgB0ACAAcgBhAGQAaQB1AHMAKQAsACAAcwBlAG4AcwBpAHQAaQB2AGkAdAB5ACAAKAAxAC4ANQAlACAAQwByAGEAYgAgAGYAbAB1AHgAIABmAG8AcgAgAHAAbwBpAG4AdAAtAGwAaQBrAGUAIABzAG8AdQByAGMAZQBzACkALAAgAGEAbgBkACAAZQBuAGUAcgBnAHkAIAByAGEAbgBnAGUAIAAoADAALgAyAC0AMQAwADAAVABlAFYAKQAuACAAVwBlACAAYwBvAG4AcwB0AHIAdQBjAHQAZQBkACAAYQAgAGMAYQB0AGEAbABvAGcAIABvAGYAIABWAEgARQAgAGcAYQBtAG0AYQAtAHIAYQB5ACAAcwBvAHUAcgBjAGUAcwAgAGYAcgBvAG0AIAB0AGgAZQAgAEgARwBQAFMAIABkAGEAdABhACAAcwBlAHQAIAB3AGkAdABoACAAYQAgAHMAeQBzAHQAZQBtAGEAdABpAGMAIABwAHIAbwBjAGUAZAB1AHIAZQAgAGYAbwByACAAYgBvAHQAaAAgAHMAbwB1AHIAYwBlACAAZABlAHQAZQBjAHQAaQBvAG4AIABhAG4AZAAgAGMAaABhAHIAYQBjAHQAZQByAGkAegBhAHQAaQBvAG4AIABvAGYAIABtAG8AcgBwAGgAbwBsAG8AZwB5ACAAYQBuAGQAIABzAHAAZQBjAHQAcgB1AG0ALgAgAFcAZQAgAHAAcgBlAHMAZQBuAHQAIAB0AGgAaQBzACAAbABpAGsAZQBsAGkAaABvAG8AZAAtACAAYgBhAHMAZQBkACAAbQBlAHQAaABvAGQAIABpAG4AIABkAGUAdABhAGkAbAAsACAAaQBuAGMAbAB1AGQAaQBuAGcAIAB0AGgAZQAgAGkAbgB0AHIAbwBkAHUAYwB0AGkAbwBuACAAbwBmACAAYQAgAG0AbwBkAGUAbAAgAGMAbwBtAHAAbwBuAGUAbgB0ACAAdABvACAAYQBjAGMAbwB1AG4AdAAgAGYAbwByACAAdQBuAHIAZQBzAG8AbAB2AGUAZAAsACAAbABhAHIAZwBlAC0AcwBjAGEAbABlACAAZQBtAGkAcwBzAGkAbwBuACAAYQBsAG8AbgBnACAAdABoAGUAIABHAGEAbABhAGMAdABpAGMAIABwAGwAYQBuAGUALgAgAEkAbgAgAHQAbwB0AGEAbAAsACAAdABoAGUAIAByAGUAcwB1AGwAdABpAG4AZwAgAEgARwBQAFMAIABjAGEAdABhAGwAbwBnACAAYwBvAG4AdABhAGkAbgBzACAANwA4ACAAVgBIAEUAIABzAG8AdQByAGMAZQBzACwAIABvAGYAIAB3AGgAaQBjAGgAIAAxADQAIABhAHIAZQAgAG4AbwB0ACAAcgBlAGEAbgBhAGwAeQB6AGUAZAAgAGgAZQByAGUALAAgAGYAbwByACAAZQB4AGEAbQBwAGwAZQAsACAAZAB1AGUAIAB0AG8AIAB0AGgAZQBpAHIAIABjAG8AbQBwAGwAZQB4ACAAbQBvAHIAcABoAG8AbABvAGcAeQAsACAAbgBhAG0AZQBsAHkAIABzAGgAZQBsAGwALQBsAGkAawBlACAAcwBvAHUAcgBjAGUAcwAgAGEAbgBkACAAdABoAGUAIABHAGEAbABhAGMAdABpAGMAIABjAGUAbgB0AGUAcgAgAHIAZQBnAGkAbwBuAC4AIABXAGgAZQByAGUAIABwAG8AcwBzAGkAYgBsAGUALAAgAHcAZQAgAHAAcgBvAHYAaQBkAGUAIABhACAAZgBpAHIAbQAgAGkAZABlAG4AdABpAGYAaQBjAGEAdABpAG8AbgAgAG8AZgAgAHQAaABlACAAVgBIAEUAIABzAG8AdQByAGMAZQAgAG8AcgAgAHAAbABhAHUAcwBpAGIAbABlACAAYQBzAHMAbwBjAGkAYQB0AGkAbwBuAHMAIAB3AGkAdABoACAAcwBvAHUAcgBjAGUAcwAgAGkAbgAgAG8AdABoAGUAcgAgAGEAcwB0AHIAbwBuAG8AbQBpAGMAYQBsACAAYwBhAHQAYQBsAG8AZwBzAC4AIABXAGUAIABhAGwAcwBvACAAcwB0AHUAZABpAGUAZAAgAHQAaABlACAAYwBoAGEAcgBhAGMAdABlAHIAaQBzAHQAaQBjAHMAIABvAGYAIAB0AGgAZQAgAFYASABFACAAcwBvAHUAcgBjAGUAcwAgAHcAaQB0AGgAIABzAG8AdQByAGMAZQAgAHAAYQByAGEAbQBlAHQAZQByACAAZABpAHMAdAByAGkAYgB1AHQAaQBvAG4AcwAuACAAMQA2ACAAbgBlAHcAIABzAG8AdQByAGMAZQBzACAAdwBlAHIAZQAgAHAAcgBlAHYAaQBvAHUAcwBsAHkAIAB1AG4AawBuAG8AdwBuACAAbwByACAAdQBuAHAAdQBiAGwAaQBzAGgAZQBkACwAIABhAG4AZAAgAHcAZQAgAGkAbgBkAGkAdgBpAGQAdQBhAGwAbAB5ACAAZABpAHMAYwB1AHMAcwAgAHQAaABlAGkAcgAgAGkAZABlAG4AdABpAGYAaQBjAGEAdABpAG8AbgBzACAAbwByACAAcABvAHMAcwBpAGIAbABlACAAYQBzAHMAbwBjAGkAYQB0AGkAbwBuAHMALgAgAFcAZQAgAGYAaQByAG0AbAB5ACAAaQBkAGUAbgB0AGkAZgBpAGUAZAAgADMAMQAgAHMAbwB1AHIAYwBlAHMAIABhAHMAIABwAHUAbABzAGEAcgAgAHcAaQBuAGQAIABuAGUAYgB1AGwAYQBlACAAKABQAFcATgBlACkALAAgAHMAdQBwAGUAcgBuAG8AdgBhACAAcgBlAG0AbgBhAG4AdABzACAAKABTAE4AUgBzACkALAAgAGMAbwBtAHAAbwBzAGkAdABlACAAUwBOAFIAcwAsACAAbwByACAAZwBhAG0AbQBhAC0AcgBhAHkAIABiAGkAbgBhAHIAaQBlAHMALgAgAEEAbQBvAG4AZwAgAHQAaABlACAANAA3ACAAcwBvAHUAcgBjAGUAcwAgAG4AbwB0ACAAeQBlAHQAIABpAGQAZQBuAHQAaQBmAGkAZQBkACwAIABtAG8AcwB0ACAAbwBmACAAdABoAGUAbQAgACgAMwA2ACkAIABoAGEAdgBlACAAcABvAHMAcwBpAGIAbABlACAAYQBzAHMAbwBjAGkAYQB0AGkAbwBuAHMAIAB3AGkAdABoACAAYwBhAHQAYQBsAG8AZwBlAGQAIABvAGIAagBlAGMAdABzACwAIABuAG8AdABhAGIAbAB5ACAAUABXAE4AZQAgAGEAbgBkACAAZQBuAGUAcgBnAGUAdABpAGMAIABwAHUAbABzAGEAcgBzACAAdABoAGEAdAAgAGMAbwB1AGwAZAAgAHAAbwB3AGUAcgAgAFYASABFACAAUABXAE4AZQAuAAAANmh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovQStBLzYxMi9BMQAADKwASAAuAEUALgBTAC4AUwAuACAAQwBvAGwAbABhAGIAbwByAGEAdABpAG8AbgA7ACAAQQBiAGQAYQBsAGwAYQAgAEgALgA7ACAAQQBiAHIAYQBtAG8AdwBzAGsAaQAgAEEALgA7ACAAQQBoAGEAcgBvAG4AaQBhAG4AIABGAC4AOwAgAEEAaQB0ACAAQgBlAG4AawBoAGEAbABpACAARgAuADsAIABBAG4AZwB1AGUAbgBlAHIAIABFAC4ATwAuADsAIABBAHIAYQBrAGEAdwBhACAATQAuADsAIABBAHIAcgBpAGUAdABhACAATQAuADsAIABBAHUAYgBlAHIAdAAgAFAALgA7ACAAQgBhAGMAawBlAHMAIABNAC4AOwAgAEIAYQBsAHoAZQByACAAQQAuADsAIABCAGEAcgBuAGEAcgBkACAATQAuADsAIABCAGUAYwBoAGUAcgBpAG4AaQAgAFkALgA7ACAAQgBlAGMAawBlAHIAIABUAGoAdQBzACAASgAuADsAIABCAGUAcgBnAGUAIABEAC4AOwAgAEIAZQByAG4AaABhAHIAZAAgAFMALgA7ACAAQgBlAHIAbgBsAG8AZQBoAHIAIABLAC4AOwAgAEIAbABhAGMAawB3AGUAbABsACAAUgAuADsAIABCAG8AZQB0AHQAYwBoAGUAcgAgAE0ALgA7ACAAQgBvAGkAcwBzAG8AbgAgAEMALgA7ACAAQgBvAGwAbQBvAG4AdAAgAEoALgA7ACAAQgBvAG4AbgBlAGYAbwB5ACAAUwAuADsAIABCAG8AcgBkAGEAcwAgAFAALgA7ACAAQgByAGUAZwBlAG8AbgAgAEoALgA7ACAAQgByAHUAbgAgAEYALgA7ACAAQgByAHUAbgAgAFAALgA7ACAAQgByAHkAYQBuACAATQAuADsAIABCAHUAZQBjAGgAZQBsAGUAIABNAC4AOwAgAEIAdQBsAGkAawAgAFQALgA7ACAAQwBhAHAAYQBzAHMAbwAgAE0ALgA7ACAAQwBhAHIAcgBpAGcAYQBuACAAUwAuADsAIABDAGEAcgBvAGYAZgAgAFMALgA7ACAAQwBhAHIAbwBzAGkAIABBAC4AOwAgAEMAYQBzAGEAbgBvAHYAYQAgAFMALgA7ACAAQwBlAHIAcgB1AHQAaQAgAE0ALgA7ACAAQwBoAGEAawByAGEAYgBvAHIAdAB5ACAATgAuADsAIABDAGgAYQB2AGUAcwAgAFIALgBDAC4ARwAuADsAIABDAGgAZQBuACAAQQAuADsAIABDAGgAZQB2AGEAbABpAGUAcgAgAEoALgA7ACAAQwBvAGwAYQBmAHIAYQBuAGMAZQBzAGMAbwAgAFMALgA7ACAAQwBvAG4AZABvAG4AIABCAC4AOwAgAEMAbwBuAHIAYQBkACAASgAuADsAIABEAGEAdgBpAGQAcwAgAEkALgBEAC4AOwAgAEQAZQBjAG8AYwBrACAASgAuADsAIABEAGUAaQBsACAAQwAuADsAIABEAGUAdgBpAG4AIABKAC4AOwAgAGQAZQBXAGkAbAB0ACAAUAAuADsAIABEAGkAcgBzAG8AbgAgAEwALgA7ACAARABqAGEAbgBuAGEAdABpAC0AQQB0AGEAaQAgAEEALgA7ACAARABvAG0AYQBpAG4AawBvACAAVwAuADsAIABEAG8AbgBhAHQAaAAgAEEALgA7ACAARAByAHUAcgB5ACAATAAuAE8AJwBDAC4AOwAgAEQAdQB0AHMAbwBuACAASwAuADsAIABEAHkAawBzACAASgAuADsAIABFAGQAdwBhAHIAZABzACAAVAAuADsAIABFAGcAYgBlAHIAdABzACAASwAuADsAIABFAGcAZQByACAAUAAuADsAIABFAG0AZQByAHkAIABHAC4AOwAgAEUAcgBuAGUAbgB3AGUAaQBuACAASgAuAC0AUAAuADsAIABFAHMAYwBoAGIAYQBjAGgAIABTAC4AOwAgAEYAYQByAG4AaQBlAHIAIABDAC4AOwAgAEYAZQBnAGEAbgAgAFMALgA7ACAARgBlAHIAbgBhAG4AZABlAHMAIABNAC4AVgAuADsAIABGAGkAYQBzAHMAbwBuACAAQQAuADsAIABGAG8AbgB0AGEAaQBuAGUAIABHAC4AOwAgAEYAbwBlAHIAcwB0AGUAcgAgAEEALgA7ACAARgB1AG4AawAgAFMALgA7ACAARgB1AGUAcwBzAGwAaQBuAGcAIABNAC4AOwAgAEcAYQBiAGkAYwBpACAAUwAuADsAIABHAGEAbABsAGEAbgB0ACAAWQAuAEEALgA7ACAARwBhAHIAcgBpAGcAbwB1AHgAIABUAC4AOwAgAEcAYQBzAHQAIABIAC4AOwAgAEcAYQB0AGUAIABGAC4AOwAgAEcAaQBhAHYAaQB0AHQAbwAgAEcALgA7ACAARwBpAGUAYgBlAGwAcwAgAEIALgA7ACAARwBsAGEAdwBpAG8AbgAgAEQALgA7ACAARwBsAGkAYwBlAG4AcwB0AGUAaQBuACAASgAuAEYALgA7ACAARwBvAHQAdABzAGMAaABhAGwAbAAgAEQALgA7ACAARwByAG8AbgBkAGkAbgAgAE0ALgAtAEgALgA7ACAASABhAGgAbgAgAEoALgA7ACAASABhAHUAcAB0ACAATQAuADsAIABIAGEAdwBrAGUAcwAgAEoALgA7ACAASABlAGkAbgB6AGUAbABtAGEAbgBuACAARwAuADsAIABIAGUAbgByAGkAIABHAC4AOwAgAEgAZQByAG0AYQBuAG4AIABHAC4AOwAgAEgAaQBuAHQAbwBuACAASgAuAEEALgA7ACAASABvAGYAbQBhAG4AbgAgAFcALgA7ACAASABvAGkAcwBjAGgAZQBuACAAQwAuADsAIABIAG8AbABjAGgAIABUAC4ATAAuADsAIABIAG8AbABsAGUAcgAgAE0ALgA7ACAASABvAHIAbgBzACAARAAuADsAIABJAHYAYQBzAGMAZQBuAGsAbwAgAEEALgA7ACAASQB3AGEAcwBhAGsAaQAgAEgALgA7ACAASgBhAGMAaABvAGwAawBvAHcAcwBrAGEAIABBAC4AOwAgAEoAYQBtAHIAbwB6AHkAIABNAC4AOwAgAEoAYQBuAGsAbwB3AHMAawB5ACAARAAuADsAIABKAGEAbgBrAG8AdwBzAGsAeQAgAEYALgA7ACAASgBpAG4AZwBvACAATQAuADsAIABKAG8AdQB2AGkAbgAgAEwALgA7ACAASgB1AG4AZwAtAFIAaQBjAGgAYQByAGQAdAAgAEkALgA7ACAASwBhAHMAdABlAG4AZABpAGUAYwBrACAATQAuAEEALgA7ACAASwBhAHQAYQByAHoAeQBOAHMAawBpACAASwAuADsAIABLAGEAdABzAHUAcgBhAGcAYQB3AGEAIABNAC4AOwAgAEsAYQB0AHoAIABVAC4AOwAgAEsAZQByAHMAegBiAGUAcgBnACAARAAuADsAIABLAGgAYQBuAGcAdQBsAHkAYQBuACAARAAuADsAIABLAGgAZQBsAGkAZgBpACAAQgAuADsAIABLAGkAbgBnACAASgAuADsAIABLAGwAZQBwAHMAZQByACAAUwAuADsAIABLAGwAbwBjAGgAawBvAHYAIABEAC4AOwAgAEsAbAB1AHoAbgBpAGEAawAgAFcALgA7ACAASwBvAG0AaQBuACAATgB1AC4AOwAgAEsAbwBzAGEAYwBrACAASwAuADsAIABLAHIAYQBrAGEAdQAgAFMALgA7ACAASwByAGEAdQBzACAATQAuADsAIABLAHIAdQBlAGcAZQByACAAUAAuAFAALgA7ACAATABhAGYAZgBvAG4AIABIAC4AOwAgAEwAYQBtAGEAbgBuAGEAIABHAC4AOwAgAEwAYQB1ACAASgAuADsAIABMAGUAZQBzACAASgAuAC0AUAAuADsAIABMAGUAZgBhAHUAYwBoAGUAdQByACAASgAuADsAIABMAGUAbQBpAGUAcgBlACAAQQAuADsAIABMAGUAbQBvAGkAbgBlAC0ARwBvAHUAbQBhAHIAZAAgAE0ALgA7ACAATABlAG4AYQBpAG4AIABKAC4ALQBQAC4AOwAgAEwAZQBzAGUAcgAgAEUALgA7ACAATABvAGgAcwBlACAAVAAuADsAIABMAG8AcgBlAG4AdAB6ACAATQAuADsAIABMAGkAdQAgAFIALgA7ACAATABvAHAAZQB6AC0AQwBvAHQAbwAgAFIALgA7ACAATAB5AHAAbwB2AGEAIABJAC4AOwAgAE0AYQByAGEAbgBkAG8AbgAgAFYALgA7ACAATQBhAGwAeQBzAGgAZQB2ACAARAAuADsAIABNAGEAcgBjAG8AdwBpAHQAaAAgAEEALgA7ACAATQBhAHIAaQBhAHUAZAAgAEMALgA7ACAATQBhAHIAeAAgAFIALgA7ACAATQBhAHUAcgBpAG4AIABHAC4AOwAgAE0AYQB4AHQAZQBkACAATgAuADsAIABNAGEAeQBlAHIAIABNAC4AOwAgAE0AZQBpAG4AdABqAGUAcwAgAFAALgBKAC4AOwAgAE0AZQB5AGUAcgAgAE0ALgA7ACAATQBpAHQAYwBoAGUAbABsACAAQQAuAE0ALgBXAC4AOwAgAE0AbwBkAGUAcgBzAGsAaQAgAFIALgA7ACAATQBvAGgAYQBtAGUAZAAgAE0ALgA7ACAATQBvAGgAcgBtAGEAbgBuACAATAAuADsAIABNAG8AcgBhACAASwAuADsAIABNAG8AdQBsAGkAbgAgAEUALgA7ACAATQB1AHIAYQBjAGgAIABUAC4AOwAgAE4AYQBrAGEAcwBoAGkAbQBhACAAUwAuADsAIABkAGUAIABOAGEAdQByAG8AaQBzACAATQAuADsAIABOAGQAaQB5AGEAdgBhAGwAYQAgAEgALgA7ACAATgBpAGUAZABlAHIAdwBhAG4AZwBlAHIAIABGAC4AOwAgAE4AaQBlAG0AaQBlAGMAIABKAC4AOwAgAE8AYQBrAGUAcwAgAEwALgA7ACAATwAnAEIAcgBpAGUAbgAgAFAALgA7ACAATwBkAGEAawBhACAASAAuADsAIABPAGgAbQAgAFMALgA7ACAATwBzAHQAcgBvAHcAcwBrAGkAIABNAC4AOwAgAE8AeQBhACAASQAuADsAIABQAGEAZABvAHYAYQBuAGkAIABNAC4AOwAgAFAAYQBuAHQAZQByACAATQAuADsAIABQAGEAcgBzAG8AbgBzACAAUgAuAEQALgA7ACAAUABhAHoAIABBAHIAcgBpAGIAYQBzACAATQAuADsAIABQAGUAawBlAHUAcgAgAE4ALgBXAC4AOwAgAFAAZQBsAGwAZQB0AGkAZQByACAARwAuADsAIABQAGUAcgBlAG4AbgBlAHMAIABDAC4AOwAgAFAAZQB0AHIAdQBjAGMAaQAgAFAALgAtAE8ALgA7ACAAUABlAHkAYQB1AGQAIABCAC4AOwAgAFAAaQBlAGwAIABRAC4AOwAgAFAAaQB0AGEAIABTAC4AOwAgAFAAbwBpAHIAZQBhAHUAIABWAC4AOwAgAFAAbwBvAG4AIABIAC4AOwAgAFAAcgBvAGsAaABvAHIAbwB2ACAARAAuADsAIABQAHIAbwBrAG8AcABoACAASAAuADsAIABQAHUAZQBoAGwAaABvAGYAZQByACAARwAuADsAIABQAHUAbgBjAGgAIABNAC4AOwAgAFEAdQBpAHIAcgBlAG4AYgBhAGMAaAAgAEEALgA7ACAAUgBhAGEAYgAgAFMALgA7ACAAUgBhAHUAdABoACAAUgAuADsAIABSAGUAaQBtAGUAcgAgAEEALgA7ACAAUgBlAGkAbQBlAHIAIABPAC4AOwAgAFIAZQBuAGEAdQBkACAATQAuADsAIABkAGUAIABsAG8AcwAgAFIAZQB5AGUAcwAgAFIALgA7ACAAUgBpAGUAZwBlAHIAIABGAC4AOwAgAFIAaQBuAGMAaABpAHUAcwBvACAATAAuADsAIABSAG8AbQBvAGwAaQAgAEMALgA7ACAAUgBvAHcAZQBsAGwAIABHAC4AOwAgAFIAdQBkAGEAawAgAEIALgA7ACAAUgB1AGwAdABlAG4AIABDAC4AQgAuADsAIABTAGEAZgBpAC0ASABhAHIAYgAgAFMALgA7ACAAUwBhAGgAYQBrAGkAYQBuACAAVgAuADsAIABTAGEAaQB0AG8AIABTAC4AOwAgAFMAYQBuAGMAaABlAHoAIABEAC4AQQAuADsAIABTAGEAbgB0AGEAbgBnAGUAbABvACAAQQAuADsAIABTAGEAcwBhAGsAaQAgAE0ALgA7ACAAUwBjAGgAYQBuAGQAcgBpACAATQAuADsAIABTAGMAaABsAGkAYwBrAGUAaQBzAGUAcgAgAFIALgA7ACAAUwBjAGgAdQBlAHMAcwBsAGUAcgAgAEYALgA7ACAAUwBjAGgAdQBsAHoAIABBAC4AOwAgAFMAYwBoAHcAYQBuAGsAZQAgAFUALgA7ACAAUwBjAGgAdwBlAG0AbQBlAHIAIABTAC4AOwAgAFMAZQBnAGwAYQByAC0AQQByAHIAbwB5AG8AIABNAC4AOwAgAFMAZQB0AHQAaQBtAG8AIABNAC4AOwAgAFMAZQB5AGYAZgBlAHIAdAAgAEEALgBTAC4AOwAgAFMAaABhAGYAaQAgAE4ALgA7ACAAUwBoAGkAbABvAG4AIABJAC4AOwAgAFMAaABpAG4AaQBuAGcAYQB5AGEAbQB3AGUAIABLAC4AOwAgAFMAaQBtAG8AbgBpACAAUgAuADsAIABTAG8AbAAgAEgALgA7ACAAUwBwAGEAbgBpAGUAcgAgAEYALgA7ACAAUwBwAGkAcgAtAEoAYQBjAG8AYgAgAE0ALgA7ACAAUwB0AGEAdwBhAHIAegAgAEwALgA7ACAAUwB0AGUAZQBuAGsAYQBtAHAAIABSAC4AOwAgAFMAdABlAGcAbQBhAG4AbgAgAEMALgA7ACAAUwB0AGUAcABwAGEAIABDAC4AOwAgAFMAdQBzAGgAYwBoACAASQAuADsAIABUAGEAawBhAGgAYQBzAGgAaQAgAFQALgA7ACAAVABhAHYAZQByAG4AZQB0ACAASgAuAC0AUAAuADsAIABUAGEAdgBlAHIAbgBpAGUAcgAgAFQALgA7ACAAVABhAHkAbABvAHIAIABBAC4ATQAuADsAIABUAGUAcgByAGkAZQByACAAUgAuADsAIABUAGkAYgBhAGwAZABvACAATAAuADsAIABUAGkAegBpAGEAbgBpACAARAAuADsAIABUAGwAdQBjAHoAeQBrAG8AbgB0ACAATQAuADsAIABUAHIAaQBjAGgAYQByAGQAIABDAC4AOwAgAFQAcwBpAHIAbwB1ACAATQAuADsAIABUAHMAdQBqAGkAIABOAC4AOwAgAFQAdQBmAGYAcwAgAFIALgA7ACAAVQBjAGgAaQB5AGEAbQBhACAAWQAuADsAIAB2AGEAbgAgAGQAZQByACAAVwBhAGwAdAAgAEQALgBKAC4AOwAgAHYAYQBuACAARQBsAGQAaQBrACAAQwAuADsAIAB2AGEAbgAgAFIAZQBuAHMAYgB1AHIAZwAgAEMALgA7ACAAdgBhAG4AIABTAG8AZQBsAGUAbgAgAEIALgA7ACAAVgBhAHMAaQBsAGUAaQBhAGQAaQBzACAARwAuADsAIABWAGUAaAAgAEoALgA7ACAAVgBlAG4AdABlAHIAIABDAC4AOwAgAFYAaQBhAG4AYQAgAEEALgA7ACAAVgBpAG4AYwBlAG4AdAAgAFAALgA7ACAAVgBpAG4AawAgAEoALgA7ACAAVgBvAGkAcwBpAG4AIABGAC4AOwAgAFYAbwBlAGwAawAgAEgALgBKAC4AOwAgAFYAdQBpAGwAbABhAHUAbQBlACAAVAAuADsAIABXAGEAZABpAGEAcwBpAG4AZwBoACAAWgAuADsAIABXAGEAZwBuAGUAcgAgAFMALgBKAC4AOwAgAFcAYQBnAG4AZQByACAAUAAuADsAIABXAGEAZwBuAGUAcgAgAFIALgBNAC4AOwAgAFcAaABpAHQAZQAgAFIALgA7ACAAVwBpAGUAcgB6AGMAaABvAGwAcwBrAGEAIABBAC4AOwAgAFcAaQBsAGwAbQBhAG4AbgAgAFAALgA7ACAAVwBvAGUAcgBuAGwAZQBpAG4AIABBAC4AOwAgAFcAbwB1AHQAZQByAHMAIABEAC4AOwAgAFkAYQBuAGcAIABSAC4AOwAgAFoAYQBiAG8AcgBvAHYAIABEAC4AOwAgAFoAYQBjAGgAYQByAGkAYQBzACAATQAuADsAIABaAGEAbgBpAG4AIABSAC4AOwAgAFoAZAB6AGkAYQByAHMAawBpACAAQQAuAEEALgA7ACAAWgBlAGMAaAAgAEEALgA7ACAAWgBlAGYAaQAgAEYALgA7ACAAWgBpAGUAZwBsAGUAcgAgAEEALgA7ACAAWgBvAHIAbgAgAEoALgA7ACAAWgB5AHcAdQBjAGsAYQAgAE4ALgAAABMyMDE4LTA0LTA5VDA4OjMyOjM4AAAAEzIwMjItMTItMjBUMDA6MDA6MDAAAAA2aHR0cHM6Ly9jZHMudW5pc3RyYS5mci92aXppZXItb3JnL2xpY2VuY2VzX3Zpemllci5odG1sAAAAB2NhdGFsb2cAAAAHYmliY29kZQAAABMAMgAwADEAOABBACYAQQAuAC4ALgA2ADEAMgBBAC4ALgAuADEASH/AAAAAAAAJZ2FtbWEtcmF5AAAE52h0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQStBLzYxMi9BMS9zb3VyY2VzPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQStBLzYxMi9BMS9saXN0Pzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQStBLzYxMi9BMS9zbnJjYXQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BK0EvNjEyL0ExL2xzY29tcD86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0ErQS82MTIvQTEvY29tcG9uPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9BK0EvNjEyL0ExOjo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BK0EvNjEyL0ExL2NvbXBvbj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0ErQS82MTIvQTEvbHNjb21wPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovQStBLzYxMi9BMTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQStBLzYxMi9BMS9zb3VyY2VzPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQStBLzYxMi9BMS9zbnJjYXQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL0ErQS82MTIvQTE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL0ErQS82MTIvQTE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0ErQS82MTIvQTEvbGlzdD8AAAI3aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoAAABo3ZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwAAABBXN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZAAAACBkb2k6MTAuMjYwOTMvY2RzL3Zpemllci4zNjEyMDAwMQAAAB9pdm86Ly9jZHMudml6aWVyL2ovYSthLzYxOC9hMTg2AAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAADkovQStBLzYxOC9BMTg2AAAAHQBEAHkAbgBhAG0AaQBjACAAcwBwAGUAYwB0AHIAYQAgAG8AZgAgADEAMAAgAHAAdQBsAHMAYQByAHMAAAAIcmVzZWFyY2gAAAcFAFAAdQBsAHMAYQByAHMAIABzAGMAaQBuAHQAaQBsAGwAYQB0AGUALgAgAEQAeQBuAGEAbQBpAGMAIABzAHAAZQBjAHQAcgBhACAAcwBoAG8AdwAgAGIAcgBpAGcAaAB0AG4AZQBzAHMAIAB2AGEAcgBpAGEAdABpAG8AbgAgAG8AZgAgAHAAdQBsAHMAYQByAHMAIABpAG4AIAB0AGgAZQAgAHQAaQBtAGUAIABhAG4AZAAgAGYAcgBlAHEAdQBlAG4AYwB5ACAAZABvAG0AYQBpAG4ALgAgAFMAZQBjAG8AbgBkAGEAcgB5ACAAcwBwAGUAYwB0AHIAYQAgAGQAZQBtAG8AbgBzAHQAcgBhAHQAZQAgAHQAaABlACAAZABpAHMAdAByAGkAYgB1AHQAaQBvAG4AIABvAGYAIABmAGwAdQBjAHQAdQBhAHQAaQBvAG4AIABwAG8AdwBlAHIAIABpAG4AIAB0AGgAZQAgAGQAeQBuAGEAbQBpAGMAIABzAHAAZQBjAHQAcgBhAC4AIABEAHkAbgBhAG0AaQBjACAAcwBwAGUAYwB0AHIAYQAgAHMAdAByAG8AbgBnAGwAeQAgAGQAZQBwAGUAbgBkACAAbwBuACAAbwBiAHMAZQByAHYAYQB0AGkAbwBuAGEAbAAgAGYAcgBlAHEAdQBlAG4AYwBpAGUAcwAsACAAYgB1AHQAIAB3AGUAcgBlACAAbwBmAHQAZQBuACAAbwBiAHMAZQByAHYAZQBkACAAYQB0ACAAZgByAGUAcQB1AGUAbgBjAGkAZQBzACAAbABvAHcAZQByACAAdABoAGEAbgAgADEALgA1AEcASAB6AC4AIABTAGMAaQBuAHQAaQBsAGwAYQB0AGkAbwBuACAAbwBiAHMAZQByAHYAYQB0AGkAbwBuAHMAIABhAHQAIABoAGkAZwBoAGUAcgAgAGYAcgBlAHEAdQBlAG4AYwBpAGUAcwAgAGgAZQBsAHAAIAB0AG8AIABjAG8AbgBzAHQAcgBhAGkAbgAgAHQAaABlACAAdAB1AHIAYgB1AGwAZQBuAGMAZQAgAGYAZQBhAHQAdQByAGUAIABvAGYAIAB0AGgAZQAgAGkAbgB0AGUAcgBzAHQAZQBsAGwAYQByACAAbQBlAGQAaQB1AG0AIABvAHYAZQByACAAYQAgAHcAaQBkAGUAIABmAHIAZQBxAHUAZQBuAGMAeQAgAHIAYQBuAGcAZQAgAGEAbgBkACAAYwBhAG4AIABkAGUAdABlAGMAdAAgAHQAaABlACAAcwBjAGkAbgB0AGkAbABsAGEAdABpAG8AbgBzACAAbwBmACAAbQBvAHIAZQAgAGQAaQBzAHQAYQBuAHQAIABwAHUAbABzAGEAcgBzAC4AIABUAGUAbgAgAHAAdQBsAHMAYQByAHMAIAB3AGUAcgBlACAAbwBiAHMAZQByAHYAZQBkACAAYQB0ACAAMgAyADUAMABNAEgAegAgACgAUwAtAGIAYQBuAGQAKQAgAHcAaQB0AGgAIAB0AGgAZQAgAEoAaQBhAG0AdQBzAGkAIAA2ADYAbQAgAHQAZQBsAGUAcwBjAG8AcABlACAAdABvACAAcwB0AHUAZAB5ACAAdABoAGUAaQByACAAcwBjAGkAbgB0AGkAbABsAGEAdABpAG8AbgBzAC4AIABUAGgAZQBpAHIAIABkAHkAbgBhAG0AaQBjACAAcwBwAGUAYwB0AHIAYQAgAHcAZQByAGUAIABmAGkAcgBzAHQAIABvAGIAdABhAGkAbgBlAGQALAAgAGYAcgBvAG0AIAB3AGgAaQBjAGgAIAB0AGgAZQAgAGQAZQBjAG8AcgByAGUAbABhAHQAaQBvAG4AIABiAGEAbgBkAHcAaQBkAHQAaABzACAAYQBuAGQAIAB0AGkAbQBlACAAcwBjAGEAbABlAHMAIABvAGYAIABkAGkAZgBmAHIAYQBjAHQAaQB2AGUAIABzAGMAaQBuAHQAaQBsAGwAYQB0AGkAbwBuACAAdwBlAHIAZQAgAHQAaABlAG4AIABkAGUAcgBpAHYAZQBkACAAYgB5ACAAYQB1AHQAbwBjAG8AcgByAGUAbABhAHQAaQBvAG4ALgAgAFMAZQBjAG8AbgBkAGEAcgB5ACAAcwBwAGUAYwB0AHIAYQAgAHcAZQByAGUAIABjAGEAbABjAHUAbABhAHQAZQBkACAAYgB5ACAAZgBvAHIAbQBpAG4AZwAgAHQAaABlACAARgBvAHUAcgBpAGUAcgAgAHAAbwB3AGUAcgAgAHMAcABlAGMAdAByAGEAIABvAGYAIAB0AGgAZQAgAGQAeQBuAGEAbQBpAGMAIABzAHAAZQBjAHQAcgBhAC4AIABNAG8AcwB0ACAAbwBmACAAdABoAGUAIABuAGUAdwBsAHkAIABvAGIAdABhAGkAbgBlAGQAIABkAHkAbgBhAG0AaQBjACAAcwBwAGUAYwB0AHIAYQAgAGEAcgBlACAAYQB0ACAAdABoAGUAIABoAGkAZwBoAGUAcwB0ACAAZgByAGUAcQB1AGUAbgBjAHkAIABvAHIAIABoAGEAdgBlACAAdABoAGUAIABsAG8AbgBnAGUAcwB0ACAAdABpAG0AZQAgAHMAcABhAG4AIABvAGYAIABhAG4AeQAgAHAAdQBiAGwAaQBzAGgAZQBkACAAZABhAHQAYQAgAGYAbwByACAAdABoAGUAcwBlACAAcAB1AGwAcwBhAHIAcwAuACAARgBvAHIAIABQAFMAUgBzACAAQgAwADUANAAwACsAMgAzACwAIABCADIAMwAyADQAKwA2ADAAIABhAG4AZAAgAEIAMgAzADUAMQArADYAMQAsACAAdABoAGUAcwBlACAAdwBlAHIAZQAgAHQAaABlACAAZgBpAHIAcwB0ACAAZAB5AG4AYQBtAGkAYwAgAHMAcABlAGMAdAByAGEAIABlAHYAZQByACAAcgBlAHAAbwByAHQAZQBkAC4AIABUAGgAZQAgAGYAcgBlAHEAdQBlAG4AYwB5AC0AZABlAHAAZQBuAGQAZQBuAGMAZQAgAG8AZgAgAHMAYwBpAG4AdABpAGwAbABhAHQAaQBvAG4AIABwAGEAcgBhAG0AZQB0AGUAcgBzACAAaQBuAGQAaQBjAGEAdABlAHMAIAB0AGgAYQB0ACAAdABoAGUAIABpAG4AdABlAHIAdgBlAG4AaQBuAGcAIABtAGUAZABpAHUAbQAgAGMAYQBuACAAcgBhAHIAZQBsAHkAIABiAGUAIABpAGQAZQBhAGwAbAB5ACAAdAB1AHIAYgB1AGwAZQBuAHQAIAB3AGkAdABoACAAYQAgAEsAbwBsAG0AbwBnAG8AcgBvAHYAIABzAHAAZQBjAHQAcgB1AG0ALgAgAFQAaABlACAAdABoAGkAbgAgAHMAYwByAGUAZQBuACAAbQBvAGQAZQBsACAAdwBvAHIAawBlAGQAIAB3AGUAbABsACAAYQB0ACAAUwAtAGIAYQBuAGQAIABmAG8AcgAgAHQAaABlACAAcwBjAGkAbgB0AGkAbABsAGEAdABpAG8AbgAgAG8AZgAgAFAAUwBSACAAQgAxADkAMwAzACsAMQA2AC4AIABQAGEAcgBhAGIAbwBsAGkAYwAgAGEAcgBjAHMAIAB3AGUAcgBlACAAZABlAHQAZQBjAHQAZQBkACAAaQBuACAAdABoAGUAIABzAGUAYwBvAG4AZABhAHIAeQAgAHMAcABlAGMAdAByAGEAIABvAGYAIAB0AGgAcgBlAGUAIABwAHUAbABzAGEAcgBzACwAIABQAFMAUgBzACAAQgAwADMANQA1ACsANQA0ACwAIABCADAANQA0ADAAKwAyADMAIABhAG4AZAAgAEIAMgAxADUANAArADQAMAAsACAAYQBsAGwAIABvAGYAIAB3AGgAaQBjAGgAIAB3AGUAcgBlACAAYQBzAHkAbQBtAGUAdAByAGkAYwBhAGwAbAB5ACAAZABpAHMAdAByAGkAYgB1AHQAZQBkAC4AIABUAGgAZQAgAGkAbgB2AGUAcgB0AGUAZAAgAGEAcgBjAGwAZQB0AHMAIABvAGYAIABQAFMAUgAgAEIAMAAzADUANQArADUANAAgAHcAZQByAGUAIABzAGUAZQBuACAAdABvACAAZQB2AG8AbAB2AGUAIABhAGwAbwBuAGcAIAB0AGgAZQAgAG0AYQBpAG4AIABwAGEAcgBhAGIAbwBsAGEAIAB3AGkAdABoAGkAbgAgAGEAIABjAG8AbgB0AGkAbgB1AG8AdQBzACAAbwBiAHMAZQByAHYAaQBuAGcAIABzAGUAcwBzAGkAbwBuACAAbwBmACAAMQAyACAAaABvAHUAcgBzACwAIABmAHIAbwBtACAAdwBoAGkAYwBoACAAdABoAGUAIABhAG4AZwB1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IABoAGkAZwBoACAAYQBuAGcAdQBsAGEAcgAgAHIAZQBzAG8AbAB1AHQAaQBvAG4AIAAoADwAfgAyADUAIgApACAAYQB0ACAAZABpAGYAZgBlAHIAZQBuAHQAIAB3AGEAdgBlAGwAZQBuAGcAdABoAHMAIABlAHgAaQBzAHQAIAB0AG8AIABzAHQAdQBkAHkAIAB0AGgAZQAgAGkAbgB0AGUAcgBzAHQAZQBsAGwAYQByACAAbQBlAGQAaQB1AG0AIAAoAEkAUwBNACkALAAgAGIAdQB0ACAAbgBvACAAYwBvAG0AcABhAHIAYQBiAGwAZQAgAGgAaQBnAGgALQByAGUAcwBvAGwAdQB0AGkAbwBuACAAYQBuAGQAIABoAGkAZwBoAC0AcwBlAG4AcwBpAHQAaQB2AGkAdAB5ACAAcwB1AHIAdgBlAHkAIABlAHgAaQBzAHQAcwAgAGEAdAAgAGwAbwBuAGcAIAByAGEAZABpAG8AIAB3AGEAdgBlAGwAZQBuAGcAdABoAHMAIABhAHIAbwB1AG4AZAAgADIAMQBjAG0ALgAgAE8AdQByACAAZwBvAGEAbAAgAGkAcwAgAHQAbwAgAGkAbgB2AGUAcwB0AGkAZwBhAHQAZQAgAHQAaABlACAAMgAxAGMAbQAgAHIAYQBkAGkAbwAgAGMAbwBuAHQAaQBuAHUAdQBtACAAZQBtAGkAcwBzAGkAbwBuACAAaQBuACAAdABoAGUAIABuAG8AcgB0AGgAZQByAG4AIABHAGEAbABhAGMAdABpAGMAIABwAGwAYQBuAGUAIABhAHQAIAA8ADIANQAiACAAcgBlAHMAbwBsAHUAdABpAG8AbgAuACAAVwBlACAAbwBiAHMAZQByAHYAZQBkACAAYQAgAGwAYQByAGcAZQAgAGYAcgBhAGMAdABpAG8AbgAgAG8AZgAgAHQAaABlACAARwBhAGwAYQBjAHQAaQBjACAAcABsAGEAbgBlACAAaQBuACAAdABoAGUAIABmAGkAcgBzAHQAIABxAHUAYQBkAHIAYQBuAHQAIABvAGYAIAB0AGgAZQAgAE0AaQBsAGsAeQAgAFcAYQB5ACAAKABsAD0AMQA0AC4AMAB7AGQAZQBnAH0ALQA2ADcALgA0AHsAZABlAGcAfQAgAGEAbgBkACAAfABiAHwAPAA9ADEALgAyADUAewBkAGUAZwB9ACkAIAB3AGkAdABoACAAdABoAGUAIABLAGEAcgBsACAARwAuACAASgBhAG4AcwBrAHkAIABWAGUAcgB5ACAATABhAHIAZwBlACAAQQByAHIAYQB5ACAAKABWAEwAQQApACAAaQBuACAAdABoAGUAIABDAC0AYwBvAG4AZgBpAGcAdQByAGEAdABpAG8AbgAgAGMAbwB2AGUAcgBpAG4AZwAgAHMAaQB4ACAAYwBvAG4AdABpAG4AdQB1AG0AIABzAHAAZQBjAHQAcgBhAGwAIAB3AGkAbgBkAG8AdwBzAC4AIABUAGgAZQBzAGUAIABkAGEAdABhACAAcAByAG8AdgBpAGQAZQAgAGEAIABkAGUAdABhAGkAbABlAGQAIAB2AGkAZQB3ACAAbwBuACAAdABoAGUAIABjAG8AbQBwAGEAYwB0ACAAYQBzACAAdwBlAGwAbAAgAGEAcwAgAGUAeAB0AGUAbgBkAGUAZAAgAHIAYQBkAGkAbwAgAGUAbQBpAHMAcwBpAG8AbgAgAG8AZgAgAG8AdQByACAARwBhAGwAYQB4AHkAIABhAG4AZAAgAHQAaABvAHUAcwBhAG4AZABzACAAbwBmACAAZQB4AHQAcgBhAGcAYQBsAGEAYwB0AGkAYwAgAGIAYQBjAGsAZwByAG8AdQBuAGQAIABzAG8AdQByAGMAZQBzAC4AIABXAGUAIAB1AHMAZQBkACAAdABoAGUAIABCAEwATwBCAEMAQQBUACAAcwBvAGYAdAB3AGEAcgBlACAAYQBuAGQAIABlAHgAdAByAGEAYwB0AGUAZAAgADEAMAA5ADEANgAgAHMAbwB1AHIAYwBlAHMALgAgAEEAZgB0AGUAcgAgAHIAZQBtAG8AdgBpAG4AZwAgAHMAcAB1AHIAaQBvAHUAcwAgAHMAbwB1AHIAYwBlACAAZABlAHQAZQBjAHQAaQBvAG4AcwAgAGMAYQB1AHMAZQBkACAAYgB5ACAAdABoAGUAIABzAGkAZABlAGwAbwBiAGUAcwAgAG8AZgAgAHQAaABlACAAcwB5AG4AdABoAGUAcwBpAHMAZQBkACAAYgBlAGEAbQAsACAAdwBlACAAYwBsAGEAcwBzAGkAZgBpAGUAZAAgADEAMAAzADgANwAgAHMAbwB1AHIAYwBlAHMAIABhAHMAIAByAGUAbABpAGEAYgBsAGUAIABkAGUAdABlAGMAdABpAG8AbgBzAC4AVwBlACAAcwBtAG8AbwB0AGgAZQBkACAAdABoAGUAIABpAG0AYQBnAGUAcwAgAHQAbwAgAGEAIABjAG8AbQBtAG8AbgAgAHIAZQBzAG8AbAB1AHQAaQBvAG4AIABvAGYAIAAyADUAIgAgAGEAbgBkACAAZQB4AHQAcgBhAGMAdABlAGQAIAB0AGgAZQAgAHAAZQBhAGsAIABmAGwAdQB4ACAAZABlAG4AcwBpAHQAeQAgAG8AZgAgAGUAYQBjAGgAIABzAG8AdQByAGMAZQAgAGkAbgAgAGUAYQBjAGgAIABzAHAAZQBjAHQAcgBhAGwAIAB3AGkAbgBkAG8AdwAgACgAUwBQAFcAKQAgAHQAbwAgAGQAZQB0AGUAcgBtAGkAbgBlACAAdABoAGUAIABzAHAAZQBjAHQAcgBhAGwAIABpAG4AZABpAGMAZQBzACAAewBhAGwAcABoAGEAfQAgACgAYQBzAHMAdQBtAGkAbgBnACAASQAoAG4AdQApAHsAcAByAG8AcAAuAHQAbwB9AG4AdQBeAGEAbABwAGgAYQBeACkALgAgAEIAeQAgAGMAcgBvAHMAcwAtAG0AYQB0AGMAaABpAG4AZwAgAHcAaQB0AGgAIABjAGEAdABhAGwAbwBnAHMAIABvAGYAIABIAEkASQAgAHIAZQBnAGkAbwBuAHMALAAgAFMATgBSAHMALAAgAFAATgBlACwAIABhAG4AZAAgAHAAdQBsAHMAYQByAHMALAAgAHcAZQAgAGYAbwB1AG4AZAAgAHIAYQBkAGkAbwAgAGMAbwB1AG4AdABlAHIAcABhAHIAdABzACAAZgBvAHIAIAA4ADQAMAAgAEgASQBJACAAcgBlAGcAaQBvAG4AcwAsACAANQAyACAAUwBOAFIAcwAsACAAMQA2ADQAIABQAE4AZQAsACAAYQBuAGQAIAAzADgAIABwAHUAbABzAGEAcgBzAC4AIABXAGUAIABmAG8AdQBuAGQAIAA3ADkAIABjAG8AbgB0AGkAbgB1AHUAbQAgAHMAbwB1AHIAYwBlAHMAIAB0AGgAYQB0ACAAYQByAGUAIABhAHMAcwBvAGMAaQBhAHQAZQBkACAAdwBpAHQAaAAgAFgALQByAGEAeQAgAHMAbwB1AHIAYwBlAHMALgAgAFcAZQAgAGkAZABlAG4AdABpAGYAaQBlAGQAIAA2ADkAOQAgAHUAbAB0AHIAYQBzAHQAZQBlAHAAIABzAHAAZQBjAHQAcgBhAGwAIABzAG8AdQByAGMAZQBzACAAKABhAGwAcABoAGEAPAAtADEALgAzACkAIAB0AGgAYQB0ACAAYwBvAHUAbABkACAAYgBlACAAaABpAGcAaAAtAHIAZQBkAHMAaABpAGYAdAAgAGcAYQBsAGEAeABpAGUAcwAuACAAQQByAG8AdQBuAGQAIAA5ADAAMAAwACAAbwBmACAAdABoAGUAIABzAG8AdQByAGMAZQBzACAAdwBlACAAZQB4AHQAcgBhAGMAdABlAGQAIABhAHIAZQAgAG4AbwB0ACAAYwBsAGEAcwBzAGkAZgBpAGUAZAAgAHMAcABlAGMAaQBmAGkAYwBhAGwAbAB5ACwAIABiAHUAdAAgAGIAYQBzAGUAZAAgAG8AbgAgAHQAaABlAGkAcgAgAHMAcABhAHQAaQBhAGwAIABhAG4AZAAgAHMAcABlAGMAdAByAGEAbAAgAGQAaQBzAHQAcgBpAGIAdQB0AGkAbwBuACwAIABhACAAbABhAHIAZwBlACAAZgByAGEAYwB0AGkAbwBuACAAbwBmACAAdABoAGUAbQAgAGkAcwAgAGwAaQBrAGUAbAB5ACAAdABvACAAYgBlACAAZQB4AHQAcgBhAGcAYQBsAGEAYwB0AGkAYwAgAGIAYQBjAGsAZwByAG8AdQBuAGQAIABzAG8AdQByAGMAZQBzAC4AIABNAG8AcgBlACAAdABoAGEAbgAgADcANwA1ADAAIABzAG8AdQByAGMAZQBzACAAZABvACAAbgBvAHQAIABoAGEAdgBlACAAYwBvAHUAbgB0AGUAcgBwAGEAcgB0AHMAIABpAG4AIAB0AGgAZQAgAFMASQBNAEIAQQBEACAAZABhAHQAYQBiAGEAcwBlACwAIABhAG4AZAAgAG0AbwByAGUAIAB0AGgAYQBuACAAMwA3ADYAMAAgAHMAbwB1AHIAYwBlAHMAIABkAG8AIABuAG8AdAAgAGgAYQB2AGUAIABjAG8AdQBuAHQAZQByAHAAYQByAHQAcwAgAGkAbgAgAHQAaABlACAATgBFAEQAIABkAGEAdABhAGIAYQBzAGUALgAgAFMAdAB1AGQAeQBpAG4AZwAgAHQAaABlACAAbABvAG4AZwAgAHcAYQB2AGUAbABlAG4AZwB0AGgAcwAgAGMAbQAgAGMAbwBuAHQAaQBuAHUAdQBtACAAZQBtAGkAcwBzAGkAbwBuACAAYQBuAGQAIAB0AGgAZQAgAGEAcwBzAG8AYwBpAGEAdABlAGQAIABzAHAAZQBjAHQAcgBhAGwAIABpAG4AZABpAGMAZQBzACAAYQBsAGwAbwB3AHMAIAB1AHMAIAB0AG8AIABjAGgAYQByAGEAYwB0AGUAcgBpAHoAZQAgAGEAIABsAGEAcgBnAGUAIABmAHIAYQBjAHQAaQBvAG4AIABvAGYAIABHAGEAbABhAGMAdABpAGMAIABhAG4AZAAgAGUAeAB0AHIAYQBnAGEAbABhAGMAdABpAGMAIAByAGEAZABpAG8AIABzAG8AdQByAGMAZQBzACAAaQBuACAAdABoAGUAIABhAHIAZQBhACAAbwBmACAAdABoAGUAIABuAG8AcgB0AGgAZQByAG4AIABpAG4AbgBlAHIAIABNAGkAbABrAHkAIABXAGEAeQAuACAAVABoAGkAcwAgAGQAYQB0AGEAYgBhAHMAZQAgAHcAaQBsAGwAIABiAGUAIABlAHgAdAByAGUAbQBlAGwAeQAgAHUAcwBlAGYAdQBsACAAZgBvAHIAIABmAHUAdAB1AHIAZQAgAHMAdAB1AGQAaQBlAHMAIABvAGYAIABhACAAZABpAHYAZQByAHMAZQAgAHMAZQB0ACAAbwBmACAAYQBzAHQAcgBvAHAAaAB5AHMAaQBjAGEAbAAgAG8AYgBqAGUAYwB0AHMALgAAADhodHRwczovL2Nkc2FyYy5jZHMudW5pc3RyYS5mci92aXotYmluL2NhdC9KL0ErQS82MTkvQTEyNAAAAOUAVwBhAG4AZwAgAFkALgA7ACAAQgBpAGgAcgAgAFMALgA7ACAAUgB1AGcAZQBsACAATQAuADsAIABCAGUAdQB0AGgAZQByACAASAAuADsAIABKAG8AaABuAHMAdABvAG4AIABLAC4ARwAuADsAIABPAHQAdAAgAEoALgA7ACAAUwBvAGwAZQByACAASgAuAEQALgA7ACAAQgByAHUAbgB0AGgAYQBsAGUAcgAgAEEALgA7ACAAQQBuAGQAZQByAHMAbwBuACAA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AHIAdABpAGMAbABlACAAdAByAGEAbgBzAHAAbwByAHQAIAB3AGkAdABoAGkAbgAgAHQAaABlACAAbgBlAGIAdQBsAGEALAAgAGEAYwBjAG8AdQBuAHQAaQBuAGcAIABmAG8AcgAgAGkAdABzACAAYQBuAG8AbQBhAGwAbwB1AHMAbAB5ACAAbABhAHIAZwBlACAAcwBpAHoAZQAgAGEAbgBkACAAcwBwAGUAYwB0AHIAYQBsACAAYwBoAGEAcgBhAGMAdABlAHIAaQBzAHQAaQBjAHMALgAgAFQAaABlACAAbgBlAGIAdQBsAGEAIABpAHMAIABzAHQAdQBkAGkAZQBkACAAdQBzAGkAbgBnACAAYQAgAGQAZQBlAHAAIABlAHgAcABvAHMAdQByAGUAIABmAHIAbwBtACAAbwB2AGUAcgAgADEAMgAgAHkAZQBhAHIAcwAgAG8AZgAgAEgALgBFAC4AUwAuAFMALgAgAEkAIABvAHAAZQByAGEAdABpAG8AbgAsACAAdABvAGcAZQB0AGgAZQByACAAdwBpAHQAaAAgAGQAYQB0AGEAIABmAHIAbwBtACAASAAuAEUALgBTAC4AUwAuACAASQBJACAAaQBtAHAAcgBvAHYAaQBuAGcAIAB0AGgAZQAgAGwAbwB3ACAAZQBuAGUAcgBnAHkAIABzAGUAbgBzAGkAdABpAHYAaQB0AHkALgAgAEUAbgBoAGEAbgBjAGUAZAAgAGUAbgBlAHIAZwB5AC0AIABkAGUAcABlAG4AZABlAG4AdAAgAG0AbwByAHAAaABvAGwAbwBnAGkAYwBhAGwAIABhAG4AZAAgAHMAcABhAHQAaQBhAGwAbAB5AC0AcgBlAHMAbwBsAHYAZQBkACAAcwBwAGUAYwB0AHIAYQBsACAAYQBuAGEAbAB5AHMAZQBzACAAcAByAG8AYgBlACAAdABoAGUAIABWAGUAcgB5ACAASABpAGcAaAAgAEUAbgBlAHIAZwB5ACAAKABWAEgARQAsACAARQA+ADAALgAxAFQAZQBWACkAIABnAGEAbQBtAGEALQByAGEAeQAgAHAAcgBvAHAAZQByAHQAaQBlAHMAIABvAGYAIAB0AGgAZQAgAG4AZQBiAHUAbABhAC4AIABUAGgAZQAgAG4AZQBiAHUAbABhACAAZQBtAGkAcwBzAGkAbwBuACAAaQBzACAAcgBlAHYAZQBhAGwAZQBkACAAdABvACAAZQB4AHQAZQBuAGQAIABvAHUAdAAgAHQAbwAgADEALgA1ACAAZABlAGcAcgBlAGUAcwAgAGYAcgBvAG0AIAB0AGgAZQAgAHAAdQBsAHMAYQByACwAIAB+ADEALgA1ACAAdABpAG0AZQBzACAAZgB1AHIAdABoAGUAcgAgAHQAaABhAG4AIABwAHIAZQB2AGkAbwB1AHMAbAB5ACAAcwBlAGUAbgAsACAAbQBhAGsAaQBuAGcAIABIAEUAUwBTACAASgAxADgAMgA1AC0AMQAzADcALAAgAHcAaQB0AGgAIABhAG4AIABpAG4AdAByAGkAbgBzAGkAYwAgAGQAaQBhAG0AZQB0AGUAcgAgAG8AZgAgAH4AMQAwADAAcABjACwAIABwAG8AdABlAG4AdABpAGEAbABsAHkAIAB0AGgAZQAgAGwAYQByAGcAZQBzAHQAIABnAGEAbQBtAGEALQByAGEAeQAgAFAAVwBOACAAYwB1AHIAcgBlAG4AdABsAHkAIABrAG4AbwB3AG4ALgAgAEMAaABhAHIAYQBjAHQAZQByAGkAcwBhAHQAaQBvAG4AIABvAGYAIAB0AGgAZQAgAG4AZQBiAHUAbABhACcAcwAgAHMAdAByAG8AbgBnAGwAeQAgAGUAbgBlAHIAZwB5AC0AZABlAHAAZQBuAGQAZQBuAHQAIABtAG8AcgBwAGgAbwBsAG8AZwB5ACAAZQBuAGEAYgBsAGUAcwAgAHQAaABlACAAcABhAHIAdABpAGMAbABlACAAdAByAGEAbgBzAHAAbwByAHQAIABtAGUAYwBoAGEAbgBpAHMAbQBzACAAdABvACAAYgBlACAAYwBvAG4AcwB0AHIAYQBpAG4AZQBkAC4AIABBACAAZABlAHAAZQBuAGQAZQBuAGMAZQAgAG8AZgAgAHQAaABlACAAbgBlAGIAdQBsAGEAIABlAHgAdABlAG4AdAAgAHcAaQB0AGgAIABlAG4AZQByAGcAeQAgAG8AZgAgAFIAewBwAHIAbwBwAC4AdABvAH0AIABFAF4AYQBsAHAAaABhAF4AIAB3AGkAdABoACAAYQBsAHAAaABhAD0ALQAwAC4AMgA5ACsALwAtADAALgAwADQAKABzAHQAYQB0ACkAKwAvAC0AMAAuADAANQAoAHMAeQBzACkAIABkAGkAcwBmAGEAdgBvAHUAcgBzACAAYQAgAHAAdQByAGUAIABkAGkAZgBmAHUAcwBpAG8AbgAgAHMAYwBlAG4AYQByAGkAbwAgAGYAbwByACAAcABhAHIAdABpAGMAbABlACAAdAByAGEAbgBzAHAAbwByAHQAIAB3AGkAdABoAGkAbgAgAHQAaABlACAAbgBlAGIAdQBsAGEALgAgAFQAaABlACAAdABvAHQAYQBsACAAZwBhAG0AbQBhAC0AcgBhAHkAIABmAGwAdQB4ACAAbwBmACAAdABoAGUAIABuAGUAYgB1AGwAYQAgAGEAYgBvAHYAZQAgADEAfgBUAGUAVgAgAGkAcwAgAGYAbwB1AG4AZAAgAHQAbwAgAGIAZQAgACgAMQAuADEAMgArAC8ALQAwAC4AMAAzACgAcwB0AGEAdAApACsALwAtADAALgAyADUAKABzAHkAcwApACkAeAAxADAAXgAtADEAMQBeAGMAbQBeAC0AMgBeAHMAXgAtADEAXgAsACAAYwBvAHIAcgBlAHMAcABvAG4AZABpAG4AZwAgAHQAbwAgAH4ANgA0ACUAIABvAGYAIAB0AGgAZQAgAGYAbAB1AHgAIABvAGYAIAB0AGgAZQAgAEMAcgBhAGIAIABOAGUAYgB1AGwAYQAuACAASABFAFMAUwAgAEoAMQA4ADIANQAtADEAMwA3ACAAaQBzACAAYQAgAFAAVwBOACAAdwBpAHQAaAAgAGMAbABlAGEAcgAgAGUAbgBlAHIAZwB5AC0AZABlAHAAZQBuAGQAZQBuAHQAIABtAG8AcgBwAGgAbwBsAG8AZwB5ACAAYQB0ACAAVgBIAEUAIABnAGEAbQBtAGEALQByAGEAeQAgAGUAbgBlAHIAZwBpAGUAcwAuACAAVABoAGkAcwAgAHMAbwB1AHIAYwBlACAAaQBzACAAdQBzAGUAZAAgAGEAcwAgAGEAIABsAGEAYgBvAHIAYQB0AG8AcgB5ACAAdABvACAAaQBuAHYAZQBzAHQAaQBnAGEAdABlACAAcABhAHIAdABpAGMAbABlACAAdAByAGEAbgBzAHAAbwByAHQAIAB3AGkAdABoAGkAbgAgAG0AaQBkAGQAbABlAC0AYQBnAGUAZAAgAFAAVwBOAGUALgAgAEQAZQBlAHAAIABvAGIAcwBlAHIAdgBhAHQAaQBvAG4AcwAgAG8AZgAgAHQAaABpAHMAIABoAGkAZwBoAGwAeQAgAHMAcABhAHQAaQBhAGwAbAB5AC0AZQB4AHQAZQBuAGQAZQBkACAAUABXAE4AIABlAG4AYQBiAGwAZQAgAGEAIABzAHAAZQBjAHQAcgBhAGwAIABtAGEAcAAgAG8AZgAgAHQAaABlACAAcgBlAGcAaQBvAG4AIAB0AG8AIABiAGUAIABwAHIAbwBkAHUAYwBlAGQALAAgAHAAcgBvAHYAaQBkAGkAbgBnACAAaQBuAHMAaQBnAGgAdABzACAAaQBuAHQAbwAgAHQAaABlACAAcwBwAGUAYwB0AHIAYQBsACAAdgBhAHIAaQBhAHQAaQBvAG4AIAB3AGkAdABoAGkAbgAgAHQAaABlACAAbgBlAGIAdQBsAGEALgAAADhodHRwczovL2Nkc2FyYy5jZHMudW5pc3RyYS5mci92aXotYmluL2NhdC9KL0ErQS82MjEvQTExNgAACzYASAAuAEUALgBTAC4AUwAuACAAQwBvAGwAbABhAGIAbwByAGEAdABpAG8AbgA7ACAAQQBiAGQAYQBsAGwAYQAgAEgALgA7ACAAQQBoAGEAcgBvAG4AaQBhAG4AIABGAC4AOwAgAEEAaQB0ACAAQgBlAG4AawBoAGEAbABpACAARgAuADsAIABBAG4AZwB1AGUAbgBlAHIAIABFAC4ATwAuADsAIABBAHIAYQBrAGEAdwBhACAATQAuADsAIABBAHIAYwBhAHIAbwAgAEMALgA7ACAAQQByAG0AYQBuAGQAIABDAC4AOwAgAEEAcgByAGkAZQB0AGEAIABNAC4AOwAgAEIAYQBjAGsAZQBzACAATQAuADsAIABCAGEAcgBuAGEAcgBkACAATQAuADsAIABCAGUAYwBoAGUAcgBpAG4AaQAgAFkALgA7ACAAQgBlAGMAawBlAHIAIABUAGoAdQBzACAASgAuADsAIABCAGUAcgBnAGUAIABEAC4AOwAgAEIAZQByAG4AbABvAGUAaAByACAASwAuADsAIABCAGwAYQBjAGsAdwBlAGwAbAAgAFIALgA7ACAAQgBvAGUAdAB0AGMAaABlAHIAIABNAC4AOwAgAEIAbwBpAHMAcwBvAG4AIABDAC4AOwAgAEIAbwBsAG0AbwBuAHQAIABKAC4AOwAgAEIAbwBuAG4AZQBmAG8AeQAgAFMALgA7ACAAQgBvAHIAZABhAHMAIABQAC4AOwAgAEIAcgBlAGcAZQBvAG4AIABKAC4AOwAgAEIAcgB1AG4AIABGAC4AOwAgAEIAcgB1AG4AIABQAC4AOwAgAEIAcgB5AGEAbgAgAE0ALgA7ACAAQgB1AGUAYwBoAGUAbABlACAATQAuADsAIABCAHUAbABpAGsAIABUAC4AOwAgAEIAeQBsAHUAbgBkACAAVAAuADsAIABDAGEAcABhAHMAcwBvACAATQAuADsAIABDAGEAcgBvAGYAZgAgAFMALgA7ACAAQwBhAHIAbwBzAGkAIABBAC4AOwAgAEMAYQBzAGEAbgBvAHYAYQAgAFMALgA7ACAAQwBlAHIAcgB1AHQAaQAgAE0ALgA7ACAAQwBoAGEAawByAGEAYgBvAHIAdAB5ACAATgAuADsAIABDAGgAYQBuAGQAIABUAC4AOwAgAEMAaABhAG4AZAByAGEAIABTAC4AOwAgAEMAaABhAHYAZQBzACAAUgAuAEMALgBHAC4AOwAgAEMAaABlAG4AIABBAC4AOwAgAEMAbwBsAGEAZgByAGEAbgBjAGUAcwBjAG8AIABTAC4AOwAgAEMAbwBuAGQAbwBuACAAQgAuADsAIABEAGEAdgBpAGQAcwAgAEkALgBEAC4AOwAgAEQAZQBpAGwAIABDAC4AOwAgAEQAZQB2AGkAbgAgAEoALgA7ACAAZABlAFcAaQBsAHQAIABQAC4AOwAgAEQAaQByAHMAbwBuACAATAAuADsAIABEAGoAYQBuAG4AYQB0AGkALQBBAHQAYQBpACAAQQAuADsAIABEAG0AeQB0AHIAaQBpAGUAdgAgAEEALgA7ACAARABvAG4AYQB0AGgAIABBAC4AOwAgAEQAbwByAG8AcwBoAGUAbgBrAG8AIABWAC4AOwAgAEQAcgB1AHIAeQAgAEwALgBPACcAQwAuADsAIABEAHkAawBzACAASgAuADsAIABFAGcAYgBlAHIAdABzACAASwAuADsAIABFAG0AZQByAHkAIABHAC4AOwAgAEUAcgBuAGUAbgB3AGUAaQBuACAASgAuAC0AUAAuADsAIABFAHMAYwBoAGIAYQBjAGgAIABTAC4AOwAgAEYAZQBnAGEAbgAgAFMALgA7ACAARgBpAGEAcwBzAG8AbgAgAEEALgA7ACAARgBvAG4AdABhAGkAbgBlACAARwAuADsAIABGAHUAbgBrACAAUwAuADsAIABGAHUAZQBzAHMAbABpAG4AZwAgAE0ALgA7ACAARwBhAGIAaQBjAGkAIABTAC4AOwAgAEcAYQBsAGwAYQBuAHQAIABZAC4AQQAuADsAIABHAGEAdABlACAARgAuADsAIABHAGkAYQB2AGkAdAB0AG8AIABHAC4AOwAgAEcAbABhAHcAaQBvAG4AIABEAC4AOwAgAEcAbABpAGMAZQBuAHMAdABlAGkAbgAgAEoALgBGAC4AOwAgAEcAbwB0AHQAcwBjAGgAYQBsAGwAIABEAC4AOwAgAEcAcgBvAG4AZABpAG4AIABNAC4ALQBIAC4AOwAgAEgAYQBoAG4AIABKAC4AOwAgAEgAYQB1AHAAdAAgAE0ALgA7ACAASABlAGkAbgB6AGUAbABtAGEAbgBuACAARwAuADsAIABIAGUAbgByAGkAIABHAC4AOwAgAEgAZQByAG0AYQBuAG4AIABHAC4AOwAgAEgAaQBuAHQAbwBuACAASgAuAEEALgA7ACAASABvAGYAbQBhAG4AbgAgAFcALgA7ACAASABvAGkAcwBjAGgAZQBuACAAQwAuADsAIABIAG8AbABjAGgAIABUAC4ATAAuADsAIABIAG8AbABsAGUAcgAgAE0ALgA7ACAASABvAHIAbgBzACAARAAuADsAIABIAHUAYgBlAHIAIABEAC4AOwAgAEkAdwBhAHMAYQBrAGkAIABIAC4AOwAgAEoAYQBjAGgAbwBsAGsAbwB3AHMAawBhACAAQQAuADsAIABKAGEAbQByAG8AegB5ACAATQAuADsAIABKAGEAbgBrAG8AdwBzAGsAeQAgAEQALgA7ACAASgBhAG4AawBvAHcAcwBrAHkAIABGAC4AOwAgAEoAbwB1AHYAaQBuACAATAAuADsAIABKAHUAbgBnAC0AUgBpAGMAaABhAHIAZAB0ACAASQAuADsAIABLAGEAcwB0AGUAbgBkAGkAZQBjAGsAIABNAC4AQQAuADsAIABLAGEAdABhAHIAegB5AG4AcwBrAGkAIABLAC4AOwAgAEsAYQB0AHMAdQByAGEAZwBhAHcAYQAgAE0ALgA7ACAASwBhAHQAegAgAFUALgA7ACAASwBlAHIAcwB6AGIAZQByAGcAIABEAC4AOwAgAEsAaABhAG4AZwB1AGwAeQBhAG4AIABEAC4AOwAgAEsAaABlAGwAaQBmAGkAIABCAC4AOwAgAEsAaQBuAGcAIABKAC4AOwAgAEsAbABlAHAAcwBlAHIAIABTAC4AOwAgAEsAbAB1AHoAbgBpAGEAawAgAFcALgA7ACAASwBvAG0AaQBuACAATgB1AC4AOwAgAEsAbwBzAGEAYwBrACAASwAuADsAIABLAHIAYQB1AHMAIABNAC4AOwAgAEwAYQBtAGEAbgBuAGEAIABHAC4AOwAgAEwAYQB1ACAASgAuADsAIABMAGUAZgBhAHUAYwBoAGUAdQByACAASgAuADsAIABMAGUAbQBpAGUAcgBlACAAQQAuADsAIABMAGUAbQBvAGkAbgBlAC0ARwBvAHUAbQBhAHIAZAAgAE0ALgA7ACAATABlAG4AYQBpAG4AIABKAC4ALQBQAC4AOwAgAEwAZQBzAGUAcgAgAEUALgA7ACAATABvAGgAcwBlACAAVAAuADsAIABMAG8AcABlAHoALQBDAG8AdABvACAAUgAuADsAIABMAHkAcABvAHYAYQAgAEkALgA7ACAATQBhAGwAeQBzAGgAZQB2ACAARAAuADsAIABNAGEAcgBhAG4AZABvAG4AIABWAC4AOwAgAE0AYQByAGMAbwB3AGkAdABoACAAQQAuADsAIABNAGEAcgBpAGEAdQBkACAAQwAuADsAIABNAGEAcgB0AGkALQBEAGUAdgBlAHMAYQAgAEcALgA7ACAATQBhAHIAeAAgAFIALgA7ACAATQBhAHUAcgBpAG4AIABHAC4AOwAgAE0AZQBpAG4AdABqAGUAcwAgAFAALgBKAC4AOwAgAE0AaQB0AGMAaABlAGwAbAAgAEEALgBNAC4AVwAuADsAIABNAG8AZABlAHIAcwBrAGkAIABSAC4AOwAgAE0AbwBoAGEAbQBlAGQAIABNAC4AOwAgAE0AbwBoAHIAbQBhAG4AbgAgAEwALgA7ACAATQBvAG8AcgBlACAAQwAuADsAIABNAG8AdQBsAGkAbgAgAEUALgA7ACAATQB1AHIAYQBjAGgAIABUAC4AOwAgAE4AYQBrAGEAcwBoAGkAbQBhACAAUwAuADsAIABkAGUAIABOAGEAdQByAG8AaQBzACAATQAuADsAIABOAGQAaQB5AGEAdgBhAGwAYQAgAEgALgA7ACAATgBpAGUAZABlAHIAdwBhAG4AZwBlAHIAIABGAC4AOwAgAE4AaQBlAG0AaQBlAGMAIABKAC4AOwAgAE8AYQBrAGUAcwAgAEwALgA7ACAATwAnAEIAcgBpAGUAbgAgAFAALgA7ACAATwBkAGEAawBhACAASAAuADsAIABPAGgAbQAgAFMALgA7ACAATwBzAHQAcgBvAHcAcwBrAGkAIABNAC4AOwAgAE8AeQBhACAASQAuADsAIABQAGEAbgB0AGUAcgAgAE0ALgA7ACAAUABhAHIAcwBvAG4AcwAgAFIALgBEAC4AOwAgAFAAZQByAGUAbgBuAGUAcwAgAEMALgA7ACAAUABlAHQAcgB1AGMAYwBpACAAUAAuAC0ATwAuADsAIABQAGUAeQBhAHUAZAAgAEIALgA7ACAAUABpAGUAbAAgAFEALgA7ACAAUABpAHQAYQAgAFMALgA7ACAAUABvAGkAcgBlAGEAdQAgAFYALgA7ACAAUAByAGkAeQBhAG4AYQAgAE4AbwBlAGwAIABBAC4AOwAgAFAAcgBvAGsAaABvAHIAbwB2ACAARAAuAEEALgA7ACAAUAByAG8AawBvAHAAaAAgAEgALgA7ACAAUAB1AGUAaABsAGgAbwBmAGUAcgAgAEcALgA7ACAAUAB1AG4AYwBoACAATQAuADsAIABRAHUAaQByAHIAZQBuAGIAYQBjAGgAIABBAC4AOwAgAFIAYQBhAGIAIABTAC4AOwAgAFIAYQB1AHQAaAAgAFIALgA7ACAAUgBlAGkAbQBlAHIAIABBAC4AOwAgAFIAZQBpAG0AZQByACAATwAuADsAIABSAGUAbgBhAHUAZAAgAE0ALgA7ACAAUgBpAGUAZwBlAHIAIABGAC4AOwAgAFIAaQBuAGMAaABpAHUAcwBvACAATAAuADsAIABSAG8AbQBvAGwAaQAgAEMALgA7ACAAUgBvAHcAZQBsAGwAIABHAC4AOwAgAFIAdQBkAGEAawAgAEIALgA7ACAAUgB1AGkAegAtAFYAZQBsAGEAcwBjAG8AIABFAC4AOwAgAFMAYQBoAGEAawBpAGEAbgAgAFYALgA7ACAAUwBhAGkAdABvACAAUwAuADsAIABTAGEAbgBjAGgAZQB6ACAARAAuAEEALgA7ACAAUwBhAG4AdABhAG4AZwBlAGwAbwAgAEEALgA7ACAAUwBhAHMAYQBrAGkAIABNAC4AOwAgAFMAYwBoAGwAaQBjAGsAZQBpAHMAZQByACAAUgAuADsAIABTAGMAaAB1AGUAcwBzAGwAZQByACAARgAuADsAIABTAGMAaAB1AGwAegAgAEEALgA7ACAAUwBjAGgAdQB0AHQAZQAgAEgALgA7ACAAUwBjAGgAdwBhAG4AawBlACAAVQAuADsAIABTAGMAaAB3AGUAbQBtAGUAcgAgAFMALgA7ACAAUwBlAGcAbABhAHIALQBBAHIAcgBvAHkAbwAgAE0ALgA7ACAAUwBlAG4AbgBpAGEAcABwAGEAbgAgAE0ALgA7ACAAUwBlAHkAZgBmAGUAcgB0ACAAQQAuAFMALgA7ACAAUwBoAGEAZgBpACAATgAuADsAIABTAGgAaQBsAG8AbgAgAEkALgA7ACAAUwBoAGkAbgBpAG4AZwBhAHkAYQBtAHcAZQAgAEsALgA7ACAAUwBpAG0AbwBuAGkAIABSAC4AOwAgAFMAaQBuAGgAYQAgAEEALgA7ACAAUwBvAGwAIABIAC4AOwAgAFMAcABlAGMAbwB2AGkAdQBzACAAQQAuADsAIABTAHAAaQByAC0ASgBhAGMAbwBiACAATQAuADsAIABTAHQAYQB3AGEAcgB6ACAATAAuADsAIABTAHQAZQBlAG4AawBhAG0AcAAgAFIALgA7ACAAUwB0AGUAZwBtAGEAbgBuACAAQwAuADsAIABTAHQAZQBwAHAAYQAgAEMALgA7ACAAVABhAGsAYQBoAGEAcwBoAGkAIABUAC4AOwAgAFQAYQB2AGUAcgBuAGUAdAAgAEoALgAtAFAALgA7ACAAVABhAHYAZQByAG4AaQBlAHIAIABUAC4AOwAgAFQAYQB5AGwAbwByACAAQQAuAE0ALgA7ACAAVABlAHIAcgBpAGUAcgAgAFIALgA7ACAAVABpAGIAYQBsAGQAbwAgAEwALgA7ACAAVABpAHoAaQBhAG4AaQAgAEQALgA7ACAAVABsAHUAYwB6AHkAawBvAG4AdAAgAE0ALgA7ACAAVAByAGkAYwBoAGEAcgBkACAAQwAuADsAIABUAHMAaQByAG8AdQAgAE0ALgA7ACAAVABzAHUAagBpACAATgAuADsAIABUAHUAZgBmAHMAIABSAC4AOwAgAFUAYwBoAGkAeQBhAG0AYQAgAFkALgA7ACAAdgBhAG4AIABkAGUAcgAgAFcAYQBsAHQAIABEAC4ASgAuADsAIAB2AGEAbgAgAEUAbABkAGkAawAgAEMALgA7ACAAdgBhAG4AIABSAGUAbgBzAGIAdQByAGcAIABDAC4AOwAgAHYAYQBuACAAUwBvAGUAbABlAG4AIABCAC4AOwAgAFYAYQBzAGkAbABlAGkAYQBkAGkAcwAgAEcALgA7ACAAVgBlAGgAIABKAC4AOwAgAFYAZQBuAHQAZQByACAAQwAuADsAIABWAGkAbgBjAGUAbgB0ACAAUAAuADsAIABWAGkAbgBrACAASgAuADsAIABWAG8AaQBzAGkAbgAgAEYALgA7ACAAVgBvAGUAbABrACAASAAuAEoALgA7ACAAVgB1AGkAbABsAGEAdQBtAGUAIABUAC4AOwAgAFcAYQBkAGkAYQBzAGkAbgBnAGgAIABaAC4AOwAgAFcAYQBnAG4AZQByACAAUwAuAEoALgA7ACAAVwBhAGcAbgBlAHIAIABSAC4ATQAuADsAIABXAGgAaQB0AGUAIABSAC4AOwAgAFcAaQBlAHIAegBjAGgAbwBsAHMAawBhACAAQQAuADsAIABZAGEAbgBnACAAUgAuADsAIABZAG8AbgBlAGQAYQAgAEgALgA7ACAAWgBhAGIAbwByAG8AdgAgAEQALgA7ACAAWgBhAGMAaABhAHIAaQBhAHMAIABNAC4AOwAgAFoAYQBuAGkAbgAgAFIALgA7ACAAWgBkAHoAaQBhAHIAcwBrAGkAIABBAC4AQQAuADsAIABaAGUAYwBoACAAQQAuADsAIABaAGUAZgBpACAARgAuADsAIABaAGkAZQBnAGwAZQByACAAQQAuADsAIABaAG8AcgBuACAASgAuADsAIABaAHkAdwB1AGMAawBhACAATgAuAAAAEzIwMTktMDEtMTVUMDk6MTE6NDEAAAATMjAyMi0xMS0wNFQwMDowMDowMAAAADZodHRwczovL2Nkcy51bmlzdHJhLmZyL3Zpemllci1vcmcvbGljZW5jZXNfdml6aWVyLmh0bWwAAAAHY2F0YWxvZwAAAAdiaWJjb2RlAAAAEwAyADAAMQA5AEEAJgBBAC4ALgAuADYAMgAxAEEALgAxADEANgBIf8AAAAAAAA9yYWRpbyNnYW1tYS1yYXkAAAF7aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL0ErQS82MjEvQTExNjo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovQStBLzYyMS9BMTE2Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BK0EvNjIxL0ExMTYvYm94Zml0cz86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0ErQS82MjEvQTExNi9saXN0PwAAAJA6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2gAAAB5dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cAAAAEU6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQAAAAgZG9pOjEwLjI2MDkzL2Nkcy92aXppZXIuMzYyMTAxMTYAAAAfaXZvOi8vY2RzLnZpemllci9qL2ErYS82MjYvYTEwNAAAABF2czpjYXRhbG9nc2VydmljZQAAAA5KL0ErQS82MjYvQTEwNAAAABkATABPAFQAQQBBAFMAIABwAHUAbABzAGEAcgAgAGQAaQBzAGMAbwB2AGUAcgBpAGUAcwAAAAhyZXNlYXJjaAAABtcAVwBlACAAcAByAGUAcwBlAG4AdAAgAGEAbgAgAG8AdgBlAHIAdgBpAGUAdwAgAG8AZgAgAHQAaABlACAATABPAEYAQQBSACAAVABpAGUAZAAtAEEAcgByAGEAeQAgAEEAbABsAC0AUwBrAHkAIABTAHUAcgB2AGUAeQAgACgATABPAFQAQQBBAFMAKQAgAGYAbwByACAAcgBhAGQAaQBvACAAcAB1AGwAcwBhAHIAcwAgAGEAbgBkACAAZgBhAHMAdAAgAHQAcgBhAG4AcwBpAGUAbgB0AHMALgAgAFQAaABlACAAcwB1AHIAdgBlAHkAIAB1AHMAZQBzACAAdABoAGUAIABoAGkAZwBoAC0AYgBhAG4AZAAgAGEAbgB0AGUAbgBuAGEAcwAgAG8AZgAgAHQAaABlACAATABPAEYAQQBSACAAUwB1AHAAZQByAHQAZQByAHAALAAgAHQAaABlACAAZABlAG4AcwBlACAAaQBuAG4AZQByACAAcABhAHIAdAAgAG8AZgAgAHQAaABlACAATABPAEYAQQBSACAAYwBvAHIAZQAsACAAdABvACAAcwB1AHIAdgBlAHkAIAB0AGgAZQAgAG4AbwByAHQAaABlAHIAbgAgAHMAawB5ACAAKAB7AGQAZQBsAHQAYQB9AD4AMAB7AGQAZQBnAH0AKQAgAGEAdAAgAGEAIABjAGUAbgB0AHIAYQBsACAAbwBiAHMAZQByAHYAaQBuAGcAIABmAHIAZQBxAHUAZQBuAGMAeQAgAG8AZgAgADEAMwA1AE0ASAB6AC4AIABBACAAdABvAHQAYQBsACAAbwBmACAAMgAxADkAIAB0AGkAZQBkAC0AYQByAHIAYQB5ACAAYgBlAGEAbQBzACAAKABjAG8AaABlAHIAZQBuAHQAIABzAHUAbQBtAGEAdABpAG8AbgAgAG8AZgAgAHMAdABhAHQAaQBvAG4AIABzAGkAZwBuAGEAbABzACwAIABjAG8AdgBlAHIAaQBuAGcAIAAxADIAIABzAHEAdQBhAHIAZQAgAGQAZQBnAHIAZQBlAHMAKQAsACAAYQBzACAAdwBlAGwAbAAgAGEAcwAgAHQAaAByAGUAZQAgAGkAbgBjAG8AaABlAHIAZQBuAHQAIABiAGUAYQBtAHMAIAAoAGMAbwB2AGUAcgBpAG4AZwAgADYANwAgAHMAcQB1AGEAcgBlACAAZABlAGcAcgBlAGUAcwApACAAYQByAGUAIABmAG8AcgBtAGUAZAAgAGkAbgAgAGUAYQBjAGgAIABzAHUAcgB2AGUAeQAgAHAAbwBpAG4AdABpAG4AZwAuACAARgBvAHIAIABlAGEAYwBoACAAbwBmACAAdABoAGUAIAAyADIAMgAgAGIAZQBhAG0AcwAsACAAdABvAHQAYQBsACAAaQBuAHQAZQBuAHMAaQB0AHkAIABpAHMAIAByAGUAYwBvAHIAZABlAGQAIABhAHQAIAA0ADkAMQAuADUAMgAgAHsAbQB1AH0AcwAgAHQAaQBtAGUAIAByAGUAcwBvAGwAdQB0AGkAbwBuAC4AIABFAGEAYwBoACAAbwBiAHMAZQByAHYAYQB0AGkAbwBuACAAaQBuAHQAZQBnAHIAYQB0AGUAcwAgAGYAbwByACAAMQBoAHIAIABhAG4AZAAgAGMAbwB2AGUAcgBzACAAMgA1ADkAMgAgAGMAaABhAG4AbgBlAGwAcwAgAGYAcgBvAG0AIAAxADEAOQAgAHQAbwAgADEANQAxAE0ASAB6AC4AIABUAGgAaQBzACAAaQBuAHMAdAByAHUAbQBlAG4AdABhAGwAIABzAGUAdAB1AHAAIABhAGwAbABvAHcAcwAgAEwATwBUAEEAQQBTACAAdABvACAAcgBlAGEAYwBoACAAYQAgAGQAZQB0AGUAYwB0AGkAbwBuACAAdABoAHIAZQBzAGgAbwBsAGQAIABvAGYAIAAxAC0ANQBtAEoAeQAgAGYAbwByACAAcABlAHIAaQBvAGQAaQBjACAAZQBtAGkAcwBzAGkAbwBuAC4AIABUAGgAdQBzACAAZgBhAHIALAAgAHQAaABlACAATABPAFQAQQBBAFMAIABzAHUAcgB2AGUAeQAgAGgAYQBzACAAcgBlAHMAdQBsAHQAZQBkACAAaQBuACAAdABoAGUAIABkAGkAcwBjAG8AdgBlAHIAeQAgAG8AZgAgADcAMwAgAHIAYQBkAGkAbwAgAHAAdQBsAHMAYQByAHMALgAgAEEAbQBvAG4AZwAgAHQAaABlAHMAZQAgAGEAcgBlACAAdAB3AG8AIABtAGkAbABkAGwAeQAgAHIAZQBjAHkAYwBsAGUAZAAgAGIAaQBuAGEAcgB5ACAAbQBpAGwAbABpAHMAZQBjAG8AbgBkACAAcAB1AGwAcwBhAHIAcwAgACgAUAA9ADEAMwAgAGEAbgBkACAAMwAzAG0AcwApACwAIABhAHMAIAB3AGUAbABsACAAYQBzACAAdABoAGUAIABzAGwAbwB3AGUAcwB0AC0AcwBwAGkAbgBuAGkAbgBnACAAcgBhAGQAaQBvACAAcAB1AGwAcwBhAHIAIABjAHUAcgByAGUAbgB0AGwAeQAgAGsAbgBvAHcAbgAgACgAUAA9ADIAMwAuADUAcwApAC4AIABUAGgAZQAgAHMAdQByAHYAZQB5ACAAaABhAHMAIAB0AGgAdQBzACAAZgBhAHIAIABkAGUAdABlAGMAdABlAGQAIAAzADEAMQAgAGsAbgBvAHcAbgAgAHAAdQBsAHMAYQByAHMALAAgAHcAaQB0AGgAIABzAHAAaQBuACAAcABlAHIAaQBvAGQAcwAgAHIAYQBuAGcAaQBuAGcAIABmAHIAbwBtACAANABtAHMAIAB0AG8AIAA1AC4AMABzACAAYQBuAGQAIABkAGkAcwBwAGUAcgBzAGkAbwBuACAAbQBlAGEAcwB1AHIAZQBzACAAZgByAG8AbQAgADMALgAwACAAdABvACAAMgAxADcAcABjAC8AYwBtAF4AMwBeAC4AIABLAG4AbwB3AG4AIABwAHUAbABzAGEAcgBzACAAYQByAGUAIABkAGUAdABlAGMAdABlAGQAIABhAHQAIABmAGwAdQB4ACAAZABlAG4AcwBpAHQAaQBlAHMAIABjAG8AbgBzAGkAcwB0AGUAbgB0ACAAdwBpAHQAaAAgAGwAaQB0AGUAcgBhAHQAdQByAGUAIAB2AGEAbAB1AGUAcwAuACAAVwBlACAAZgBpAG4AZAAgAHQAaABhAHQAIAB0AGgAZQAgAEwATwBUAEEAQQBTACAAcAB1AGwAcwBhAHIAIABkAGkAcwBjAG8AdgBlAHIAaQBlAHMAIABoAGEAdgBlACwAIABvAG4AIABhAHYAZQByAGEAZwBlACwAIABsAG8AbgBnAGUAcgAgAHMAcABpAG4AIABwAGUAcgBpAG8AZABzACAAdABoAGEAbgAgAHQAaABlACAAawBuAG8AdwBuACAAcAB1AGwAcwBhAHIAIABwAG8AcAB1AGwAYQB0AGkAbwBuAC4AIABUAGgAaQBzACAAbQBhAHkAIAByAGUAZgBsAGUAYwB0ACAAZABpAGYAZgBlAHIAZQBuAHQAIABzAGUAbABlAGMAdABpAG8AbgAgAGIAaQBhAHMAZQBzACAAYgBlAHQAdwBlAGUAbgAgAEwATwBUAEEAQQBTACAAYQBuAGQAIABwAHIAZQB2AGkAbwB1AHMAIABzAHUAcgB2AGUAeQBzACwAIAB0AGgAbwB1AGcAaAAgAGkAdAAgAGkAcwAgAGEAbABzAG8AIABwAG8AcwBzAGkAYgBsAGUAIAB0AGgAYQB0ACAAcwBsAG8AdwBlAHIALQBzAHAAaQBuAG4AaQBuAGcAIABwAHUAbABzAGEAcgBzACAAcAByAGUAZgBlAHIAZQBuAHQAaQBhAGwAbAB5ACAAaABhAHYAZQAgAHMAdABlAGUAcABlAHIAIAByAGEAZABpAG8AIABzAHAAZQBjAHQAcgBhAC4AIABMAE8AVABBAEEAUwAgAGkAcwAgAHQAaABlACAAZABlAGUAcABlAHMAdAAgAGEAbABsAC0AcwBrAHkAIABwAHUAbABzAGEAcgAgAHMAdQByAHYAZQB5ACAAdQBzAGkAbgBnACAAYQAgAGQAaQBnAGkAdABhAGwAIABhAHAAZQByAHQAdQByAGUAIABhAHIAcgBhAHkAOwAgAHcAZQAgAGQAaQBzAGMAdQBzAHMAIABzAG8AbQBlACAAbwBmACAAdABoAGUAIABsAGUAcwBzAG8AbgBzACAAbABlAGEAcgBuAGUAZAAgAHQAaABhAHQAIABjAGEAbgAgAGkAbgBmAG8AcgBtACAAdABoAGUAIABhAHAAcAByAG8AYQBjAGgAIABmAG8AcgAgAHMAaQBtAGkAbABhAHIAIABzAHUAcgB2AGUAeQBzACAAdQBzAGkAbgBnACAAZgB1AHQAdQByAGUAIAByAGEAZABpAG8AIAB0AGUAbABlAHMAYwBvAHAAZQBzACAAcwB1AGMAaAAgAGEAcwAgAHQAaABlACAAUwBxAHUAYQByAGUAIABLAGkAbABvAG0AZQB0AHIAZQAgAEEAcgByAGEAeQAuAAAAOGh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovQStBLzYyNi9BMTA0AAABMgBTAGEAbgBpAGQAYQBzACAAUwAuADsAIABDAG8AbwBwAGUAcgAgAFMALgA7ACAAQgBhAHMAcwBhACAAQwAuAEcALgA7ACAASABlAHMAcwBlAGwAcwAgAEoALgBXAC4AVAAuADsAIABLAG8AbgBkAHIAYQB0AGkAZQB2ACAAVgAuAEkALgA7ACAATQBpAGMAaABpAGwAbABpACAARAAuADsAIABTAHQAYQBwAHAAZQByAHMAIABCAC4AVwAuADsAIABUAGEAbgAgAEMALgBNAC4AOwAgAFYAYQBuACAATABlAGUAdQB3AGUAbgAgAEoALgA7ACAAQwBlAHIAcgBpAGcAbwBuAGUAIABMAC4AOwAgAEYAYQBsAGwAbwB3AHMAIABSAC4AQQAuADsAIABJAGEAYwBvAGIAZQBsAGwAaQAgAE0ALgA7ACAATwByAHIAdQAgAEUALgA7ACAAUABpAHoAegBvACAAUgAuAEYALgA7ACAAUwBoAHUAbABlAHYAcwBrAGkAIABBAC4AOwAgAFQAbwByAGkAYgBpAG8AIABNAC4AQwAuADsAIABUAGUAcgAgAFYAZQBlAG4AIABTAC4AOwAgAFoAdQBjAGMAYQAgAFAALgA7ACAAQgBvAG4AZABvAG4AbgBlAGEAdQAgAEwALgA7ACAARwByAGkAZQBzAHMAbQBlAGkAZQByACAASgAuAC0ATQAuADsAIABLAGEAcgBhAHMAdABlAHIAZwBpAG8AdQAgAEEALgA7ACAASwByAGEAbQBlAHIAIABNAC4AOwAgAFMAbwBiAGUAeQAgAEMALgAAABMyMDE5LTEwLTAyVDA5OjM1OjUwAAAAEzIwMjItMTEtMDRUMDA6MDA6MDAAAAA2aHR0cHM6Ly9jZHMudW5pc3RyYS5mci92aXppZXItb3JnL2xpY2VuY2VzX3Zpemllci5odG1sAAAAB2NhdGFsb2cAAAAHYmliY29kZQAAABMAMgAwADEAOQBBACYAQQAuAC4ALgA2ADIANgBBAC4AMQAwADQAU3/AAAAAAAAFcmFkaW8AAAF9aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BK0EvNjI2L0ExMDQvdGFibGUyPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovQStBLzYyNi9BMTA0Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL0ErQS82MjYvQTEwNDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQStBLzYyNi9BMTA0L3RhYmxlYTE/AAAAkGl2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaAAAAHl2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwAAAARXN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZAAAACBkb2k6MTAuMjYwOTMvY2RzL3Zpemllci4zNjI2MDEwNAAAAB5pdm86Ly9jZHMudml6aWVyL2ovYWovMTMwLzI2MTMAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAANSi9BSi8xMzAvMjYxMwAAACcAVABoAGUAIABBAHIAZQBjAGkAYgBvACAATABlAGcAYQBjAHkAIABGAGEAcwB0ACAAQQBMAEYAQQAgAHMAdQByAHYAZQB5AC4AIABJAEkAAAAIcmVzZWFyY2gAAAOPAEkAbgAgAHAAcgBlAHAAYQByAGEAdABpAG8AbgAgAGYAbwByACAAdABoAGUAIABmAHUAbABsACAAQQByAGUAYwBpAGIAbwAgAEwAZQBnAGEAYwB5ACAARgBhAHMAdAAgAEEATABGAEEAIAAoAEEATABGAEEATABGAEEAKQAgAGUAeAB0AHIAYQBnAGEAbABhAGMAdABpAGMAIABIAEkAIABzAHUAcgB2AGUAeQAsACAAcAByAGUAYwB1AHIAcwBvAHIAIABvAGIAcwBlAHIAdgBhAHQAaQBvAG4AcwAgAHcAZQByAGUAIABjAGEAcgByAGkAZQBkACAAbwB1AHQAIABpAG4AIAAyADAAMAA0ACAAQQB1AGcAdQBzAHQALQBTAGUAcAB0AGUAbQBiAGUAcgAgAHcAaQB0AGgAIAB0AGgAZQAgAHMAZQB2AGUAbgAtAGIAZQBhAG0AIABBAHIAZQBjAGkAYgBvACAATAAtAGIAYQBuAGQAIABGAGUAZQBkACAAQQByAHIAYQB5ACAAKABBAEwARgBBACkAIAByAGUAYwBlAGkAdgBlAHIAIABzAHkAcwB0AGUAbQAgAGEAbgBkACAAdABoAGUAIABXAGkAZABlAGIAYQBuAGQAIABBAHIAZQBjAGkAYgBvACAAUAB1AGwAcwBhAHIAIABQAHIAbwBjAGUAcwBzAG8AcgAgAHMAcABlAGMAdAByAGEAbAAgAHAAcgBvAGMAZQBzAHMAbwByAHMALgAgAFcAaABpAGwAZQAgAHQAaABlAHMAZQAgAG8AYgBzAGUAcgB2AGEAdABpAG8AbgBzACAAdwBlAHIAZQAgAGcAZQBhAHIAZQBkACAAbQBhAGkAbgBsAHkAIABhAHQAIAB0AGUAcwB0AGkAbgBnACAAYQBuAGQAIABkAGUAYgB1AGcAZwBpAG4AZwAgAHMAdQByAHYAZQB5ACAAcwB0AHIAYQB0AGUAZwB5ACwAIABoAGEAcgBkAHcAYQByAGUALAAgAGEAbgBkACAAcwBvAGYAdAB3AGEAcgBlACwAIABhAHAAcAByAG8AeABpAG0AYQB0AGUAbAB5ACAANAA4AGgAcgAgAG8AZgAgAHQAZQBsAGUAcwBjAG8AcABlACAAdABpAG0AZQAgAHkAaQBlAGwAZABlAGQAIABzAGMAaQBlAG4AYwBlAC0AcQB1AGEAbABpAHQAeQAgAGQAYQB0AGEALgAgAFQAbwAgAHQAZQBzAHQAIABvAHUAcgAgAGEAYgBpAGwAaQB0AHkAIAB0AG8AIABkAGkAcwBjAHIAaQBtAGkAbgBhAHQAZQAgAGMAbwBzAG0AaQBjACAAcwBpAGcAbgBhAGwAcwAgAGYAcgBvAG0AIAByAGEAZABpAG8ALQBmAHIAZQBxAHUAZQBuAGMAeQAgAGkAbgB0AGUAcgBmAGUAcgBlAG4AYwBlACAAYQBuAGQAIABuAG8AaQBzAGUALAAgADEANgA1ACAAYwBhAG4AZABpAGQAYQB0AGUAcwAgAHIAYQBuAGcAaQBuAGcAIABpAG4AIAByAGUAbABpAGEAYgBpAGwAaQB0AHkAIABsAGkAawBlAGwAaQBoAG8AbwBkACAAdwBlAHIAZQAgAHIAZQBvAGIAcwBlAHIAdgBlAGQAIAB3AGkAdABoACAAdABoAGUAIABzAGkAbgBnAGwAZQAtAGIAZQBhAG0AIABMAC0AYgBhAG4AZAAgAHcAaQBkAGUAIABzAHkAcwB0AGUAbQAgAGEAdAAgAEEAcgBlAGMAaQBiAG8AIABpAG4AIAAyADAAMAA1ACAASgBhAG4AdQBhAHIAeQAtAEYAZQBiAHIAdQBhAHIAeQAuACAATwBmACAAdABoAG8AcwBlACwAIAA0ADEAJQAgAHcAZQByAGUAIABjAG8AbgBmAGkAcgBtAGUAZAAgAGEAcwAgAHIAZQBhAGwALgAgAFcAZQAgAHAAcgBlAHMAZQBuAHQAIAB0AGgAZQAgAHIAZQBzAHUAbAB0AHMAIABvAGYAIABiAG8AdABoACAAdABoAGUAIABBAEwARgBBACAAYQBuAGQAIAB0AGgAZQAgAHMAaQBuAGcAbABlAC0AYgBlAGEAbQAgAG8AYgBzAGUAcgB2AGEAdABpAG8AbgBzACAAZgBvAHIAIAB0AGgAZQAgAHMAYQBtAHAAbABlACAAbwBmACAAMQA2ADYAIABjAG8AbgBmAGkAcgBtAGUAZAAgAEgASQAgAHMAbwB1AHIAYwBlAHMALAAgAGEAcwAgAHcAZQBsAGwAIABhAHMAIABvAHUAcgAgAGEAcwBzAGUAcwBzAG0AZQBuAHQAIABvAGYAIAB0AGgAZQBpAHIAIABvAHAAdABpAGMAYQBsACAAYwBvAHUAbgB0AGUAcgBwAGEAcgB0AHMALgAAADdodHRwczovL2Nkc2FyYy5jZHMudW5pc3RyYS5mci92aXotYmluL2NhdC9KL0FKLzEzMC8yNjEzAAABFgBHAGkAbwB2AGEAbgBlAGwAbABpACAAUgAuADsAIABIAGEAeQBuAGUAcwAgAE0ALgBQAC4AOwAgAEsAZQBuAHQAIABCAC4AUgAuADsAIABQAGUAcgBpAGwAbABhAHQAIABQAC4AOwAgAEMAYQB0AGkAbgBlAGwAbABhACAAQgAuADsAIABIAG8AZgBmAG0AYQBuACAARwAuAEwALgA7ACAATQBvAG0AagBpAGEAbgAgAEUALgA7ACAAUgBvAHMAZQBuAGIAZQByAGcAIABKAC4ATAAuADsAIABTAGEAaQBuAHQAbwBuAGcAZQAgAEEALgA7ACAAUwBwAGUAawBrAGUAbgBzACAASwAuADsAIABTAHQAaQBlAHIAdwBhAGwAdAAgAFMALgA7ACAAQgByAG8AcwBjAGgAIABOAC4AOwAgAE0AYQBzAHQAZQByAHMAIABLAC4ATAAuADsAIABTAHAAcgBpAG4AZwBvAGIAIABDAC4ATQAuADsAIABLAGEAcgBhAGMAaABlAG4AdABzAGUAdgAgAEkALgBEAC4AOwAgAEsAYQByAGEAYwBoAGUAbgB0AHMAZQB2AGEAIABWAC4ARQAuADsAIABLAG8AbwBwAG0AYQBuAG4AIABSAC4AQQAuADsAIABNAHUAbABsAGUAcgAgAEUALgA7ACAAdgBhAG4AIABEAHIAaQBlAGwAIABXAC4AOwAgAHYAYQBuACAAWgBlAGUAIABMAC4AAAATMjAwOC0wMi0wOVQxNDozNTozMwAAABMyMDIyLTEwLTE4VDAwOjAwOjAwAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAwADUAQQBKAC4ALgAuAC4AMQAzADAALgAyADYAMQAzAEd/wAAAAAAABXJhZGlvAAAC+2h0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9BSi8xMzAvMjYxMzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovQUovMTMwLzI2MTM6OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0FKLzEzMC8yNjEzL2RhdGE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9BSi8xMzAvMjYxMzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9BSi8xMzAvMjYxMzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQUovMTMwLzI2MTMvdGFibGUzPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovQUovMTMwLzI2MTMvZGF0YT86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0FKLzEzMC8yNjEzL3RhYmxlMz8AAAEvOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6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YQBsACAAcwBvAGwAdQB0AGkAbwBuAHMAIAB0AGgAYQB0ACAAcgBlAHEAdQBpAHIAZQAgAHQAaABlAG0AIAB0AG8AIABiAGUAIABkAG8AdQBiAGwAZQAgAGQAZQBnAGUAbgBlAHIAYQB0AGUAIABiAGkAbgBhAHIAaQBlAHMALgAgAEEAbABsACAAbwBmACAAdABoAGUAIABsAG8AdwBlAHMAdAAgAHMAdQByAGYAYQBjAGUAIABnAHIAYQB2AGkAdAB5ACAAVwBEAHMAIABoAGEAdgBlACAAbQBlAHQAYQBsACAAbABpAG4AZQBzACAAaQBuACAAdABoAGUAaQByACAAcwBwAGUAYwB0AHIAYQAgAGkAbQBwAGwAeQBpAG4AZwAgAGwAbwBuAGcAIABnAHIAYQB2AGkAdABhAHQAaQBvAG4AYQBsACAAcwBlAHQAdABsAGkAbgBnACAAdABpAG0AZQBzACAAbwByACAAbwBuAGcAbwBpAG4AZwAgAGEAYwBjAHIAZQB0AGkAbwBuAC4AIABOAG8AdABhAGIAbAB5ACwAIABzAGkAeAAgAG8AZgAgAHQAaABlACAAVwBEAHMAIABpAG4AIABvAHUAcgAgAHMAYQBtAHAAbABlACAAaABhAHYAZQAgAGIAaQBuAGEAcgB5ACAAbQBlAHIAZwBlAHIAIAB0AGkAbQBlAHMAIAA8ADEAMABHAHkAcgAuACAARgBvAHUAcgAgAGgAYQB2AGUAIAA+AH4AMAAuADkATQBfAHsAcwB1AG4AfQBfACAAYwBvAG0AcABhAG4AaQBvAG4AcwAuACAASQBmACAAdABoAGUAIABjAG8AbQBwAGEAbgBpAG8AbgBzACAAYQByAGUAIABtAGEAcwBzAGkAdgBlACAAVwBEAHMALAAgAHQAaABlAHMAZQAgAGYAbwB1AHIAIABiAGkAbgBhAHIAaQBlAHMAIAB3AGkAbABsACAAZQB2AG8AbAB2AGUAIABpAG4AdABvACAAcwB0AGEAYgBsAGUAIABtAGEAcwBzACAAdAByAGEAbgBzAGYAZQByACAAQQBNACAAQwBWAG4AIABzAHkAcwB0AGUAbQBzACAAYQBuAGQAIABwAG8AcwBzAGkAYgBsAHkAIABlAHgAcABsAG8AZABlACAAYQBzACAAdQBuAGQAZQByAGwAdQBtAGkAbgBvAHUAcwAgAHMAdQBwAGUAcgBuAG8AdgBhAGUALgAgAEkAZgAgAHQAaABlACAAYwBvAG0AcABhAG4AaQBvAG4AcwAgAGEAcgBlACAAbgBlAHUAdAByAG8AbgAgAHMAdABhAHIAcwAsACAAdABoAGUAbgAgAHQAaABlAHMAZQAgAG0AYQB5ACAAYgBlACAAbQBpAGwAbABpAHMAZQBjAG8AbgBkACAAcAB1AGwAcwBhAHIAIABiAGkAbgBhAHIAaQBlAHMALgAgAFQAaABlAHMAZQAgAGQAaQBzAGMAbwB2AGUAcgBpAGUAcwAgAGkAbgBjAHIAZQBhAHMAZQAgAHQAaABlACAAbgB1AG0AYgBlAHIAIABvAGYAIABkAGUAdABhAGMAaABlAGQALAAgAGQAbwB1AGIAbABlACAAZABlAGcAZQBuAGUAcgBhAHQAZQAgAGIAaQBuAGEAcgBpAGUAcwAgAGkAbgAgAHQAaABlACAARQB4AHQAcgBlAG0AZQBsAHkAIABsAG8AdwAgAG0AYQBzAHMAIAAoAEUATABNACkAIABTAHUAcgB2AGUAeQAgAHQAbwAgADUANAA7ACAAMwAxACAAbwBmACAAdABoAGUAcwBlACAAYgBpAG4AYQByAGkAZQBzACAAdwBpAGwAbAAgAG0AZQByAGcAZQAgAHcAaQB0AGgAaQBuACAAYQAgAEgAdQBiAGIAbABlACAAdABpAG0AZQAuAAAANmh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovQXBKLzc2OS82NgAAAEIAQgByAG8AdwBuACAAVwAuAFIALgA7ACAASwBpAGwAaQBjACAATQAuADsAIABBAGwAbABlAG4AZABlACAAUAByAGkAZQB0AG8AIABDAC4AOwAgAEcAaQBhAG4AbgBpAG4AYQBzACAAQQAuADsAIABLAGUAbgB5AG8AbgAgAFMALgBKAC4AAAATMjAxNS0wMS0xM1QwNzo1ODo1MQAAABMyMDIyLTEwLTI0VDAwOjAwOjAwAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAxADMAQQBwAEoALgAuAC4ANwA2ADkALgAuAC4ANgA2AEJ/wAAAAAAAB29wdGljYWwAAAEgaHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL0FwSi83NjkvNjY6OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0FwSi83NjkvNjYvd2RiaW4/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9BcEovNzY5LzY2AAAAZDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6OjoAAABednM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3NlcgAAADM6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjoAAAAgZG9pOjEwLjI2MDkzL2Nkcy92aXppZXIuMTc2OTAwNjYAAAAeaXZvOi8vY2RzLnZpemllci9qL2Fwai84NjEvMTEzAAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAADUovQXBKLzg2MS8xMTMAAAAsAFYATABCAEEAIABhAHMAdAByAG8AbQBlAHQAcgB5ACAAYwBvAG0AYgBpAG4AZQBkACAAdwBpAHQAaAAgAEcAYQBpAGEAIABEAFIAMQAgAGUAcABvAGMAaAAAAAhyZXNlYXJjaAAABXUAVABoAGUAIABjAGEAbgBvAG4AaQBjAGEAbAAgAG0AZQB0AGgAbwBkAHMAIABmAG8AcgAgAGcAcgBhAHYAaQB0AGEAdABpAG8AbgBhAGwAIAB3AGEAdgBlACAAZABlAHQAZQBjAHQAaQBvAG4AIABhAHIAZQAgAGcAcgBvAHUAbgBkAC0AIABhAG4AZAAgAHMAcABhAGMAZQAtACAAYgBhAHMAZQBkACAAbABhAHMAZQByACAAaQBuAHQAZQByAGYAZQByAG8AbQBlAHQAcgB5ACwAIABwAHUAbABzAGEAcgAgAHQAaQBtAGkAbgBnACwAIABhAG4AZAAgAHAAbwBsAGEAcgBpAHoAYQB0AGkAbwBuACAAbwBmACAAdABoAGUAIABjAG8AcwBtAGkAYwAgAG0AaQBjAHIAbwB3AGEAdgBlACAAYgBhAGMAawBnAHIAbwB1AG4AZAAuACAAQgB1AHQAIABhAHMAIABoAGEAcwAgAGIAZQBlAG4AIABzAHUAZwBnAGUAcwB0AGUAZAAgAGIAeQAgAG4AdQBtAGUAcgBvAHUAcwAgAGkAbgB2AGUAcwB0AGkAZwBhAHQAbwByAHMALAAgAGEAcwB0AHIAbwBtAGUAdAByAHkAIABvAGYAZgBlAHIAcwAgAGEAbgAgAGEAZABkAGkAdABpAG8AbgBhAGwAIABwAGEAdABoACAAdABvACAAZwByAGEAdgBpAHQAYQB0AGkAbwBuAGEAbAAgAHcAYQB2AGUAIABkAGUAdABlAGMAdABpAG8AbgAuACAARwByAGEAdgBpAHQAYQB0AGkAbwBuAGEAbAAgAHcAYQB2AGUAcwAgAGQAZQBmAGwAZQBjAHQAIABsAGkAZwBoAHQAIAByAGEAeQBzACAAbwBmACAAZQB4AHQAcgBhAGcAYQBsAGEAYwB0AGkAYwAgAG8AYgBqAGUAYwB0AHMALAAgAGMAcgBlAGEAdABpAG4AZwAgAGEAcABwAGEAcgBlAG4AdAAgAHAAcgBvAHAAZQByACAAbQBvAHQAaQBvAG4AcwAgAGkAbgAgAGEAIABxAHUAYQBkAHIAdQBwAG8AbABhAHIAIAAoAGEAbgBkACAAaABpAGcAaABlAHIALQBvAHIAZABlAHIAIABtAG8AZABlAHMAKQAgAHAAYQB0AHQAZQByAG4ALgAgAEEAcwB0AHIAbwBtAGUAdAByAHkAIABvAGYAIABlAHgAdAByAGEAZwBhAGwAYQBjAHQAaQBjACAAcgBhAGQAaQBvACAAcwBvAHUAcgBjAGUAcwAgAGkAcwAgAHMAZQBuAHMAaQB0AGkAdgBlACAAdABvACAAZwByAGEAdgBpAHQAYQB0AGkAbwBuAGEAbAAgAHcAYQB2AGUAcwAgAHcAaQB0AGgAIABmAHIAZQBxAHUAZQBuAGMAaQBlAHMAIABiAGUAdAB3AGUAZQBuACAAcgBvAHUAZwBoAGwAeQAgADEAMABeAC0AMQA4AF4AIABhAG4AZAAgADEAMABeAC0AOABeAEgAegAgACgASABfADAAXwAgAGEAbgBkACAAMQAvADMAeQByAF4ALQAxAF4AKQAsACAAbwB2AGUAcgBsAGEAcABwAGkAbgBnACAAYQBuAGQAIABiAHIAaQBkAGcAaQBuAGcAIAB0AGgAZQAgAHAAdQBsAHMAYQByACAAdABpAG0AaQBuAGcAIABhAG4AZAAgAEMATQBCACAAcABvAGwAYQByAGkAegBhAHQAaQBvAG4AIAByAGUAZwBpAG0AZQBzAC4AIABXAGUAIABwAHIAZQBzAGUAbgB0ACAAYQAgAG0AZQB0AGgAbwBkAG8AbABvAGcAeQAgAGYAbwByACAAYQBzAHQAcgBvAG0AZQB0AHIAaQBjACAAZwByAGEAdgBpAHQAYQB0AGkAbwBuAGEAbAAgAHcAYQB2AGUAIABkAGUAdABlAGMAdABpAG8AbgAgAGkAbgAgAHQAaABlACAAcAByAGUAcwBlAG4AYwBlACAAbwBmACAAbABhAHIAZwBlACAAaQBuAHQAcgBpAG4AcwBpAGMAIAB1AG4AYwBvAHIAcgBlAGwAYQB0AGUAZAAgAHAAcgBvAHAAZQByACAAbQBvAHQAaQBvAG4AcwAgACgAaQAuAGUALgAsACAAcgBhAGQAaQBvACAAagBlAHQAcwApAC4AIABXAGUAIABvAGIAdABhAGkAbgAgADkANQAlACAAYwBvAG4AZgBpAGQAZQBuAGMAZQAgAGwAaQBtAGkAdABzACAAbwBuACAAdABoAGUAIABzAHQAbwBjAGgAYQBzAHQAaQBjACAAZwByAGEAdgBpAHQAYQB0AGkAbwBuAGEAbAAgAHcAYQB2AGUAIABiAGEAYwBrAGcAcgBvAHUAbgBkACAAdQBzAGkAbgBnACAANwAxADEAIAByAGEAZABpAG8AIABzAG8AdQByAGMAZQBzACwAIAB7AE8AbQBlAGcAYQB9AF8ARwBXAF8APAAwAC4AMAAwADYANAAsACAAYQBuAGQAIAB1AHMAaQBuAGcAIAA1ADAAOAAgAHIAYQBkAGkAbwAgAHMAbwB1AHIAYwBlAHMAIABjAG8AbQBiAGkAbgBlAGQAIAB3AGkAdABoACAAdABoAGUAIABmAGkAcgBzAHQAIABHAGEAaQBhACAAZABhAHQAYQAgAHIAZQBsAGUAYQBzAGUAOgAgAHsATwBtAGUAZwBhAH0AXwBHAFcAXwA8ADAALgAwADEAMQAuACAAVABoAGUAcwBlACAAbABpAG0AaQB0AHMAIABwAHIAbwBiAGUAIABnAHIAYQB2AGkAdABhAHQAaQBvAG4AYQBsACAAdwBhAHYAZQAgAGYAcgBlAHEAdQBlAG4AYwBpAGUAcwAgADYAeAAxADAAXgAtADEAOABeAEgAegA8AH4AZgA8AH4AMQB4ADEAMABeAC0AOQBeAEgAegAuACAAVQBzAGkAbgBnACAAYQAgAFcASQBTAEUALQBHAGEAaQBhACAAYwBhAHQAYQBsAG8AZwAgAG8AZgAgADUANgA3ADcAMgAxACAAQQBHAE4ALAAgAHcAZQAgAHAAcgBlAGQAaQBjAHQAIABhACAAbABpAG0AaQB0ACAAZQB4AHAAZQBjAHQAZQBkACAAZgByAG8AbQAgAEcAYQBpAGEAIABhAGwAbwBuAGUAIABvAGYAIAB7AE8AbQBlAGcAYQB9AF8ARwBXAF8APAAwAC4AMAAwADAANgAsACAAdwBoAGkAYwBoACAAaQBzACAAcwBpAGcAbgBpAGYAaQBjAGEAbgB0AGwAeQAgAGgAaQBnAGgAZQByACAAdABoAGEAbgAgAHcAYQBzACAAbwByAGkAZwBpAG4AYQBsAGwAeQAgAGYAbwByAGUAYwBhAHMAdAAuACAASQBuAGMAaQBkAGUAbgB0AGEAbABsAHkALAAgAHcAZQAgAGQAZQB0AGUAYwB0ACAAYQBuAGQAIAByAGUAcABvAHIAdAAgAG8AbgAgADIAMgAgAG4AZQB3ACAAZQB4AGEAbQBwAGwAZQBzACAAbwBmACAAbwBwAHQAaQBjAGEAbAAgAHMAdQBwAGUAcgBsAHUAbQBpAG4AYQBsACAAbQBvAHQAaQBvAG4AIAB3AGkAdABoACAAcgBlAGQAcwBoAGkAZgB0AHMAIAAwAC4AMQAzAC0AMwAuADgAOQAuAAAAN2h0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovQXBKLzg2MS8xMTMAAAAmAEQAYQByAGwAaQBuAGcAIABKAC4AOwAgAFQAcgB1AGUAYgBlAG4AYgBhAGMAaAAgAEEALgBFAC4AOwAgAFAAYQBpAG4AZQAgAEoALgAAABMyMDE5LTA4LTEyVDEyOjE3OjQwAAAAEzIwMjItMTEtMDRUMDA6MDA6MDAAAAA2aHR0cHM6Ly9jZHMudW5pc3RyYS5mci92aXppZXItb3JnL2xpY2VuY2VzX3Zpemllci5odG1sAAAAB2NhdGFsb2cAAAAHYmliY29kZQAAABMAMgAwADEAOABBAHAASgAuAC4ALgA4ADYAMQAuAC4AMQAxADMARH/AAAAAAAANb3B0aWNhbCNyYWRpbwAAASRodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0FwSi84NjEvMTEzL3RhYmxlNz86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovQXBKLzg2MS8xMTM6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL0FwSi84NjEvMTEzAAAAZGl2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAABednM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3NlcgAAADNzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAAgZG9pOjEwLjI2MDkzL2Nkcy92aXppZXIuMTg2MTAxMTMAAAAdaXZvOi8vY2RzLnZpemllci9qL2Fwai85MDUvNzYAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAAMSi9BcEovOTA1Lzc2AAAALQAzAHIAZAAgAEgAQQBXAEMAIABjAGEAdAAuACAAbwBmACAAVgBIAEUAIABnAGEAbQBtAGEALQByAGEAeQAgAHMAbwB1AHIAYwBlAHMAIAAoADMASABXAEMAKQAAAAhyZXNlYXJjaAAAA98AVwBlACAAcAByAGUAcwBlAG4AdAAgAGEAIABuAGUAdwAgAGMAYQB0AGEAbABvAGcAIABvAGYAIABUAGUAVgAgAGcAYQBtAG0AYQAtAHIAYQB5ACAAcwBvAHUAcgBjAGUAcwAgAHUAcwBpAG4AZwAgADEANQAyADMAIABkAGEAeQBzACAAbwBmACAAZABhAHQAYQAgAGYAcgBvAG0AIAB0AGgAZQAgAEgAaQBnAGgALQBBAGwAdABpAHQAdQBkAGUAIABXAGEAdABlAHIAIABDAGgAZQByAGUAbgBrAG8AdgAgACgASABBAFcAQwApACAATwBiAHMAZQByAHYAYQB0AG8AcgB5AC4AIABUAGgAZQAgAGMAYQB0AGEAbABvAGcAIAByAGUAcAByAGUAcwBlAG4AdABzACAAdABoAGUAIABtAG8AcwB0ACAAcwBlAG4AcwBpAHQAaQB2AGUAIABzAHUAcgB2AGUAeQAgAG8AZgAgAHQAaABlACAAbgBvAHIAdABoAGUAcgBuACAAZwBhAG0AbQBhAC0AcgBhAHkAIABzAGsAeQAgAGEAdAAgAGUAbgBlAHIAZwBpAGUAcwAgAGEAYgBvAHYAZQAgAHMAZQB2AGUAcgBhAGwAIABUAGUAVgAsACAAdwBpAHQAaAAgAHQAaAByAGUAZQAgAHQAaQBtAGUAcwAgAHQAaABlACAAZQB4AHAAbwBzAHUAcgBlACAAYwBvAG0AcABhAHIAZQBkACAAdABvACAAdABoAGUAIABwAHIAZQB2AGkAbwB1AHMAIABIAEEAVwBDACAAYwBhAHQAYQBsAG8AZwAsACAAMgBIAFcAQwAuACAAVwBlACAAcgBlAHAAbwByAHQAIAA2ADUAIABzAG8AdQByAGMAZQBzACAAZABlAHQAZQBjAHQAZQBkACAAYQB0ACAAPgA9ADUAewBzAGkAZwBtAGEAfQAgAHMAaQBnAG4AaQBmAGkAYwBhAG4AYwBlACwAIABhAGwAbwBuAGcAIAB3AGkAdABoACAAdABoAGUAIABwAG8AcwBpAHQAaQBvAG4AcwAgAGEAbgBkACAAcwBwAGUAYwB0AHIAYQBsACAAZgBpAHQAcwAgAGYAbwByACAAZQBhAGMAaAAgAHMAbwB1AHIAYwBlAC4AIABUAGgAZQAgAGMAYQB0AGEAbABvAGcAIABjAG8AbgB0AGEAaQBuAHMAIABlAGkAZwBoAHQAIABzAG8AdQByAGMAZQBzACAAdABoAGEAdAAgAGgAYQB2AGUAIABuAG8AIABjAG8AdQBuAHQAZQByAHAAYQByAHQAIABpAG4AIAB0AGgAZQAgADIASABXAEMAIABjAGEAdABhAGwAbwBnACwAIABiAHUAdAAgAGEAcgBlACAAdwBpAHQAaABpAG4AIAAxAHsAZABlAGcAfQAgAG8AZgAgAHAAcgBlAHYAaQBvAHUAcwBsAHkAIABkAGUAdABlAGMAdABlAGQAIABUAGUAVgAgAGUAbQBpAHQAdABlAHIAcwAsACAAYQBuAGQAIAAyADAAIABzAG8AdQByAGMAZQBzACAAdABoAGEAdAAgAGEAcgBlACAAbQBvAHIAZQAgAHQAaABhAG4AIAAxAHsAZABlAGcAfQAgAGEAdwBhAHkAIABmAHIAbwBtACAAYQBuAHkAIABwAHIAZQB2AGkAbwB1AHMAbAB5ACAAZABlAHQAZQBjAHQAZQBkACAAVABlAFYAIABzAG8AdQByAGMAZQAuACAATwBmACAAdABoAGUAcwBlACAAMgAwACAAbgBlAHcAIABzAG8AdQByAGMAZQBzACwAIAAxADQAIABoAGEAdgBlACAAYQAgAHAAbwB0AGUAbgB0AGkAYQBsACAAYwBvAHUAbgB0AGUAcgBwAGEAcgB0ACAAaQBuACAAdABoAGUAIABmAG8AdQByAHQAaAAgAEYAZQByAG0AaQAgAEwAYQByAGcAZQAgAEEAcgBlAGEAIABUAGUAbABlAHMAYwBvAHAAZQAgAGMAYQB0AGEAbABvAGcAIABvAGYAIABnAGEAbQBtAGEALQByAGEAeQAgAHMAbwB1AHIAYwBlAHMALgAgAFcAZQAgAGEAbABzAG8AIABlAHgAcABsAG8AcgBlACAAcABvAHQAZQBuAHQAaQBhAGwAIABhAHMAcwBvAGMAaQBhAHQAaQBvAG4AcwAgAG8AZgAgADMASABXAEMAIABzAG8AdQByAGMAZQBzACAAdwBpAHQAaAAgAHAAdQBsAHMAYQByAHMAIABpAG4AIAB0AGgAZQAgAEEAdQBzAHQAcgBhAGwAaQBhACAAVABlAGwAZQBzAGMAbwBwAGUAIABOAGEAdABpAG8AbgBhAGwAIABGAGEAYwBpAGwAaQB0AHkAIAAoAEEAVABOAEYAKQAgAHAAdQBsAHMAYQByACAAYwBhAHQAYQBsAG8AZwAgAGEAbgBkACAAcwB1AHAAZQByAG4AbwB2AGEAIAByAGUAbQBuAGEAbgB0AHMAIABpAG4AIAB0AGgAZQAgAEcAYQBsAGEAYwB0AGkAYwAgAHMAdQBwAGUAcgBuAG8AdgBhACAAcgBlAG0AbgBhAG4AdAAgAGMAYQB0AGEAbABvAGcALgAAADZodHRwczovL2Nkc2FyYy5jZHMudW5pc3RyYS5mci92aXotYmluL2NhdC9KL0FwSi85MDUvNzYAAAYJAEEAbABiAGUAcgB0ACAAQQAuADsAIABBAGwAZgBhAHIAbwAgAFIALgA7ACAAQQBsAHYAYQByAGUAegAgAEMALgA7ACAAQQBuAGcAZQBsAGUAcwAgAEMAYQBtAGEAYwBoAG8AIABKAC4AUgAuADsAIABBAHIAdABlAGEAZwBhAC0AVgBlAGwAYQB6AHEAdQBlAHoAIABKAC4AQwAuADsAIABBAHIAdQBuAGIAYQBiAHUAIABLAC4AUAAuADsAIABBAHYAaQBsAGEAIABSAG8AagBhAHMAIABEAC4AOwAgAEEAeQBhAGwAYQAgAFMAbwBsAGEAcgBlAHMAIABIAC4AQQAuADsAIABCAGEAZwBoAG0AYQBuAHkAYQBuACAAVgAuADsAIABCAGUAbABtAG8AbgB0AC0ATQBvAHIAZQBuAG8AIABFAC4AOwAgAEIAZQBuAFoAdgBpACAAUwAuAFkALgA7ACAAQgByAGkAcwBiAG8AaQBzACAAQwAuADsAIABDAGEAYgBhAGwAbABlAHIAbwAtAE0AbwByAGEAIABLAC4AUwAuADsAIABDAGEAcABpAHMAdAByAGEAbgAgAFQALgA7ACAAQwBhAHIAcgBhAG0AaQBuAGEAbgBhACAAQQAuADsAIABDAGEAcwBhAG4AbwB2AGEAIABTAC4AOwAgAEMAbwB0AHQAaQAgAFUALgA7ACAAQwBvAHUAdABpAG4AbwAgAGQAZQAgAEwAZQBvAG4AIABTAC4AOwAgAEQAZQAgAGwAYQAgAEYAdQBlAG4AdABlACAARQAuADsAIABEAGkAYQB6ACAASABlAHIAbgBhAG4AZABlAHoAIABSAC4AOwAgAEQAaQBhAHoALQBDAHIAdQB6ACAATAAuADsAIABEAGkAbgBnAHUAcwAgAEIALgBMAC4AOwAgAEQAdQBWAGUAcgBuAG8AaQBzACAATQAuAEEALgA7ACAARAB1AHIAbwBjAGgAZQByACAATQAuADsAIABEAGkAYQB6AC0AVgBlAGwAZQB6ACAASgAuAEMALgA7ACAARQBsAGwAcwB3AG8AcgB0AGgAIABSAC4AVwAuADsAIABFAG4AZwBlAGwAIABLAC4AOwAgAEUAcwBwAGkAbgBvAHoAYQAgAEMALgA7ACAARgBhAG4AIABLAC4ATAAuADsAIABGAGEAbgBnACAASwAuADsAIABGAGUAcgBuAGEAbgBkAGUAegAgAEEAbABvAG4AcwBvACAATQAuADsAIABGAGwAZQBpAHMAYwBoAGgAYQBjAGsAIABIAC4AOwAgAEYAcgBhAGkAagBhACAATgAuADsAIABHAGEAbAB2AGEAbgAtAEcAYQBtAGUAegAgAEEALgA7ACAARwBhAHIAYwBpAGEAIABEAC4AOwAgAEcAYQByAGMAaQBhAC0ARwBvAG4AegBhAGwAZQB6ACAASgAuAEEALgA7ACAARwBhAHIAZgBpAGEAcwAgAEYALgA7ACAARwBpAGEAYwBpAG4AdABpACAARwAuADsAIABHAG8AbgB6AGEAbABlAHoAIABNAC4ATQAuADsAIABHAG8AbwBkAG0AYQBuACAASgAuAEEALgA7ACAASABhAHIAZABpAG4AZwAgAEoALgBQAC4AOwAgAEgAZQByAG4AYQBuAGQAZQB6ACAAUwAuADsAIABIAGkAbgB0AG8AbgAgAEoALgA7ACAASABvAG4AYQAgAEIALgA7ACAASAB1AGEAbgBnACAARAAuADsAIABIAHUAZQB5AG8AdABsAC0AWgBhAGgAdQBhAG4AdABpAHQAbABhACAARgAuADsAIABIAHUAbgB0AGUAbQBlAHkAZQByACAAUAAuADsAIABJAHIAaQBhAHIAdABlACAAQQAuADsAIABKAGEAcgBkAGkAbgAtAEIAbABpAGMAcQAgAEEALgA7ACAASgBvAHMAaABpACAAVgAuADsAIABLAGkAZQBkAGEAIABEAC4AOwAgAEwAYQByAGEAIABBAC4AOwAgAEwAZQBlACAAVwAuAEgALgA7ACAATABlAG8AbgAgAFYAYQByAGcAYQBzACAASAAuADsAIABMAGkAbgBuAGUAbQBhAG4AbgAgAEoALgBUAC4AOwAgAEwAbwBuAGcAaQBuAG8AdAB0AGkAIABBAC4ATAAuADsAIABMAHUAaQBzAC0AUgBhAHkAYQAgAEcALgA7ACAATAB1AG4AZABlAGUAbgAgAEoALgA7ACAATABvAHAAZQB6AC0AQwBvAHQAbwAgAFIALgA7ACAATQBhAGwAbwBuAGUAIABLAC4AOwAgAE0AYQByAGEAbgBkAG8AbgAgAFYALgA7ACAATQBhAHIAdABpAG4AZQB6ACAATwAuADsAIABNAGEAcgB0AGkAbgBlAHoALQBDAGEAcwB0AGUAbABsAGEAbgBvAHMAIABJAC4AOwAgAE0AYQByAHQAaQBuAGUAegAtAEMAYQBzAHQAcgBvACAASgAuADsAIABNAGEAdAB0AGgAZQB3AHMAIABKAC4AQQAuADsAIABNAGkAcgBhAG4AZABhAC0AUgBvAG0AYQBnAG4AbwBsAGkAIABQAC4AOwAgAE0AbwByAGEAbABlAHMALQBTAG8AdABvACAASgAuAEEALgA7ACAATQBvAHIAZQBuAG8AIABFAC4AOwAgAE0AbwBzAHQAYQBmAGEAIABNAC4AOwAgAE4AYQB5AGUAcgBoAG8AZABhACAAQQAuADsAIABOAGUAbABsAGUAbgAgAEwALgA7ACAATgBlAHcAYgBvAGwAZAAgAE0ALgA7ACAATgBpAHMAYQAgAE0ALgBVAC4AOwAgAE4AbwByAGkAZQBnAGEALQBQAGEAcABhAHEAdQBpACAAUgAuADsAIABPAGwAaQB2AGUAcgBhAC0ATgBpAGUAdABvACAATAAuADsAIABPAG0AbwBkAGUAaQAgAE4ALgA7ACAAUABlAGkAcwBrAGUAcgAgAEEALgA7ACAAUABlAHIAZQB6ACAAQQByAGEAdQBqAG8AIABZAC4AOwAgAFAAZQByAGUAegAtAFAAZQByAGUAegAgAEUALgBHAC4AOwAgAFIAZQBuACAAWgAuADsAIABSAGgAbwAgAEMALgBEAC4AOwAgAFIAaQB2AGkAZQByAGUAIABDAC4AOwAgAFIAbwBzAGEALQBHAG8AbgB6AGEAbABlAHoAIABEAC4AOwAgAFIAdQBpAHoALQBWAGUAbABhAHMAYwBvACAARQAuADsAIABTAGEAbABhAHoAYQByACAASAAuADsAIABTAGEAbABlAHMAYQAgAEcAcgBlAHUAcwAgAEYALgBTAC4AOwAgAFMAYQBuAGQAbwB2AGEAbAAgAEEALgA7ACAAUwBjAGgAbgBlAGkAZABlAHIAIABNAC4AOwAgAFMAYwBoAG8AbwByAGwAZQBtAG0AZQByACAASAAuADsAIABTAGUAcgBuAGEAIABGAC4AOwAgAFMAaQBuAG4AaQBzACAARwAuADsAIABTAG0AaQB0AGgAIABBAC4ASgAuADsAIABTAHAAcgBpAG4AZwBlAHIAIABSAC4AVwAuADsAIABTAHUAcgBhAGoAYgBhAGwAaQAgAFAALgA7ACAAVABvAGwAbABlAGYAcwBvAG4AIABLAC4AOwAgAFQAbwByAHIAZQBzACAASQAuADsAIABUAG8AcgByAGUAcwAtAEUAcwBjAG8AYgBlAGQAbwAgAFIALgA7ACAAVQBrAHcAYQB0AHQAYQAgAFQALgBOAC4AOwAgAFUAcgBlAG4AYQAtAE0AZQBuAGEAIABGAC4AOwAgAFcAZQBpAHMAZwBhAHIAYgBlAHIAIABUAC4AOwAgAFcAZQByAG4AZQByACAARgAuADsAIABXAGkAbABsAG8AeAAgAEUALgA7ACAAWgBlAHAAZQBkAGEAIABBAC4AOwAgAFoAaABvAHUAIABIAC4AOwAgAGQAZQAgAEwAZQBvAG4AIABDAC4AOwAgAEEAbAB2AGEAcgBlAHoAIABKAC4ARAAuADsAIABUAGgAZQAgAEgAQQBXAEMAIABDAG8AbABsAGEAYgBvAHIAYQB0AGkAbwBuAAAAEzIwMjItMDgtMDRUMTE6NTU6MTQAAAATMjAyMy0wNi0wOFQxMTo1Mzo1NgAAADZodHRwczovL2Nkcy51bmlzdHJhLmZyL3Zpemllci1vcmcvbGljZW5jZXNfdml6aWVyLmh0bWwAAAAHY2F0YWxvZwAAAAdiaWJjb2RlAAAAEwAyADAAMgAwAEEAcABKAC4ALgAuADkAMAA1AC4ALgAuADcANgBBf8AAAAAAAAlnYW1tYS1yYXkAAAHIaHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9BcEovOTA1Lzc2Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BcEovOTA1Lzc2L3RhYmxlND86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0FwSi85MDUvNzYvc291cmNlcz86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL0FwSi85MDUvNzYvdGFibGUzPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9BcEovOTA1Lzc2AAAAvDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6AAAAlHZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHAAAABXOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6AAAAIGRvaToxMC4yNjA5My9jZHMvdml6aWVyLjE5MDUwMDc2AAAAHml2bzovL2Nkcy52aXppZXIvai9hcGovOTE0LzEyMQAAABF2czpjYXRhbG9nc2VydmljZQAAAA1KL0FwSi85MTQvMTIxAAAAJQBMAGkAbQBpAHQAcwAgAG8AbgAgAFMATQBCAEgAIABiAGkAbgBhAHIAaQBlAHMAIABmAHIAbwBtACAATgBBAE4ATwBHAHIAYQB2AAAACHJlc2VhcmNoAAAGowBTAHUAcABlAHIAbQBhAHMAcwBpAHYAZQAgAGIAbABhAGMAawAgAGgAbwBsAGUAIABiAGkAbgBhAHIAaQBlAHMAIAAoAFMATQBCAEgAQgBzACkAIABzAGgAbwB1AGwAZAAgAGYAbwByAG0AIABmAHIAZQBxAHUAZQBuAHQAbAB5ACAAaQBuACAAZwBhAGwAYQBjAHQAaQBjACAAbgB1AGMAbABlAGkAIABhAHMAIABhACAAcgBlAHMAdQBsAHQAIABvAGYAIABnAGEAbABhAHgAeQAgAG0AZQByAGcAZQByAHMALgAgAEEAdAAgAHMAdQBiAHAAYQByAHMAZQBjACAAcwBlAHAAYQByAGEAdABpAG8AbgBzACwAIABiAGkAbgBhAHIAaQBlAHMAIABiAGUAYwBvAG0AZQAgAHMAdAByAG8AbgBnACAAcwBvAHUAcgBjAGUAcwAgAG8AZgAgAGwAbwB3AC0AZgByAGUAcQB1AGUAbgBjAHkAIABnAHIAYQB2AGkAdABhAHQAaQBvAG4AYQBsACAAdwBhAHYAZQBzACAAKABHAFcAcwApACwAIAB0AGEAcgBnAGUAdABlAGQAIABiAHkAIABQAHUAbABzAGEAcgAgAFQAaQBtAGkAbgBnACAAQQByAHIAYQB5AHMALgAgAFcAZQAgAHUAcwBlAGQAIAByAGUAYwBlAG4AdAAgAHUAcABwAGUAcgAgAGwAaQBtAGkAdABzACAAbwBuACAAYwBvAG4AdABpAG4AdQBvAHUAcwAgAEcAVwBzACAAZgByAG8AbQAgAHQAaABlACAATgBvAHIAdABoACAAQQBtAGUAcgBpAGMAYQBuACAATgBhAG4AbwBoAGUAcgB0AHoAIABPAGIAcwBlAHIAdgBhAHQAbwByAHkAIABmAG8AcgAgAEcAcgBhAHYAaQB0AGEAdABpAG8AbgBhAGwAIABXAGEAdgBlAHMAIAAoAE4AQQBOAE8ARwByAGEAdgApACAAMQAxAHkAcgAgAGQAYQB0AGEAIABzAGUAdAAgAHQAbwAgAHAAbABhAGMAZQAgAGMAbwBuAHMAdAByAGEAaQBuAHQAcwAgAG8AbgAgAHAAdQB0AGEAdABpAHYAZQAgAFMATQBCAEgAQgBzACAAaQBuACAAbgBlAGEAcgBiAHkAIABtAGEAcwBzAGkAdgBlACAAZwBhAGwAYQB4AGkAZQBzAC4AIABXAGUAIABjAG8AbQBwAGkAbABlAGQAIABhACAAYwBvAG0AcAByAGUAaABlAG4AcwBpAHYAZQAgAGMAYQB0AGEAbABvAGcAIABvAGYAIAB+ADQANAAwADAAMAAgAGcAYQBsAGEAeABpAGUAcwAgAGkAbgAgAHQAaABlACAAbABvAGMAYQBsACAAdQBuAGkAdgBlAHIAcwBlACAAKAB1AHAAIAB0AG8AIAByAGUAZABzAGgAaQBmAHQAIAB+ADAALgAwADUAKQAgAGEAbgBkACAAcABvAHAAdQBsAGEAdABlAGQAIAB0AGgAZQBtACAAdwBpAHQAaAAgAGgAeQBwAG8AdABoAGUAdABpAGMAYQBsACAAYgBpAG4AYQByAGkAZQBzACwAIABhAHMAcwB1AG0AaQBuAGcAIAB0AGgAYQB0ACAAdABoAGUAIAB0AG8AdABhAGwAIABtAGEAcwBzACAAbwBmACAAdABoAGUAIABiAGkAbgBhAHIAeQAgAGkAcwAgAGUAcQB1AGEAbAAgAHQAbwAgAHQAaABlACAAUwBNAEIASAAgAG0AYQBzAHMAIABkAGUAcgBpAHYAZQBkACAAZgByAG8AbQAgAGcAbABvAGIAYQBsACAAcwBjAGEAbABpAG4AZwAgAHIAZQBsAGEAdABpAG8AbgBzAC4AIABBAHMAcwB1AG0AaQBuAGcAIABjAGkAcgBjAHUAbABhAHIAIABlAHEAdQBhAGwALQBtAGEAcwBzACAAYgBpAG4AYQByAGkAZQBzACAAZQBtAGkAdAB0AGkAbgBnACAAYQB0ACAATgBBAE4ATwBHAHIAYQB2ACcAcwAgAG0AbwBzAHQAIABzAGUAbgBzAGkAdABpAHYAZQAgAGYAcgBlAHEAdQBlAG4AYwB5ACAAbwBmACAAOABuAEgAegAsACAAdwBlACAAZgBvAHUAbgBkACAAdABoAGEAdAAgADIAMQA2ACAAZwBhAGwAYQB4AGkAZQBzACAAYQByAGUAIAB3AGkAdABoAGkAbgAgAE4AQQBOAE8ARwByAGEAdgAnAHMAIABzAGUAbgBzAGkAdABpAHYAaQB0AHkAIAB2AG8AbAB1AG0AZQAuACAAVwBlACAAcgBhAG4AawBlAGQAIAB0AGgAZQAgAHAAbwB0AGUAbgB0AGkAYQBsACAAUwBNAEIASABCAHMAIABiAGEAcwBlAGQAIABvAG4AIABHAFcAIABkAGUAdABlAGMAdABhAGIAaQBsAGkAdAB5ACAAYgB5ACAAYwBhAGwAYwB1AGwAYQB0AGkAbgBnACAAdABoAGUAIAB0AG8AdABhAGwAIABzAGkAZwBuAGEAbAAtAHQAbwAtAG4AbwBpAHMAZQAgAHIAYQB0AGkAbwAgAHMAdQBjAGgAIABiAGkAbgBhAHIAaQBlAHMAIAB3AG8AdQBsAGQAIABpAG4AZAB1AGMAZQAgAHcAaQB0AGgAaQBuACAAdABoAGUAIABOAEEATgBPAEcAcgBhAHYAIABhAHIAcgBhAHkALgAgAFcAZQAgAHAAbABhAGMAZQBkACAAYwBvAG4AcwB0AHIAYQBpAG4AdABzACAAbwBuACAAdABoAGUAIABjAGgAaQByAHAAIABtAGEAcwBzACAAYQBuAGQAIABtAGEAcwBzACAAcgBhAHQAaQBvACAAbwBmACAAdABoAGUAIAAyADEANgAgAGgAeQBwAG8AdABoAGUAdABpAGMAYQBsACAAYgBpAG4AYQByAGkAZQBzAC4AIABGAG8AcgAgADEAOQAgAGcAYQBsAGEAeABpAGUAcwAsACAAbwBuAGwAeQAgAHYAZQByAHkAIAB1AG4AZQBxAHUAYQBsAC0AbQBhAHMAcwAgAGIAaQBuAGEAcgBpAGUAcwAgAGEAcgBlACAAYQBsAGwAbwB3AGUAZAAsACAAdwBpAHQAaAAgAHQAaABlACAAbQBhAHMAcwAgAG8AZgAgAHQAaABlACAAcwBlAGMAbwBuAGQAYQByAHkAIABsAGUAcwBzACAAdABoAGEAbgAgADEAMAAlACAAdABoAGEAdAAgAG8AZgAgAHQAaABlACAAcAByAGkAbQBhAHIAeQAsACAAcgBvAHUAZwBoAGwAeQAgAGMAbwBtAHAAYQByAGEAYgBsAGUAIAB0AG8AIABjAG8AbgBzAHQAcgBhAGkAbgB0AHMAIABvAG4AIABhAG4AIABTAE0AQgBIAEIAIABpAG4AIAB0AGgAZQAgAE0AaQBsAGsAeQAgAFcAYQB5AC4AIABIAG8AdwBlAHYAZQByACwAIAB3AGUAIABkAGUAbQBvAG4AcwB0AHIAYQB0AGUAZAAgAHQAaABhAHQAIAB0AGgAZQAgACgAdAB5AHAAaQBjAGEAbABsAHkAIABsAGEAcgBnAGUAKQAgAHUAbgBjAGUAcgB0AGEAaQBuAHQAaQBlAHMAIABpAG4AIAB0AGgAZQAgAG0AYQBzAHMAIABtAGUAYQBzAHUAcgBlAG0AZQBuAHQAcwAgAGMAYQBuACAAdwBlAGEAawBlAG4AIAB0AGgAZQAgAHUAcABwAGUAcgAgAGwAaQBtAGkAdABzACAAbwBuACAAdABoAGUAIABjAGgAaQByAHAAIABtAGEAcwBzAC4AIABBAGQAZABpAHQAaQBvAG4AYQBsAGwAeQAsACAAdwBlACAAdwBlAHIAZQAgAGEAYgBsAGUAIAB0AG8AIABlAHgAYwBsAHUAZABlACAAYgBpAG4AYQByAGkAZQBzACAAZABlAGwAaQB2AGUAcgBlAGQAIABiAHkAIABtAGEAagBvAHIAIABtAGUAcgBnAGUAcgBzACAAKABtAGEAcwBzACAAcgBhAHQAaQBvACAAbwBmACAAYQB0ACAAbABlAGEAcwB0ACAAMQAvADQAKQAgAGYAbwByACAAcwBlAHYAZQByAGEAbAAgAG8AZgAgAHQAaABlAHMAZQAgAGcAYQBsAGEAeABpAGUAcwAuACAAVwBlACAAYQBsAHMAbwAgAGQAZQByAGkAdgBlAGQAIAB0AGgAZQAgAGYAaQByAHMAdAAgAGwAaQBtAGkAdAAgAG8AbgAgAHQAaABlACAAZABlAG4AcwBpAHQAeQAgAG8AZgAgAGIAaQBuAGEAcgBpAGUAcwAgAGQAZQBsAGkAdgBlAHIAZQBkACAAYgB5ACAAbQBhAGoAbwByACAAbQBlAHIAZwBlAHIAcwAgAHAAdQByAGUAbAB5ACAAYgBhAHMAZQBkACAAbwBuACAARwBXACAAZABhAHQAYQAuAAAAN2h0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovQXBKLzkxNC8xMjEAAAL3AEEAcgB6AG8AdQBtAGEAbgBpAGEAbgAgAFoALgA7ACAAQgBhAGsAZQByACAAUAAuAFQALgA7ACAAQgByAGEAegBpAGUAcgAgAEEALgA7ACAAQgByAG8AbwBrACAAUAAuAFIALgA7ACAAQgB1AHIAawBlAC0AUwBwAG8AbABhAG8AcgAgAFMALgA7ACAAQgBlAGMAcwB5ACAAQgAuADsAIABDAGgAYQByAGkAcwBpACAATQAuADsAIABDAGgAYQB0AHQAZQByAGoAZQBlACAAUwAuADsAIABDAG8AcgBkAGUAcwAgAEoALgBNAC4AOwAgAEMAbwByAG4AaQBzAGgAIABOAC4ASgAuADsAIABDAHIAYQB3AGYAbwByAGQAIABGAC4AOwAgAEMAcgBvAG0AYQByAHQAaQBlACAASAAuAFQALgA7ACAARABlAEMAZQBzAGEAcgAgAE0ALgBFAC4AOwAgAEQAZQBtAG8AcgBlAHMAdAAgAFAALgBCAC4AOwAgAEQAbwBsAGMAaAAgAFQALgA7ACAARQBsAGwAaQBvAHQAdAAgAFIALgBEAC4AOwAgAEUAbABsAGkAcwAgAEoALgBBAC4AOwAgAEYAZQByAHIAYQByAGEAIABFAC4AQwAuADsAIABGAG8AbgBzAGUAYwBhACAARQAuADsAIABHAGEAcgB2AGUAcgAtAEQAYQBuAGkAZQBsAHMAIABOAC4AOwAgAEcAZQBuAHQAaQBsAGUAIABQAC4AQQAuADsAIABHAG8AbwBkACAARAAuAEMALgA7ACAASABhAHoAYgBvAHUAbgAgAEoALgBTAC4AOwAgAEkAcwBsAG8AIABLAC4AOwAgAEoAZQBuAG4AaQBuAGcAcwAgAFIALgBKAC4AOwAgAEoAbwBuAGUAcwAgAE0ALgBMAC4AOwAgAEsAYQBpAHMAZQByACAAQQAuAFIALgA7ACAASwBhAHAAbABhAG4AIABEAC4ATAAuADsAIABLAGUAbABsAGUAeQAgAEwALgBaAC4AOwAgAEsAZQB5ACAASgAuAFMALgA7ACAATABhAG0AIABNAC4AVAAuADsAIABMAGEAegBpAG8AIABUAC4ASgAuAFcALgA7ACAATAB1AG8AIABKAC4AOwAgAEwAeQBuAGMAaAAgAFIALgBTAC4AOwAgAE0AYQAgAEMALgAtAFAALgA7ACAATQBhAGQAaQBzAG8AbgAgAEQALgBSAC4AOwAgAE0AYwBMAGEAdQBnAGgAbABpAG4AIABNAC4AQQAuADsAIABNAGkAbgBnAGEAcgBlAGwAbABpACAAQwAuAE0ALgBGAC4AOwAgAE4AZwAgAEMALgA7ACAATgBpAGMAZQAgAEQALgBKAC4AOwAgAFAAZQBuAG4AdQBjAGMAaQAgAFQALgBUAC4AOwAgAFAAbwBsACAATgAuAFMALgA7ACAAUgBhAG4AcwBvAG0AIABTAC4ATQAuADsAIABSAGEAeQAgAFAALgBTAC4AOwAgAFMAaABhAHAAaQByAG8ALQBBAGwAYgBlAHIAdAAgAEIALgBKAC4AOwAgAFMAaQBlAG0AZQBuAHMAIABYAC4AOwAgAFMAaQBtAG8AbgAgAEoALgA7ACAAUwBwAGkAZQB3AGEAawAgAFIALgA7ACAAUwB0AGEAaQByAHMAIABJAC4ASAAuADsAIABTAHQAaQBuAGUAYgByAGkAbgBnACAARAAuAFIALgA7ACAAUwB0AG8AdgBhAGwAbAAgAEsALgA7ACAAUwB3AGkAZwBnAHUAbQAgAEoALgBLAC4AOwAgAFQAYQB5AGwAbwByACAAUwAuAFIALgA7ACAAVgBhAGwAbABpAHMAbgBlAHIAaQAgAE0ALgA7ACAAVgBpAGcAZQBsAGEAbgBkACAAUwAuAEoALgA7ACAAVwBpAHQAdAAgAEMALgBBAC4AOwAgAFQAaABlACAATgBBAE4ATwBHAHIAYQB2ACAAQwBvAGwAbABhAGIAbwByAGEAdABpAG8AbgAuAAAAEzIwMjMtMDEtMDlUMDk6MzY6MjAAAAATMjAyMy0wMS0xOFQwNjo0MzoxNQAAADZodHRwczovL2Nkcy51bmlzdHJhLmZyL3Zpemllci1vcmcvbGljZW5jZXNfdml6aWVyLmh0bWwAAAAHY2F0YWxvZwAAAAdiaWJjb2RlAAAAEwAyADAAMgAxAEEAcABKAC4ALgAuADkAMQA0AC4ALgAxADIAMQBBf8AAAAAAAAhpbmZyYXJlZAAAAldodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL0FwSi85MTQvMTIxOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BcEovOTE0LzEyMS90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL0FwSi85MTQvMTIxOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BcEovOTE0LzEyMS90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL0FwSi85MTQvMTIxOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9BcEovOTE0LzEyMQAAANc6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAMt2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3NlcgAAAHU6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjoAAAAgZG9pOjEwLjI2MDkzL2Nkcy92aXppZXIuMTkxNDAxMjEAAAAeaXZvOi8vY2RzLnZpemllci9qL2Fwai85MTYvMTAwAAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAADUovQXBKLzkxNi8xMDAAAAAjAEcAYQBsAGEAYwB0AGkAYwAgAHAAdQBsAHMAYQByACAAdwBpAHQAaAAgAHAAcgBvAHAAZQByACAAbQBvAHQAaQBvAG4AcwAAAAhyZXNlYXJjaAAABfsAVwBlACAAZQB4AHAAbABvAHIAZQAgAHQAaABlACAAcABvAHMAcwBpAGIAaQBsAGkAdAB5ACAAbwBmACAAaQBuAGYAZQByAHIAaQBuAGcAIAB0AGgAZQAgAHAAcgBvAHAAZQByAHQAaQBlAHMAIABvAGYAIAB0AGgAZQAgAEcAYQBsAGEAYwB0AGkAYwAgAHAAbwBwAHUAbABhAHQAaQBvAG4AIABvAGYAIABuAGUAdQB0AHIAbwBuACAAcwB0AGEAcgBzACAAdABoAHIAbwB1AGcAaAAgAG0AYQBjAGgAaQBuAGUAIABsAGUAYQByAG4AaQBuAGcALgAgAEkAbgAgAHAAYQByAHQAaQBjAHUAbABhAHIALAAgAGkAbgAgAHQAaABpAHMAIABwAGEAcABlAHIAIAB3AGUAIABmAG8AYwB1AHMAIABvAG4AIAB0AGgAZQBpAHIAIABkAHkAbgBhAG0AaQBjAGEAbAAgAGMAaABhAHIAYQBjAHQAZQByAGkAcwB0AGkAYwBzACAAYQBuAGQAIABzAGgAbwB3ACAAdABoAGEAdAAgAGEAbgAgAGEAcgB0AGkAZgBpAGMAaQBhAGwAIABuAGUAdQByAGEAbAAgAG4AZQB0AHcAbwByAGsAIABpAHMAIABhAGIAbABlACAAdABvACAAZQBzAHQAaQBtAGEAdABlACAAdwBpAHQAaAAgAGgAaQBnAGgAIABhAGMAYwB1AHIAYQBjAHkAIAB0AGgAZQAgAHAAYQByAGEAbQBlAHQAZQByAHMAIAB0AGgAYQB0ACAAYwBvAG4AdAByAG8AbAAgAHQAaABlACAAYwB1AHIAcgBlAG4AdAAgAHAAbwBzAGkAdABpAG8AbgBzACAAbwBmACAAYQAgAG0AbwBjAGsAIABwAG8AcAB1AGwAYQB0AGkAbwBuACAAbwBmACAAcAB1AGwAcwBhAHIAcwAuACAARgBvAHIAIAB0AGgAaQBzACAAcAB1AHIAcABvAHMAZQAsACAAdwBlACAAaQBtAHAAbABlAG0AZQBuAHQAIABhACAAcwBpAG0AcABsAGkAZgBpAGUAZAAgAHAAbwBwAHUAbABhAHQAaQBvAG4ALQBzAHkAbgB0AGgAZQBzAGkAcwAgAGYAcgBhAG0AZQB3AG8AcgBrACAAKAB3AGgAZQByAGUAIABzAGUAbABlAGMAdABpAG8AbgAgAGIAaQBhAHMAZQBzACAAYQByAGUAIABuAGUAZwBsAGUAYwB0AGUAZAAgAGEAdAAgAHQAaABpAHMAIABzAHQAYQBnAGUAKQAgAGEAbgBkACAAYwBvAG4AYwBlAG4AdAByAGEAdABlACAAbwBuACAAdABoAGUAIABuAGEAdABhAGwAIABrAGkAYwBrAC0AdgBlAGwAbwBjAGkAdAB5ACAAZABpAHMAdAByAGkAYgB1AHQAaQBvAG4AIABhAG4AZAAgAHQAaABlACAAZABpAHMAdAByAGkAYgB1AHQAaQBvAG4AIABvAGYAIABiAGkAcgB0AGgAIABkAGkAcwB0AGEAbgBjAGUAcwAgAGYAcgBvAG0AIAB0AGgAZQAgAEcAYQBsAGEAYwB0AGkAYwAgAHAAbABhAG4AZQAuACAAQgB5ACAAdgBhAHIAeQBpAG4AZwAgAHQAaABlAHMAZQAgAGEAbgBkACAAZQB2AG8AbAB2AGkAbgBnACAAdABoAGUAIABwAHUAbABzAGEAcgAgAHQAcgBhAGoAZQBjAHQAbwByAGkAZQBzACAAaQBuACAAdABpAG0AZQAsACAAdwBlACAAZwBlAG4AZQByAGEAdABlACAAYQAgAHMAZQByAGkAZQBzACAAbwBmACAAcwBpAG0AdQBsAGEAdABpAG8AbgBzACAAdABoAGEAdAAgAGEAcgBlACAAdQBzAGUAZAAgAHQAbwAgAHQAcgBhAGkAbgAgAGEAbgBkACAAdgBhAGwAaQBkAGEAdABlACAAYQAgAHMAdQBpAHQAYQBiAGwAeQAgAHMAdAByAHUAYwB0AHUAcgBlAGQAIABjAG8AbgB2AG8AbAB1AHQAaQBvAG4AYQBsACAAbgBlAHUAcgBhAGwAIABuAGUAdAB3AG8AcgBrAC4AIABXAGUAIABkAGUAbQBvAG4AcwB0AHIAYQB0AGUAIAB0AGgAYQB0ACAAbwB1AHIAIABuAGUAdAB3AG8AcgBrACAAaQBzACAAYQBiAGwAZQAgAHQAbwAgAHIAZQBjAG8AdgBlAHIAIAB0AGgAZQAgAHAAYQByAGEAbQBlAHQAZQByAHMAIABnAG8AdgBlAHIAbgBpAG4AZwAgAHQAaABlACAAZABpAHMAdAByAGkAYgB1AHQAaQBvAG4AIABvAGYAIABrAGkAYwBrACAAdgBlAGwAbwBjAGkAdAB5ACAAYQBuAGQAIABHAGEAbABhAGMAdABpAGMAIABoAGUAaQBnAGgAdAAgAHcAaQB0AGgAIABhACAAbQBlAGEAbgAgAHIAZQBsAGEAdABpAHYAZQAgAGUAcgByAG8AcgAgAG8AZgAgAGEAYgBvAHUAdAAgADEAMABeAC0AMgBeAC4AIABXAGUAIABkAGkAcwBjAHUAcwBzACAAdABoAGUAIABsAGkAbQBpAHQAYQB0AGkAbwBuAHMAIABvAGYAIABvAHUAcgAgAGkAZABlAGEAbABpAHoAZQBkACAAYQBwAHAAcgBvAGEAYwBoACAAYQBuAGQAIABzAHQAdQBkAHkAIABhACAAdABvAHkAIABwAHIAbwBiAGwAZQBtACAAdABvACAAaQBuAHQAcgBvAGQAdQBjAGUAIABzAGUAbABlAGMAdABpAG8AbgAgAGUAZgBmAGUAYwB0AHMAIABpAG4AIABhACAAcABoAGUAbgBvAG0AZQBuAG8AbABvAGcAaQBjAGEAbAAgAHcAYQB5ACAAYgB5ACAAaQBuAGMAbwByAHAAbwByAGEAdABpAG4AZwAgAHQAaABlACAAbwBiAHMAZQByAHYAZQBkACAAcAByAG8AcABlAHIAIABtAG8AdABpAG8AbgBzACAAbwBmACAAMgAxADYAIABpAHMAbwBsAGEAdABlAGQAIABwAHUAbABzAGEAcgBzAC4AIABPAHUAcgAgAGEAbgBhAGwAeQBzAGkAcwAgAGgAaQBnAGgAbABpAGcAaAB0AHMAIAB0AGgAYQB0ACAAYgB5ACAAaQBuAGMAcgBlAGEAcwBpAG4AZwAgAHQAaABlACAAcwBhAG0AcABsAGUAIABvAGYAIABwAHUAbABzAGEAcgBzACAAdwBpAHQAaAAgAGEAYwBjAHUAcgBhAHQAZQAgAHAAcgBvAHAAZQByAC0AbQBvAHQAaQBvAG4AIABtAGUAYQBzAHUAcgBlAG0AZQBuAHQAcwAgAGIAeQAgAGEAIABmAGEAYwB0AG8AcgAgAG8AZgAgAH4AMQAwACwAIABvAG4AZQAgAG8AZgAgAHQAaABlACAAZgB1AHQAdQByAGUAIABiAHIAZQBhAGsAdABoAHIAbwB1AGcAaABzACAAbwBmACAAdABoAGUAIABTAHEAdQBhAHIAZQAgAEsAaQBsAG8AbQBlAHQAcgBlACAAQQByAHIAYQB5ACwAIAB3AGUAIABtAGkAZwBoAHQAIABzAHUAYwBjAGUAZQBkACAAaQBuACAAYwBvAG4AcwB0AHIAYQBpAG4AaQBuAGcAIAB0AGgAZQAgAGIAaQByAHQAaAAgAHMAcABhAHQAaQBhAGwAIABhAG4AZAAgAGsAaQBjAGsALQB2AGUAbABvAGMAaQB0AHkAIABkAGkAcwB0AHIAaQBiAHUAdABpAG8AbgAgAG8AZgAgAHQAaABlACAAbgBlAHUAdAByAG8AbgAgAHMAdABhAHIAcwAgAGkAbgAgAHQAaABlACAATQBpAGwAawB5ACAAVwBhAHkAIAB3AGkAdABoACAAaABpAGcAaAAgAHAAcgBlAGMAaQBzAGkAbwBuACAAdABoAHIAbwB1AGcAaAAgAG0AYQBjAGgAaQBuAGUAIABsAGUAYQByAG4AaQBuAGcALgAAADdodHRwczovL2Nkc2FyYy5jZHMudW5pc3RyYS5mci92aXotYmluL2NhdC9KL0FwSi85MTYvMTAwAAAAOQBSAG8AbgBjAGgAaQAgAE0ALgA7ACAARwByAGEAYgBlAHIAIABWAC4AOwAgAEcAYQByAGMAaQBhAC0ARwBhAHIAYwBpAGEAIABBAC4AOwAgAFIAZQBhACAATgAuADsAIABQAG8AbgBzACAASgAuAEEALgAAABMyMDIzLTAxLTI2VDE0OjMxOjQzAAAAEzIwMjMtMDItMTVUMDc6NDY6MjcAAAA2aHR0cHM6Ly9jZHMudW5pc3RyYS5mci92aXppZXItb3JnL2xpY2VuY2VzX3Zpemllci5odG1sAAAAB2NhdGFsb2cAAAAHYmliY29kZQAAABMAMgAwADIAMQBBAHAASgAuAC4ALgA5ADEANgAuAC4AMQAwADAAUn/AAAAAAAANb3B0aWNhbCNyYWRpbwAAASRodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL0FwSi85MTYvMTAwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL0FwSi85MTYvMTAwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9BcEovOTE2LzEwMC90YWJsZTU/AAAAZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2gAAABednI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cAAAADM6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQAAAAgZG9pOjEwLjI2MDkzL2Nkcy92aXppZXIuMTkxNjAxMDAAAAAhaXZvOi8vY2RzLnZpemllci9qL21ucmFzLzM1OS8xNTI0AAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAAEEovTU5SQVMvMzU5LzE1MjQAAAArADEAMAAgAG4AZQB3ACAAcAB1AGwAcwBhAHIAcwAgAGkAbgAgAEEAcgBlAGMAaQBiAG8AIABkAHIAaQBmAHQALQBzAGMAYQBuACAAcwB1AHIAdgBlAHkAAAAIcmVzZWFyY2gAAAPJAFcAZQAgAHAAcgBlAHMAZQBuAHQAIAB0AGgAZQAgAHIAZQBzAHUAbAB0AHMAIABvAGYAIABhACAANAAzADAALQBNAEgAegAgAHMAdQByAHYAZQB5ACAAZgBvAHIAIABwAHUAbABzAGEAcgBzACAAYwBvAG4AZAB1AGMAdABlAGQAIABkAHUAcgBpAG4AZwAgAHQAaABlACAAdQBwAGcAcgBhAGQAZQAgAHQAbwAgAHQAaABlACAAMwAwADUALQBtACAAQQByAGUAYwBpAGIAbwAgAHIAYQBkAGkAbwAgAHQAZQBsAGUAcwBjAG8AcABlAC4AIABPAHUAcgAgAHMAdQByAHYAZQB5ACAAYwBvAHYAZQByAGUAZAAgAGEAIAB0AG8AdABhAGwAIABvAGYAIAAxADEANAA3AGQAZQBnAF4AMgBeACAAbwBmACAAcwBrAHkAIAB1AHMAaQBuAGcAIABhACAAZAByAGkAZgB0AC0AcwBjAGEAbgAgAHQAZQBjAGgAbgBpAHEAdQBlAC4AIABXAGUAIABkAGUAdABlAGMAdABlAGQAIAAzADMAIABwAHUAbABzAGEAcgBzACwAIAAxADAAIABvAGYAIAB3AGgAaQBjAGgAIAB3AGUAcgBlACAAbgBvAHQAIABrAG4AbwB3AG4AIABwAHIAaQBvAHIAIAB0AG8AIAB0AGgAZQAgAHMAdQByAHYAZQB5ACAAbwBiAHMAZQByAHYAYQB0AGkAbwBuAHMALgAgAFQAaABlACAAaABpAGcAaABsAGkAZwBoAHQAIABvAGYAIAB0AGgAZQAgAG4AZQB3ACAAZABpAHMAYwBvAHYAZQByAGkAZQBzACAAaQBzACAAUABTAFIAIABKADAANAAwADcAKwAxADYAMAA3ACwAIAB3AGgAaQBjAGgAIABoAGEAcwAgAGEAIABzAHAAaQBuACAAcABlAHIAaQBvAGQAIABvAGYAIAAyADUALgA3AG0AcwAsACAAYQAgAGMAaABhAHIAYQBjAHQAZQByAGkAcwB0AGkAYwAgAGEAZwBlACAAbwBmACAAMQAuADUARwB5AHIAIABhAG4AZAAgAGkAcwAgAGkAbgAgAGEAIAAxAC4AOAAtAHkAcgAgAG8AcgBiAGkAdAAgAGEAYgBvAHUAdAAgAGEAIABsAG8AdwAtAG0AYQBzAHMAIAAoAD4AMAAuADIATQApACAAYwBvAG0AcABhAG4AaQBvAG4ALgAgAFQAaABlACAAbABvAG4AZwAgAG8AcgBiAGkAdABhAGwAIABwAGUAcgBpAG8AZAAgAGEAbgBkACAAcwBtAGEAbABsACAAZQBjAGMAZQBuAHQAcgBpAGMAaQB0AHkAIAAoAGUAPQAwAC4AMAAwADAAOQApACAAbQBhAGsAZQAgAHQAaABlACAAYgBpAG4AYQByAHkAIABzAHkAcwB0AGUAbQAgAGEAbgAgAGkAbQBwAG8AcgB0AGEAbgB0ACAAbgBlAHcAIABhAGQAZABpAHQAaQBvAG4AIAB0AG8AIAB0AGgAZQAgAGUAbgBzAGUAbQBiAGwAZQAgAG8AZgAgAGIAaQBuAGEAcgB5ACAAcAB1AGwAcwBhAHIAcwAgAHMAdQBpAHQAYQBiAGwAZQAgAHQAbwAgAHQAZQBzAHQAIABmAG8AcgAgAHYAaQBvAGwAYQB0AGkAbwBuAHMAIABvAGYAIAB0AGgAZQAgAHMAdAByAG8AbgBnACAAZQBxAHUAaQB2AGEAbABlAG4AYwBlACAAcAByAGkAbgBjAGkAcABsAGUALgAgAFcAZQAgAGEAbABzAG8AIAByAGUAcABvAHIAdAAgAG8AbgAgAG8AdQByACAAaQBuAGkAdABpAGEAbABsAHkAIAB1AG4AcwB1AGMAYwBlAHMAcwBmAHUAbAAgAGEAdAB0AGUAbQBwAHQAcwAgAHQAbwAgAGQAZQB0AGUAYwB0ACAAbwBwAHQAaQBjAGEAbABsAHkAIAB0AGgAZQAgAGMAbwBtAHAAYQBuAGkAbwBuACAAdABvACAASgAwADQAMAA3ACsAMQA2ADAANwAsACAAdwBoAGkAYwBoACAAaQBtAHAAbAB5ACAAdABoAGEAdAAgAGkAdABzACAAYQBiAHMAbwBsAHUAdABlACAAdgBpAHMAdQBhAGwAIABtAGEAZwBuAGkAdAB1AGQAZQAgAGkAcwAgAD4AMQAyAC4AMQAuACAASQBmACwAIABhAHMAIABlAHgAcABlAGMAdABlAGQAIABvAG4AIABlAHYAbwBsAHUAdABpAG8AbgBhAHIAeQAgAGcAcgBvAHUAbgBkAHMALAAgAHQAaABlACAAYwBvAG0AcABhAG4AaQBvAG4AIABpAHMAIABhAG4AIABIAGUAIAB3AGgAaQB0AGUAIABkAHcAYQByAGYALAAgAG8AdQByACAAbgBvAG4ALQBkAGUAdABlAGMAdABpAG8AbgAgAGkAbQBwAGwAaQBlAHMAIABhACAAYwBvAG8AbABpAG4AZwAgAGEAZwBlACAAbwBmACAAbABlAGEAcwB0ACAAMQBHAHkAcgAuAAAAOmh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovTU5SQVMvMzU5LzE1MjQAAACZAEwAbwByAGkAbQBlAHIAIABEAC4AUgAuADsAIABYAGkAbABvAHUAcgBpAHMAIABLAC4ATQAuADsAIABGAHIAdQBjAGgAdABlAHIAIABBAC4AUwAuADsAIABTAHQAYQBpAHIAcwAgAEkALgBIAC4AOwAgAEMAYQBtAGkAbABvACAARgAuADsAIABWAGEAegBxAHUAZQB6ACAAQQAuAE0ALgA7ACAARQBkAGUAcgAgAEoALgBBAC4AOwAgAE0AYwBMAGEAdQBnAGgAbABpAG4AIABNAC4AQQAuADsAIABSAG8AYgBlAHIAdABzACAATQAuAFMALgBFAC4AOwAgAEgAZQBzAHMAZQBsAHMAIABKAC4AVwAuAFQALgA7ACAAcgBhAG4AcwBvAG0AIABTAC4ATQAuAAAAEzIwMDYtMDctMTBUMTE6NDU6MzYAAAATMjAyMS0xMC0yMVQwMDowMDowMAAAADZodHRwczovL2Nkcy51bmlzdHJhLmZyL3Zpemllci1vcmcvbGljZW5jZXNfdml6aWVyLmh0bWwAAAAHY2F0YWxvZwAAAAdiaWJjb2RlAAAAEwAyADAAMAA1AE0ATgBSAEEAUwAuADMANQA5AC4AMQA1ADIANABMf8AAAAAAAAVyYWRpbwAAAdtodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzM1OS8xNTI0L3RhYmxlMz86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL01OUkFTLzM1OS8xNTI0Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzM1OS8xNTI0Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy8zNTkvMTUyNC90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy8zNTkvMTUyNC90YWJsZTU/AAAAvGl2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoAAAAlHZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHAAAABXc3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkAAAAAAAAACFpdm86Ly9jZHMudml6aWVyL2ovbW5yYXMvNDI3LzEwNTIAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAAQSi9NTlJBUy80MjcvMTA1MgAAACEASABUAFIAVQAgAHMAdQByAHYAZQB5AC4AIABUAGkAbQBpAG4AZwAgAG8AZgAgADUANAAgAHAAdQBsAHMAYQByAHMAAAAIcmVzZWFyY2gAAAR1AFcAZQAgAHAAcgBlAHMAZQBuAHQAIAA3ADUAIABwAHUAbABzAGEAcgBzACAAZABpAHMAYwBvAHYAZQByAGUAZAAgAGkAbgAgAHQAaABlACAAbQBpAGQALQBsAGEAdABpAHQAdQBkAGUAIABwAG8AcgB0AGkAbwBuACAAbwBmACAAdABoAGUAIABIAGkAZwBoACAAVABpAG0AZQAgAFIAZQBzAG8AbAB1AHQAaQBvAG4AIABVAG4AaQB2AGUAcgBzAGUAIABzAHUAcgB2AGUAeQAsACAANQA0ACAAbwBmACAAdwBoAGkAYwBoACAAaABhAHYAZQAgAGYAdQBsAGwAIAB0AGkAbQBpAG4AZwAgAHMAbwBsAHUAdABpAG8AbgBzAC4AIABBAGwAbAAgAHQAaABlACAAcAB1AGwAcwBhAHIAcwAgAGgAYQB2AGUAIABzAHAAaQBuACAAcABlAHIAaQBvAGQAcwAgAGcAcgBlAGEAdABlAHIAIAB0AGgAYQBuACAAMQAwADAAbQBzACwAIABhAG4AZAAgAG4AbwBuAGUAIABvAGYAIAB0AGgAbwBzAGUAIAB3AGkAdABoACAAdABpAG0AaQBuAGcAIABzAG8AbAB1AHQAaQBvAG4AcwAgAGkAcwAgAGkAbgAgAGIAaQBuAGEAcgBpAGUAcwAuACAAVAB3AG8AIABkAGkAcwBwAGwAYQB5ACAAcABhAHIAdABpAGMAdQBsAGEAcgBsAHkAIABpAG4AdABlAHIAZQBzAHQAaQBuAGcAIABiAGUAaABhAHYAaQBvAHUAcgA7ACAAUABTAFIAIABKADEAMAA1ADQALQA1ADkANAA0ACAAaQBzACAAZgBvAHUAbgBkACAAdABvACAAYgBlACAAYQBuACAAaQBuAHQAZQByAG0AaQB0AHQAZQBuAHQAIABwAHUAbABzAGEAcgAsACAAYQBuAGQAIABQAFMAUgAgAEoAMQA4ADAAOQAtADAAMQAxADkAIABoAGEAcwAgAGcAbABpAHQAYwBoAGUAZAAgAHQAdwBpAGMAZQAgAHMAaQBuAGMAZQAgAGkAdABzACAAZABpAHMAYwBvAHYAZQByAHkALgAgAEkAbgAgAHQAaABlACAAcwBlAGMAbwBuAGQAIABoAGEAbABmACAAbwBmACAAdABoAGUAIABwAGEAcABlAHIAIAB3AGUAIABkAGkAcwBjAHUAcwBzACAAdABoAGUAIABkAGUAdgBlAGwAbwBwAG0AZQBuAHQAIABhAG4AZAAgAGEAcABwAGwAaQBjAGEAdABpAG8AbgAgAG8AZgAgAGEAbgAgAGEAcgB0AGkAZgBpAGMAaQBhAGwAIABuAGUAdQByAGEAbAAgAG4AZQB0AHcAbwByAGsAIABpAG4AIAB0AGgAZQAgAGQAYQB0AGEALQBwAHIAbwBjAGUAcwBzAGkAbgBnACAAcABpAHAAZQBsAGkAbgBlACAAZgBvAHIAIAB0AGgAZQAgAHMAdQByAHYAZQB5AC4AIABXAGUAIABkAGkAcwBjAHUAcwBzACAAdABoAGUAIAB0AGUAcwB0AHMAIAB0AGgAYQB0ACAAdwBlAHIAZQAgAHUAcwBlAGQAIAB0AG8AIABnAGUAbgBlAHIAYQB0AGUAIABzAGMAbwByAGUAcwAgAGEAbgBkACAAZgBpAG4AZAAgAHQAaABhAHQAIABvAHUAcgAgAG4AZQB1AHIAYQBsACAAbgBlAHQAdwBvAHIAawAgAHcAYQBzACAAYQBiAGwAZQAgAHQAbwAgAHIAZQBqAGUAYwB0ACAAbwB2AGUAcgAgADkAOQBwAGUAcgAgAGMAZQBuAHQAIABvAGYAIAB0AGgAZQAgAGMAYQBuAGQAaQBkAGEAdABlAHMAIABwAHIAbwBkAHUAYwBlAGQAIABpAG4AIAB0AGgAZQAgAGQAYQB0AGEAIABwAHIAbwBjAGUAcwBzAGkAbgBnACwAIABhAG4AZAAgAGEAYgBsAGUAIAB0AG8AIABiAGwAaQBuAGQAbAB5ACAAZABlAHQAZQBjAHQAIAA4ADUAcABlAHIAIABjAGUAbgB0ACAAbwBmACAAcAB1AGwAcwBhAHIAcwAuACAAVwBlACAAcwB1AGcAZwBlAHMAdAAgAHQAaABhAHQAIABpAG0AcAByAG8AdgBlAG0AZQBuAHQAcwAgAHQAbwAgAHQAaABlACAAYQBjAGMAdQByAGEAYwB5ACAAcwBoAG8AdQBsAGQAIABiAGUAIABwAG8AcwBzAGkAYgBsAGUAIABpAGYAIABmAHUAcgB0AGgAZQByACAAYwBhAHIAZQAgAGkAcwAgAHQAYQBrAGUAbgAgAHcAaABlAG4AIAB0AHIAYQBpAG4AaQBuAGcAIABhAG4AIABhAHIAdABpAGYAaQBjAGkAYQBsACAAbgBlAHUAcgBhAGwAIABuAGUAdAB3AG8AcgBrADsAIABmAG8AcgAgAGUAeABhAG0AcABsAGUALAAgAGUAbgBzAHUAcgBpAG4AZwAgAHQAaABhAHQAIABhACAAcgBlAHAAcgBlAHMAZQBuAHQAYQB0AGkAdgBlACAAcwBhAG0AcABsAGUAIABvAGYAIAB0AGgAZQAgAHAAdQBsAHMAYQByACAAcABvAHAAdQBsAGEAdABpAG8AbgAgAGkAcwAgAHUAcwBlAGQAIABkAHUAcgBpAG4AZwAgAHQAaABlACAAdAByAGEAaQBuAGkAbgBnACAAcAByAG8AYwBlAHMAcwAsACAAbwByACAAdABoAGUAIAB1AHMAZQAgAG8AZgAgAGQAaQBmAGYAZQByAGUAbgB0ACAAYQByAHQAaQBmAGkAYwBpAGEAbAAgAG4AZQB1AHIAYQBsACAAbgBlAHQAdwBvAHIAawBzACAAZgBvAHIAIAB0AGgAZQAgAGQAZQB0AGUAYwB0AGkAbwBuACAAbwBmACAAZABpAGYAZgBlAHIAZQBuAHQAIAB0AHkAcABlAHMAIABvAGYAIABwAHUAbABzAGEAcgBzAC4AAAA6aHR0cHM6Ly9jZHNhcmMuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jYXQvSi9NTlJBUy80MjcvMTA1MgAAAQsAQgBhAHQAZQBzACAAUwAuAEQALgA7ACAAQgBhAGkAbABlAHMAIABNAC4AOwAgAEIAYQByAHMAZABlAGwAbAAgAEIALgBSAC4AOwAgAEIAaABhAHQAIABOAC4ARAAuAFIALgA7ACAAQgB1AHIAZwBhAHkAIABNAC4AOwAgAEIAdQByAGsAZQAtAFMAcABvAGwAYQBvAHIAIABTAC4AOwAgAEMA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AHQAIAB0AGgAZQAgAHQAeQBwAGkAYwBhAGwAIABjAGgAYQByAGEAYwB0AGUAcgBpAHMAdABpAGMAcwAgAG8AZgAgACcAbwBsAGQAJwAgAHAAdQBsAHMAYQByAHMALAAgAHcAaQB0AGgAIABsAG8AdwAgAGYAcgBhAGMAdABpAG8AbgBhAGwAIABsAGkAbgBlAGEAcgAgAGEAbgBkACAAYwBpAHIAYwB1AGwAYQByACAAcABvAGwAYQByAGkAegBhAHQAaQBvAG4AIABhAG4AZAAgAG4AYQByAHIAbwB3ACwAIABtAHUAbAB0AGkALQBjAG8AbQBwAG8AbgBlAG4AdAAgAHAAcgBvAGYAaQBsAGUAcwAuACAAQQBsAHQAaABvAHUAZwBoACAAdABoAGUAIABwAG8AcwBpAHQAaQBvAG4AIABhAG4AZwBsAGUAIABzAHcAaQBuAGcAcwAgAGEAcgBlACAAZwBlAG4AZQByAGEAbABsAHkAIABjAG8AbQBwAGwAZQB4ACwAIABmAG8AcgAgAHQAdwBvACAAbwBmACAAdABoAGUAIABhAG4AYQBsAHkAcwBlAGQAIABwAHUAbABzAGEAcgBzACAAKABKADEANgAyADIALQAzADcANQAxACAAYQBuAGQAIABKADEANwAxADAALQAyADYAMQA2ACkAIAB3AGUAIABvAGIAdABhAGkAbgBlAGQAIABhAG4AIABpAG4AZABpAGMAYQB0AGkAbwBuACAAbwBmACAAdABoAGUAIABnAGUAbwBtAGUAdAByAHkAIAB2AGkAYQAgAHQAaABlACAAcgBvAHQAYQB0AGkAbgBnACAAdgBlAGMAdABvAHIAIABtAG8AZABlAGwALgAgAFcAZQAgAHcAZQByAGUAIABhAGIAbABlACAAdABvACAAZABlAHQAZQByAG0AaQBuAGUAIABhACAAdgBhAGwAdQBlACAAbwBmACAAdABoAGUAIAByAG8AdABhAHQAaQBvAG4AIABtAGUAYQBzAHUAcgBlACAAKABSAE0AKQAgAGYAbwByACAAMwA0ACAAbwBmACAAdABoAGUAIABzAG8AdQByAGMAZQBzACAAdwBoAGkAYwBoACwAIAB3AGgAZQBuACAAYwBvAG0AYgBpAG4AZQBkACAAdwBpAHQAaAAgAHQAaABlAGkAcgAgAGQAaQBzAHAAZQByAHMAaQBvAG4AIABtAGUAYQBzAHUAcgBlAHMAIAAoAEQATQApACwAIAB5AGkAZQBsAGQAcwAgAGEAbgAgAGkAbgB0AGUAZwByAGEAdABlAGQAIABtAGEAZwBuAGUAdABpAGMAIABmAGkAZQBsAGQAIABzAHQAcgBlAG4AZwB0AGgAIABhAGwAbwBuAGcAIAB0AGgAZQAgAGwAaQBuAGUAIABvAGYAIABzAGkAZwBoAHQALgAgAFcAaQB0AGgAIAB0AGgAZQAgAGQAYQB0AGEAIABwAHIAZQBzAGUAbgB0AGUAZAAgAGgAZQByAGUALAAgAHQAaABlACAAdABvAHQAYQBsACAAbgB1AG0AYgBlAHIAIABvAGYAIAB2AGEAbAB1AGUAcwAgAG8AZgAgAFIATQAgAGEAcwBzAG8AYwBpAGEAdABlAGQAIAB3AGkAdABoACAAcAB1AGwAcwBhAHIAcwAgAGQAaQBzAGMAbwB2AGUAcgBlAGQAIABkAHUAcgBpAG4AZwAgAHQAaABlACAASABUAFIAVQAgAHMAbwB1AHQAaABlAHIAbgAgAHMAdQByAHYAZQB5ACAAcwB1AG0AcwAgAHQAbwAgADUAMQAuACAAVABoAGUAIABSAE0AcwAgAGEAcgBlACAAbgBvAHQAIABjAG8AbgBzAGkAcwB0AGUAbgB0ACAAdwBpAHQAaAAgAHQAaABlACAAaAB5AHAAbwB0AGgAZQBzAGkAcwAgAG8AZgAgAGEAIABjAG8AdQBuAHQAZQByAC0AYwBsAG8AYwBrAHcAaQBzAGUAIABkAGkAcgBlAGMAdABpAG8AbgAgAG8AZgAgAHQAaABlACAARwBhAGwAYQBjAHQAaQBjACAAbQBhAGcAbgBlAHQAaQBjACAAZgBpAGUAbABkACAAdwBpAHQAaABpAG4AIABhAG4AIABhAG4AbgB1AGwAdQBzACAAaQBuAGMAbAB1AGQAZQBkACAAYgBlAHQAdwBlAGUAbgAgADQAIABhAG4AZAAgADYAawBwAGMAIABmAHIAbwBtACAAdABoAGUAIABHAGEAbABhAGMAdABpAGMAIABDAGUAbgB0AHIAZQAuACAAQQAgAHAAYQByAHQAaQBhAGwAIABhAGcAcgBlAGUAbQBlAG4AdAAgAHcAaQB0AGgAIABhACAAYwBvAHUAbgB0AGUAcgAtAGMAbABvAGMAawB3AGkAcwBlACAAcwBlAG4AcwBlACAAbwBmACAAdABoAGUAIABHAGEAbABhAGMAdABpAGMAIABtAGEAZwBuAGUAdABpAGMAIABmAGkAZQBsAGQAIAB3AGkAdABoAGkAbgAgAHQAaABlACAAcwBwAGkAcgBhAGwAIABhAHIAbQBzACAAaQBzACwAIABoAG8AdwBlAHYAZQByACwAIABmAG8AdQBuAGQAIABpAG4AIAB0AGgAZQAgAGEAcgBlAGEAIABvAGYAIAB0AGgAZQAgAEMAYQByAGkAbgBhAC0AUwBhAGcAaQB0AHQAYQByAGkAdQBzACAAYQByAG0ALgAAADpodHRwczovL2Nkc2FyYy5jZHMudW5pc3RyYS5mci92aXotYmluL2NhdC9KL01OUkFTLzQzNi8zNTU3AAAA5wBUAGkAYgB1AHIAegBpACAAQwAuADsAIABKAG8AaABuAHMAdABvAG4AIABTAC4AOwAgAEIAYQBpAGwAZQBzACAATQAuADsAIABCAGEAdABlAHMAIABTAC4ARAAuADsAIABCAGgAYQB0ACAATgAuAEQALgBSAC4AOwAgAEIAdQByAGcAYQB5ACAATQAuADsAIABCAHUAcgBrAGUALQBTAHAAbwBsAGEAbwByACAAUwAuADsAIABDAGgAYQBtAHAAaQBvAG4AIABEAC4AOwAgAEMAbwBzAHQAZQByACAAUAAuADsAIABEACcAQQBtAGkAYwBvACAATgAuADsAIABLAGUAaQB0AGgAIABNAC4ASgAuADsAIABLAHIAYQBtAGUAcgAgAE0ALgA7ACAATABlAHYAaQBuACAATAAuADsAIABNAGkAbABpAGEAIABTAC4AOwAgAE4AZwAgAEMALgA7ACAAUABvAHMAcwBlAG4AdABpACAAQQAuADsAIABTAHQAYQBwAHAAZQByAHMAIABCAC4AVwAuADsAIABUAGgAbwByAG4AdABvAG4AIABEAC4AOwAgAHYAYQBuACAAUwB0AHIAYQB0AGUAbgAgAFcALgAAABMyMDE3LTExLTIxVDA5OjQ0OjI5AAAAEzIwMjEtMTAtMjFUMDA6MDA6MDAAAAA2aHR0cHM6Ly9jZHMudW5pc3RyYS5mci92aXppZXItb3JnL2xpY2VuY2VzX3Zpemllci5odG1sAAAAB2NhdGFsb2cAAAAHYmliY29kZQAAABMAMgAwADEAMwBNAE4AUgBBAFMALgA0ADMANgAuADMANQA1ADcAVH/AAAAAAAAFcmFkaW8AAAMXaHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80MzYvMzU1Nzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9NTlJBUy80MzYvMzU1Nzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDM2LzM1NTcvdGFibGUxPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovTU5SQVMvNDM2LzM1NTc6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQzNi8zNTU3L3RhYmxlMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovTU5SQVMvNDM2LzM1NTc6OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQzNi8zNTU3L3RhYmxlMT86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQzNi8zNTU3L3RhYmxlMj8AAAEvOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoAAABAXZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwAAAAmTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZAAAAAAAAAAhaXZvOi8vY2RzLnZpemllci9qL21ucmFzLzQ1OC8zMzQxAAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAAEEovTU5SQVMvNDU4LzMzNDEAAAAsADQAMgAgAG0AaQBsAGwAaQBzAGUAYwBvAG4AZAAgAHAAdQBsAHMAYQByAHMAIABoAGkAZwBoAC0AcAByAGUAYwBpAHMAaQBvAG4AIAB0AGkAbQBpAG4AZwAAAAhyZXNlYXJjaAAABmAAVwBlACAAcgBlAHAAbwByAHQAIABvAG4AIAB0AGgAZQAgAGgAaQBnAGgALQBwAHIAZQBjAGkAcwBpAG8AbgAgAHQAaQBtAGkAbgBnACAAbwBmACAANAAyACAAcgBhAGQAaQBvACAAbQBpAGwAbABpAHMAZQBjAG8AbgBkACAAcAB1AGwAcwBhAHIAcwAgACgATQBTAFAAcwApACAAbwBiAHMAZQByAHYAZQBkACAAYgB5ACAAdABoAGUAIABFAHUAcgBvAHAAZQBhAG4AIABQAHUAbABzAGEAcgAgAFQAaQBtAGkAbgBnACAAQQByAHIAYQB5ACAAKABFAFAAVABBACkALgAgAFQAaABpAHMAIABFAFAAVABBACAARABhAHQAYQAgAFIAZQBsAGUAYQBzAGUAIAAxAC4AMAAgAGUAeAB0AGUAbgBkAHMAIAB1AHAAIAB0AG8AIABtAGkAZAAtADIAMAAxADQAIABhAG4AZAAgAGIAYQBzAGUAbABpAG4AZQBzACAAcgBhAG4AZwBlACAAZgByAG8AbQAgADcALQAxADgAeQByAC4AIABJAHQAIABmAG8AcgBtAHMAIAB0AGgAZQAgAGIAYQBzAGkAcwAgAGYAbwByACAAdABoAGUAIABzAHQAbwBjAGgAYQBzAHQAaQBjACAAZwByAGEAdgBpAHQAYQB0AGkAbwBuAGEAbAAtAHcAYQB2AGUAIABiAGEAYwBrAGcAcgBvAHUAbgBkACwAIABhAG4AaQBzAG8AdAByAG8AcABpAGMAIABiAGEAYwBrAGcAcgBvAHUAbgBkACwAIABhAG4AZAAgAGMAbwBuAHQAaQBuAHUAbwB1AHMALQB3AGEAdgBlACAAbABpAG0AaQB0AHMAIAByAGUAYwBlAG4AdABsAHkAIABwAHIAZQBzAGUAbgB0AGUAZAAgAGIAeQAgAHQAaABlACAARQBQAFQAQQAgAGUAbABzAGUAdwBoAGUAcgBlAC4AIABUAGgAZQAgAEIAYQB5AGUAcwBpAGEAbgAgAHQAaQBtAGkAbgBnACAAYQBuAGEAbAB5AHMAaQBzACAAcABlAHIAZgBvAHIAbQBlAGQAIAB3AGkAdABoACAAVABFAE0AUABPAE4ARQBTAFQAIAB5AGkAZQBsAGQAcwAgAHQAaABlACAAZABlAHQAZQBjAHQAaQBvAG4AIABvAGYAIABzAGUAdgBlAHIAYQBsACAAbgBlAHcAIABwAGEAcgBhAG0AZQB0AGUAcgBzADoAIABzAGUAdgBlAG4AIABwAGEAcgBhAGwAbABhAHgAZQBzACwAIABuAGkAbgBlACAAcAByAG8AcABlAHIAIABtAG8AdABpAG8AbgBzACAAYQBuAGQALAAgAGkAbgAgAHQAaABlACAAYwBhAHMAZQAgAG8AZgAgAHMAaQB4ACAAYgBpAG4AYQByAHkAIABwAHUAbABzAGEAcgBzACwAIABhAG4AIABhAHAAcABhAHIAZQBuAHQAIABjAGgAYQBuAGcAZQAgAG8AZgAgAHQAaABlACAAcwBlAG0AaQBtAGEAagBvAHIAIABhAHgAaQBzAC4AIABXAGUAIABmAGkAbgBkACAAdABoAGUAIABOAEUAMgAwADAAMQAgAEcAYQBsAGEAYwB0AGkAYwAgAGUAbABlAGMAdAByAG8AbgAgAGQAZQBuAHMAaQB0AHkAIABtAG8AZABlAGwAIAB0AG8AIABiAGUAIABhACAAYgBlAHQAdABlAHIAIABtAGEAdABjAGgAIAB0AG8AIABvAHUAcgAgAHAAYQByAGEAbABsAGEAeAAgAGQAaQBzAHQAYQBuAGMAZQBzACAAKABhAGYAdABlAHIAIABjAG8AcgByAGUAYwB0AGkAbwBuACAAZgByAG8AbQAgAHQAaABlACAATAB1AHQAegAtAEsAZQBsAGsAZQByACAAYgBpAGEAcwApACAAdABoAGEAbgAgAHQAaABlACAATQAyACAAYQBuAGQAIABNADMAIABtAG8AZABlAGwAcwAgAGIAeQAgAFMAYwBoAG4AaQB0AHoAZQBsAGUAcgAuACAASABvAHcAZQB2AGUAcgAsACAAdwBlACAAbQBlAGEAcwB1AHIAZQAgAGEAbgAgAGEAdgBlAHIAYQBnAGUAIAB1AG4AYwBlAHIAdABhAGkAbgB0AHkAIABvAGYAIAA4ADAAIABwAGUAcgAgAGMAZQBuAHQAIAAoAGYAcgBhAGMAdABpAG8AbgBhAGwAKQAgAGYAbwByACAATgBFADIAMAAwADEALAAgAHQAaAByAGUAZQAgAHQAaQBtAGUAcwAgAGwAYQByAGcAZQByACAAdABoAGEAbgAgAHcAaABhAHQAIABpAHMAIAB0AHkAcABpAGMAYQBsAGwAeQAgAGEAcwBzAHUAbQBlAGQAIABpAG4AIAB0AGgAZQAgAGwAaQB0AGUAcgBhAHQAdQByAGUALgAgAFcAZQAgAHIAZQB2AGkAcwBpAHQAIAB0AGgAZQAgAHQAcgBhAG4AcwB2AGUAcgBzAGUAIAB2AGUAbABvAGMAaQB0AHkAIABkAGkAcwB0AHIAaQBiAHUAdABpAG8AbgAgAGYAbwByACAAYQAgAHMAZQB0ACAAbwBmACAAMQA5ACAAaQBzAG8AbABhAHQAZQBkACAAYQBuAGQAIAA1ADcAIABiAGkAbgBhAHIAeQAgAE0AUwBQAHMAIABhAG4AZAAgAGYAaQBuAGQAIABuAG8AIABzAHQAYQB0AGkAcwB0AGkAYwBhAGwAIABkAGkAZgBmAGUAcgBlAG4AYwBlACAAYgBlAHQAdwBlAGUAbgAgAHQAaABlAHMAZQAgAHQAdwBvACAAcABvAHAAdQBsAGEAdABpAG8AbgBzAC4AIABXAGUAIABkAGUAdABlAGMAdAAgAFMAaABhAHAAaQByAG8AIABkAGUAbABhAHkAIABpAG4AIAB0AGgAZQAgAHQAaQBtAGkAbgBnACAAcgBlAHMAaQBkAHUAYQBsAHMAIABvAGYAIABQAFMAUgBzACAASgAxADYAMAAwAC0AMwAwADUAMwAgAGEAbgBkACAASgAxADkAMQA4AC0AMAA2ADQAMgAsACAAaQBtAHAAbAB5AGkAbgBnACAAcAB1AGwAcwBhAHIAIABhAG4AZAAgAGMAbwBtAHAAYQBuAGkAbwBuACAAbQBhAHMAcwBlAHMAIABtAHAAPQAxAC4AMgAyAF4AKwAwAC4ANQBeAF8ALQAwAC4AMwA1AF8ATQBfAHsAcwB1AG4AfQBfACwAIABtAGMAPQAwAC4AMgAxAF4AKwAwAC4AMAA2AF4AXwAtADAALgAwADQAXwBNAF8AewBzAHUAbgB9AF8AIABhAG4AZAAgAG0AcAA9ADEALgAyADUAXgArADAALgA2AF4AXwAtADAALgA0AF8ATQBfAHsAcwB1AG4AfQB9ACwAIABtAGMAPQAwAC4AMgAzAF4AKwAwAC4AMAA3AF4AXwAtADAALgAwADUAXwBNAF8AewBzAHUAbgB9AF8ALAAgAHIAZQBzAHAAZQBjAHQAaQB2AGUAbAB5AC4AIABGAGkAbgBhAGwAbAB5ACwAIAB3AGUAIAB1AHMAZQAgAHQAaABlACAAbQBlAGEAcwB1AHIAZQBtAGUAbgB0ACAAbwBmACAAdABoAGUAIABvAHIAYgBpAHQAYQBsACAAcABlAHIAaQBvAGQAIABkAGUAcgBpAHYAYQB0AGkAdgBlACAAdABvACAAcwBlAHQAIABhACAAcwB0AHIAaQBuAGcAZQBuAHQAIABjAG8AbgBzAHQAcgBhAGkAbgB0ACAAbwBuACAAdABoAGUAIABkAGkAcwB0AGEAbgBjAGUAIAB0AG8AIABQAFMAUgBzACAASgAxADAAMQAyACsANQAzADAANwAgAGEAbgBkACAASgAxADkAMAA5AC0AMwA3ADQANAAsACAAYQBuAGQAIABzAGUAdAAgAGwAaQBtAGkAdABzACAAbwBuACAAdABoAGUAIABsAG8AbgBnAGkAdAB1AGQAZQAgAG8AZgAgAGEAcwBjAGUAbgBkAGkAbgBnACAAbgBvAGQAZQAgAHQAaAByAG8AdQBnAGgAIAB0AGgAZQAgAHMAZQBhAHIAYwBoACAAbwBmACAAdABoAGUAIABhAG4AbgB1AGEAbAAtAG8AcgBiAGkAdABhAGwAIABwAGEAcgBhAGwAbABhAHgAIABmAG8AcgAgAFAAUwBSAHMAIABKADEANgAwADAALQAzADAANQAzACAAYQBuAGQAIABKADEAOQAwADkALQAzADcANAA0AC4AAAA6aHR0cHM6Ly9jZHNhcmMuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jYXQvSi9NTlJBUy80NTgvMzM0MQAAAiMARABlAHMAdgBpAGcAbgBlAHMAIABHAC4AOwAgAEMAYQBiAGEAbABsAGUAcgBvACAAUgAuAE4ALgA7ACAATABlAG4AdABhAHQAaQAgAEwALgA7ACAAVgBlAHIAYgBpAGUAcwB0ACAASgAuAFAALgBXAC4AOwAgAEMAaABhAG0AcABpAG8AbgAgAEQALgBKAC4AOwAgAFMAdABhAHAAcABlAHIAcwAgAEIALgBXAC4AOwAgAEoAYQBuAHMAcwBlAG4AIABHAC4ASAAuADsAIABMAGEAegBhAHIAdQBzACAAUAAuADsAIABPAHMAbABvAHcAcwBrAGkAIABTAC4AOwAgAEIAYQBiAGEAawAgAFMALgA7ACAAQgBhAHMAcwBhACAAQwAuAEcALgA7ACAAQgByAGUAbQAgAFAALgA7ACAAQgB1AHIAZwBhAHkAIABNAC4AOwAgAEMAbwBnAG4AYQByAGQAIABJAC4AOwAgAEcAYQBpAHIAIABKAC4AUgAuADsAIABHAHIAYQBpAGsAbwB1ACAARQAuADsAIABHAHUAaQBsAGwAZQBtAG8AdAAgAEwALgA7ACAASABlAHMAcwBlAGwAcwAgAEoALgBXAC4AVAAuADsAIABKAGUAcwBzAG4AZQByACAAQQAuADsAIABKAG8AcgBkAGEAbgAgAEMALgA7ACAASwBhAHIAdQBwAHAAdQBzAGEAbQB5ACAAUgAuADsAIABLAHIAYQBtAGUAcgAgAE0ALgA7ACAATABhAHMAcwB1AHMAIABBAC4AOwAgAEwAYQB6AGEAcgBpAGQAaQBzACAASwAuADsAIABMAGUAZQAgAEsALgBKAC4AOwAgAEwAaQB1ACAASwAuADsAIABMAHkAbgBlACAAQQAuAEcALgA7ACAATQBjAGsAZQBlACAASgAuADsAIABNAGkAbgBnAGEAcgBlAGwAbABpACAAQwAuAE0ALgBGAC4AOwAgAFAAZQByAHIAbwBkAGkAbgAgAEQALgA7ACAAUABlAHQAaQB0AGUAYQB1ACAAQQAuADsAIABQAG8AcwBzAGUAbgB0AGkAIABBAC4AOwAgAFAAdQByAHYAZQByACAATQAuAEIALgA7ACAAUgBvAHMAYQBkAG8AIABQAC4AQQAuADsAIABTAGEAbgBpAGQAYQBzACAAUwAuADsAIABTAGUAcwBhAG4AYQAgAEEALgA7ACAAUwBoAGEAaQBmAHUAbABsAGEAaAAgAEcALgA7ACAAUwBtAGkAdABzACAAUgAuADsAIABUAGEAeQBsAG8AcgAgAFMALgBSAC4AOwAgAFQAaABlAHUAcgBlAGEAdQAgAEcALgA7ACAAVABpAGIAdQByAHoAaQAgAEMALgA7ACAAVgBhAG4AIABIAGEAYQBzAHQAZQByAGUAbgAgAFIALgA7ACAAVgBlAGMAYwBoAGkAbwAgAEEALgAAABMyMDE3LTExLTEwVDEzOjA1OjU2AAAAEzIwMjEtMTAtMjFUMDA6MDA6MDAAAAA2aHR0cHM6Ly9jZHMudW5pc3RyYS5mci92aXppZXItb3JnL2xpY2VuY2VzX3Zpemllci5odG1sAAAAB2NhdGFsb2cAAAAHYmliY29kZQAAABMAMgAwADEANgBNAE4AUgBBAFMALgA0ADUAOAAuADMAMwA0ADEARH/AAAAAAAAFcmFkaW8AAAicaHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80NTgvMzM0MS90YWIyLTEyPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9NTlJBUy80NTgvMzM0MTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovTU5SQVMvNDU4LzMzNDE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ1OC8zMzQxL3RhYjItMTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ1OC8zMzQxOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80NTgvMzM0MTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDU4LzMzNDEvdGFiMi0xMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovTU5SQVMvNDU4LzMzNDE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL01OUkFTLzQ1OC8zMzQxOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80NTgvMzM0MS90YWIyLTEyPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9NTlJBUy80NTgvMzM0MTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovTU5SQVMvNDU4LzMzNDE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ1OC8zMzQxL3RhYjItMTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ1OC8zMzQxOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80NTgvMzM0MTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDU4LzMzNDEvdGFiMi0xMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovTU5SQVMvNDU4LzMzNDE6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL01OUkFTLzQ1OC8zMzQxOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80NTgvMzM0MS90YWIyLTEyPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9NTlJBUy80NTgvMzM0MTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovTU5SQVMvNDU4LzMzNDEAAAMWaXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAux2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyAAABv3N0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAAAAAAAgaXZvOi8vY2RzLnZpemllci9qL21ucmFzLzQ1OS8xNDAAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAAPSi9NTlJBUy80NTkvMTQwAAAAJgBNADgAMQAsACAATABNAEMAIABhAG4AZAAgADMAQwAgADIANwAzAC8AQwBvAG0AYQAgADEANwAtADYAMABrAGUAVgAgAG8AYgBzAC4AAAAIcmVzZWFyY2gAAAYWAFcAZQAgAHAAcgBlAHMAZQBuAHQAIAByAGUAcwB1AGwAdABzACAAbwBmACAAYQAgAGQAZQBlAHAAIABzAHUAcgB2AGUAeQAgAG8AZgAgAHQAaAByAGUAZQAgAGUAeAB0AHIAYQBnAGEAbABhAGMAdABpAGMAIABmAGkAZQBsAGQAcwAsACAATQA4ADEAIAAoAGUAeABwAG8AcwB1AHIAZQAgAG8AZgAgADkALgA3AE0AcwApACwAIABMAGEAcgBnAGUAIABNAGEAZwBlAGwAbABhAG4AaQBjACAAQwBsAG8AdQBkACAAKAA2AC4AOAAgAE0AcwApACAAYQBuAGQAIAAzAEMAIAAyADcAMwAvAEMAbwBtAGEAIAAoADkALgAzAE0AcwApACwAIABpAG4AIAB0AGgAZQAgAGgAYQByAGQAIABYAC0AcgBhAHkAIAAoADEANwAtADYAMABrAGUAVgApACAAZQBuAGUAcgBnAHkAIABiAGEAbgBkACAAdwBpAHQAaAAgAHQAaABlACAASQBCAEkAUwAgAHQAZQBsAGUAcwBjAG8AcABlACAAbwBuAGIAbwBhAHIAZAAgAHQAaABlACAASQBOAFQARQBHAFIAQQBMACAAbwBiAHMAZQByAHYAYQB0AG8AcgB5ACwAIABiAGEAcwBlAGQAIABvAG4AIAAxADIAIAB5AGUAYQByAHMAIABvAGYAIABvAGIAcwBlAHIAdgBhAHQAaQBvAG4AcwAgACgAMgAwADAAMwAtADIAMAAxADUAKQAuACAAVABoAGUAIABjAG8AbQBiAGkAbgBlAGQAIABzAHUAcgB2AGUAeQAgAHIAZQBhAGMAaABlAHMAIABhACAANAB7AHMAaQBnAG0AYQB9ACAAcABlAGEAawAgAHMAZQBuAHMAaQB0AGkAdgBpAHQAeQAgAG8AZgAgADAALgAxADgAbQBDAHIAYQBiACAAKAAyAC4ANgB4ADEAMABeAC0AMQAyAF4AZQByAGcALwBzAC8AYwBtAF4AMgBeACkAIABhAG4AZAAgAHMAZQBuAHMAaQB0AGkAdgBpAHQAeQAgAGIAZQB0AHQAZQByACAAdABoAGEAbgAgADAALgAyADUAIABhAG4AZAAgADAALgA4ADcAbQBDAHIAYQBiACAAbwB2AGUAcgAgADEAMAAgAHAAZQByACAAYwBlAG4AdAAgAGEAbgBkACAAOQAwACAAcABlAHIAIABjAGUAbgB0ACAAbwBmACAAaQB0AHMAIABmAHUAbABsACAAYQByAGUAYQAgAG8AZgAgADQAOQAwADAAIABkAGUAZwBeADIAXgAsACAAcgBlAHMAcABlAGMAdABpAHYAZQBsAHkALgAgAFcAZQAgAGgAYQB2AGUAIABkAGUAdABlAGMAdABlAGQAIABpAG4AIAB0AG8AdABhAGwAIAAxADQANwAgAHMAbwB1AHIAYwBlAHMAIABhAHQAIABTAC8ATgA+ADQAewBzAGkAZwBtAGEAfQAsACAAaQBuAGMAbAB1AGQAaQBuAGcAIAAzADcAIABzAG8AdQByAGMAZQBzACAAbwBiAHMAZQByAHYAZQBkACAAaQBuACAAaABhAHIAZAAgAFgALQByAGEAeQBzACAAZgBvAHIAIAB0AGgAZQAgAGYAaQByAHMAdAAgAHQAaQBtAGUALgAgAFQAaABlACAAcwB1AHIAdgBlAHkAIABpAHMAIABkAG8AbQBpAG4AYQB0AGUAZAAgAGIAeQAgAGUAeAB0AHIAYQBnAGEAbABhAGMAdABpAGMAIABzAG8AdQByAGMAZQBzACwAIABtAG8AcwB0AGwAeQAgAGEAYwB0AGkAdgBlACAAZwBhAGwAYQBjAHQAaQBjACAAbgB1AGMAbABlAGkAIAAoAEEARwBOACkALgAgAFQAaABlACAAcwBhAG0AcABsAGUAIABvAGYAIABpAGQAZQBuAHQAaQBmAGkAZQBkACAAcwBvAHUAcgBjAGUAcwAgAGMAbwBuAHQAYQBpAG4AcwAgADkAOAAgAEEARwBOACAAKABpAG4AYwBsAHUAZABpAG4AZwAgADYANAAgAFMAZQB5AGYAZQByAHQAIABnAGEAbABhAHgAaQBlAHMALAAgAHMAZQB2AGUAbgAgAGwAbwB3AC0AaQBvAG4AaQB6AGEAdABpAG8AbgAgAG4AdQBjAGwAZQBhAHIAIABlAG0AaQBzAHMAaQBvAG4ALQBsAGkAbgBlACAAcgBlAGcAaQBvAG4AIABnAGEAbABhAHgAaQBlAHMALAAgAHQAaAByAGUAZQAgAFgALQByAGEAeQAgAGIAcgBpAGcAaAB0ACAAbwBwAHQAaQBjAGEAbABsAHkAIABuAG8AcgBtAGEAbAAgAGcAYQBsAGEAeABpAGUAcwAsACAAMQA2ACAAYgBsAGEAegBhAHIAcwAgAGEAbgBkACAAZQBpAGcAaAB0ACAAQQBHAE4AIABvAGYAIAB1AG4AYwBsAGUAYQByACAAbwBwAHQAaQBjAGEAbAAgAGMAbABhAHMAcwApACwAIAB0AHcAbwAgAGcAYQBsAGEAeAB5ACAAYwBsAHUAcwB0AGUAcgBzACAAKABDAG8AbQBhACAAYQBuAGQAIABBAGIAZQBsAGwAIAAzADIANgA2ACkALAAgADEANwAgAG8AYgBqAGUAYwB0AHMAIABsAG8AYwBhAHQAZQBkACAAaQBuACAAdABoAGUAIABMAGEAcgBnAGUAIABhAG4AZAAgAFMAbQBhAGwAbAAgAE0AYQBnAGUAbABsAGEAbgBpAGMAIABDAGwAbwB1AGQAcwAgACgAMQAzACAAaABpAGcAaAAtACAAYQBuAGQAIAB0AHcAbwAgAGwAbwB3AC0AbQBhAHMAcwAgAFgALQByAGEAeQAgAGIAaQBuAGEAcgBpAGUAcwAgAGEAbgBkACAAdAB3AG8AIABYAC0AcgBhAHkAIABwAHUAbABzAGEAcgBzACkALAAgAHQAaAByAGUAZQAgAEcAYQBsAGEAYwB0AGkAYwAgAGMAYQB0AGEAYwBsAHkAcwBtAGkAYwAgAHYAYQByAGkAYQBiAGwAZQBzACwAIABvAG4AZQAgAHUAbAB0AHIAYQBsAHUAbQBpAG4AbwB1AHMAIABYAC0AcgBhAHkAIABzAG8AdQByAGMAZQAgACgATQA4ADIAIABYAC0AMQApACAAYQBuAGQAIABvAG4AZQAgAGIAbABlAG4AZABlAGQAIABzAG8AdQByAGMAZQAgACgAUwBXAEkARgBUACAASgAxADEAMAA1AC4ANwArADUAOAA1ADQAKQAuACAAVABoAGUAIABuAGEAdAB1AHIAZQAgAG8AZgAgADIANQAgAHMAbwB1AHIAYwBlAHMAIAByAGUAbQBhAGkAbgBzACAAdQBuAGsAbgBvAHcAbgAsACAAcwBvACAAdABoAGEAdAAgAHQAaABlACAAcwB1AHIAdgBlAHkAJwBzACAAaQBkAGUAbgB0AGkAZgBpAGMAYQB0AGkAbwBuACAAaQBzACAAYwB1AHIAcgBlAG4AdABsAHkAIABjAG8AbQBwAGwAZQB0AGUAIABhAHQAIAA4ADMAIABwAGUA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AHQAaABlACAAcgBlAGYAdQByAGIAaQBzAGgAZQBkACAATQBvAGwAbwBuAGcAbABvACAATwBiAHMAZQByAHYAYQB0AG8AcgB5ACAAUwB5AG4AdABoAGUAcwBpAHMAIABSAGEAZABpAG8AIABUAGUAbABlAHMAYwBvAHAAZQAgACgATQBPAFMAVAApACAAbgBlAGEAcgAgAEMAYQBuAGIAZQByAHIAYQAsACAAQQB1AHMAdAByAGEAbABpAGEALgAgAFcAZQAgAGMAdQByAHIAZQBuAHQAbAB5ACAAbwBiAHMAZQByAHYAZQAgAG0AbwByAGUAIAB0AGgAYQBuACAANAAwADAAIABtAGEAaQBuAGwAeQAgAGIAcgBpAGcAaAB0ACAAcwBvAHUAdABoAGUAcgBuACAAcgBhAGQAaQBvACAAcAB1AGwAcwBhAHIAcwAgAHcAaQB0AGgAIAB1AHAAIAB0AG8AIABkAGEAaQBsAHkAIABjAGEAZABlAG4AYwBlAHMALgAgAEYAbwByACAAMgAwADUAIAAoADgAIABpAG4AIABiAGkAbgBhAHIAaQBlAHMALAAgADQAIABtAGkAbABsAGkAcwBlAGMAbwBuAGQAIABwAHUAbABzAGEAcgBzACkALAAgAHcAZQAgAHAAdQBiAGwAaQBzAGgAIAB1AHAAZABhAHQAZQBkACAAdABpAG0AaQBuAGcAIABtAG8AZABlAGwAcwAsACAAdABvAGcAZQB0AGgAZQByACAAdwBpAHQAaAAgAHQAaABlAGkAcgAgAGYAbAB1AHgAIABkAGUAbgBzAGkAdABpAGUAcwAsACAAZgBsAHUAeAAgAGQAZQBuAHMAaQB0AHkAIAB2AGEAcgBpAGEAYgBpAGwAaQB0AHkALAAgAGEAbgBkACAAcAB1AGwAcwBlACAAdwBpAGQAdABoAHMAIABhAHQAIAA4ADQAMwAgAE0ASAB6ACwAIABkAGUAcgBpAHYAZQBkACAAZgByAG8AbQAgAG8AYgBzAGUAcgB2AGEAdABpAG8AbgBzACAAcwBwAGEAbgBuAGkAbgBnACAAYgBlAHQAdwBlAGUAbgAgADEALgA0ACAAYQBuAGQAIAAzACAAeQByAC4AIABJAG4AIABjAG8AbQBwAGEAcgBpAHMAbwBuACAAdwBpAHQAaAAgAHQAaABlACAAQQBUAE4ARgAgAHAAdQBsAHMAYQByACAAYwBhAHQAYQBsAG8AZwB1AGUALAAgAHcAZQAgAGkAbQBwAHIAbwB2AGUAIAB0AGgAZQAgAHAAcgBlAGMAaQBzAGkAbwBuACAAbwBmACAAdABoAGUAIAByAG8AdABhAHQAaQBvAG4AYQBsACAAYQBuAGQAIABhAHMAdAByAG8AbQBlAHQAcgBpAGMAIABwAGEAcgBhAG0AZQB0AGUAcgBzACAAZgBvAHIAIAAxADIAMwAgAHAAdQBsAHMAYQByAHMALAAgAGYAbwByACAANAA3ACAAYgB5ACAAYQB0ACAAbABlAGEAcwB0ACAAYQBuACAAbwByAGQAZQByACAAbwBmACAAbQBhAGcAbgBpAHQAdQBkAGUALgAgAFQAaABlACAAdABpAG0AZQAgAHMAcABhAG4AcwAgAGIAZQB0AHcAZQBlAG4AIABvAHUAcgAgAG0AZQBhAHMAdQByAGUAbQBlAG4AdABzACAAYQBuAGQAIAB0AGgAbwBzAGUAIABpAG4AIAB0AGgAZQAgAGwAaQB0AGUAcgBhAHQAdQByAGUAIABhAHIAZQAgAHUAcAAgAHQAbwAgADQAOAAgAHkAcgAsACAAdwBoAGkAYwBoACAAYQBsAGwAbwB3ACAAdQBzACAAdABvACAAaQBuAHYAZQBzAHQAaQBnAGEAdABlACAAdABoAGUAaQByACAAbABvAG4AZwAtAHQAZQByAG0AIABzAHAAaQBuAC0AZABvAHcAbgAgAGgAaQBzAHQAbwByAHkAIABhAG4AZAAgAHQAbwAgAGUAcwB0AGkAbQBhAHQAZQAgAHAAcgBvAHAAZQByACAAbQBvAHQAaQBvAG4AcwAgAGYAbwByACAANgAwACAAcAB1AGwAcwBhAHIAcwAsACAAbwBmACAAdwBoAGkAYwBoACAAMgA0ACAAYQByAGUAIABuAGUAdwBsAHkAIABkAGUAdABlAHIAbQBpAG4AZQBkACAAYQBuAGQAIABtAG8AcwB0ACAAYQByAGUAIABtAGEAagBvAHIAIABpAG0AcAByAG8AdgBlAG0AZQBuAHQAcwAuACAAVABoAGUAIAByAGUAcwB1AGwAdABzACAAYQByAGUAIABjAG8AbgBzAGkAcwB0AGUAbgB0ACAAdwBpAHQAaAAgAGkAbgB0AGUAcgBmAGUAcgBvAG0AZQB0AHIAaQBjACAAbQBlAGEAcwB1AHIAZQBtAGUAbgB0AHMAIABmAHIAbwBtACAAdABoAGUAIABsAGkAdABlAHIAYQB0AHUAcgBlAC4AIABBACAAbQBvAGQAZQBsACAAdwBpAHQAaAAgAHQAdwBvACAARwBhAHUAcwBzAGkAYQBuACAAYwBvAG0AcABvAG4AZQBuAHQAcwAgAGMAZQBuAHQAcgBlAGQAIABhAHQAIAAxADMAOQAgAGEAbgBkACAANAA2ADMAawBtAC8AcwAgAGYAaQB0AHMAIAB0AGgAZQAgAHQAcgBhAG4AcwB2AGUAcgBzAGUAIAB2AGUAbABvAGMAaQB0AHkAIABkAGkAcwB0AHIAaQBiAHUAdABpAG8AbgAgAGIAZQBzAHQALgAgAFQAaABlACAAcAB1AGwAcwBlACAAZAB1AHQAeQAgAGMAeQBjAGwAZQAgAGQAaQBzAHQAcgBpAGIAdQB0AGkAbwBuAHMAIABhAHQAIAA1ADAAIABhAG4AZAAgADEAMAAgAHAAZQByACAAYwBlAG4AdAAgAG0AYQB4AGkAbQB1AG0AIABhAHIAZQAgAGIAZQBzAHQAIABkAGUAcwBjAHIAaQBiAGUAZAAgAGIAeQAgAGwAbwBnAG4AbwByAG0AYQBsACAAZABpAHMAdAByAGkAYgB1AHQAaQBvAG4AcwAgAHcAaQB0AGgAIABtAGUAZABpAGEAbgBzACAAbwBmACAAMgAuADMAIABhAG4AZAAgADQALgA0ACAAcABlAHIAIABjAGUAbgB0ACwAIAByAGUAcwBwAGUAYwB0AGkAdgBlAGwAeQAuACAAVwBlACAAZABpAHMAYwB1AHMAcwAgAHQAdwBvACAAcAB1AGwAcwBhAHIAcwAgAHQAaABhAHQAIABlAHgAaABpAGIAaQB0ACAAcwBwAGkAbgAtAGQAbwB3AG4AIAByAGEAdABlACAAYwBoAGEAbgBnAGUAcwAgAGEAbgBkACAAZAByAGkAZgB0AGkAbgBnACAAcwB1AGIAcAB1AGwAcwBlAHMALgAgAEYAaQBuAGEAbABsAHkALAAgAHcAZQAgAGQAZQBzAGMAcgBpAGIAZQAgAHQAaABlACAAYQB1AHQAbwBuAG8AbQBvAHUAcwAgAG8AYgBzAGUAcgB2AGkAbgBnACAAcwB5AHMAdABlAG0AIABhAG4AZAAgAHQAaABlACAAZAB5AG4AYQBtAGkAYwAgAHMAYwBoAGUAZAB1AGwAZQByACAAdABoAGEAdAAgAGgAYQBzACAAaQBuAGMAcgBlAGEAcwBlAGQAIAB0AGgAZQAgAG8AYgBzAGUAcgB2AGkAbgBnACAAZQBmAGYAaQBjAGkAZQBuAGMAeQAgAGIAeQAgAGEAIABmAGEAYwB0AG8AcgAgAG8AZgAgADIALQAzACAAaQBuACAAYwBvAG0AcABhAHIAaQBzAG8AbgAgAHcAaQB0AGgAIABzAHQAYQB0AGkAYwAgAHMAYwBoAGUAZAB1AGwAaQBuAGcALgAAADpodHRwczovL2Nkc2FyYy5jZHMudW5pc3RyYS5mci92aXotYmluL2NhdC9KL01OUkFTLzQ4NC8zNjkxAAAA9QBKAGEAbgBrAG8AdwBzAGsAaQAgAEYALgA7ACAAQgBhAGkAbABlAHMAIABNAC4AOwAgAFYAYQBuACAAUwB0AHIAYQB0AGUAbgAgAFcALgA7ACAASwBlAGEAbgBlACAARQAuAEYALgA7ACAARgBsAHkAbgBuACAAQwAuADsAIABCAGEAcgByACAARQAuAEQALgA7ACAAQgBhAHQAZQBtAGEAbgAgAFQALgA7ACAAQgBoAGEAbgBkAGEAcgBpACAAUwAuADsAIABDAGEAbABlAGIAIABNAC4AOwAgAEMAYQBtAHAAYgBlAGwAbAAtAFcAaQBsAHMAbwBuACAARAAuADsAIABGAGEAcgBhAGgAIABXAC4AOwAgAEcAcgBlAGUAbgAgAEEALgBKAC4AOwAgAEgAdQBuAHMAdABlAGEAZAAgAFIALgBXAC4AOwAgAEoAYQBtAGUAcwBvAG4AIABBAC4AOwAgAE8AcwBsAG8AdwBzAGsAaQAgAFMALgA7ACAAUABhAHIAdABoAGEAcwBhAHIAYQB0AGgAeQAgAEEALgA7ACAAUgBvAHMAYQBkAG8AIABQAC4AQQAuADsAIABWAGUAbgBrAGEAdAByAGEAbQBhAG4AIABLAHIAaQBzAGgAbgBhAG4AIABWAC4AAAATMjAyMC0wMi0wNVQxNDo0ODo1NQAAABMyMDIxLTEwLTIxVDAwOjAwOjAwAAAANmh0dHBzOi8vY2RzLnVuaXN0cmEuZnIvdml6aWVyLW9yZy9saWNlbmNlc192aXppZXIuaHRtbAAAAAdjYXRhbG9nAAAAB2JpYmNvZGUAAAATADIAMAAxADkATQBOAFIAQQBTAC4ANAA4ADQALgAzADYAOQAxAEp/wAAAAAAABXJhZGlvAAABhmh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9NTlJBUy80ODQvMzY5MTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovTU5SQVMvNDg0LzM2OTE6OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ4NC8zNjkxL3RhYmxlYjE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80ODQvMzY5MS90YWJsZWIyPwAAAJA6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2gAAAB5dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cAAAAEU6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQAAAAAAAAAIWl2bzovL2Nkcy52aXppZXIvai9tbnJhcy80OTMvMTA2MwAAABF2czpjYXRhbG9nc2VydmljZQAAABBKL01OUkFTLzQ5My8xMDYzAAAAIgBEAGkAcwBjAG8AdgBlAHIAeQAgAGEAbgBkACAAdABpAG0AaQBuAGcAIABvAGYAIAA0ADAAIABwAHUAbABzAGEAcgBzAAAACHJlc2VhcmNoAAAGsgBXAGUAIABwAHIAZQBzAGUAbgB0ACAAdABoAGUAIAByAGUAcwB1AGwAdABzACAAbwBmACAAcAByAG8AYwBlAHMAcwBpAG4AZwAgAGEAbgAgAGEAZABkAGkAdABpAG8AbgBhAGwAIAA0ADQAIABwAGUAcgAgAGMAZQBuAHQAIABvAGYAIAB0AGgAZQAgAEgAaQBnAGgAIABUAGkAbQBlACAAUgBlAHMAbwBsAHUAdABpAG8AbgAgAFUAbgBpAHYAZQByAHMAZQAgAFMAbwB1AHQAaAAgAEwAbwB3ACAATABhAHQAaQB0AHUAZABlACAAKABIAFQAUgBVAC0AUwAgAEwAbwB3AEwAYQB0ACkAIABwAHUAbABzAGEAcgAgAHMAdQByAHYAZQB5ACwAIAB0AGgAZQAgAG0AbwBzAHQAIABzAGUAbgBzAGkAdABpAHYAZQAgAGIAbABpAG4AZAAgAHAAdQBsAHMAYQByACAAcwB1AHIAdgBlAHkAIABvAGYAIAB0AGgAZQAgAHMAbwB1AHQAaABlAHIAbgAgAEcAYQBsAGEAYwB0AGkAYwAgAHAAbABhAG4AZQAgAHQAbwAgAGQAYQB0AGUALgAgAE8AdQByACAAcABhAHIAdABpAGEAbABsAHkAIABjAG8AaABlAHIAZQBuAHQAIABzAGUAZwBtAGUAbgB0AGUAZAAgAGEAYwBjAGUAbABlAHIAYQB0AGkAbwBuACAAcwBlAGEAcgBjAGgAIABwAGkAcABlAGwAaQBuAGUAIABpAHMAIABkAGUAcwBpAGcAbgBlAGQAIAB0AG8AIABlAG4AYQBiAGwAZQAgAHQAaABlACAAZABpAHMAYwBvAHYAZQByAHkAIABvAGYAIABwAHUAbABzAGEAcgBzACAAaQBuACAAcwBoAG8AcgB0ACwAIABoAGkAZwBoAGwAeQAgAGEAYwBjAGUAbABlAHIAYQB0AGUAZAAgAG8AcgBiAGkAdABzACwAIAB3AGgAaQBsAGUAIABvAHUAcgAgADcAMgAtAG0AaQBuACAAaQBuAHQAZQBnAHIAYQB0AGkAbwBuACAAbABlAG4AZwB0AGgAcwAgAHcAaQBsAGwAIABhAGwAbABvAHcAIAB1AHMAIAB0AG8AIABkAGkAcwBjAG8AdgBlAHIAIABwAHUAbABzAGEAcgBzACAAYQB0ACAAdABoAGUAIABsAG8AdwBlAHIAIABlAG4AZAAgAG8AZgAgAHQAaABlACAAcAB1AGwAcwBhAHIAIABsAHUAbQBpAG4AbwBzAGkAdAB5ACAAZABpAHMAdAByAGkAYgB1AHQAaQBvAG4ALgAgAFcAZQAgAHIAZQBwAG8AcgB0ACAAdABoAGUAIABkAGkAcwBjAG8AdgBlAHIAeQAgAG8AZgAgADQAMAAgAHAAdQBsAHMAYQByAHMALAAgAGkAbgBjAGwAdQBkAGkAbgBnACAAdABoAHIAZQBlACAAbQBpAGwAbABpAHMAZQBjAG8AbgBkACAAcAB1AGwAcwBhAHIALQB3AGgAaQB0AGUAIABkAHcAYQByAGYAIABiAGkAbgBhAHIAeQAgAHMAeQBzAHQAZQBtAHMAIAAoAFAAUwBSAHMAIABKADEANQAzADcALQA1ADMAMQAyACwAIABKADEANQA0ADcALQA1ADcAMAA5ACwAIABhAG4AZAAgAEoAMQA2ADEAOAAtADQANgAyADQAKQAsACAAYQAgAGIAbABhAGMAawAtAHcAaQBkAG8AdwAgAGIAaQBuAGEAcgB5ACAAcwB5AHMAdABlAG0AIAAoAFAAUwBSACAASgAxADcANAA1AC0AMgAzACkAIABhAG4AZAAgAGEAIABjAGEAbgBkAGkAZABhAHQAZQAgAGIAbABhAGMAawAtAHcAaQBkAG8AdwAgAGIAaQBuAGEAcgB5ACAAcwB5AHMAdABlAG0AIAAoAFAAUwBSACAASgAxADcAMgA3AC0AMgA5ADUAMQApACwAIABhACAAZwBsAGkAdABjAGgAaQBuAGcAIABwAHUAbABzAGEAcgAgACgAUABTAFIAIABKADEANwAwADYALQA0ADQAMwA0ACkALAAgAGEAbgAgAGUAYwBsAGkAcABzAGkAbgBnACAAYgBpAG4AYQByAHkAIABwAHUAbABzAGEAcgAgAHcAaQB0AGgAIABhACAAMQAuADUALQB5AHIAIABvAHIAYgBpAHQAYQBsACAAcABlAHIAaQBvAGQAIAAoAFAAUwBSACAASgAxADYANQAzAC0ANAA1ACkALAAgAGEAbgBkACAAYQAgAHAAYQBpAHIAIABvAGYAIABsAG8AbgBnACAAcwBwAGkAbgAtAHAAZQByAGkAbwBkACAAYgBpAG4AYQByAHkAIABwAHUAbABzAGEAcgBzACAAdwBoAGkAYwBoACAAZABpAHMAcABsAGEAeQAgAGUAaQB0AGgAZQByACAAbgB1AGwAbABpAG4AZwAgAG8AcgAgAGkAbgB0AGUAcgBtAGkAdAB0AGUAbgB0ACAAYgBlAGgAYQB2AGkAbwB1AHIAIAAoAFAAUwBSAHMAIABKADEAOAAxADIALQAxADUAIABhAG4AZAAgAEoAMQA4ADMAMQAtADAANAApAC4AIABXAGUAIABzAGgAbwB3ACAAdABoAGEAdAAgAHQAaABlACAAdABvAHQAYQBsACAAcABvAHAAdQBsAGEAdABpAG8AbgAgAG8AZgAgADEAMAAwACAAcAB1AGwAcwBhAHIAcwAgAGQAaQBzAGMAbwB2AGUAcgBlAGQAIABpAG4AIAB0AGgAZQAgAEgAVABSAFUALQBTACAATABvAHcATABhAHQAIABzAHUAcgB2AGUAeQAgAHQAbwAgAGQAYQB0AGUAIAByAGUAcAByAGUAcwBlAG4AdABzACAAYgBvAHQAaAAgAGEAbgAgAG8AbABkAGUAcgAgAGEAbgBkACAAbABvAHcAZQByACAAbAB1AG0AaQBuAG8AcwBpAHQAeQAgAHAAbwBwAHUAbABhAHQAaQBvAG4ALAAgAGEAbgBkACAAaQBuAGQAaQBjAGEAdABlAHMAIAB0AGgAYQB0ACAAdwBlACAAaABhAHYAZQAgAHkAZQB0ACAAdABvACAAcgBlAGEAYwBoACAAdABoAGUAIABiAG8AdAB0AG8AbQAgAG8AZgAgAHQAaABlACAAbAB1AG0AaQBuAG8AcwBpAHQAeQAgAGQAaQBzAHQAcgBpAGIAdQB0AGkAbwBuACAAZgB1AG4AYwB0AGkAbwBuAC4AIABXAGUAIABwAHIAZQBzAGUAbgB0ACAAZQB2AGEAbAB1AGEAdABpAG8AbgBzACAAbwBmACAAdABoAGUAIABwAGUAcgBmAG8AcgBtAGEAbgBjAGUAIABvAGYAIABvAHUAcgAgAHMAZQBhAHIAYwBoACAAdABlAGMAaABuAGkAcQB1AGUAIABhAG4AZAAgAG8AZgAgAHQAaABlACAAbwB2AGUAcgBhAGwAbAAgAHkAaQBlAGwAZAAgAG8AZgAgAHQAaABlACAAcwB1AHIAdgBlAHkALAAgAGMAbwBuAHMAaQBkAGUAcgBpAG4AZwAgAHQAaABlACAAOQA0ACAAcABlAHIAIABjAGUAbgB0ACAAbwBmACAAdABoAGUAIABzAHUAcgB2AGUAeQAgAHcAaABpAGMAaAAgAHcAZQAgAGgAYQB2AGUAIABwAHIAbwBjAGUAcwBzAGUAZAAgAHQAbwAgAGQAYQB0AGUALgAgAFcAZQAgAHMAaABvAHcAIAB0AGgAYQB0ACAAbwB1AHIAIABwAHUAbABzAGEAcgAgAHkAaQBlAGwAZAAgAGYAYQBsAGwAcwAgAGIAZQBsAG8AdwAgAGUAYQByAGwAaQBlAHIAIABwAHIAZQBkAGkAYwB0AGkAbwBuAHMAIABiAHkAIABhAHAAcAByAG8AeABpAG0AYQB0AGUAbAB5ACAAMgA1ACAAcABlAHIAIABjAGUAbgB0ACAAKABlAHMAcABlAGMAaQBhAGwAbAB5ACAAaQBuACAAdABoAGUAIABjAGEAcwBlACAAbwBmACAAbQBpAGwAbABpAHMAZQBjAG8AbgBkACAAcAB1AGwAcwBhAHIAcwApACwAIABhAG4AZAAgAGQAaQBzAGMAdQBzAHMAIABlAHgAcABsAGEAbgBhAHQAaQBvAG4AcwAgAGYAbwByACAAdABoAGkAcwAgAGQAaQBzAGMAcgBlAHAAYQBuAGMAeQAgAGEAcwAgAHcAZQBsAGwAIABhAHMAIABmAHUAdAB1AHIAZQAgAGEAZABhAHAAdABhAHQAaQBvAG4AcwAgAGkAbgAgAFIARgBJACAAbQBpAHQAaQBnAGEAdABpAG8AbgAgAGEAbgBkACAAcwBlAGEAcgBjAGgAaQBuAGcAIAB0AGUAYwBoAG4AaQBxAHUAZQBzACAAdwBoAGkAYwBoACAAbQBhAHkAIABhAGQAZAByAGUAcwBzACAAdABoAGUAcwBlACAAcwBoAG8AcgB0AGYAYQBsAGwAcwAuAAAAOmh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovTU5SQVMvNDkzLzEwNjMAAAFAAEMAYQBtAGUAcgBvAG4AIABBAC4ARAAuADsAIABDAGgAYQBtAHAAaQBvAG4AIABEAC4ASgAuADsAIABCAGEAaQBsAGUAcwAgAE0ALgA7ACAAQgBhAGwAYQBrAHIAaQBzAGgAbgBhAG4AIABWAC4AOwAgAEIAYQByAHIAIABFAC4ARAAuADsAIABCAGEAcwBzAGEAIABDAC4ARwAuADsAIABCAGEAdABlAHMAIABTAC4AOwAgAEIAaABhAG4AZABhAHIAaQAgAFMALgA7ACAAQgBoAGEAdAAgAE4ALgBEAC4AUgAuADsAIABCAHUAcgBnAGEAeQAgAE0ALgA7ACAAQgB1AHIAawBlAC0AUwBwAG8AbABhAG8AcgAgAFMALgA7ACAARgBsAHkAbgBuACAAQwAuAE0ALgBMAC4AOwAgAEoAYQBtAGUAcwBvAG4AIABBAC4AOwAgAEoAbwBoAG4AcwB0AG8AbgAgAFMALgA7ACAASwBlAGkAdABoACAATQAuAEoALgA7ACAASwByAGEAbQBlAHIAIABNAC4AOwAgAEwAZQB2AGkAbgAgAEwALgA7ACAATAB5AG4AZQAgAEEALgBHAC4AOwAgAE4AZwAgAEMALgA7ACAAUABlAHQAcgBvAGYAZgAgAEUALgA7ACAAUABvAHMAcwBlAG4AdABpACAAQQAuADsAIABTAG0AaQB0AGgAIABEAC4AQQAuADsAIABTAHQAYQBwAHAAZQByAHMAIABCAC4AVwAuADsAIAB2AGEAbgAgAFMAdAByAGEAdABlAG4AIABXAC4AOwAgAFQAaQBiAHUAcgB6AGkAIABDAC4AOwAgAFcAdQAgAEoALgAAABMyMDIzLTA3LTI4VDA5OjUxOjM5AAAAEzIwMjMtMDctMjhUMDg6NTg6NTEAAAA2aHR0cHM6Ly9jZHMudW5pc3RyYS5mci92aXppZXItb3JnL2xpY2VuY2VzX3Zpemllci5odG1sAAAAB2NhdGFsb2cAAAAHYmliY29kZQAAABMAMgAwADIAMABNAE4AUgBBAFMALgA0ADkAMwAuADEAMAA2ADMAQ3/AAAAAAAAFcmFkaW8AABGXaHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80OTMvMTA2Mzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDkzLzEwNjMvdGFibGVhMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ5My8xMDYzL3RhYmxlYTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTY/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ5My8xMDYzOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80OTMvMTA2Mzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDkzLzEwNjMvdGFibGVhMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ5My8xMDYzL3RhYmxlYTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTY/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ5My8xMDYzOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80OTMvMTA2Mzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDkzLzEwNjMvdGFibGVhMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ5My8xMDYzL3RhYmxlYTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTY/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ5My8xMDYzOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80OTMvMTA2Mzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDkzLzEwNjMvdGFibGVhMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ5My8xMDYzL3RhYmxlYTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTY/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ5My8xMDYzOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80OTMvMTA2Mzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDkzLzEwNjMvdGFibGVhMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ5My8xMDYzL3RhYmxlYTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTY/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ5My8xMDYzOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy80OTMvMTA2Mzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNDkzLzEwNjMvdGFibGVhMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzQ5My8xMDYzL3RhYmxlYTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTY/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTQ/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9NTlJBUy80OTMvMTA2My90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL01OUkFTLzQ5My8xMDYzAAAHy2l2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6OgAABal2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHAAAAOZc3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6c3RkOjo6cHkgVk8gc2VwOjo6AAAAAAAAACFpdm86Ly9jZHMudml6aWVyL2ovbW5yYXMvNTAxLzExMTYAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAAQSi9NTlJBUy81MDEvMTExNgAAACMARwBBAEkAQQAgAHAAdQBsAHMAYQByAHMAIABhAG4AZAAgAHcAaABlAHIAZQAgAHQAbwAgAGYAaQBuAGQAIAB0AGgAZQBtAAAACHJlc2VhcmNoAAAGYwBXAGgAaQBsAGUAIAB0AGgAZQAgAG0AYQBqAG8AcgBpAHQAeQAgAG8AZgAgAG0AYQBzAHMAaQB2AGUAIABzAHQAYQByAHMAIABoAGEAdgBlACAAYQAgAHMAdABlAGwAbABhAHIAIABjAG8AbQBwAGEAbgBpAG8AbgAsACAAbQBvAHMAdAAgAHAAdQBsAHMAYQByAHMAIABhAHAAcABlAGEAcgAgAHQAbwAgAGIAZQAgAGkAcwBvAGwAYQB0AGUAZAAuACAAVABhAGsAZQBuACAAYQB0ACAAZgBhAGMAZQAgAHYAYQBsAHUAZQAsACAAdABoAGkAcwAgAHMAdQBnAGcAZQBzAHQAcwAgAHQAaABhAHQAIABtAG8AcwB0ACAAbQBhAHMAcwBpAHYAZQAgAGIAaQBuAGEAcgBpAGUAcwAgAGIAcgBlAGEAawAgAGEAcABhAHIAdAAgAGQAdQBlACAAdABvACAAcwB0AHIAbwBuAGcAIABuAGEAdABhAGwAIABrAGkAYwBrAHMAIAByAGUAYwBlAGkAdgBlAGQAIABpAG4AIABzAHUAcABlAHIAbgBvAHYAYQAgAGUAeABwAGwAbwBzAGkAbwBuAHMALgAgAEgAbwB3AGUAdgBlAHIALAAgAHQAaABlACAAbwBiAHMAZQByAHYAZQBkACAAYgBpAG4AYQByAHkAIABmAHIAYQBjAHQAaQBvAG4AIABjAGEAbgAgAHMAdABpAGwAbAAgAGIAZQAgAHMAdQBiAGoAZQBjAHQAIAB0AG8AIABzAHQAcgBvAG4AZwAgAHMAZQBsAGUAYwB0AGkAbwBuACAAZQBmAGYAZQBjAHQAcwAsACAAYQBzACAAbQBvAG4AaQB0AG8AcgBpAG4AZwAgAG8AZgAgAG4AZQB3AGwAeQAgAGQAaQBzAGMAbwB2AGUAcgBlAGQAIABwAHUAbABzAGEAcgBzACAAaQBzACAAcgBhAHIAZQBsAHkAIABjAGEAcgByAGkAZQBkACAAbwB1AHQAIABmAG8AcgAgAGwAbwBuAGcAIABlAG4AbwB1AGcAaAAgAHQAbwAgAGMAbwBuAGMAbAB1AHMAaQB2AGUAbAB5ACAAcgB1AGwAZQAgAG8AdQB0ACAAbQB1AGwAdABpAHAAbABpAGMAaQB0AHkALgAgAEgAZQByAGUALAAgAHcAZQAgAHUAcwBlACAAdABoAGUAIABzAGUAYwBvAG4AZAAgAEcAYQBpAGEAIABkAGEAdABhACAAcgBlAGwAZQBhAHMAZQAgAHQAbwAgAHMAZQBhAHIAYwBoACAAZgBvAHIAIABjAG8AbQBwAGEAbgBpAG8AbgBzACAAdABvACAAMQA1ADMANAAgAHIAbwB0AGEAdABpAG8AbgAtAHAAbwB3AGUAcgBlAGQAIABwAHUAbABzAGEAcgBzACAAdwBpAHQAaAAgAHAAbwBzAGkAdABpAG8AbgBzACAAawBuAG8AdwBuACAAdABvACAAYgBlAHQAdABlAHIAIAB0AGgAYQBuACAAMAAuADUAYQByAGMAcwBlAGMALgAgAFcAZQAgAGYAaQBuAGQAIAAyADIAIABtAGEAdABjAGgAZQBzACAAdABvACAAawBuAG8AdwBuACAAcAB1AGwAcwBhAHIAcwAsACAAaQBuAGMAbAB1AGQAaQBuAGcAIAAxACAAbgBvAHQAIAByAGUAcABvAHIAdABlAGQAIABlAGwAcwBlAHcAaABlAHIAZQAsACAAYQBuAGQAIAA4ACAAbgBlAHcAIABwAG8AcwBzAGkAYgBsAGUAIABjAG8AbQBwAGEAbgBpAG8AbgBzACAAdABvACAAeQBvAHUAbgBnACAAcAB1AGwAcwBhAHIAcwAuACAAVwBlACAAZQB4AGEAbQBpAG4AZQAgAHQAaABlACAAcABoAG8AdABvAG0AZQB0AHIAaQBjACAAYQBuAGQAIABrAGkAbgBlAG0AYQB0AGkAYwAgAHAAcgBvAHAAZQByAHQAaQBlAHMAIABvAGYAIAB0AGgAZQBzAGUAIABzAHkAcwB0AGUAbQBzACAAYQBuAGQAIABwAHIAbwB2AGkAZABlACAAZQBtAHAAaQByAGkAYwBhAGwAIAByAGUAbABhAHQAaQBvAG4AcwAgAGYAbwByACAAaQBkAGUAbgB0AGkAZgB5AGkAbgBnACAARwBhAGkAYQAgAHMAbwB1AHIAYwBlAHMAIAB3AGkAdABoACAAcABvAHQAZQBuAHQAaQBhAGwAIABtAGkAbABsAGkAcwBlAGMAbwBuAGQAIABwAHUAbABzAGEAcgAgAGMAbwBtAHAAYQBuAGkAbwBuAHMALgAgAE8AdQByACAAcgBlAHMAdQBsAHQAcwAgAGMAbwBuAGYAaQByAG0AIAB0AGgAYQB0ACAAdABoAGUAIABvAGIAcwBlAHIAdgBlAGQAIABtAHUAbAB0AGkAcABsAGkAYwBpAHQAeQAgAGYAcgBhAGMAdABpAG8AbgAgAGkAcwAgAHMAbQBhAGwAbAAuACAASABvAHcAZQB2AGUAcgAsACAAdwBlACAAcwBoAG8AdwAgAHQAaABhAHQAIAB0AGgAZQAgAG4AdQBtAGIAZQByACAAbwBmACAAYgBpAG4AYQByAGkAZQBzACAAYgBlAGwAbwB3ACAAdABoAGUAIABzAGUAbgBzAGkAdABpAHYAaQB0AHkAIABvAGYAIABHAGEAaQBhACAAYQBuAGQAIAByAGEAZABpAG8AIAB0AGkAbQBpAG4AZwAgAGkAbgAgAG8AdQByACAAcwBhAG0AcABsAGUAIABjAG8AdQBsAGQAIABzAHQAaQBsAGwAIABiAGUAIABzAGkAZwBuAGkAZgBpAGMAYQBuAHQAbAB5ACAAaABpAGcAaABlAHIALgAgAFcAZQAgAGMAbwBuAHMAdAByAGEAaQBuACAAdABoAGUAIABiAGkAbgBhAHIAeQAgAGYAcgBhAGMAdABpAG8AbgAgAG8AZgAgAHkAbwB1AG4AZwAgAHAAdQBsAHMAYQByAHMAIAB0AG8AIABiAGUAIABmAF4AdAByAHUAZQBeAF8AeQBvAHUAbgBnAF8APAA1AC4AMwAoADgALgAzACkAIABwAGUAcgAgAGMAZQBuAHQAIAB1AG4AZABlAHIAIAByAGUAYQBsAGkAcwB0AGkAYwAgACgAYwBvAG4AcwBlAHIAdgBhAHQAaQB2AGUAKQAgAGEAcwBzAHUAbQBwAHQAaQBvAG4AcwAgAGYAbwByACAAdABoAGUAIABiAGkAbgBhAHIAeQAgAHAAcgBvAHAAZQByAHQAaQBlAHMAIABhAG4AZAAgAGMAdQByAHIAZQBuAHQAIABzAGUAbgBzAGkAdABpAHYAaQB0AHkAIAB0AGgAcgBlAHMAaABvAGwAZABzAC4AIABGAG8AcgAgAG0AYQBzAHMAaQB2AGUAIABzAHQAYQByAHMAIAAoAD4AMQAwAE0AXwB7AHMAdQBuAH0AXwApACAAaQBuACAAcABhAHIAdABpAGMAdQBsAGEAcgAsACAAdwBlACAAZgBpAG4AZAAgAGYAXgB0AHIAdQBlAF4AXwBPAEIAXwA8ADMALgA3ACAAcABlAHIAIABjAGUAbgB0ACwAIAB3AGgAaQBjAGgAIABzAGUAdABzACAAYQAgAGYAaQByAG0AIABpAG4AZABlAHAAZQBuAGQAZQBuAHQAIAB1AHAAcABlAHIAIABsAGkAbQBpAHQAIABvAG4AIAB0AGgAZQAgAEcAYQBsAGEAYwB0AGkAYwAgAG4AZQB1AHQAcgBvAG4AIABzAHQAYQByACAAbQBlAHIAZwBlAHIAIAByAGEAdABlACwAIAA8AD0ANwAuADIAeAAxADAAXgAtADQAXgAvAHkAcgAuACAATwBuAGcAbwBpAG4AZwAgAGEAbgBkACAAZgB1AHQAdQByAGUAIABwAHIAbwBqAGUAYwB0AHMALAAgAHMAdQBjAGgAIABhAHMAIAB0AGgAZQAgAEMASABJAE0ARQAvAHAAdQBsAHMAYQByACAAcAByAG8AZwByAGEAbQAsACAATQBlAGUAcgBUAGkAbQBlACwAIABIAEkAUgBBAFgALAAgAGEAbgBkACAAdQBsAHQAaQBtAGEAdABlAGwAeQAgAHQAaABlACAAUwBLAEEALAAgAHcAaQBsAGwAIABzAGkAZwBuAGkAZgBpAGMAYQBuAHQAbAB5ACAAaQBtAHAAcgBvAHYAZQAgAHQAaABlAHMAZQAgAGMAbwBuAHMAdAByAGEAaQBuAHQAcwAgAGkAbgAgAHQAaABlACAAZgB1AHQAdQByAGUALgAAADpodHRwczovL2Nkc2FyYy5jZHMudW5pc3RyYS5mci92aXotYmluL2NhdC9KL01OUkFTLzUwMS8xMTE2AAAADQBBAG4AdABvAG4AaQBhAGQAaQBzACAASgAuAAAAEzIwMjEtMDEtMTNUMDc6MTM6MTAAAAATMjAyMS0xMC0yMVQwMDowMDowMAAAADZodHRwczovL2Nkcy51bmlzdHJhLmZyL3Zpemllci1vcmcvbGljZW5jZXNfdml6aWVyLmh0bWwAAAAHY2F0YWxvZwAAAAdiaWJjb2RlAAAAEwAyADAAMgAxAE0ATgBSAEEAUwAuADUAMAAxAC4AMQAxADEANgBBf8AAAAAAAAVyYWRpbwAAAmtodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL01OUkFTLzUwMS8xMTE2L2NhdGFsb2c/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9NTlJBUy81MDEvMTExNjo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9Si9NTlJBUy81MDEvMTExNjo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovTU5SQVMvNTAxLzExMTYvY2F0YWxvZz86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovTU5SQVMvNTAxLzExMTY6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL01OUkFTLzUwMS8xMTE2AAAA12l2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6AAAAy3ZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyAAAAdXN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6OnN0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OgAAAAAAAAAjaXZvOi8vY2RzLnZpemllci9qL290aGVyL3JhYS8yMS4xMDcAAAARdnM6Y2F0YWxvZ3NlcnZpY2UAAAAQSi9vdGhlci9SQUEvMjEuMQAAABUATgBlAHcAIABwAHUAbABzAGEAcgBzACAAZgByAG8AbQAgAEcARwBQAFMAAAAIcmVzZWFyY2gAAAaoAEQAaQBzAGMAbwB2AGUAcgB5ACAAbwBmACAAcAB1AGwAcwBhAHIAcwAgAGkAcwAgAG8AbgBlACAAbwBmACAAdABoAGUAIABtAGEAaQBuACAAZwBvAGEAbABzACAAZgBvAHIAIABsAGEAcgBnAGUAIAByAGEAZABpAG8AIAB0AGUAbABlAHMAYwBvAHAAZQBzAC4AIABUAGgAZQAgAEYAaQB2AGUALQBoAHUAbgBkAHIAZQBkAC0AbQBlAHQAZQByACAAQQBwAGUAcgB0AHUAcgBlACAAUwBwAGgAZQByAGkAYwBhAGwAIAByAGEAZABpAG8AIABUAGUAbABlAHMAYwBvAHAAZQAgACgARgBBAFMAVAApACwAIAB0AGgAYQB0ACAAaQBuAGMAbwByAHAAbwByAGEAdABlAHMAIABhAG4AIABMAC0AYgBhAG4AZAAgADEAOQAtAGIAZQBhAG0AIAByAGUAYwBlAGkAdgBlAHIAIAB3AGkAdABoACAAYQAgAHMAeQBzAHQAZQBtACAAdABlAG0AcABlAHIAYQB0AHUAcgBlACAAbwBmACAAYQBiAG8AdQB0ACAAMgAwACAASwAsACAAaQBzACAAdABoAGUAIABtAG8AcwB0ACAAcwBlAG4AcwBpAHQAaQB2AGUAIAByAGEAZABpAG8AIAB0AGUAbABlAHMAYwBvAHAAZQAgAHUAdABpAGwAaQB6AGUAZAAgAGYAbwByACAAZABpAHMAYwBvAHYAZQByAGkAbgBnACAAcAB1AGwAcwBhAHIAcwAuACAAVwBlACAAZABlAHMAaQBnAG4AZQBkACAAdABoAGUAIABzAG4AYQBwAHMAaABvAHQAIABvAGIAcwBlAHIAdgBhAHQAaQBvAG4AIABtAG8AZABlACAAZgBvAHIAIABhACAARgBBAFMAVAAgAGsAZQB5ACAAcwBjAGkAZQBuAGMAZQAgAHAAcgBvAGoAZQBjAHQALAAgAHQAaABlACAARwBhAGwAYQBjAHQAaQBjACAAUABsAGEAbgBlACAAUAB1AGwAcwBhAHIAIABTAG4AYQBwAHMAaABvAHQAIAAoAEcAUABQAFMAKQAgAHMAdQByAHYAZQB5ACwAIABpAG4AIAB3AGgAaQBjAGgAIABlAHYAZQByAHkAIABmAG8AdQByACAAbgBlAGEAcgBiAHkAIABwAG8AaQBuAHQAaQBuAGcAcwAgAGMAYQBuACAAbwBiAHMAZQByAHYAZQAgAGEAIABjAG8AdgBlAHIAIABvAGYAIABhACAAcwBrAHkAIABwAGEAdABjAGgAIABvAGYAIAAwAC4AMQA1ADcANQAgAHMAcQB1AGEAcgBlACAAZABlAGcAcgBlAGUAcwAgAHQAaAByAG8AdQBnAGgAIABiAGUAYQBtAC0AcwB3AGkAdABjAGgAaQBuAGcAIABvAGYAIAB0AGgAZQAgAEwALQBiAGEAbgBkACAAMQA5AC0AYgBlAGEAbQAgAHIAZQBjAGUAaQB2AGUAcgAuACAAVABoAGUAIABpAG4AdABlAGcAcgBhAHQAaQBvAG4AIAB0AGkAbQBlACAAZgBvAHIAIABlAGEAYwBoACAAcABvAGkAbgB0AGkAbgBnACAAaQBzACAAMwAwADAAIABzAGUAYwBvAG4AZABzACAAcwBvACAAdABoAGEAdAAgAHQAaABlACAARwBQAFAAUwAgAG8AYgBzAGUAcgB2AGEAdABpAG8AbgBzACAAZgBvAHIAIABhACAAYwBvAHYAZQByACAAYwBhAG4AIABiAGUAIABtAGEAZABlACAAaQBuACAAMgAxACAAbQBpAG4AdQB0AGUAcwAuACAAVABoAGUAIABnAG8AYQBsACAAbwBmACAAdABoAGUAIABHAFAAUABTACAAcwB1AHIAdgBlAHkAIABpAHMAIAB0AG8AIABkAGkAcwBjAG8AdgBlAHIAIABwAHUAbABzAGEAcgBzACAAdwBpAHQAaABpAG4AIAB0AGgAZQAgAEcAYQBsAGEAYwB0AGkAYwAgAGwAYQB0AGkAdAB1AGQAZQAgAG8AZgAgACsALwAtADEAMAB7AGQAZQBnAH0AIABmAHIAbwBtACAAdABoAGUAIABHAGEAbABhAGMAdABpAGMAIABwAGwAYQBuAGUALAAgAGEAbgBkACAAdABoAGUAIABoAGkAZwBoAGUAcwB0ACAAcAByAGkAbwByAGkAdAB5ACAAaQBzACAAZwBpAHYAZQBuACAAdABvACAAdABoAGUAIABpAG4AbgBlAHIAIABHAGEAbABhAHgAeQAgAHcAaQB0AGgAaQBuACAAKwAvAC0ANQB7AGQAZQBnAH0ALgAgAFUAcAAgAHQAbwAgAG4AbwB3ACwAIAB0AGgAZQAgAEcAUABQAFMAIABzAHUAcgB2AGUAeQAgAGgAYQBzACAAZABpAHMAYwBvAHYAZQByAGUAZAAgADIAMAAxACAAcAB1AGwAcwBhAHIAcwAsACAAaQBuAGMAbAB1AGQAaQBuAGcAIABjAHUAcgByAGUAbgB0AGwAeQAgAHQAaABlACAAZgBhAGkAbgB0AGUAcwB0ACAAcAB1AGwAcwBhAHIAcwAgAHcAaABpAGMAaAAgAGMAYQBuAG4AbwB0ACAAYgBlACAAZABlAHQAZQBjAHQAZQBkACAAYgB5ACAAbwB0AGgAZQByACAAdABlAGwAZQBzAGMAbwBwAGUAcwAsACAAcAB1AGwAcwBhAHIAcwAgAHcAaQB0AGgAIABlAHgAdAByAGUAbQBlAGwAeQAgAGgAaQBnAGgAIABkAGkAcwBwAGUAcgBzAGkAbwBuACAAbQBlAGEAcwB1AHIAZQBzACAAKABEAE0AcwApACAAdwBoAGkAYwBoACAAYwBoAGEAbABsAGUAbgBnAGUAIAB0AGgAZQAgAGMAdQByAHIAZQBuAHQAbAB5ACAAdwBpAGQAZQBsAHkAIAB1AHMAZQBkACAAbQBvAGQAZQBsAHMAIABmAG8AcgAgAHQAaABlACAARwBhAGwAYQBjAHQAaQBjACAAZQBsAGUAYwB0AHIAbwBuACAAZABlAG4AcwBpAHQAeQAgAGQAaQBzAHQAcgBpAGIAdQB0AGkAbwBuACwAIABwAHUAbABzAGEAcgBzACAAYwBvAGkAbgBjAGkAZABlAG4AdAAgAHcAaQB0AGgAIABzAHUAcABlAHIAbgBvAHYAYQAgAHIAZQBtAG4AYQBuAHQAcwAsACAANAAwACAAbQBpAGwAbABpAHMAZQBjAG8AbgBkACAAcAB1AGwAcwBhAHIAcwAsACAAMQA2ACAAYgBpAG4AYQByAHkAIABwAHUAbABzAGEAcgBzACwAIABzAG8AbQBlACAAbgB1AGwAbABpAG4AZwAgAGEAbgBkACAAbQBvAGQAZQAtAGMAaABhAG4AZwBpAG4AZwAgAHAAdQBsAHMAYQByAHMAIABhAG4AZAAgAHIAbwB0AGEAdABpAG4AZwAgAHIAYQBkAGkAbwAgAHQAcgBhAG4AcwBpAGUAbgB0AHMAIAAoAFIAUgBBAFQAcwApAC4AIABUAGgAZQAgAGYAbwBsAGwAbwB3AC0AdQBwACAAbwBiAHMAZQByAHYAYQB0AGkAbwBuAHMAIABmAG8AcgAgAGMAbwBuAGYAaQByAG0AYQB0AGkAbwBuACAAbwBmACAAbgBlAHcAIABwAHUAbABzAGEAcgBzACAAaABhAHYAZQAgAHAAbwBsAGEAcgBpAHoAYQB0AGkAbwBuAC0AcwBpAGcAbgBhAGwAcwAgAHIAZQBjAG8AcgBkAGUAZAAgAGYAbwByACAAcABvAGwAYQByAGkAegBhAHQAaQBvAG4AIABwAHIAbwBmAGkAbABlAHMAIABvAGYAIAB0AGgAZQAgAHAAdQBsAHMAYQByAHMALgAgAFIAZQAtAGQAZQB0AGUAYwB0AGkAbwBuACAAbwBmACAAcAByAGUAdgBpAG8AdQBzAGwAeQAgAGsAbgBvAHcAbgAgAHAAdQBsAHMAYQByAHMAIABpAG4AIAB0AGgAZQAgAHMAdQByAHYAZQB5ACAAZABhAHQAYQAgAGEAbABzAG8AIABsAGUAYQBkAHMAIAB0AG8AIABzAGkAZwBuAGkAZgBpAGMAYQBuAHQAIABpAG0AcAByAG8AdgBlAG0AZQBuAHQAcwAgAGkAbgAgAHAAYQByAGEAbQBlAHQAZQByAHMAIABmAG8AcgAgADYANAAgAHAAdQBsAHMAYQByAHMALgAgAFQAaABlACAARwBQAFAAUwAgAHMAdQByAHYAZQB5ACAAZABpAHMAYwBvAHYAZQByAGkAZQBzACAAYQByAGUAIABwAHUAYgBsAGkAcwBoAGUAZAAgAGEAbgBkACAAdwBpAGwAbAAgAGIAZQAgAHUAcABkAGEAdABlAGQAIABhAHQAIABoAHQAdABwADoALwAvAHoAbQB0AHQALgBiAGEAbwAuAGEAYwAuAGMAbgAvAEcAUABQAFMALwAuAAAAPGh0dHBzOi8vY2RzYXJjLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY2F0L0ovb3RoZXIvUkFBLzIxLjEwNwAAAXAASABhAG4AIABKAC4ATAAuADsAIABXAGEAbgBnACAAQwAuADsAIABXAGEAbgBnACAAUAAuAEYALgA7ACAAVwBhAG4AZwAgAFQALgA7ACAAWgBoAG8AdQAgAEQALgBKAC4AOwAgAFMAdQBuACAASgAuAC0ASAAuADsAIABZAGEAbgAgAFkALgA7ACAAUwB1ACAAVwAuAC0AUQAuADsAIABKAGkAbgBnACAAVwAuAC0AQwAuADsAIABDAGgAZQBuACAAWAAuADsAIABHAGEAbwAgAFgALgBZAC4AOwAgAEgAbwB1ACAATAAuAC0ARwAuADsAIABYAHUAIABKAC4AOwAgAEwAZQBlACAASwAuAEoALgA7ACAAVwBhAG4AZwAgAE4ALgA7ACAASgBpAGEAbgBnACAAUAAuADsAIABYAHUAIABSAC4ALQBYAC4AOwAgAFkAYQBuACAASgAuADsAIABHAGEAbgAgAEgALgAtAFEALgA7ACAARwB1AGEAbgAgAFgALgA7ACAASAB1AGEAbgBnACAAVwAuAC0ASgAuADsAIABKAGkAYQBuAGcAIABKAC4ALQBDAC4AOwAgAEwAaQAgAEgALgA7ACAATQBlAG4AIABZAC4ALQBQAC4AOwAgAFMAdQBuACAAQwAuADsAIABXAGEAbgBnACAAQgAuAC0ASgAuADsAIABXAGEAbgBnACAASAAuAEcALgA7ACAAVwBhAG4AZwAgAFMALgAtAFEALgA7ACAAWABpAGUAIABKAC4ALQBUAC4AOwAgAFgAdQAgAEgALgA7ACAAWQBhAG8AIABSAC4AOwAgAFkAbwB1ACAAWAAuAC0AUAAuADsAIABZAHUAIABEAC4ASgAuADsAIABZAHUAYQBuACAASgAuAC0AUAAuADsAIABZAHUAZQBuACAAUgAuADsAIABaAGgAYQBuAGcAIABDAC4ALQBGAC4AOwAgAFoAaAB1ACAAWQAuAAAAEzIwMjItMDQtMjBUMDc6MjE6NDkAAAATMjAyMy0wOC0wN1QwODo0OToyNQAAADZodHRwczovL2Nkcy51bmlzdHJhLmZyL3Zpemllci1vcmcvbGljZW5jZXNfdml6aWVyLmh0bWwAAAAHY2F0YWxvZwAAAAdiaWJjb2RlAAAAEwAyADAAMgAxAFIAQQBBAC4ALgAuAC4AMgAxAC4ALgAxADAANwBIf8AAAAAAAAVyYWRpbwAACSVodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL290aGVyL1JBQS8yMS4xMDcvdGFibGUzPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTc/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlMj86OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovb3RoZXIvUkFBLzIxLjEwNzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovb3RoZXIvUkFBLzIxLjEwNzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTU/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlMz86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL290aGVyL1JBQS8yMS4xMDcvdGFibGU3Pzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL290aGVyL1JBQS8yMS4xMDc6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1KL290aGVyL1JBQS8yMS4xMDc6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPUovb3RoZXIvUkFBLzIxLjEwNzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTI/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlMz86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL290aGVyL1JBQS8yMS4xMDcvdGFibGU1Pzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTc/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9Si9vdGhlci9SQUEvMjEuMTA3Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1KL290aGVyL1JBQS8yMS4xMDc6OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL290aGVyL1JBQS8yMS4xMDcvdGFibGUyPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTM/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvSi9vdGhlci9SQUEvMjEuMTA3L3RhYmxlNT86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9KL290aGVyL1JBQS8yMS4xMDcvdGFibGU3Pzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPUovb3RoZXIvUkFBLzIxLjEwNzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL0ovb3RoZXIvUkFBLzIxLjEwNy90YWJsZTU/AAADzWl2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2gAAALpdnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cAAAAdFzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6Ojpw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L3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL1ZJSS8xNTYvdGFibGUxPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPVZJSS8xNTY6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPVZJSS8xNTY6OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9WSUkvMTU2L3RhYmxlMT86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1WSUkvMTU2Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1WSUkvMTU2Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvVklJLzE1Ni90YWJsZTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9VklJLzE1Njo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9VklJLzE1Njo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL1ZJSS8xNTYvdGFibGUxPwAAAb1pdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoAAABpXZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHAAAAD5c3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6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ci92aXotYmluL2NvbmVzZWFyY2gvVklJLzE4OS90YWJsZTE/Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL1ZpemllUi0yPy1zb3VyY2U9VklJLzE4OTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi92b3RhYmxlPy1zb3VyY2U9VklJLzE4OTo6OnB5IFZPIHNlcDo6Omh0dHA6Ly90YXB2aXppZXIuY2RzLnVuaXN0cmEuZnIvVEFQVml6aWVSL3RhcDo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9jb25lc2VhcmNoL1ZJSS8xODkvdGFibGUxPzo6OnB5IFZPIHNlcDo6Omh0dHA6Ly92aXppZXIuY2RzLnVuaXN0cmEuZnIvdml6LWJpbi9WaXppZVItMj8tc291cmNlPVZJSS8xODk6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vdm90YWJsZT8tc291cmNlPVZJSS8xODk6OjpweSBWTyBzZXA6OjpodHRwOi8vdGFwdml6aWVyLmNkcy51bmlzdHJhLmZyL1RBUFZpemllUi90YXA6OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vY29uZXNlYXJjaC9WSUkvMTg5L3RhYmxlMT86OjpweSBWTyBzZXA6OjpodHRwOi8vdml6aWVyLmNkcy51bmlzdHJhLmZyL3Zpei1iaW4vVml6aWVSLTI/LXNvdXJjZT1WSUkvMTg5Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL3ZvdGFibGU/LXNvdXJjZT1WSUkvMTg5Ojo6cHkgVk8gc2VwOjo6aHR0cDovL3RhcHZpemllci5jZHMudW5pc3RyYS5mci9UQVBWaXppZVIvdGFwOjo6cHkgVk8gc2VwOjo6aHR0cDovL3Zpemllci5jZHMudW5pc3RyYS5mci92aXotYmluL2NvbmVzZWFyY2gvVklJLzE4OS90YWJsZTE/AAABvTo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2gAAAGldnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cAAAAPk6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQAAAAAAAAAHWl2bzovL25hc2EuaGVhc2FyYy9hdG5mcHVsc2FyAAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAABEFUTkYAAAATAEEAVABOAEYAIABQAHUAbABzAGEAcgAgAEMAYQB0AGEAbABvAGcAAAAIcmVzZWFyY2gAAAnTAFQAaABlACAAQQB1AHMAdAByAGEAbABpAGEAIABUAGUAbABlAHMAYwBvAHAAZQAgAE4AYQB0AGkAbwBuAGEAbAAgAEYAYQBjAGkAbABpAHQAeQAgACgAQQBUAE4ARgApACAAUAB1AGwAcwBhAHIAIABDAGEAdABhAGwAbwBnACAAaQBzACAAYQAgAGMAYQB0AGEAbABvAGcAIABvAGYAIABrAG4AbwB3AG4AIABwAHUAbABzAGEAcgBzACAAYwBvAG0AcABpAGwAZQBkACAAYgB5ACAAUgAuAE4ALgAgAE0AYQBuAGMAaABlAHMAdABlAHIAIABlAHQAIABhAGwALgAgAGEAbgBkACAAaQBzACAAZABlAHMAYwBlAG4AZABlAGQAIABmAHIAbwBtACAAcAB1AGwAcwBhAHIAIABkAGEAdABhAGIAYQBzAGUAIAB1AHMAZQBkACAAZgBvAHIAIAB0AGgAZQAgAHAAYQBwAGUAcgAgACYAcQB1AG8AdAA7AEMAYQB0AGEAbABvAGcAIABvAGYAIAA1ADUAOAAgAFAAdQBsAHMAYQByAHMAJgBxAHUAbwB0ADsAIABiAHkAIABKAC4ASAAuACAAVABhAHkAbABvAHIALAAgAFIALgBOAC4AIABNAGEAbgBjAGgAZQBzAHQAZQByACAAYQBuAGQAIABBAC4ARwAuACAATAB5AG4AZQAgADEAOQA5ADMALAAgAEEAcABKAFMALAAgADgAOAAsACAANQAyADkALQA1ADYAOAAuACAAVABoAGUAIABjAHUAcgByAGUAbgB0ACAAYwBhAHQAYQBsAG8AZwAgAGgAYQBzACAAYgBlAGUAbgAgAHMAdQBwAHAAbABlAG0AZQBuAHQAZQBkACAAYgB5ACAAaQBuAGMAbAB1AHMAaQBvAG4AIABvAGYAIABwAHUAYgBsAGkAcwBoAGUAZAAgAGQAYQB0AGEAIABmAHIAbwBtACAAbQBvAHIAZQAgAHIAZQBjAGUAbgB0ACAAcgBhAGQAaQBvACAAcwB1AHIAdgBlAHkAcwAsACAAaQBuACAAcABhAHIAdABpAGMAdQBsAGEAcgAsACAAdABoAGUAIABQAGEAcgBrAGUAcwAgAE0AdQBsAHQAaQBiAGUAYQBtACAAKABQAE0AKQAgAFAAdQBsAHMAYQByACAAUwB1AHIAdgBlAHkAIAAoAE0AYQBuAGMAaABlAHMAdABlAHIAIABlAHQAIABhAGwALgAgADIAMAAwADEALAAgAE0ATgBSAEEAUwAsACAAMwAyADgALAAgADEANwAtADMANQApACAAWwBhAHYAYQBpAGwAYQBiAGwAZQAgAGEAdAAgAHQAaABlACAASABFAEEAUwBBAFIAQwAgAGEAcwAgAHQAaABlACAAUABNAFAAVQBMAFMAQQBSACAAdABhAGIAbABlAF0AIABhAG4AZAAgAHQAaABlACAAUwB3AGkAbgBiAHUAcgBuAGUAIABJAG4AdABlAHIAbQBlAGQAaQBhAHQAZQAgAEwAYQB0AGkAdAB1AGQAZQAgAFAAdQBsAHMAYQByACAAUwB1AHIAdgBlAHkAIAAoAEUAZAB3AGEAcgBkAHMAIABlAHQAIABhAGwALgAgADIAMAAwADEALAAgAE0ATgBSAEEAUwAsACAAMwAyADYALAAgADMANQA4AC0AMwA3ADQAKQAsACAAYgBvAHQAaAAgAG0AYQBkAGUAIAB1AHMAaQBuAGcAIAB0AGgAZQAgAEEAVABOAEYAIABQAGEAcgBrAGUAcwAgADYANAAtAG0AIAByAGEAZABpAG8AIAB0AGUAbABlAHMAYwBvAHAAZQAuACAAQgBpAG4AYQByAHkAIABwAGEAcgBhAG0AZQB0AGUAcgBzACAAZgBvAHIAIABrAG4AbwB3AG4AIABiAGkAbgBhAHIAeQAgAHAAdQBsAHMAYQByAHMAIABhAHIAZQAgAGEAbABzAG8AIABpAG4AYwBsAHUAZABlAGQAIABhAHMAIAB3AGUAbABsACAAYQBzACAAYQBsAGwAIABhAHYAYQBpAGwAYQBiAGwAZQAgAGEAcwB0AHIAbwBtAGUAdAByAGkAYwAgAGEAbgBkACAAcwBwAGkAbgAgAHAAYQByAGEAbQBlAHQAZQByACAAaQBuAGYAbwByAG0AYQB0AGkAbwBuACAAZgBvAHIAIABhAGwAbAAgAHAAdQBsAHMAYQByAHMALgAgAFQAaABlACAAYwBhAHQAYQBsAG8AZwAgAGkAbgBjAGwAdQBkAGUAcwAgAGEAbABsACAAcAB1AGIAbABpAHMAaABlAGQAIAByAG8AdABhAHQAaQBvAG4ALQBwAG8AdwBlAHIAZQBkACAAcAB1AGwAcwBhAHIAcwAuACAAVAB3AG8AIABzAGUAcABhAHIAYQB0AGUAIABzAG0AYQBsAGwAIABzAHUAYgBzAGUAdABzACAAbwBmACAAcAB1AGwAcwBhAHIAcwAgAGQAZQB0AGUAYwB0AGUAZAAgAG8AbgBsAHkAIABhAHQAIABoAGkAZwBoACAAZQBuAGUAcgBnAGkAZQBzACAAYQByAGUAIABhAGwAcwBvACAAaQBuAGMAbAB1AGQAZQBkACAAaQBuACAAdABoAGUAIABjAHUAcgByAGUAbgB0ACAAdABhAGIAbABlADoAIAB0AGgAZQAgAGYAaQByAHMAdAAgAGcAcgBvAHUAcAAgAGMAbwBtAHAAcgBpAHMAZQBzACAAWAAtAHIAYQB5ACAAYQBuAGQAIABnAGEAbQBtAGEALQByAGEAeQAgAHAAdQBsAHMAYQByAHMAIAB3AGgAaQBjAGgAIABhAHIAZQAgAGEAcABwAGEAcgBlAG4AdABsAHkAIABwAG8AdwBlAHIAZQBkACAAYgB5ACAAcwBwAGkAbgAtAGQAbwB3AG4AIABlAG4AZQByAGcAeQAsACAAYgB1AHQAIAB3AGgAaQBjAGgAIABoAGEAdgBlACAAbgBvAHQAIABiAGUAZQBuACAAZABlAHQAZQBjAHQAZQBkACAAYQB0ACAAcgBhAGQAaQBvACAAdwBhAHYAZQBsAGUAbgBnAHQAaABzACwAIAB3AGgAaQBsAGUAIAB0AGgAZQAgAHMAZQBjAG8AbgBkACAAZwByAG8AdQBwACAAYwBvAG4AdABhAGkAbgBzACAAYQBuAG8AbQBhAGwAbwB1AHMAIABYAC0AcgBhAHkAIABwAHUAbABzAGEAcgBzACAAKABBAFgAUABzACkAIABhAG4AZAAgAHMAbwBmAHQALQBnAGEAbQBtAGEALQByAGEAeQAgAHIAZQBwAGUAYQB0AGUAcgBzACAAKABTAEcAUgBzACkAIABmAG8AcgAgAHcAaABpAGMAaAAgAGMAbwBoAGUAcgBlAG4AdAAgAHAAdQBsAHMAYQB0AGkAbwBuAHMAIABoAGEAdgBlACAAYgBlAGUAbgAgAGQAZQB0AGUAYwB0AGUAZAAuACAAQQBjAGMAcgBlAHQAaQBvAG4ALQBwAG8AdwBlAHIAZQBkACAAcAB1AGwAcwBhAHIAcwAgAHMAdQBjAGgAIABhAHMAIABIAGUAcgAgAFgALQAxACAAYQBuAGQAIAB0AGgAZQAgAHIAZQBjAGUAbgB0AGwAeQAgAGQAaQBzAGMAbwB2AGUAcgBlAGQAIABYAC0AcgBhAHkAIABtAGkAbABsAGkAcwBlAGMAbwBuAGQAIABwAHUAbABzAGEAcgBzACAAcwB1AGMAaAAgAGEAcwAgAFMAQQBYACAASgAxADgAMAA4AC4ANAAtADMANgA1ADgAIABhAHIAZQAgAG4AbwB0ACAAaQBuAGMAbAB1AGQAZQBkACAAaQBuACAAdABoAGkAcwAgAHQAYQBiAGwAZQAsACAAaABvAHcAZQB2AGUAcgAuACAATQBhAG4AeQAgAHAAZQBvAHAAbABlACAAaABhAHYAZQAgAGMAbwBuAHQAcgBpAGIAdQB0AGUAZAAgAHQAbwAgAHQAaABlACAAYwBvAG0AcABpAGwAYQB0AGkAbwBuACAAbwBmACAAdABoAGUAIABkAGEAdABhACAAYwBvAG4AdABhAGkAbgBlAGQAIABpAG4AIAB0AGgAaQBzACAAYwBhAHQAYQBsAG8AZwAgAGEAbgBkACAAdABoAGUAIABkAGEAdABhAGIAYQBzAGUAIAB0AGgAYQB0ACAAaQB0ACAAdwBhAHMAIABkAGUAcgBpAHYAZQBkACAAZgByAG8AbQAuACAAVABoAGUAIABhAHUAdABoAG8AcgBzACAAcABhAHIAdABpAGMAdQBsAGEAcgBsAHkAIAB0AGgAYQBuAGsAIABBAG4AZAByAGUAdwAgAEwAeQBuAGUAIABvAGYAIAB0AGgAZQAgAFUAbgBpAHYAZQByAHMAaQB0AHkAIABvAGYAIABNAGEAbgBjAGgAZQBzAHQAZQByACwAIABKAG8AZAByAGUAbABsACAAQgBhAG4AawAgAE8AYgBzAGUAcgB2AGEAdABvAHIAeQAsACAARABhAHYAaQBkACAATgBpAGMAZQAgAG8AZgAgAFAAcgBpAG4AYwBlAHQAbwBuACAAVQBuAGkAdgBlAHIAcwBpAHQAeQAsACAAYQBuAGQAIABSAHUAcwBzAGUAbABsACAARQBkAHcAYQByAGQAcwAsACAAdABoAGUAbgAgAGEAdAAgAFMAdwBpAG4AYgB1AHIAbgBlACAAVQBuAGkAdgBlAHIAcwBpAHQAeQAgAG8AZgAgAFQAZQBjAGgAbgBvAGwAbwBnAHkALgAgAFQAaABlACAAYQBsAHMAbwAgAGEAYwBrAG4AbwB3AGwAZQBkAGcAZQAgAHQAaABlACAAZQBmAGYAbwByAHQAcwAgAG8AZgAgAFcAYQByAHcAaQBjAGsAIABVAG4AaQB2AGUAcgBzAGkAdAB5ACAAcwB0AHUAZABlAG4AdABzACAAQQBkAGEAbQAgAEcAbwBvAGQAZQAgAGEAbgBkACAAUwB0AGUAdgBlAG4AIABUAGgAbwBtAGEAcwAgAHcAaABvACAAYwBvAG0AcABpAGwAZQBkACAAYQBuAGQAIABjAGgAZQBjAGsAZQBkACAAYQAgAHIAZQBjAGUAbgB0ACAAdgBlAHIAcwBpAG8AbgAgAG8AZgAgAHQAaABlACAAZABhAHQAYQBiAGEAcwBlAC4AIABUAGgAZQAgAG8AcgBpAGcAaQBuAGEAbAAgACgAcwB1AG0AbQBlAHIAIAAyADAAMAAzACkAIABkAGEAdABhAGIAYQBzAGUAIABhAHQAIAB0AGgAZQAgAEEAVABOAEYAIAB3AGUAYgBzAGkAdABlACAAdwBhAHMAIABjAG8AbQBwAGkAbABlAGQAIAB3AGkAdABoACAAdABoAGUAIABpAG4AdgBhAGwAdQBhAGIAbABlACAAYQBzAHMAaQBzAHQAYQBuAGMAZQAgAG8AZgAgAE0AYQByAHkAYQBtACAASABvAGIAYgBzACwAIAB3AGgAaQBsAGUAIAB0AGgAZQAgAEEAVABOAEYAIAB3AGUAYgAgAGkAbgB0AGUAcgBmAGEAYwBlACAAdwBhAHMAIABkAGUAcwBpAGcAbgBlAGQAIABhAG4AZAAgAGMAbwBuAHMAdAByAHUAYwB0AGUAZAAgAGIAeQAgAEEAbABiAGUAcgB0ACAAVABlAG8AaAAsACAAYQAgAFMAdQBtAG0AZQByACAAVgBhAGMAYQB0AGkAbwBuACAAUwBjAGgAbwBsAGEAcgAgAGEAdAAgAHQAaABlACAAQQBUAE4ARgAgAGkAbgAgADIAMAAwADIALwAyADAAMAAzAC4AIABUAGgAZQAgAGEAdQB0AGgAbwByAHMAIAB3AG8AdQBsAGQAIABhAHAAcAByAGUAYwBpAGEAdABlACAAaQBmACAAYQBuAHkAbwBuAGUAIABtAGEAawBpAG4AZwAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAGMAYQB0AGEAbABvAGcAIABpAG4AIABhACAAcAB1AGIAbABpAGMAYQB0AGkAbwBuACAAYQBjAGsAbgBvAHcAbABlAGQAZwBlAHMAIAB0AGgAZQAgAHMAbwB1AHIAYwBlACAAbwBmACAAdABoAGUAaQByACAAaQBuAGYAbwByAG0AYQB0AGkAbwBuACAAYgB5ACAAcQB1AG8AdABpAG4AZwAgAHQAaABlACAAQQBUAE4ARgAgAFAAdQBsAHMAYQByACAAQwBhAHQAYQBsAG8AZwAgAHcAZQBiAHMAaQB0AGUAIABhAGQAZAByAGUAcwBzACAAbwBmACAAJgBsAHQAOwBhACAAaAByAGUAZgA9ACIAaAB0AHQAcAA6AC8ALwB3AHcAdwAuAGEAdABuAGYALgBjAHMAaQByAG8ALgBhAHUALwByAGUAcwBlAGEAcgBjAGgALwBwAHUAbABzAGEAcgAvAHAAcwByAGMAYQB0AC8AIgAmAGcAdAA7AGgAdAB0AHAAOgAvAC8AdwB3AHcALgBhAHQAbgBmAC4AYwBzAGkAcgBvAC4AYQB1AC8AcgBlAHMAZQBhAHIAYwBoAC8AcAB1AGwAcwBhAHIALwBwAHMAcgBjAGEAdAAvACYAbAB0ADsALwBhACYAZwB0ADsAAAA6aHR0cHM6Ly9oZWFzYXJjLmdzZmMubmFzYS5nb3YvVzNCcm93c2UvYWxsL2F0bmZwdWxzYXIuaHRtbAAAABIATQBhAG4AYwBoAGUAcwB0AGUAcgAsACAAVABhAHkAbABvAHIAAAATMjAyMy0wOS0yOVQwMDowMDowMAAAABMyMDIzLTA5LTI5VDAwOjAwOjAwAAAAAAAAAAdjYXRhbG9nAAAAAAAAABMAMgAwADAANQBBAEoALgAuAC4ALgAxADIAOQAuADEAOQA5ADMATX/AAAAAAAAFcmFkaW8AAAF9aHR0cHM6Ly9oZWFzYXJjLmdzZmMubmFzYS5nb3YvY2dpLWJpbi9XM0Jyb3dzZS93M3F1ZXJ5LnBsP3RhYmxlaGVhZD1uYW1lPWhlYXNhcmNfYXRuZnB1bHNhciZBY3Rpb249TW9yZStPcHRpb25zJkFjdGlvbj1QYXJhbWV0ZXIrU2VhcmNoJkNvbmVBZGQ9MTo6OnB5IFZPIHNlcDo6Omh0dHBzOi8vaGVhc2FyYy5nc2ZjLm5hc2EuZ292L2NnaS1iaW4vVzNCcm93c2UvZ2V0dm90YWJsZS5wbD9uYW1lPWF0bmZwdWxzYXI6OjpweSBWTyBzZXA6OjpodHRwczovL2hlYXNhcmMuZ3NmYy5uYXNhLmdvdi94YW1pbi92by90YXA6OjpweSBWTyBzZXA6OjpodHRwczovL2hlYXNhcmMuZ3NmYy5uYXNhLmdvdi94YW1pbi92by9jb25lP3Nob3dvZmZzZXRzJnRhYmxlPWF0bmZwdWxzYXImAAAAZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4Ojo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2gAAABednI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cAAAADM6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQAAAAAAAAAHWl2bzovL25hc2EuaGVhc2FyYy9mZXJtaWwycHNyAAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAACkZFUk1JTDJQU1IAAAAzAEYAZQByAG0AaQAgAEwAQQBUACAAUwBlAGMAbwBuAGQAIABDAGEAdABhAGwAbwBnACAAbwBmACAARwBhAG0AbQBhAC0AUgBhAHkAIABQAHUAbABzAGEAcgBzACAAKAAyAFAAQwApAAAACHJlc2VhcmNoAAAC0QBUAGgAaQBzACAAYwBhAHQAYQBsAG8AZwAgAHMAdQBtAG0AYQByAGkAegBlAHMAIAAwAC4AMQAgAEcAZQBWACAAZwBhAG0AbQBhAC0AcgBhAHkAIABwAHUAbABzAGEAcgAgAGQAZQB0AGUAYwB0AGkAbwBuAHMAIAB1AHMAaQBuAGcAIABkAGEAdABhACAAYQBjAHEAdQBpAHIAZQBkACAAYgB5ACAAdABoAGUAIABMAGEAcgBnAGUAIABBAHIAZQBhACAAVABlAGwAZQBzAGMAbwBwAGUAIAAoAEwAQQBUACkAIABvAG4AIAB0AGgAZQAgAEYAZQByAG0AaQAgAHMAYQB0AGUAbABsAGkAdABlAC4AIABUAGgAZQBzAGUAIABwAHUAbABzAGEAcgBzACAAYQByAGUAIABuAGUAdQB0AHIAbwBuACAAcwB0AGEAcgBzACAAaQBkAGUAbgB0AGkAZgBpAGUAZAAgAHUAcwBpAG4AZwAgAEwAQQBUACAAZABhAHQAYQAgAHQAaAByAG8AdQBnAGgAIABwAGUAcgBpAG8AZABpAGMAaQB0AHkAIABzAGUAYQByAGMAaABlAHMAIABpAG4AIABnAGEAbQBtAGEALQByAGEAeQAgAGEAbgBkACAAcgBhAGQAaQBvACAAZABhAHQAYQAgAGEAcgBvAHUAbgBkACAATABBAFQAIAB1AG4AYQBzAHMAbwBjAGkAYQB0AGUAZAAgAHMAbwB1AHIAYwBlACAAcABvAHMAaQB0AGkAbwBuAHMALgAgAFQAaABlACAAYwBhAHQAYQBsAG8AZwB1AGUAZAAgAHAAdQBsAHMAYQByAHMAIABhAHIAZQAgAGQAaQB2AGkAZABlAGQAIABpAG4AdABvACAAdABoAHIAZQBlACAAZwByAG8AdQBwAHMAOgAgAG0AaQBsAGwAaQBzAGUAYwBvAG4AZAAgAHAAdQBsAHMAYQByAHMALAAgAHkAbwB1AG4AZwAgAHIAYQBkAGkAbwAtAGwAbwB1AGQAIABwAHUAbABzAGEAcgBzACwAIABhAG4AZAAgAHkAbwB1AG4AZwAgAHIAYQBkAGkAbwAtAHEAdQBpAGUAdAAgAHAAdQBsAHMAYQByAHMALgAgAFQAaABpAHMAIABjAGEAdABhAGwAbwBnACAAcwB1AG0AbQBhAHIAaQB6AGUAcwAgAHIAZQBzAHUAbAB0AHMAIABvAGYAIAB0AGgAZQAgAGEAbgBhAGwAeQBzAGkAcwAgAG8AZgAgAHAAdQBsAHMAZQAgAHAAcgBvAGYAaQBsAGUAcwAgAGEAbgBkACAAZQBuAGUAcgBnAHkAIABzAHAAZQBjAHQAcgBhACwAIABhAG4AZAAgAGEAbgBhAGwAeQBzAGkAcwAgAG8AZgAgAHQAaABlACAAbwBmAGYALQBwAGUAYQBrACAAcABoAGEAcwBlACAAaQBuAHQAZQByAHYAYQBsAHMALAAgAGEAbgBkACAAYwBvAG0AcABhAHIAZQBzACAAdABoAGUAIABnAGEAbQBtAGEALQByAGEAeQAgAHAAcgBvAHAAZQByAHQAaQBlAHMAIAB3AGkAdABoACAAdABoAG8AcwBlACAAaQBuACAAdABoAGUAIAByAGEAZABpAG8ALAAgAG8AcAB0AGkAYwBhAGwALAAgAGEAbgBkACAAWAAtAHIAYQB5ACAAYgBhAG4AZABzAC4AIABGAGwAdQB4ACAAbABpAG0AaQB0AHMAIABmAG8AcgAgAHAAdQBsAHMAYQByAHMAIAB3AGkAdABoACAAbgBvACAAbwBiAHMAZQByAHYAZQBkACAAZwBhAG0AbQBhAC0AcgBhAHkAIABlAG0AaQBzAHMAaQBvAG4AIABhAHIAZQAgAHAAcgBvAHYAaQBkAGUAZAAuAAAAOmh0dHBzOi8vaGVhc2FyYy5nc2ZjLm5hc2EuZ292L1czQnJvd3NlL2FsbC9mZXJtaWwycHNyLmh0bWwAAAALAEEAYgBkAG8AIABlAHQAIABhAGwALgAAABMyMDIzLTA5LTI5VDAwOjAwOjAwAAAAEzIwMjMtMDktMjlUMDA6MDA6MDAAAAAAAAAAB2NhdGFsb2cAAAAAAAAAEwAyADAAMQAzAEEAcABKAFMALgAuADIAMAA4AC4ALgAuADEANwBBf8AAAAAAAAlnYW1tYS1yYXkAAAF9aHR0cHM6Ly9oZWFzYXJjLmdzZmMubmFzYS5nb3YvY2dpLWJpbi9XM0Jyb3dzZS93M3F1ZXJ5LnBsP3RhYmxlaGVhZD1uYW1lPWhlYXNhcmNfZmVybWlsMnBzciZBY3Rpb249TW9yZStPcHRpb25zJkFjdGlvbj1QYXJhbWV0ZXIrU2VhcmNoJkNvbmVBZGQ9MTo6OnB5IFZPIHNlcDo6Omh0dHBzOi8vaGVhc2FyYy5nc2ZjLm5hc2EuZ292L3hhbWluL3ZvL2NvbmU/c2hvd29mZnNldHMmdGFibGU9ZmVybWlsMnBzciY6OjpweSBWTyBzZXA6OjpodHRwczovL2hlYXNhcmMuZ3NmYy5uYXNhLmdvdi94YW1pbi92by90YXA6OjpweSBWTyBzZXA6OjpodHRwczovL2hlYXNhcmMuZ3NmYy5uYXNhLmdvdi9jZ2ktYmluL1czQnJvd3NlL2dldHZvdGFibGUucGw/bmFtZT1mZXJtaWwycHNyAAAAZDo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC9jb25lc2VhcmNoOjo6cHkgVk8gc2VwOjo6aXZvOi8vaXZvYS5uZXQvc3RkL3RhcCNhdXg6OjpweSBWTyBzZXA6OjoAAABednI6d2ViYnJvd3Nlcjo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZzOnBhcmFtaHR0cAAAADM6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjpzdGQ6OjpweSBWTyBzZXA6OjoAAAAAAAAAG2l2bzovL25hc2EuaGVhc2FyYy9wbXB1bHNhcgAAABF2czpjYXRhbG9nc2VydmljZQAAAAZQYXJrZXMAAAAqAFAAYQByAGsAZQBzACAATQB1AGwAdABpAGIAZQBhAG0AIABTAHUAcgB2AGUAeQAgAE4AZQB3ACAAUAB1AGwAcwBhAHIAIABDAGEAdABhAGwAbwBnAAAACHJlc2VhcmNoAAAFRgBUAGgAZQAgAFAAYQByAGsAZQBzACAATQB1AGwAdABpAGIAZQBhAG0AIAAoAFAATQApACAAUAB1AGwAcwBhAHIAIABTAHUAcgB2AGUAeQAgAHUAcwBlAHMAIAB0AGgAZQAgADIAMAAgAGMAbQAgAG0AdQBsAHQAaQBiAGUAYQBtACAAcgBlAGMAZQBpAHYAZQByACAAcwB5AHMAdABlAG0AIABhAG4AZAAgAG0AdQBsAHQAaQBiAGUAYQBtACAAZgBpAGwAdABlAHIAIABiAGEAbgBrAHMALAAgAGQAaQBnAGkAdABpAHoAZQByACAAYQBuAGQAIABkAGEAdABhAC0AYQBjAHEAdQBpAHMAaQB0AGkAbwBuACAAcwB5AHMAdABlAG0AIAB0AG8AIABzAHUAcgB2AGUAeQAgAGEAIAByAGUAZwBpAG8AbgAgAHcAaQB0AGgAaQBuACAAfABiAHwAIAAmAGwAdAA7ACAANQAgAGQAZQBnAHIAZQBlAHMAIABpAG4AIAB0AGgAZQAgAGkAbgBuAGUAcgAgAEcAYQBsAGEAYwB0AGkAYwAgAHAAbABhAG4AZQAgAGYAbwByACAAcAB1AGwAcwBhAHIAcwAsACAAbQBhAG4AeQAgAG8AZgAgAHcAaABpAGMAaAAgAHcAaQBsAGwAIABiAGUAIAB5AG8AdQBuAGcAIABhAG4AZAAvAG8AcgAgAHMAaABvAHIAdAAtAHAAZQByAGkAbwBkAC4AIABUAGgAaQBzACAAcwB1AHIAdgBlAHkAIABpAHMAIABhACAAZgBhAGMAdABvAHIAIABvAGYAIAA3ACAAdABpAG0AZQBzACAAbQBvAHIAZQAgAHMAZQBuAHMAaQB0AGkAdgBlACAAdABvACAAeQBvAHUAbgBnACAAYQBuAGQAIABkAGkAcwB0AGEAbgB0ACAAcAB1AGwAcwBhAHIAcwAgAHQAaABhAG4AIAB0AGgAYQB0ACAAbwBmACAASgBvAGgAbgBzAHQAbwBuACAAZQB0ACAAYQBsAC4AIAAoADEAOQA5ADIALAAgAE0ATgBSAEEAUwAsACAAMgA1ADUALAAgADQAMAAxACkAIAB3AGgAaQBjAGgAIABkAGUAdABlAGMAdABlAGQAIAAxADAAMAAgAHAAdQBsAHMAYQByAHMALgAgAFQAaABlACAAcAByAGUAcwBlAG4AdAAgAHMAbwB1AHIAYwBlACAAbABpAHMAdAAgAGMAbwBuAHQAYQBpAG4AcwAgAHAAdQBsAHMAYQByAHMAIAB0AGgAYQB0ACAAaABhAHYAZQAgAGIAZQBlAG4AIABuAGUAdwBsAHkAIABkAGkAcwBjAG8AdgBlAHIAZQBkACAAaQBuACAAdABoAGUAIABjAG8AdQByAHMAZQAgAG8AZgAgAHQAaABpAHMAIABzAHUAcgB2AGUAeQAuACAAVABoAGUAIABQAE0AIABTAHUAcgB2AGUAeQAgAGkAcwAgAHMAcABlAGMAaQBmAGkAYwBhAGwAbAB5ACAAdABhAHIAZwBlAHQAZQBkACAAZgBvAHIAIAAoAGkAKQAgAG8AYgBzAGMAdQByAGUAZAAgAHIAZQBnAGkAbwBuAHMAIABvAGYAIAB0AGgAZQAgAEcAYQBsAGEAYwB0AGkAYwAgAHAAbABhAG4AZQAsACAAKABpAGkAKQAgAHkAbwB1AG4AZwAgAHAAdQBsAHMAYQByAHMALABhAG4AZAAgACgAaQBpAGkAKQAgAGIAaQBuAGEAcgB5ACAAcAB1AGwAcwBhAHIAcwAgAHcAaQB0AGgAIABtAGEAcwBzAGkAdgBlACAAYwBvAG0AcABhAG4AaQBvAG4AcwAuACAAQQBzACAAbwBmACAAQQB1AGcAdQBzAHQAIAAxADkAOQA5ACwAIABhAG4AYQBsAHkAcwBpAHMAIABvAGYAIABhAGIAbwB1AHQAIAA1ADAAJQAgAG8AZgAgAHQAaABlACAAdABvAHQAYQBsACAAZQB4AHAAZQBjAHQAZQBkACAAZABhAHQAYQAgAHQAbwAgAGIAZQAgAGMAbwBsAGwAZQBjAHQAZQBkACAAaABhAHMAIAByAGUAcwB1AGwAdABlAGQAIABpAG4AIAB0AGgAZQAgAGMAbwBuAGYAaQByAG0AZQBkACAAZABlAHQAZQBjAHQAaQBvAG4AIABvAGYAIABvAHYAZQByACAANAAwADAAIABuAGUAdwAgAHAAdQBsAHMAYQByAHMAIAAoAGEAbgAgAGkAbgBjAHIAZQBhAHMAZQAgAG8AZgAgAG0AbwByAGUAIAB0AGgAYQBuACAANQAwACUAIABvAGYAIAB0AGgAZQAgAGsAbgBvAHcAbgAgAHAAbwBwAHUAbABhAHQAaQBvAG4AKQAuACAASABlAHIAZQAgAGEAcgBlACAAcwBvAG0AZQAgAG8AZgAgAHQAaABlACAAZgBlAGEAdAB1AHIAZQBzACAAbwBmACAAdABoAGUAIABQAE0AIABTAHUAcgB2AGUAeQA6ACAAJgBsAHQAOwBwAHIAZQAmAGcAdAA7ACAAUwB1AHIAdgBlAHkAIABBAHIAZQBhADoAIAAtADIANgAwACAAJgBsAHQAOwAgAGwAIAAmAGwAdAA7ACAANQAwACAAZABlAGcAIAAsACAALQA1ACAAJgBsAHQAOwAgAGIAIAAmAGwAdAA7ACAANQAgAGQAZQBnACAAQwBlAG4AdABlAHIAIABGAHIAZQBxAHUAZQBuAGMAeQA6ACAAMQAzADcANAAgAE0ASAB6ACAAQgBhAG4AZAB3AGkAZAB0AGgAOgAgADIAOAA4ACAATQBIAHoAIAAoADkANgAgAGMAaABhAG4AbgBlAGwAcwAgAHgAIAAzACAATQBIAHoAIABwAGUAcgAgAGMAaABhAG4AbgBlAGwAIAB4ACAAMgAgAHAAbwBsAGEAcgBpAHoAYQB0AGkAbwBuAHMAKQAgAFMAYQBtAHAAbABpAG4AZwAgAFIAYQB0AGUAOgAgADAALgAyADUAIABtAHMAIAB4ACAAMQAgAGIAaQB0ACAAcABlAHIAIABjAGgAYQBuAG4AZQBsACAASQBuAHQAZQBnAHIAYQB0AGkAbwBuACAAVABpAG0AZQA6ACAAMwA1ACAAbQBpAG4AIABwAGUAcgAgAHAAbwBpAG4AdABpAG4AZwAgACgAMQAzACAAYgBlAGEAbQBzACAAcABlAHIAIABwAG8AaQBuAHQAaQBuAGcAKQAgAEQAYQB0AGEAIABTAHQAbwByAGEAZwBlADoAIABEAEwAVAAgAHQAYQBwAGUAIAAoAGEAYgBvAHUAdAAgADMANQAgAEcAQgAgAHAAZQByACAAdABhAHAAZQApACAAUwBlAG4AcwBpAHQAaQB2AGkAdAB5ADoAIABhAGIAbwB1AHQAIAA3ACAAdABpAG0AZQBzACAAYgBlAHQAdABlAHIAIAB0AGgAYQBuACAAcAByAGUAdgBpAG8AdQBzACAANAAwADAAIABNAEgAegAgAHMAdQByAHYAZQB5AHMAIAAmAGwAdAA7AC8AcAByAGUAJgBnAHQAOwAAADhodHRwczovL2hlYXNhcmMuZ3NmYy5uYXNhLmdvdi9XM0Jyb3dzZS9hbGwvcG1wdWxzYXIuaHRtbAAAAC8ATQBhAG4AYwBoAGUAcwB0AGUAcgAgAGUAdAAgAGEAbAAuADsAIABNAG8AcgByAGkAcwAgAGUAdAAgAGEAbAAuADsAIABLAHIAYQBtAGUAcgAgAGUAdAAgAGEAbAAuAAAAEzIwMjMtMDktMjlUMDA6MDA6MDAAAAATMjAyMy0wOS0yOVQwMDowMDowMAAAAAAAAAAHY2F0YWxvZwAAAAAAAAATADIAMAAwADMATQBOAFIAQQBTAC4AMwA0ADIALgAxADIAOQA5AEt/wAAAAAAABXJhZGlvAAABd2h0dHBzOi8vaGVhc2FyYy5nc2ZjLm5hc2EuZ292L3hhbWluL3ZvL2NvbmU/c2hvd29mZnNldHMmdGFibGU9cG1wdWxzYXImOjo6cHkgVk8gc2VwOjo6aHR0cHM6Ly9oZWFzYXJjLmdzZmMubmFzYS5nb3YvY2dpLWJpbi9XM0Jyb3dzZS93M3F1ZXJ5LnBsP3RhYmxlaGVhZD1uYW1lPWhlYXNhcmNfcG1wdWxzYXImQWN0aW9uPU1vcmUrT3B0aW9ucyZBY3Rpb249UGFyYW1ldGVyK1NlYXJjaCZDb25lQWRkPTE6OjpweSBWTyBzZXA6OjpodHRwczovL2hlYXNhcmMuZ3NmYy5uYXNhLmdvdi9jZ2ktYmluL1czQnJvd3NlL2dldHZvdGFibGUucGw/bmFtZT1wbXB1bHNhcjo6OnB5IFZPIHNlcDo6Omh0dHBzOi8vaGVhc2FyYy5nc2ZjLm5hc2EuZ292L3hhbWluL3ZvL3RhcAAAAGRpdm86Ly9pdm9hLm5ldC9zdGQvY29uZXNlYXJjaDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Oml2bzovL2l2b2EubmV0L3N0ZC90YXAjYXV4AAAAXnZzOnBhcmFtaHR0cDo6OnB5IFZPIHNlcDo6OnZyOndlYmJyb3dzZXI6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2czpwYXJhbWh0dHAAAAAzc3RkOjo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6Ojo6cHkgVk8gc2VwOjo6c3RkAAAAAAAAABlpdm86Ly9uYXNhLmhlYXNhcmMvcHVsc2FyAAAAEXZzOmNhdGFsb2dzZXJ2aWNlAAAABlRheWxvcgAAAA4AUAB1AGwAcwBhAHIAIABDAGEAdABhAGwAbwBnAAAACHJlc2VhcmNoAAABMwBUAGgAaQBzACAAZABhAHQAYQBiAGEAcwBlACAAaQBzACAAYgBhAHMAZQBkACAAbwBuACAAdABoAGUAIAAxADkAOQA1ACAATQBhAHkAIAAzACAAZQBsAGUAYwB0AHIAbwBuAGkAYwAgAHYAZQByAHMAaQBvAG4AIABvAGYAIAB0AGgAZQAgAFQAYQB5AGwAbwByACAAZQB0ACAAYQBsAC4AIABQAHUAbABzAGEAcgAgAEMAYQB0AGEAbABvAGcAIABhAG4AZAAgAGMAbwBuAHQAYQBpAG4AcwAgAGQAYQB0AGEAIABvAG4AIAA3ADAANgAgAHAAdQBsAHMAYQByAHMALAAgAGkALgBlAC4ALAAgAGkAdAAgAGMAbwBuAHQAYQBpAG4AcwAgADIANQAlACAAbQBvAHIAZQAgAGUAbgB0AHIAaQBlAHMAIAB0AGgAYQBuACAAdABoAGUAIAB2AGUAcgBzAGkAbwBuACAAcAB1AGIAbABpAHMAaABlAGQAIABiAHkAIABUAGEAeQBsAG8AcgAgAGUAdAAgAGEAbAAuACAAaQBuACAAMQA5ADkAMwAgAEEAcABKAFMALgAgAFQAaABlACAASABFAEEAUwBBAFIAQwAgAG8AYgB0AGEAaQBuAGUAZAAgAHQAaABpAHMAIABlAGwAZQBjAHQAcgBvAG4AaQBjACAAdgBlAHIAcwBpAG8AbgAgAGYAcgBvAG0AIAB0AGgAZQAgAFAAcgBpAG4AYwBlAHQAbwBuACAAVQBuAGkAdgBlAHIAcwBpAHQAeQAgAEYAVABQACAAcwBpAHQAZQAuAAAANmh0dHBzOi8vaGVhc2FyYy5nc2ZjLm5hc2EuZ292L1czQnJvd3NlL2FsbC9wdWxzYXIuaHRtbAAAAA0AVABhAHkAbABvAHIAIABlAHQAIABhAGwALgAAABMyMDIzLTA5LTI5VDAwOjAwOjAwAAAAEzIwMjMtMDktMjlUMDA6MDA6MDAAAAAAAAAAB2NhdGFsb2cAAAAAAAAAEwAxADkAOQAzAEEAcABKAFMALgAuAC4AOAA4AC4ALgA1ADIAOQBUf8AAAAAAAAVyYWRpbwAAAXFodHRwczovL2hlYXNhcmMuZ3NmYy5uYXNhLmdvdi94YW1pbi92by9jb25lP3Nob3dvZmZzZXRzJnRhYmxlPXB1bHNhciY6OjpweSBWTyBzZXA6OjpodHRwczovL2hlYXNhcmMuZ3NmYy5uYXNhLmdvdi9jZ2ktYmluL1czQnJvd3NlL3czcXVlcnkucGw/dGFibGVoZWFkPW5hbWU9aGVhc2FyY19wdWxzYXImQWN0aW9uPU1vcmUrT3B0aW9ucyZBY3Rpb249UGFyYW1ldGVyK1NlYXJjaCZDb25lQWRkPTE6OjpweSBWTyBzZXA6OjpodHRwczovL2hlYXNhcmMuZ3NmYy5uYXNhLmdvdi9jZ2ktYmluL1czQnJvd3NlL2dldHZvdGFibGUucGw/bmFtZT1wdWxzYXI6OjpweSBWTyBzZXA6OjpodHRwczovL2hlYXNhcmMuZ3NmYy5uYXNhLmdvdi94YW1pbi92by90YXAAAABkaXZvOi8vaXZvYS5uZXQvc3RkL2NvbmVzZWFyY2g6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojo6OjpweSBWTyBzZXA6Ojppdm86Ly9pdm9hLm5ldC9zdGQvdGFwI2F1eAAAAF52czpwYXJhbWh0dHA6OjpweSBWTyBzZXA6Ojp2cjp3ZWJicm93c2VyOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwOjo6cHkgVk8gc2VwOjo6dnM6cGFyYW1odHRwAAAAM3N0ZDo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6Ojo6OnB5IFZPIHNlcDo6OnN0ZAAAAAA=
pyvo-1.5.2/pyvo/registry/tests/test_regtap.py000066400000000000000000000765741462331236700214500ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.registry.regtap """ import io import re from functools import partial from urllib.parse import parse_qsl import pytest from astropy import time from pyvo.registry import regtap from pyvo.registry import rtcons from pyvo.registry.regtap import REGISTRY_BASEURL from pyvo.registry import search as regsearch from pyvo.dal import DALOverflowWarning from pyvo.dal import query as dalq from pyvo.dal import tap, sia2 from astropy.utils.data import get_pkg_data_contents from .commonfixtures import messenger_vocabulary, FAKE_GAVO # noqa: F401 get_pkg_data_contents = partial( get_pkg_data_contents, package=__package__, encoding='binary') @pytest.fixture(name='capabilities') def _capabilities(mocker): def callback(request, context): return get_pkg_data_contents('data/capabilities.xml') with mocker.register_uri( 'GET', REGISTRY_BASEURL + '/capabilities', content=callback ) as matcher: yield matcher # to update this, run # import requests # from pyvo.registry import regtap # # with open("data/regtap.xml", "wb") as f: # f.write(requests.get(regtap.REGISTRY_BASEURL+"/sync", { # "LANG": "ADQL", # "QUERY": regtap.get_RegTAP_query(keywords="pulsar", ucd=["pos.distance"])}).content) @pytest.fixture(name='regtap_pulsar_distance_response') def _regtap_pulsar_distance_response(mocker): with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=get_pkg_data_contents('data/regtap.xml')) as matcher: yield matcher @pytest.fixture() def keywords_fixture(mocker): def keywordstest_callback(request, context): data = dict(parse_qsl(request.body)) query = data['QUERY'] assert "res_subject ILIKE '%vizier%'" in query assert "ivo_hasword(res_description, 'vizier')" in query assert "1=ivo_hasword(res_title, 'vizier')" in query assert ".res_subject ILIKE '%pulsar%'" in query assert "1=ivo_hasword(res_description, 'pulsar')" in query assert "1=ivo_hasword(res_title, 'pulsar')" in query return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=keywordstest_callback ) as matcher: yield matcher @pytest.fixture() def single_keyword_fixture(mocker): def keywordstest_callback(request, context): data = dict(parse_qsl(request.body)) query = data['QUERY'] assert "OR rr.res_subject.res_subject ILIKE '%single%'" in query assert "1=ivo_hasword(res_description, 'single') " in query assert "1=ivo_hasword(res_title, 'single')" in query return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=keywordstest_callback ) as matcher: yield matcher @pytest.fixture() def servicetype_fixture(mocker): def servicetypetest_callback(request, context): data = dict(parse_qsl(request.body)) query = data['QUERY'] assert "'ivo://ivoa.net/std/conesearch'" not in query assert "'ivo://ivoa.net/std/sia'" not in query assert "'ivo://ivoa.net/std/ssa'" not in query assert "'ivo://ivoa.net/std/slap'" not in query assert "'ivo://ivoa.net/std/tap'" in query return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=servicetypetest_callback ) as matcher: yield matcher @pytest.fixture() def waveband_fixture(mocker): def wavebandtest_callback(request, content): data = dict(parse_qsl(request.body)) query = data['QUERY'] assert "1 = ivo_hashlist_has(rr.resource.waveband, 'optical'" in query return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=wavebandtest_callback ) as matcher: yield matcher @pytest.fixture() def datamodel_fixture(mocker): def datamodeltest_callback(request, content): data = dict(parse_qsl(request.body)) query = data['QUERY'] assert ( "(detail_xpath = '/capability/dataModel/@ivo-id'" in query) assert ( "ivo_nocasematch(detail_value, 'ivo://ivoa.net/std/obscore%'))" in query) return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=datamodeltest_callback ) as matcher: yield matcher @pytest.fixture() def aux_fixture(mocker): def auxtest_callback(request, context): data = dict(parse_qsl(request.body)) query = data['QUERY'] assert "ivo://ivoa.net/std/tap#aux" in query return get_pkg_data_contents('data/regtap.xml') with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=auxtest_callback, ) as matcher: yield matcher @pytest.fixture(name='multi_interface_fixture') def _multi_interface_fixture(mocker): # to update this, run # import requests # from pyvo.registry import regtap # # with open("data/multi-interface.xml", "wb") as f: # f.write(requests.get(regtap.REGISTRY_BASEURL+"/sync", { # "LANG": "ADQL", # "QUERY": regtap.get_RegTAP_query( # ivoid="ivo://org.gavo.dc/flashheros/q/ssa")}).content) with mocker.register_uri( 'POST', REGISTRY_BASEURL + '/sync', content=get_pkg_data_contents('data/multi-interface.xml') ) as matcher: yield matcher @pytest.fixture(name='flash_service') def _flash_service(multi_interface_fixture): return regtap.search( ivoid="ivo://org.gavo.dc/flashheros/q/ssa")[0] class TestInterfaceClass: def test_basic(self): intf = regtap.Interface("http://example.org", "", "", "") assert intf.access_url == "http://example.org" assert intf.standard_id is None assert intf.type is None assert intf.role is None assert intf.is_standard is False assert not intf.is_vosi def test_repr(self): intf = regtap.Interface("http://example.org", standard_id="ivo://gavo/std/a", intf_type="vs:paramhttp", intf_role="std") assert (repr(intf) == "Interface('http://example.org'," " standard_id='ivo://gavo/std/a', intf_type='vs:paramhttp', intf_role='std')") intf = regtap.Interface("http://example.org", standard_id="ivo://gavo/std/a", intf_type=None, intf_role=None) assert repr(intf) == ("Interface('http://example.org'," " standard_id='ivo://gavo/std/a', intf_type=None, intf_role=None)") def test_unknown_standard(self): intf = regtap.Interface("http://example.org", standard_id="ivo://gavo/std/a", intf_type="vs:paramhttp", intf_role="std") assert intf.is_standard with pytest.raises(ValueError) as excinfo: intf.to_service() assert str(excinfo.value) == ( "PyVO has no support for interfaces with standard" " id ivo://gavo/std/a.") def test_known_standard(self): intf = regtap.Interface("http://example.org", standard_id="ivo://ivoa.net/std/tap#aux", intf_type="vs:paramhttp", intf_role="std") assert isinstance(intf.to_service(), tap.TAPService) assert not intf.is_vosi def test_sia2_standard(self): intf = regtap.Interface("http://example.org", standard_id="ivo://ivoa.net/std/sia2", intf_type="vs:paramhttp", intf_role="std") assert isinstance(intf.to_service(), sia2.SIA2Service) assert not intf.is_vosi def test_secondary_interface(self): intf = regtap.Interface("http://example.org", standard_id="ivo://ivoa.net/std/tap#aux", intf_type="vs:webbrowser", intf_role="web") with pytest.raises(ValueError) as excinfo: intf.to_service() assert str(excinfo.value) == ( "This is not a standard interface. PyVO cannot speak to it.") def test_VOSI(self): intf = regtap.Interface("http://example.org", standard_id="ivo://ivoa.net/std/vosi#capabilities", intf_type="vs:ParamHTTP", intf_role="std") assert intf.is_vosi # The following tests have their assertions in the fixtures. # It would certainly not hurt to refactor this so they are # in the tests (we could also just rely on the rtcons tests # that exercise about the same thing). @pytest.mark.usefixtures('keywords_fixture', 'capabilities') def test_keywords(): regsearch(keywords=['vizier', 'pulsar']) @pytest.mark.usefixtures('single_keyword_fixture', 'capabilities') def test_single_keyword(): regsearch(keywords=['single']) regsearch(keywords='single') @pytest.mark.usefixtures('servicetype_fixture', 'capabilities') def test_servicetype(): regsearch(servicetype='table') @pytest.mark.usefixtures( 'waveband_fixture', 'capabilities', 'messenger_vocabulary') def test_waveband(): regsearch(waveband='optical') @pytest.mark.usefixtures('datamodel_fixture', 'capabilities') def test_datamodel(): regsearch(datamodel='ObsCore') @pytest.mark.usefixtures('aux_fixture', 'capabilities') def test_servicetype_aux(): regsearch(servicetype='table', includeaux=True) @pytest.mark.usefixtures('aux_fixture', 'capabilities') def test_bad_servicetype_aux(): with pytest.raises(dalq.DALQueryError): regsearch(servicetype='bad_servicetype', includeaux=True) class _NS: """a namespace exposing its keyword arguments as attributes. We need this here to let us conveniently construct _FakeResults. """ def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) class _FakeResults: """ a minimal standin for dal.query.Results to be used with dal.query.Record. It is constructed with a dictionary that should eventually be used as the mapping in the Record. """ def __init__(self, valdict): self.fieldnames = list(valdict.keys()) self.resultstable = _NS(array=_NS(data=[list(valdict.values())])) def get_regtap_results(**kwargs): """ return a RegTAP result as expected by RegistryResult with all values empty, completed with what's in kwargs. """ res = {} for key in regtap.RegistryResource.expected_columns: if isinstance(key, str): res[key] = None else: res[key[-1]] = None res.update(kwargs) return _FakeResults(res) def test_spatial(): assert (rtcons.keywords_to_constraints({ "spatial": (23, -40)})[0].get_search_condition(FAKE_GAVO) == "1 = CONTAINS(MOC(6, POINT(23, -40)), coverage)") def test_spectral(): assert (rtcons.keywords_to_constraints({ "spectral": (1e-17, 2e-17)})[0].get_search_condition(FAKE_GAVO) == "1 = ivo_interval_overlaps(spectral_start, spectral_end, 1e-17, 2e-17)") def test_to_table(multi_interface_fixture, capabilities): t = regtap.search( ivoid="ivo://org.gavo.dc/flashheros/q/ssa").get_summary() assert (set(t.columns.keys()) == {'index', 'short_name', 'title', 'description', 'interfaces'}) assert t["index"][0] == 0 assert t["title"][0] == 'Flash/Heros SSAP' assert (t["description"][0][:40] == 'Spectra from the Flash and Heros Echelle') assert (t["interfaces"][0] == 'datalink#links-1.1, soda#sync-1.0, ssa, tap#aux, web') @pytest.fixture(name='rt_pulsar_distance') def _rt_pulsar_distance(regtap_pulsar_distance_response, capabilities): return regsearch(keywords="pulsar", ucd=["pos.distance"]) def test_record_fields(rt_pulsar_distance): rec = rt_pulsar_distance["VII/156"] assert rec.ivoid == "ivo://cds.vizier/vii/156" assert rec.res_type == "vs:catalogservice" assert rec.short_name == "VII/156" assert rec.res_title == "Catalog of 558 Pulsars" assert rec.content_levels == ['research'] assert rec.res_description[:20] == "The catalogue is an up-to-date"[:20] assert rec.reference_url == "https://cdsarc.cds.unistra.fr/viz-bin/cat/VII/156" assert rec.creators == ['Taylor J.H.', ' Manchester R.N.', ' Lyne A.G.'] assert rec.content_types == ['catalog'] assert rec.source_format == "bibcode" assert rec.source_value == "1993ApJS...88..529T" assert rec.region_of_regard is None assert rec.waveband == ['radio'] assert rec.created == "1999-03-02T10:21:53" # updated might break when regenerating data/regtap.xml, # replace by the new date assert rec.updated == "2021-10-21T00:00:00" assert rec.rights == "https://cds.unistra.fr/vizier-org/licences_vizier.html" # access URL, standard_id and friends exercised in TestInterfaceSelection class TestResultIndexing: def test_get_with_index(self, rt_pulsar_distance): # this is expecte to break when the fixture is updated assert (rt_pulsar_distance[0].res_title == 'Pulsar Timing for Fermi Gamma-ray Space Telescope') def test_get_with_short_name(self, rt_pulsar_distance): assert (rt_pulsar_distance["ATNF"].res_title == 'ATNF Pulsar Catalog') def test_get_with_ivoid(self, rt_pulsar_distance): assert (rt_pulsar_distance["ivo://nasa.heasarc/atnfpulsar" ].res_title == 'ATNF Pulsar Catalog') def test_out_of_range(self, rt_pulsar_distance): with pytest.raises(IndexError) as excinfo: rt_pulsar_distance[40320] assert (str(excinfo.value) == f"index 40320 is out of bounds for axis 0 with size {len(rt_pulsar_distance)}") def test_bad_key(self, rt_pulsar_distance): with pytest.raises(KeyError) as excinfo: rt_pulsar_distance["hunkatunka"] assert (str(excinfo.value) == "'hunkatunka'") def test_not_indexable(self, rt_pulsar_distance): with pytest.raises(IndexError) as excinfo: rt_pulsar_distance[None] assert (str(excinfo.value) == "No resource matching None") @pytest.mark.usefixtures('multi_interface_fixture', 'capabilities', 'flash_service') class TestInterfaceSelection: """ tests for the selection and generation of services within RegistryResource. """ def test_exactly_one_result(self): results = regtap.search( ivoid="ivo://org.gavo.dc/flashheros/q/ssa") assert len(results) == 1 def test_access_modes(self, flash_service): assert set(flash_service.access_modes()) == { 'datalink#links-1.1', 'soda#sync-1.0', 'ssa', 'tap#aux', 'web'} def test_standard_id_multi(self, flash_service): with pytest.raises(dalq.DALQueryError) as excinfo: _ = flash_service.standard_id assert str(excinfo.value) == ("This resource supports several" " standards (datalink#links-1.1, soda#sync-1.0, ssa," " tap#aux, web). Use get_service or restrict your query" " using Servicetype.") def test_get_web_interface(self, flash_service): svc = flash_service.get_service("web") assert isinstance(svc, regtap._BrowserService) assert (svc.access_url == "http://dc.zah.uni-heidelberg.de/flashheros/q/web/form") import webbrowser orig_open = webbrowser.open try: open_args = [] webbrowser.open = lambda *args: open_args.append(args) svc.search() assert open_args == [ ("http://dc.zah.uni-heidelberg.de/flashheros/q/web/form", 2)] finally: webbrowser.open = orig_open def test_get_aux_interface(self, flash_service): svc = flash_service.get_service("tap#aux") assert (svc._baseurl == "http://dc.zah.uni-heidelberg.de/tap") def test_get_aux_as_main(self, flash_service): assert (flash_service.get_service("tap")._baseurl == "http://dc.zah.uni-heidelberg.de/tap") def test_get__main_from_aux(self, flash_service): assert (flash_service.get_service("tap")._baseurl == "http://dc.zah.uni-heidelberg.de/tap") def test_get_by_alias(self, flash_service): assert (flash_service.get_service("spectrum")._baseurl == "http://dc.zah.uni-heidelberg.de/fhssa?") def test_get_unsupported_standard(self, flash_service): with pytest.raises(ValueError) as excinfo: flash_service.get_service("soda#sync-1.0") assert str(excinfo.value) == ( "PyVO has no support for interfaces with standard id" " ivo://ivoa.net/std/soda#sync-1.0.") def test_get_nonexisting_standard(self, flash_service): with pytest.raises(ValueError) as excinfo: flash_service.get_service("http://nonsense#fancy") assert str(excinfo.value) == ( "No matching interface.") def test_unconstrained(self, flash_service): with pytest.raises(ValueError) as excinfo: flash_service.get_service(lax=False) assert str(excinfo.value) == ( "Multiple matching interfaces found. Perhaps pass in" " service_type or use a Servicetype constrain in the" " registry.search? Or use lax=True?") def test_interface_without_role(self): # There's an ugly corner case in our array simulation for # capabilities and interfaces: if there's a single untyped # interface, the returned type (or role) will be an empty # string, and the split() will return an empty list. # This swallowed the interface in pyVO 1.3. rec = get_regtap_results( access_urls="http://example.org/tap", standard_ids="ivo://ivoa.net/std/TAP", intf_types="vr:webbrowser", intf_roles="") resource = regtap.RegistryResource(rec, 0) assert len(resource.interfaces) == 1 assert resource.interfaces[0].standard_id == 'ivo://ivoa.net/std/TAP' # get_service still won't work because it needs a paramhttp # interface (and a role="std"). with pytest.raises(ValueError) as excinfo: resource.get_service('tap') assert (str(excinfo.value) == "No matching interface.") def test_sia2_query(self): rec = _makeRegistryRecord( access_urls=["http://sia2.example.com", "http://sia.example.com"], standard_ids=[ "ivo://ivoa.net/std/sia#query-2.0", "ivo://ivoa.net/std/sia"], intf_roles=["std"] * 2, intf_types=["vs:paramhttp"] * 2) assert rec.access_modes() == {"sia", "sia2"} assert rec.get_interface("sia2").access_url == 'http://sia2.example.com' assert rec.get_interface("sia").access_url == 'http://sia.example.com' def test_sia2_aux(self): rec = _makeRegistryRecord( access_urls=["http://sia2.example.com", "http://sia.example.com"], standard_ids=[ "ivo://ivoa.net/std/sia#query-aux-2.0", "ivo://ivoa.net/std/sia#aux"], intf_roles=["std"] * 2, intf_types=["vs:paramhttp"] * 2) assert rec.access_modes() == {"sia#aux", "sia2#aux"} assert rec.get_interface("sia2").access_url == 'http://sia2.example.com' assert rec.get_interface("sia").access_url == 'http://sia.example.com' def test_non_standard_interface(self): intf = regtap.Interface("http://url", standard_id="", intf_type="", intf_role="") assert intf.supports("ivo://ivoa.net/std/sia") is False def test_supports_none(self): intf = regtap.Interface("http://url", standard_id="", intf_type="", intf_role="") assert intf.supports(None) is False def test_non_searchable_service(self): rec = _makeRegistryRecord() with pytest.raises(dalq.DALServiceError) as excinfo: rec.search() assert str(excinfo.value) == ( "Resource ivo://pyvo/test_regtap.py is not a searchable service") class _FakeResult: """A fake class just sufficient for giving dal.query.Record enough to pull in the dict this is constructed. As an extra service, list values are stringified with regtap.TOKEN_SEP -- this is how they ought to come in from RegTAP services. """ def __init__(self, d): self.fieldnames = list(d.keys()) vals = [regtap.TOKEN_SEP.join(v) if isinstance(v, list) else v for v in d.values()] class _: class array: data = [vals] self.resultstable = _ def _makeRegistryRecord(**overrides): """returns a minimal RegistryResource instance, overriding some built-in defaults with the dict overrides. """ defaults = { "access_urls": "", "standard_ids": "", "intf_types": "", "intf_roles": "", "ivoid": "ivo://pyvo/test_regtap.py" } defaults.update(overrides) return regtap.RegistryResource(_FakeResult(defaults), 0) class TestInterfaceRejection: """tests for various artificial corner cases where interface selection should fail (or just not fail). """ def test_nonunique(self): rsc = _makeRegistryRecord( access_urls=["http://a", "http://b"], standard_ids=["ivo://ivoa.net/std/tap"] * 2, intf_types=["vs:paramhttp"] * 2, intf_roles=["std"] * 2) with pytest.raises(ValueError) as excinfo: rsc.get_service("tap", lax=False) assert str(excinfo.value) == ( "Multiple matching interfaces found. Perhaps pass in" " service_type or use a Servicetype constrain in the" " registry.search? Or use lax=True?") def test_nonunique_lax(self): rsc = _makeRegistryRecord( access_urls=["http://a", "http://b"], standard_ids=["ivo://ivoa.net/std/tap"] * 2, intf_types=["vs:paramhttp"] * 2, intf_roles=["std"] * 2) assert (rsc.get_service("tap")._baseurl == "http://a") def test_nonstd_ignored(self): rsc = _makeRegistryRecord( access_urls=["http://a", "http://b"], standard_ids=["ivo://ivoa.net/std/tap"] * 2, intf_types=["vs:paramhttp"] * 2, intf_roles=["std", ""]) assert (rsc.get_service("tap", lax=False)._baseurl == "http://a") def test_select_single_matching_service(self): rsc = _makeRegistryRecord( access_urls=["http://a", "http://b"], standard_ids=["", "ivo://ivoa.net/std/tap"], intf_types=["vs:webbrowser", "vs:paramhttp"], intf_roles=["", "std"]) assert (rsc.service._baseurl == "http://b") # this makes sure caching the service obtained doesn't break # things assert (rsc.service._baseurl == "http://b") def test_capless(self): rsc = _makeRegistryRecord() with pytest.raises(ValueError) as excinfo: rsc.service._baseurl assert str(excinfo.value) == ( "No matching interface.") @pytest.mark.remote_data def test_maxrec(): with pytest.warns(DALOverflowWarning) as w: _ = regsearch(servicetype="tap", maxrec=1) assert len(w) == 1 assert str(w[0].message).startswith("Partial result set.") @pytest.mark.remote_data def test_get_contact(): rsc = _makeRegistryRecord( ivoid="ivo://org.gavo.dc/flashheros/q/ssa") assert (rsc.get_contact() == "GAVO Data Center Team (++49 6221 54 1837)" " ") @pytest.mark.remote_data def test_get_alt_identifier(): rsc = _makeRegistryRecord(ivoid="ivo://cds.vizier/i/337") assert set(rsc.get_alt_identifiers()) == { 'doi:10.26093/cds/vizier.1337', 'bibcode:doi:10.5270/esa-ogmeula', 'bibcode:2016yCat.1337....0G'} @pytest.mark.remote_data class TestDatamodelQueries: # right now, the data model queries are all rather sui generis, and # rather fickly on top. Let's make sure they actually return something # against the live registry. Admittedly, this is about as much # a test of the VO infrastructure as of pyvo. def test_obscore(self): assert len(regsearch(rtcons.Datamodel('obscore'))) > 0 def test_epntap(self): assert len(regsearch(rtcons.Datamodel('epntap'))) > 0 def test_regtap(self): assert len(regsearch(rtcons.Datamodel('regtap'))) > 0 @pytest.mark.usefixtures('multi_interface_fixture', 'capabilities', 'flash_service') class TestExtraResourceMethods: """ tests for methods of RegistryResource containing some non-trivial logic (except service selection, which is in TestInterfaceSelection, and get_tables, which is in TestGetTables). """ def test_unique_standard_id(self): rsc = _makeRegistryRecord( access_urls=["http://a"], standard_ids=["ivo://ivoa.net/std/tap"], intf_types=["vs:paramhttp"], intf_roles=["std"]) assert rsc.standard_id == "ivo://ivoa.net/std/tap" @pytest.mark.remote_data def test_describe_multi(self, flash_service): out = io.StringIO() flash_service.describe(verbose=True, file=out) output = out.getvalue() assert "Flash/Heros SSAP" in output assert ("Access modes: datalink#links-1.1, soda#sync-1.0," " ssa, tap#aux, web" in output) assert "Multi-capability service" in output assert "Source: 1996A&A...312..539S" in output assert "Authors: Wolf" in output assert "Alternative identifier(s): doi:10.21938/" in output assert "More info: http://dc.zah" in output @pytest.mark.remote_data def test_describe_long_authors_list(self): """Check that long list of authors use et al..""" rsc = _makeRegistryRecord( access_urls=[], standard_ids=["ivo://pyvo/test"], short_name=["name"], intf_types=[], intf_roles=[], creator_seq=["a;" * 6], res_title=["title"] ) out = io.StringIO() rsc.describe(verbose=True, file=out) output = out.getvalue() # output should cut at 5 authors assert "Authors: a, a, a, a, a et al." in output @pytest.mark.remote_data def test_describe_long_author_name(self): """Check that long author names are truncated.""" rsc = _makeRegistryRecord( access_urls=[], standard_ids=["ivo://pyvo/test"], short_name=["name"], intf_types=[], intf_roles=[], creator_seq=["a" * 71], res_title=["title"] ) out = io.StringIO() rsc.describe(verbose=True, file=out) output = out.getvalue() # should cut the long author name at 70 characters assert f"Authors: {'a'*70}..." in output def test_no_access_url(self): rsc = _makeRegistryRecord( access_urls=[], standard_ids=[], intf_types=[], intf_roles=[]) with pytest.raises(dalq.DALQueryError) as excinfo: rsc.access_url assert str(excinfo.value) == ("The resource" " ivo://pyvo/test_regtap.py has no queriable interfaces.") def test_unique_access_url(self): rsc = _makeRegistryRecord( access_urls=["http://a"], standard_ids=["ivo://ivoa.net/std/tap"], intf_types=["vs:paramhttp"], intf_roles=[""]) assert rsc.access_url == "http://a" def test_ambiguous_access_url_warns(self, recwarn): rsc = _makeRegistryRecord( access_urls=["http://a", "http://b"], standard_ids=["ivo://ivoa.net/std/tap"] * 2, intf_types=["vs:paramhttp"] * 2, intf_roles=["std"] * 2) assert rsc.access_url == "http://a" assert ('The resource ivo://pyvo/test_regtap.py has multipl' in [str(w.message)[:50] for w in recwarn]) # TODO: While I suppose the contact test should keep requiring network, # I think we should can the network responses involved in the following; # the stuff might change upstream any time and then break our unit tests. @pytest.fixture(name='flash_tables') def _flash_tables(): rsc = _makeRegistryRecord( ivoid="ivo://org.gavo.dc/flashheros/q/ssa") return rsc.get_tables() @pytest.mark.usefixtures("flash_tables") class TestGetTables: @pytest.mark.remote_data def test_get_tables_limit_enforced(self): rsc = _makeRegistryRecord( ivoid="ivo://org.gavo.dc/tap") with pytest.raises(dalq.DALQueryError) as excinfo: rsc.get_tables() assert re.match(r"Resource ivo://org.gavo.dc/tap reports \d+ tables." " Pass a higher table_limit to see them all.", str(excinfo.value)) @pytest.mark.remote_data def test_get_tables_names(self, flash_tables): assert (list(sorted(flash_tables.keys())) == ["flashheros.data", "ivoa.obscore"]) @pytest.mark.remote_data def test_get_tables_table_instance(self, flash_tables): assert (flash_tables["ivoa.obscore"].name == "ivoa.obscore") assert (flash_tables["ivoa.obscore"].description == "This data collection is queryable in GAVO Data" " Center's obscore table.") assert (flash_tables["flashheros.data"].title == "Flash/Heros SSA table") assert (flash_tables["flashheros.data"].origin.ivoid == "ivo://org.gavo.dc/flashheros/q/ssa") @pytest.mark.remote_data def test_get_tables_column_meta(self, flash_tables): def getflashcol(name): for col in flash_tables["flashheros.data"].columns: if name == col.name: return col raise KeyError(name) assert getflashcol("accref").datatype.content == "char" assert getflashcol("accref").datatype.arraysize == "*" # TODO: upstream bug: the following needs to fixed in DaCHS before # the assertion passes # assert getflashcol("ssa_region").datatype._extendedtype == "point" assert getflashcol("mime").ucd == 'meta.code.mime' assert getflashcol("ssa_specend").unit == "m" assert (getflashcol("ssa_specend").utype == "ssa:char.spectralaxis.coverage.bounds.stop") assert (getflashcol("ssa_fluxcalib").description == "Type of flux calibration") @pytest.mark.remote_data def test_sia2_service_operation(): svcs = regsearch( servicetype='sia2', ivoid='ivo://cadc.nrc.ca/sia') assert len(svcs) == 1 res = svcs[0].search(pos=(30, 40, 0.1), time=(time.Time(58794.9, format="mjd"), time.Time(58795, format="mjd"))) assert len(res) > 10 assert "s_dec" in res.to_table().columns @pytest.mark.remote_data def test_endpoint_switching(): alt_svc = "http://vao.stsci.edu/RegTAP/TapService.aspx" previous_url = regtap.REGISTRY_BASEURL try: regtap.choose_RegTAP_service(alt_svc) assert regtap.get_RegTAP_service()._baseurl == alt_svc res = regtap.search(keywords="wirr") assert len(res) > 0 finally: regtap.choose_RegTAP_service(previous_url) pyvo-1.5.2/pyvo/registry/tests/test_rtcons.py000066400000000000000000000533451462331236700214650ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.registry.rtcons, i.e. RegTAP constraints and query building. """ import datetime from astropy.time import Time from astropy import units as u from astropy.coordinates import SkyCoord import numpy import pytest from pyvo import registry from pyvo.registry import rtcons from pyvo.dal import query as dalq from .commonfixtures import messenger_vocabulary, FAKE_GAVO, FAKE_PLAIN # noqa: F401 def _build_regtap_query_with_fake( *args, service=FAKE_GAVO, **kwargs): return rtcons.build_regtap_query( *args, service=service, **kwargs) class TestAbstractConstraint: def test_no_search_condition(self): with pytest.raises(NotImplementedError): rtcons.Constraint().get_search_condition(FAKE_GAVO) class TestSQLLiterals: @pytest.fixture(scope="class", autouse=True) def literals(self): class _WithFillers(rtcons.Constraint): _fillers = { "aString": "some harmless stuff", "nastyString": "that's not nasty", "bytes": b"keep this ascii for now", "anInt": 210, "aFloat": 5e7, "numpyStuff": numpy.float64(23.7), "timestamp": datetime.datetime(2021, 6, 30, 9, 1), } return _WithFillers()._get_sql_literals() def test_strings(self, literals): assert literals["aString"] == "'some harmless stuff'" assert literals["nastyString"] == "'that''s not nasty'" def test_bytes(self, literals): assert literals["bytes"] == "'keep this ascii for now'" def test_int(self, literals): assert literals["anInt"] == "210" def test_float(self, literals): assert literals["aFloat"] == "50000000.0" def test_numpy(self, literals): assert float(literals["numpyStuff"]) - 23.7 < 1e-10 def test_timestamp(self, literals): assert literals["timestamp"] == "'2021-06-30T09:01:00'" def test_odd_type_rejected(self): with pytest.raises(ValueError) as excinfo: rtcons.make_sql_literal({}) assert str(excinfo.value) == "Cannot format {} as a SQL literal" class TestFreetextConstraint: def test_basic(self): assert rtcons.Freetext("star").get_search_condition(FAKE_GAVO) == ( "ivoid IN (SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_description, 'star') " "UNION SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_title, 'star') " "UNION SELECT ivoid FROM rr.res_subject WHERE rr.res_subject.res_subject ILIKE '%star%')") def test_interesting_literal(self): assert rtcons.Freetext("α Cen's planets").get_search_condition(FAKE_GAVO) == ( "ivoid IN (SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_description, 'α Cen''s planets')" " UNION SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_title, 'α Cen''s planets')" " UNION SELECT ivoid FROM rr.res_subject WHERE rr.res_subject.res_subject" " ILIKE '%α Cen''s planets%')") def test_multipleLiterals(self): assert rtcons.Freetext("term1", "term2").get_search_condition(FAKE_GAVO) == ( "ivoid IN (SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_description, 'term1')" " UNION SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_title, 'term1')" " UNION SELECT ivoid FROM rr.res_subject WHERE rr.res_subject.res_subject ILIKE '%term1%')" " AND " "ivoid IN (SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_description, 'term2')" " UNION SELECT ivoid FROM rr.resource WHERE 1=ivo_hasword(res_title, 'term2')" " UNION SELECT ivoid FROM rr.res_subject WHERE rr.res_subject.res_subject ILIKE '%term2%')") def test_adaption_to_service(self): assert rtcons.Freetext("term1", "term2").get_search_condition(FAKE_PLAIN) == ( "( 1=ivo_hasword(res_description, 'term1') OR 1=ivo_hasword(res_title, 'term1')" " OR rr.res_subject.res_subject ILIKE '%term1%')" " AND ( 1=ivo_hasword(res_description, 'term2') OR 1=ivo_hasword(res_title, 'term2')" " OR rr.res_subject.res_subject ILIKE '%term2%')") class TestAuthorConstraint: def test_basic(self): assert (rtcons.Author("%Hubble%").get_search_condition(FAKE_GAVO) == "role_name LIKE '%Hubble%' AND base_role='creator'") class TestServicetypeConstraint: def test_standardmap(self): assert (rtcons.Servicetype("scs").get_search_condition(FAKE_GAVO) == "standard_id IN ('ivo://ivoa.net/std/conesearch')") def test_fulluri(self): assert (rtcons.Servicetype("http://extstandards/invention" ).get_search_condition(FAKE_GAVO) == "standard_id IN ('http://extstandards/invention')") def test_multi(self): assert (rtcons.Servicetype("http://extstandards/invention", "image" ).get_search_condition(FAKE_GAVO) == "standard_id IN ('http://extstandards/invention'," " 'ivo://ivoa.net/std/sia')") def test_includeaux(self): assert (rtcons.Servicetype("http://extstandards/invention", "image" ).include_auxiliary_services().get_search_condition(FAKE_GAVO) == "standard_id IN ('http://extstandards/invention'," " 'http://extstandards/invention#aux'," " 'ivo://ivoa.net/std/sia'," " 'ivo://ivoa.net/std/sia#aux')") def test_junk_rejected(self): with pytest.raises(dalq.DALQueryError) as excinfo: rtcons.Servicetype("junk") assert str(excinfo.value) == ("Service type junk is neither" " a full standard URI nor one of the bespoke identifiers" " image, sia, spectrum, ssap, ssa, scs, conesearch, line, slap," " table, tap, sia2") def test_legacy_term(self): assert (rtcons.Servicetype("conesearch").get_search_condition(FAKE_GAVO) == "standard_id IN ('ivo://ivoa.net/std/conesearch')") def test_sia2(self): assert ( rtcons.Servicetype("conesearch", "sia2").get_search_condition(FAKE_GAVO) == ("standard_id IN ('ivo://ivoa.net/std/conesearch')" " OR standard_id like 'ivo://ivoa.net/std/sia#query-2.%'")) def test_sia2_aux(self): constraint = rtcons.Servicetype("conesearch", "sia2").include_auxiliary_services() assert (constraint.get_search_condition(FAKE_GAVO) == ("standard_id IN ('ivo://ivoa.net/std/conesearch', 'ivo://ivoa.net/std/conesearch#aux')" " OR standard_id like 'ivo://ivoa.net/std/sia#query-2.%'" " OR standard_id like 'ivo://ivoa.net/std/sia#query-aux-2.%'")) @pytest.mark.usefixtures('messenger_vocabulary') class TestWavebandConstraint: def test_basic(self): assert (rtcons.Waveband("Infrared", "EUV").get_search_condition(FAKE_GAVO) == "1 = ivo_hashlist_has(rr.resource.waveband, 'infrared')" " OR 1 = ivo_hashlist_has(rr.resource.waveband, 'euv')") def test_junk_rejected(self): with pytest.raises(dalq.DALQueryError) as excinfo: rtcons.Waveband("junk") assert str(excinfo.value) == ( "Waveband junk is not in the IVOA messenger vocabulary http://www.ivoa.net/rdf/messenger.") def test_normalisation(self): assert (rtcons.Waveband("oPtIcAl").get_search_condition(FAKE_GAVO) == "1 = ivo_hashlist_has(rr.resource.waveband, 'optical')") class TestDatamodelConstraint: def test_junk_rejected(self): with pytest.raises(dalq.DALQueryError) as excinfo: rtcons.Datamodel("junk") assert str(excinfo.value) == ( "Unknown data model id junk. Known are: epntap, obscore, regtap.") def test_obscore(self): cons = rtcons.Datamodel("ObsCore") assert (cons.get_search_condition(FAKE_GAVO) == "detail_xpath = '/capability/dataModel/@ivo-id'" " AND 1 = ivo_nocasematch(detail_value," " 'ivo://ivoa.net/std/obscore%')") assert (cons._extra_tables == ["rr.res_detail"]) def test_epntap(self): cons = rtcons.Datamodel("epntap") assert (cons.get_search_condition(FAKE_GAVO) == "table_utype LIKE 'ivo://vopdc.obspm/std/epncore#schema-2.%'" " OR table_utype LIKE 'ivo://ivoa.net/std/epntap#table-2.%'") assert (cons._extra_tables == ["rr.res_table"]) def test_regtap(self): cons = rtcons.Datamodel("regtap") assert (cons.get_search_condition(FAKE_GAVO) == "detail_xpath = '/capability/dataModel/@ivo-id'" " AND 1 = ivo_nocasematch(detail_value," " 'ivo://ivoa.net/std/RegTAP#1.%')") assert (cons._extra_tables == ["rr.res_detail"]) class TestIvoidConstraint: def test_basic(self): cons = rtcons.Ivoid("ivo://example/some_path") assert (cons.get_search_condition(FAKE_GAVO) == "ivoid = 'ivo://example/some_path'") class TestUCDConstraint: def test_basic(self): cons = rtcons.UCD("phot.mag;em.opt.%", "phot.mag;em.ir.%") assert (cons.get_search_condition(FAKE_GAVO) == "ucd LIKE 'phot.mag;em.opt.%' OR ucd LIKE 'phot.mag;em.ir.%'") class TestSpatialConstraint: def test_point(self): cons = registry.Spatial([23, -40]) assert cons.get_search_condition(FAKE_GAVO) == "1 = CONTAINS(MOC(6, POINT(23, -40)), coverage)" assert cons._extra_tables == ["rr.stc_spatial"] def test_circle_and_order(self): cons = registry.Spatial([23, -40, 0.25], order=7) assert cons.get_search_condition(FAKE_GAVO) == "1 = CONTAINS(MOC(7, CIRCLE(23, -40, 0.25)), coverage)" def test_polygon(self): cons = registry.Spatial([23, -40, 26, -39, 25, -43]) assert cons.get_search_condition(FAKE_GAVO) == ( "1 = CONTAINS(MOC(6, POLYGON(23, -40, 26, -39, 25, -43)), coverage)") def test_moc(self): cons = registry.Spatial("0/1-3 3/") assert cons.get_search_condition(FAKE_GAVO) == "1 = CONTAINS(MOC('0/1-3 3/'), coverage)" def test_SkyCoord(self): cons = registry.Spatial(SkyCoord(3 * u.deg, -30 * u.deg)) assert cons.get_search_condition(FAKE_GAVO) == "1 = CONTAINS(MOC(6, POINT(3.0, -30.0)), coverage)" assert cons._extra_tables == ["rr.stc_spatial"] def test_SkyCoord_Circle(self): cons = registry.Spatial((SkyCoord(3 * u.deg, -30 * u.deg), 3)) assert cons.get_search_condition(FAKE_GAVO) == "1 = CONTAINS(MOC(6, CIRCLE(3.0, -30.0, 3)), coverage)" assert cons._extra_tables == ["rr.stc_spatial"] def test_enclosed(self): cons = registry.Spatial("0/1-3", intersect="enclosed") assert cons.get_search_condition(FAKE_GAVO) == "1 = CONTAINS(coverage, MOC('0/1-3'))" def test_overlaps(self): cons = registry.Spatial("0/1-3", intersect="overlaps") assert cons.get_search_condition(FAKE_GAVO) == "1 = INTERSECTS(coverage, MOC('0/1-3'))" def test_not_an_intersect_mode(self): with pytest.raises(ValueError, match="'intersect' should be one of 'covers', 'enclosed'," " or 'overlaps' but its current value is 'wrong'."): registry.Spatial("0/1-3", intersect="wrong") def test_no_MOC(self): cons = registry.Spatial((SkyCoord(3 * u.deg, -30 * u.deg), 3)) with pytest.raises(rtcons.RegTAPFeatureMissing) as excinfo: cons.get_search_condition(FAKE_PLAIN) assert (str(excinfo.value) == "Current RegTAP service does not support MOC.") def test_no_spatial_table(self): cons = registry.Spatial((SkyCoord(3 * u.deg, -30 * u.deg), 3)) previous = FAKE_GAVO.tables.pop("rr.stc_spatial") try: with pytest.raises(rtcons.RegTAPFeatureMissing) as excinfo: cons.get_search_condition(FAKE_GAVO) assert (str(excinfo.value) == "stc_spatial missing on current RegTAP service") finally: FAKE_GAVO.tables["rr.spatial"] = previous class TestSpectralConstraint: # These tests might need some float literal fuzziness. I'm just # too lazy at this point to see if pytest has something on board # that would be useful there. def test_energy_float(self): cons = registry.Spectral(1e-19) assert cons.get_search_condition(FAKE_GAVO) == "1e-19 BETWEEN spectral_start AND spectral_end" def test_energy_eV(self): cons = registry.Spectral(5 * u.eV) assert (cons.get_search_condition(FAKE_GAVO) == "8.01088317e-19 BETWEEN spectral_start AND spectral_end") def test_energy_interval(self): cons = registry.Spectral((1e-10 * u.erg, 2e-10 * u.erg)) assert cons.get_search_condition(FAKE_GAVO) == ( "1 = ivo_interval_overlaps(spectral_start, spectral_end, 1e-17, 2e-17)") def test_wavelength(self): cons = registry.Spectral(5000 * u.Angstrom) assert (cons.get_search_condition(FAKE_GAVO) == "3.9728917142978567e-19 BETWEEN spectral_start AND spectral_end") def test_wavelength_interval(self): cons = registry.Spectral((20 * u.cm, 22 * u.cm)) assert (cons.get_search_condition(FAKE_GAVO) == "1 = ivo_interval_overlaps(spectral_start, spectral_end," " 9.932229285744642e-25, 9.029299350676949e-25)") def test_frequency(self): cons = registry.Spectral(2 * u.GHz) assert (cons.get_search_condition(FAKE_GAVO) == "1.32521403e-24 BETWEEN spectral_start AND spectral_end") def test_frequency_interval(self): cons = registry.Spectral((88 * u.MHz, 102 * u.MHz)) assert (cons.get_search_condition(FAKE_GAVO) == "1 = ivo_interval_overlaps(spectral_start, spectral_end," " 5.830941732e-26, 6.758591553e-26)") def test_no_spectral(self): cons = registry.Spectral((88 * u.MHz, 102 * u.MHz)) with pytest.raises(rtcons.RegTAPFeatureMissing) as excinfo: cons.get_search_condition(FAKE_PLAIN) assert (str(excinfo.value) == "stc_spectral missing on current RegTAP service") class TestTemporalConstraint: def test_plain_float(self): cons = registry.Temporal((54130, 54200)) assert (cons.get_search_condition(FAKE_GAVO) == "1 = ivo_interval_overlaps(time_start, time_end, 54130, 54200)") def test_single_time(self): cons = registry.Temporal(Time('2022-01-10')) assert (cons.get_search_condition(FAKE_GAVO) == "59589.0 BETWEEN time_start AND time_end") def test_time_interval(self): cons = registry.Temporal((Time(2459000, format='jd'), Time(59002, format='mjd'))) assert (cons.get_search_condition(FAKE_GAVO) == "1 = ivo_interval_overlaps(time_start, time_end, 58999.5, 59002.0)") def test_multi_times_rejected(self): with pytest.raises(ValueError) as excinfo: registry.Temporal(Time(['1999-01-01', '2010-01-01'])) assert (str(excinfo.value) == "RegTAP time constraints must" " be made from single time instants.") def test_no_temporal(self): cons = registry.Temporal((Time(2459000, format='jd'), Time(59002, format='mjd'))) with pytest.raises(rtcons.RegTAPFeatureMissing) as excinfo: cons.get_search_condition(FAKE_PLAIN) assert (str(excinfo.value) == "stc_temporal missing on current RegTAP service") class TestWhereClauseBuilding: @staticmethod def where_clause_for(*args, **kwargs): cons = list(args) + rtcons.keywords_to_constraints(kwargs) return _build_regtap_query_with_fake(cons).split("\nWHERE\n", 1)[1].split("\nGROUP BY\n")[0] @pytest.mark.usefixtures('messenger_vocabulary') def test_from_constraints(self): assert self.where_clause_for( rtcons.Waveband("EUV"), rtcons.Author("%Hubble%") ) == ("(1 = ivo_hashlist_has(rr.resource.waveband, 'euv'))\n" " AND (role_name LIKE '%Hubble%' AND base_role='creator')") @pytest.mark.usefixtures('messenger_vocabulary') def test_from_keywords(self): assert self.where_clause_for( waveband="EUV", author="%Hubble%" ) == ("(1 = ivo_hashlist_has(rr.resource.waveband, 'euv'))\n" " AND (role_name LIKE '%Hubble%' AND base_role='creator')") @pytest.mark.usefixtures('messenger_vocabulary') def test_mixed(self): assert self.where_clause_for( rtcons.Waveband("EUV"), author="%Hubble%" ) == ("(1 = ivo_hashlist_has(rr.resource.waveband, 'euv'))\n" " AND (role_name LIKE '%Hubble%' AND base_role='creator')") def test_bad_keyword(self): with pytest.raises(TypeError) as excinfo: _build_regtap_query_with_fake( *rtcons.keywords_to_constraints({"foo": "bar"})) # the following assertion will fail when new constraints are # defined (or old ones vanish). I'd say that's a convenient # way to track changes; so, let's update the assertion as we # go. assert str(excinfo.value) == ("foo is not a valid registry" " constraint keyword. Use one of" " author, datamodel, ivoid, keywords, servicetype," " spatial, spectral, temporal, ucd, waveband.") def test_with_legacy_keyword(self): assert self.where_clause_for( "plain", "string" ) == ( '(ivoid IN (SELECT ivoid FROM rr.resource WHERE ' "1=ivo_hasword(res_description, 'plain') UNION SELECT ivoid FROM rr.resource " "WHERE 1=ivo_hasword(res_title, 'plain') UNION SELECT ivoid FROM " "rr.res_subject WHERE rr.res_subject.res_subject ILIKE '%plain%'))\n" ' AND (ivoid IN (SELECT ivoid FROM rr.resource WHERE ' "1=ivo_hasword(res_description, 'string') UNION SELECT ivoid FROM rr.resource " "WHERE 1=ivo_hasword(res_title, 'string') UNION SELECT ivoid FROM " "rr.res_subject WHERE rr.res_subject.res_subject ILIKE '%string%'))") class TestSelectClause: def test_expected_columns(self): # This will break as regtap.RegistryResource.expected_columns # is changed. Just update the assertion then. assert _build_regtap_query_with_fake( rtcons.keywords_to_constraints({"author": "%Hubble%"}) ).split("\nFROM\nrr.resource\n")[0] == ( "SELECT\n" "ivoid, " "res_type, " "short_name, " "res_title, " "content_level, " "res_description, " "reference_url, " "creator_seq, " "created, " "updated, " "rights, " "content_type, " "source_format, " "source_value, " "region_of_regard, " "waveband, " "\n ivo_string_agg(COALESCE(access_url, ''), ':::py VO sep:::') AS access_urls, " "\n ivo_string_agg(COALESCE(standard_id, ''), ':::py VO sep:::') AS standard_ids, " "\n ivo_string_agg(COALESCE(intf_type, ''), ':::py VO sep:::') AS intf_types, " "\n ivo_string_agg(COALESCE(intf_role, ''), ':::py VO sep:::') AS intf_roles") def test_group_by_columns(self): # Again, this will break as regtap.RegistryResource.expected_columns # is changed. Just update the assertion then. assert (_build_regtap_query_with_fake([rtcons.Author("%Hubble%")]).split("\nGROUP BY\n")[-1] == ("ivoid, " "res_type, " "short_name, " "res_title, " "content_level, " "res_description, " "reference_url, " "creator_seq, " "created, " "updated, " "rights, " "content_type, " "source_format, " "source_value, " "region_of_regard, " "waveband")) def test_joined_tables(self): expected_tables = [ # from author constraint "rr.res_role", # default tables "rr.resource", "rr.capability", "rr.interface", ] assert all(table in _build_regtap_query_with_fake([rtcons.Author("%Hubble%")]) for table in expected_tables) @pytest.mark.remote_data def test_all_constraints(): text = rtcons.Freetext("star") author = rtcons.Author(r"%ESA%") servicetype = rtcons.Servicetype("tap") waveband = rtcons.Waveband("optical") datamodel = rtcons.Datamodel("obscore") ivoid = rtcons.Ivoid(r"ivoid") ucd = rtcons.UCD(r"pos.eq.ra") moc = rtcons.Spatial("0/0-11", intersect="overlaps") spectral = rtcons.Spectral((5000 * u.Angstrom, 6000 * u.Angstrom)) time = rtcons.Temporal((50000, 60000)) result = registry.search( text, author, servicetype, waveband, datamodel, ivoid, ucd, moc, spectral, time ) assert result.fieldnames == ( 'ivoid', 'res_type', 'short_name', 'res_title', 'content_level', 'res_description', 'reference_url', 'creator_seq', 'created', 'updated', 'rights', 'content_type', 'source_format', 'source_value', 'region_of_regard', 'waveband', 'access_urls', 'standard_ids', 'intf_types', 'intf_roles') pyvo-1.5.2/pyvo/samp.py000066400000000000000000000065521462331236700150420ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A module with helpers for broadcasting results to samp clients """ import contextlib import os import tempfile from astropy.samp import SAMPIntegratedClient __all__ = [ 'find_client_id', 'send_table_to', 'send_product_to', 'send_spectrum_to', 'send_image_to', 'accessible_table', 'connection'] def find_client_id(conn, name): """returns the SAMP id of the client with samp.name samp_name. This will raise a KeyError if the client is not on the hub. """ for client_id in conn.get_registered_clients(): if conn.get_metadata(client_id).get("samp.name") == name: return client_id raise KeyError(name) def send_table_to(conn, table, client_name=None, name="data"): """ sends astropy_table via SAMP. """ with accessible_table(table) as url: message = { "samp.mtype": "table.load.votable", "samp.params": { "url": url, "name": name, }, } if client_name is None: for client_id in conn.get_registered_clients(): conn.call_and_wait(client_id, message, "10") else: client_id = find_client_id(conn, client_name) conn.call_and_wait(client_id, message, "10") def send_product_to(conn, url, mtype, client_name=None, name="data"): """ sends SAMP messages to load data. This is a helper for send_spectrum_to and send_image_to, which work exactly analogous to each other, except that the mtypes are different. If dest_client_id, this is a broadcast (and we don't wait for any responses). If dest_client_id is given, we wait for acknowledgement by the receiver. """ message = { "samp.mtype": mtype, "samp.params": { "url": url, "name": name, }, } if client_name is None: conn.notify_all(message) else: client_id = find_client_id(conn, client_name) conn.notify(client_id, message) def send_spectrum_to(conn, url, client_name=None, name="data"): """ asks a spectrum client to open a remote spectrum via SAMP. """ send_product_to( conn, url, "spectrum.load.ssa-generic", client_name=client_name, name=name) def send_image_to(conn, url, client_name=None, name="data"): """ asks an image client to open a remote image via SAMP. """ send_product_to( conn, url, "image.load.fits", client_name=client_name, name=name) @contextlib.contextmanager def accessible_table(table): """ a context manager making astropy_table available under a (file) URL for the controlled section. """ handle, f_name = tempfile.mkstemp(suffix=".xml") with open(handle, "w") as f: table.write(output=f, format="votable") try: yield "file://" + f_name finally: os.unlink(f_name) @contextlib.contextmanager def connection( client_name="pyvo client", description="A generic PyVO client", **kwargs ): """ a context manager to give the controlled block a SAMP connection. The program will disconnect as the controlled block is exited. """ client = SAMPIntegratedClient( name=client_name, description=description, **kwargs) client.connect() try: yield client finally: client.disconnect() pyvo-1.5.2/pyvo/utils/000077500000000000000000000000001462331236700146605ustar00rootroot00000000000000pyvo-1.5.2/pyvo/utils/__init__.py000066400000000000000000000001221462331236700167640ustar00rootroot00000000000000from .compat import * from .prototype import prototype_feature, activate_features pyvo-1.5.2/pyvo/utils/compat.py000066400000000000000000000001771462331236700165220ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Placeholder for compatibility constructs """ __all__ = [] pyvo-1.5.2/pyvo/utils/decorators.py000066400000000000000000000010721462331236700173770ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from functools import partial, wraps def stream_decode_content(func): @wraps(func) def wrapper(*args, **kwargs): raw = func(*args, **kwargs) raw.read = partial(raw.read, decode_content=True) return raw return wrapper def response_decode_content(func): @wraps(func) def wrapper(*args, **kwargs): response = func(*args, **kwargs) response.raw.read = partial(response.raw.read, decode_content=True) return response return wrapper pyvo-1.5.2/pyvo/utils/formatting.py000066400000000000000000000035361462331236700174130ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ A collection of routines to format metadata """ import re from itertools import chain import textwrap _parasp = re.compile(r"(?:[ \t\r\f\v]*\n){2,}[ \t\r\f\v]*") _ptag = re.compile(r"\s*(?:)|(?:\\para(?:\\ )*)\s*") def para_format_desc(text, width=78): """ format description text into paragraphs suitable for display in the shell. That is, the output will be one or more plain text paragraphs of the prescribed width (78 characters, the default). The text will be split into separate paragraphs where there occurs (1) a two or more consecutive carriage return, (2) an HTML paragraph tag, or (2) a LaTeX paragraph control sequence. It will attempt other substitutions of HTML and LaTeX markup that sometimes find their way into resource descriptions. """ paras = _parasp.split(text) paras = filter( bool, chain.from_iterable(_ptag.split(para) for para in paras)) paras = ("\n".join( map(lambda ll: ll.strip(), para.splitlines()) ) for para in paras) paras = map(deref_markup, paras) return "\n\n".join(textwrap.fill(para, width) for para in paras) _musubs = [ (re.compile(r"<"), "<"), (re.compile(r">"), ">"), (re.compile(r"&"), "&"), (re.compile(r""), ''), (re.compile(r"

"), ''), (re.compile(r"°"), " deg"), (re.compile(r"\$((?:[^\$]*[\*\+=/^_~><\\][^\$]*)|(?:\w+))\$"), r'\1'), (re.compile(r"\\deg"), " deg"), ] _alink = re.compile(r'''\s*(\S.*\S)\s*''') def deref_markup(text): """ perform some substitutions of common markup suitable for text display. This includes HTML escape sequence """ for pat, repl in _musubs: text = pat.sub(repl, text) text = _alink.sub(r"\3 <\2>", text) return text pyvo-1.5.2/pyvo/utils/http.py000066400000000000000000000012511462331236700162100ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ HTTP utils """ import platform import requests from ..version import version DEFAULT_USER_AGENT = f'pyVO/{version} Python/{platform.python_version()} ({platform.system()})' def use_session(session): """ Return the session passed in, or create a default session to use for this network request. """ if session: return session else: return create_session() def create_session(): """ Create a new empty requests session with a pyvo user agent. """ session = requests.Session() session.headers['User-Agent'] = DEFAULT_USER_AGENT return session pyvo-1.5.2/pyvo/utils/protofeature.py000066400000000000000000000040341462331236700177520ustar00rootroot00000000000000from dataclasses import dataclass __all__ = ['Feature'] @dataclass class Feature: """ A prototype feature implementing a standard that is currently in the process of being approved, but that might change as a result of the approval process. A Feature must have a name. Optionally, a feature may have a *url* that is displayed to the user in case a feature is used without the user explicitly opting in on its usage. The URL is expected to contain more information about the standard and its state in the approval process. """ name: str url: str = '' on: bool = False def should_error(self): """ Should accessing this feature fail? Returns ------- bool Whether accessing this feature should result in an error. """ return not self.on def error(self, function_name): """ Format an error message when the feature is being accesses without the user having opted in its usage. This function will be used as a callback when an error message needs to be displayed to the user, with the function name that was accessed as an argument. Extensions of this class may have additional information to display. Parameters ---------- function_name: str The name of the function associated to this feature and that the user called. Returns ------- str: The error message to be displayed to the user. """ message = (f'{function_name} is part of a prototype feature ({self.name}) that has not ' 'been activated. For information about prototype features please refer to ' 'https://pyvo.readthedocs.io/en/latest/utils/prototypes.html .') if self.url: message += f' For more information about the {self.name} feature please visit {self.url}.' message += (" To suppress this error and enable the feature use " f"`pyvo.utils.activate_features('{self.name}')`") return message pyvo-1.5.2/pyvo/utils/prototype.py000066400000000000000000000101531462331236700172770ustar00rootroot00000000000000import inspect import warnings from functools import wraps from typing import Dict, Iterable from .protofeature import Feature from pyvo.dal.exceptions import PyvoUserWarning __all__ = ['features', 'prototype_feature', 'activate_features', 'PrototypeWarning', 'PrototypeError'] features: Dict[str, "Feature"] = { 'cadc-tb-upload': Feature('cadc-tb-upload', 'https://wiki.ivoa.net/twiki/bin/view/IVOA/TAP-1_1-Next', False) } def prototype_feature(*args): """ Decorator for functions and classes that implement unstable standards which haven't been approved yet. The decorator can be used to tag individual functions or methods. Please refer to the user documentation for details. Parameters ---------- args: iterable of arguments. Currently, the decorator must always be called with one and only one argument, a string representing the feature's name associated with the decorated class or functions. Additional arguments will be ignored, while using the decorator without any arguments will result in a ``PrototypeError`` error. Returns ------- The class or function it decorates, which will be associated to the feature provided as argument. """ feature_name = _parse_args(*args) decorator = _make_decorator(feature_name) return decorator def _set_features(flag, *feature_names: Iterable[str]): names = feature_names or set(features.keys()) for name in names: if not _validate(name): continue features[name].on = flag def activate_features(*feature_names: Iterable[str]): """ Activate one or more prototype features. Parameters ---------- feature_names: Iterable[str] An arbitrary number of feature names. If a feature with that name does not exist, a `PrototypeWarning` will be issued. If no arguments are provided, all features will be activated Returns ------- """ _set_features(True, *feature_names) def deactivate_features(*feature_names: Iterable[str]): """ De-activate one or more prototype features. Parameters ---------- feature_names: Iterable[str] An arbitrary number of feature names. If a feature with that name does not exist, a `PrototypeWarning` will be issued. If no arguments are provided, all features will be de-activated Returns ------- """ _set_features(False, *feature_names) class PrototypeError(Exception): pass class PrototypeWarning(PyvoUserWarning): pass def _parse_args(*args): if not args or callable(args[0]): raise PrototypeError("The `prototype_feature` decorator must always be called with the " "feature name as an argument") return args[0] def _make_decorator(feature_name): def decorator(decorated): if inspect.isfunction(decorated): return _make_wrapper(feature_name, decorated) if inspect.isclass(decorated): method_infos = inspect.getmembers(decorated, predicate=_should_wrap) _wrap_class_methods(decorated, method_infos, feature_name) return decorated return decorator def _validate(feature_name): if feature_name not in features: warnings.warn(f'No such feature "{feature_name}"', category=PrototypeWarning) return False return True def _warn_or_raise(function, feature_name): _validate(feature_name) feature = features[feature_name] if feature.should_error(): raise PrototypeError(feature.error(function.__name__)) def _should_wrap(member): return inspect.isfunction(member) and not member.__name__.startswith('_') def _wrap_class_methods(decorated_class, method_infos, feature_name): for method_info in method_infos: setattr(decorated_class, method_info[0], _make_wrapper(feature_name, method_info[1])) def _make_wrapper(feature_name, function): @wraps(function) def wrapper(*args, **kwargs): _warn_or_raise(function, feature_name) return function(*args, **kwargs) return wrapper pyvo-1.5.2/pyvo/utils/tests/000077500000000000000000000000001462331236700160225ustar00rootroot00000000000000pyvo-1.5.2/pyvo/utils/tests/__init__.py000066400000000000000000000001001462331236700201220ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst pyvo-1.5.2/pyvo/utils/tests/test_http.py000066400000000000000000000006131462331236700204120ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.utils.http """ import platform from pyvo.utils.http import create_session from pyvo.version import version def test_create_session(): test_session = create_session() assert (test_session.headers['User-Agent'] == f'pyVO/{version} Python/{platform.python_version()} ({platform.system()})') pyvo-1.5.2/pyvo/utils/tests/test_prototype.py000066400000000000000000000076471462331236700215160ustar00rootroot00000000000000from copy import deepcopy from unittest import mock import pytest from pyvo.utils import prototype_feature, activate_features, prototype from pyvo.utils.prototype import PrototypeError, PrototypeWarning, Feature @pytest.fixture(name='prototype_function') def _prototype_function(features): features({ 'my-feature': Feature('my-feature', url='http://somewhere/else') }) @prototype_feature('my-feature') def i_am_prototype(arg): arg('called') return i_am_prototype @pytest.fixture(name='features') def _features(): previous_available = deepcopy(prototype.features) prototype.features.clear() def add(features): prototype.features.update(features) yield add prototype.features.clear() prototype.features.update(previous_available) def test_feature_turned_off_by_default(prototype_function): with pytest.raises(PrototypeError) as e: prototype_function(None) assert 'i_am_prototype is part of a prototype feature (my-feature) that has not been activated. ' in str( e.value) assert 'please visit http://somewhere/else' in str(e.value) assert 'https://pyvo.readthedocs.io/en/latest/utils/prototypes.html' in str(e.value) assert "pyvo.utils.activate_features('my-feature')" in str(e.value) def test_activate_feature(prototype_function): probe = mock.Mock() activate_features('my-feature') try: prototype_function(probe) except Exception as exc: assert False, f"Should not have raised {exc}" probe.assert_called_once_with('called') def test_non_existent_feature_warning(): with pytest.warns(PrototypeWarning) as w: activate_features('i dont exist') assert len(w) == 1 assert str(w[0].message) == 'No such feature "i dont exist"' def test_activate_all_features(features): features({ 'feat-one': Feature('feat-one'), 'feat-two': Feature('feat-two') }) activate_features() assert set(prototype.features.keys()) == {'feat-one', 'feat-two'} assert prototype.features['feat-one'].on assert prototype.features['feat-two'].on def test_decorate_class(features, recwarn): features({ 'class': Feature('class') }) probe = mock.Mock() @prototype_feature('class') class SomePrototype: def method(self): probe('method') @staticmethod def static(): probe('static') def __ignore__(self): probe('ignore') with pytest.raises(PrototypeError): SomePrototype.static() with pytest.raises(PrototypeError): SomePrototype().method() SomePrototype().__ignore__() probe.assert_called_once_with('ignore') probe.reset_mock() activate_features('class') SomePrototype.static() probe.assert_called_once_with('static') probe.reset_mock() SomePrototype().method() probe.assert_called_once_with('method') probe.reset_mock() SomePrototype().__ignore__() probe.assert_called_once_with('ignore') def test_decorator_without_args_errors_out(): with pytest.raises(PrototypeError) as e: @prototype_feature def function(): pass assert str(e.value) == ("The `prototype_feature` decorator must always be called with the feature " "name as an argument") def test_decorator_without_args_around_class(): with pytest.raises(PrototypeError) as e: @prototype_feature class Class: pass assert str(e.value) == ("The `prototype_feature` decorator must always be called with the feature " "name as an argument") def test_decorator_with_no_arguments_and_class(): with pytest.raises(PrototypeError) as e: @prototype_feature() class Class: pass assert str(e.value) == ("The `prototype_feature` decorator must always be called with the feature " "name as an argument") pyvo-1.5.2/pyvo/utils/tests/test_url.py000066400000000000000000000004651462331236700202420ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.utils.url """ from pyvo.utils.url import url_sibling def test_url(): url = "http://example.org/tap/capabilities" siblingified = url_sibling(url, "tables") assert siblingified == "http://example.org/tap/tables" pyvo-1.5.2/pyvo/utils/tests/test_vocabularies_remote.py000066400000000000000000000063431462331236700234730ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.utils.vocabularies It's hard to write meaningful tests for those that don't require network connectivity because essentially it's all just wrapping downloads. Hence, I'm just giving in rather than bother with a mock server. """ import os import pathlib import time import pytest from astropy.utils import data from pyvo.dal.exceptions import PyvoUserWarning from pyvo.utils import vocabularies @pytest.mark.remote_data class TestVocabularies: def test_basic_getting(self): # clear the lru cache in case someone else has already used # datalink/core. vocabularies.get_vocabulary.cache_clear() voc = vocabularies.get_vocabulary("datalink/core") assert "progenitor" in voc["terms"] assert data.is_url_in_cache("http://www.ivoa.net/rdf/datalink/core") def test_label_getting(self): voc = vocabularies.get_vocabulary("datalink/core") assert (vocabularies.get_label(voc, "coderived") == "Coderived Data") def test_label_getting_default(self): voc = vocabularies.get_vocabulary("datalink/core") assert vocabularies.get_label(voc, "oov", "Missing") == "Missing" def test_refreshing(self): voc = vocabularies.get_vocabulary("datalink/core", force_update=True) # first make sure that things didn't break assert "progenitor" in voc["terms"] # now guess that a download has actually happened; we don't want # to reflect cache name generation here, so we just check if there's # a recent download in the cache directory dldir = data._get_download_cache_loc() with os.scandir(dldir) as entries: last_change = 0 for entry in entries: last_change = max(last_change, entry.stat().st_mtime) assert time.time() - last_change < 2 def test_non_existing_voc(self): with pytest.raises(vocabularies.VocabularyError): vocabularies.get_vocabulary("not_an_ivoa_vocabulary") def test_failed_update(self): # Create a fake vocabulary and make it so old the machine # will want to refresh it. fake_voc = "http://www.ivoa.net/rdf/astropy-test-failure" cache_dir = pathlib.Path(data._get_download_cache_loc() ) / data._url_to_dirname(fake_voc) cache_dir.mkdir(exist_ok=True) cache_name = cache_dir / "contents" with open(cache_name, "w") as f: f.write("{}") with open(cache_dir / "url", "w") as f: f.write(fake_voc) os.utime(cache_name, (1000000000, 1000000000)) with pytest.warns(PyvoUserWarning) as msgs: vocabularies.get_vocabulary("astropy-test-failure") # this sometimes catches a warning about an unclosed socket that, # I think, originates somewhere else; let me work around it for # the moment. for msg in msgs: if str(msg.message) == ("Updating cache for the vocabulary" " astropy-test-failure failed: HTTP Error 404: Not Found"): break else: raise AssertionError("No warning about failed cache update") pyvo-1.5.2/pyvo/utils/url.py000066400000000000000000000012011462331236700160260ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst """ URL utils """ from urllib.parse import urlparse, urlunparse from posixpath import split as pathsplit, join as pathjoin def url_sibling(url, sibling): """ Replaces the last path element in an url Parameters ---------- url : str The url for which the last path element should be replaced sibling : str The replace value """ parsed = urlparse(url) newpath_segments = pathsplit(parsed.path)[:-1] + (sibling,) newpath = pathjoin(*newpath_segments) return urlunparse(list(parsed[:2]) + [newpath] + list(parsed[3:])) pyvo-1.5.2/pyvo/utils/vocabularies.py000066400000000000000000000043251462331236700177150ustar00rootroot00000000000000""" A shallow interface to IVOA vocabularies. See http://ivoa.net/documents/Vocabularies/ (>= version 2) for the larger background. In this module, we essentially wrap the retrieval and caching of the desise files. """ import functools import json import os import time import warnings from astropy.utils.data import download_file, clear_download_cache from pyvo.dal.exceptions import PyvoUserWarning IVOA_VOCABULARY_ROOT = "http://www.ivoa.net/rdf/" class VocabularyError(Exception): """A generic error that occurred when interacting with the IVOA vocabulary repository. """ @functools.lru_cache() def get_vocabulary(voc_name, force_update=False): """returns an IVOA vocabulary in its "desise" form. See Vocabularies in the VO 2 to see what is inside of this. This will use a cache to avoid repeated updates, but it will attempt to re-download if the cached copy is older than 6 months. """ src_url = IVOA_VOCABULARY_ROOT + voc_name if force_update: clear_download_cache(src_url) try: src_name = download_file( src_url, cache=True, show_progress=False, http_headers={"accept": "application/x-desise+json"}) except Exception as msg: raise VocabularyError("No such vocabulary: {} ({})".format( voc_name, msg)) if time.time() - os.path.getmtime(src_name) > 3600 * 60 * 150: # attempt a re-retrieval, but ignore failure try: src_name = download_file( IVOA_VOCABULARY_ROOT + voc_name, cache="update", show_progress=False, http_headers={"accept": "application/x-desise+json"}) except Exception as msg: warnings.warn("Updating cache for the vocabulary" f" {voc_name} failed: {msg}", category=PyvoUserWarning) with open(src_name, "r", encoding="utf-8") as f: return json.load(f) def get_label(voc, term, default=None): """returns the label of term if it's in the desise vocabulary voc, term capitalised otherwise. """ if term in voc["terms"]: return voc["terms"][term]["label"] else: return default # vi:et:sw=4:sta pyvo-1.5.2/pyvo/utils/xml/000077500000000000000000000000001462331236700154605ustar00rootroot00000000000000pyvo-1.5.2/pyvo/utils/xml/__init__.py000066400000000000000000000000001462331236700175570ustar00rootroot00000000000000pyvo-1.5.2/pyvo/utils/xml/elements.py000066400000000000000000000355171462331236700176610ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from inspect import getmembers from functools import partial import warnings from astropy.utils.xml import iterparser from astropy.io.votable.exceptions import warn_or_raise from pyvo.utils.xml.exceptions import UnknownElementWarning __all__ = [ "xmlattribute", "xmlelement", "make_add_complexcontent", "make_add_simplecontent", "Element", "ElementWithXSIType", "ContentMixin", "parse_for_object"] def parse_for_object( source, object_type, pedantic=None, filename=None, _debug_python_based_parser=False ): """ Parses an xml file (or file-like object), and returns a object of specified object_type. object_type must be a subtype of `~pyvo.utils.xml.elements.Element` type Parameters ---------- source : str or readable file-like object Path or file object containing a tableset xml file. object : object type to return (subtype `~pyvo.utils.xml.elements.Element`) pedantic : bool, optional When `True`, raise an error when the file violates the spec, otherwise issue a warning. Warnings may be controlled using the standard Python mechanisms. See the `warnings` module in the Python standard library for more information. Defaults to False. filename : str, optional A filename, URL or other identifier to use in error messages. If *filename* is None and *source* is a string (i.e. a path), then *source* will be used as a filename for error messages. Therefore, *filename* is only required when source is a file-like object. Returns ------- object : `~pyvo.utils.xml.elements.Element` object or None See also -------- pyvo.io.vosi.exceptions : The exceptions this function may raise. """ config = { 'pedantic': pedantic, 'filename': filename } if filename is None and isinstance(source, str): config['filename'] = source with iterparser.get_xml_iterator( source, _debug_python_based_parser=_debug_python_based_parser ) as iterator: return object_type( config=config, pos=(1, 1)).parse(iterator, config) class xmlattribute(property): def __init__(self, fget=None, fset=None, fdel=None, doc=None, name=None): super().__init__(fget, fset, fdel, doc) if name: self.name = name elif fget is not None: self.name = fget.__name__ else: raise ValueError( "xmlattribute either needs a getter or a element name or both") def __call__(self, fget): return self.__class__(fget, name=self.name) def getter(self, fget): return self.__class__( fget, self.fset, self.fdel, self.__doc__, self.name) def setter(self, fset): return self.__class__( self.fget, fset, self.fdel, self.__doc__, self.name) def deleter(self, fdel): return self.__class__( self.fget, self.fset, fdel, self.__doc__, self.name) class xmlelement(property): """ """ def __init__( self, fget=None, fset=None, fdel=None, fadd=None, fformat=None, doc=None, name=None, ns=None, plain=False, cls=None, multiple_exc=None ): super().__init__(fget, fset, fdel, doc) if name: self.name = name elif fget is not None: self.name = fget.__name__ else: self.name = None self.ns = ns self.plain = plain self.cls = cls self.multiple_exc = multiple_exc self.fadd = fadd self.fformat = fformat def __call__(self, fget): return self.__class__( fget, name=self.name or fget.__name__, ns=self.ns, plain=self.plain, cls=self.cls, multiple_exc=self.multiple_exc ) def __get__(self, obj, owner=None): if obj is not None: val = super().__get__(obj, owner) if self.plain: return val elif not isinstance(val, (Element, list)): element = ContentMixin(_name=self.name, _ns=self.ns) element.content = val return element else: return val else: return super().__get__(obj, owner) def getter(self, fget): return self.__class__( fget, self.fset, self.fdel, self.fadd, self.fformat, self.__doc__, self.name, self.ns, self.plain, self.cls, self.multiple_exc) def setter(self, fset): return self.__class__( self.fget, fset, self.fdel, self.fadd, self.fformat, self.__doc__, self.name, self.ns, self.plain, self.cls, self.multiple_exc) def deleter(self, fdel): return type(self)( self.fget, self.fset, fdel, self.fadd, self.fformat, self.__doc__, self.name, self.ns, self.plain, self.cls, self.multiple_exc) def adder(self, fadd): if self.cls: raise RuntimeError( 'xmlelement cls parameter has no effect when adder is' ' defined') if self.multiple_exc: raise RuntimeError( 'xmlelement multiple_exc parameter has no effect when' ' adder is defined') return self.__class__( self.fget, self.fset, self.fdel, fadd, self.fformat, self.__doc__, self.name, self.ns, self.plain, self.cls, self.multiple_exc) def formatter(self, fformat): return self.__class__( self.fget, self.fset, self.fdel, self.fadd, fformat, self.__doc__, self.name, self.ns, self.plain, self.cls, self.multiple_exc) def object_attrs(obj): objtype = type(obj) attrs = { getattr(objtype, name).name: value for name, value in getmembers(obj) if isinstance(getattr(objtype, name, None), xmlattribute)} return attrs def object_children(obj): objtype = type(obj) try: for child in obj: if isinstance(child, Element): yield (child._Element__name, None, child) except TypeError: for name, child in getmembers(obj): if child is None: continue descr = getattr(objtype, name, None) if isinstance(descr, xmlelement): element_name = descr.name if descr.fformat: fformat = partial(descr.fformat, obj) else: fformat = None yield (element_name, fformat, child) elif isinstance(child, Element): yield (child._Element__name, None, child) def object_mapping(obj): objtype = type(obj) for name, val in getmembers(obj): descr = getattr(objtype, name, None) if isinstance(descr, xmlelement): if descr.fadd is None: if descr.cls is None: fadd = make_add_simplecontent( obj, descr.name, name, descr.multiple_exc) else: fadd = make_add_complexcontent( obj, descr.name, name, descr.cls, descr.multiple_exc) else: fadd = partial(descr.fadd, obj) yield descr.name, fadd def make_add_complexcontent( self, element_name, attr_name, cls_, exc_class=None): """ Factory for generating add functions for elements with complex content. """ def add_complexcontent(iterator, tag, data, config, pos): attr = getattr(self, attr_name) element = cls_( config=config, pos=pos, _name=element_name, **data) if attr and exc_class is not None: warn_or_raise( exc_class, args=element_name, config=config, pos=pos) if isinstance(getattr(self, attr_name, None), list): getattr(self, attr_name).append(element) else: setattr(self, attr_name, element) element.parse(iterator, config) return add_complexcontent def make_add_simplecontent( self, element_name, attr_name, exc_class=None, check_func=None, data_func=None): """ Factory for generating add functions for elements with simple content. This means elements with no child elements. If exc_class is given, warn or raise if element was already set. """ def add_simplecontent(iterator, tag_ignored, data_ignored, config, pos_ignored): # Ignored parameters are kept in the API signature to be compatible # with other functions. for start, tag, data, pos in iterator: if not start and tag == element_name: attr = getattr(self, attr_name) if attr and exc_class: warn_or_raise( exc_class, args=self._Element__name, config=config, pos=pos) if check_func: check_func(data, config, pos) if data_func: data = data_func(data) if isinstance(getattr(self, attr_name), list): getattr(self, attr_name).append(data) else: setattr(self, attr_name, data or None) break return add_simplecontent class Element: """ A base class for all classes that represent XML elements. Subclasses and Mixins must initialize their independent attributes after calling ``super().__init__``. """ def __init__(self, config=None, pos=None, _name='', _ns='', **kwargs): if config is None: config = {} self._config = config self._pos = pos self.__name = _name self.__ns = _ns self._tag_mapping = {} def _add_unknown_tag(self, iterator, tag, data, config, pos): if tag != 'xml': warn_or_raise( UnknownElementWarning, UnknownElementWarning, tag, config, pos) def _end_tag(self, tag, data, pos): pass def _ignore_add(self, iterator, tag, data, config, pos): pass def parse(self, iterator, config): """ For internal use. Parse the XML content of the children of the element. Override this method and do after-parse checks after calling ``super().parse``, if you need to. Parameters ---------- iterator : xml iterator An iterator over XML elements as returned by `~astropy.utils.xml.iterparser.get_xml_iterator`. config : dict The configuration dictionary that affects how certain elements are read. """ tag_mapping = dict(object_mapping(self)) for start, tag, data, pos in iterator: if start: tag_mapping.get(tag, self._add_unknown_tag)( iterator, tag, data, config, pos) else: if tag == self._Element__name: self._end_tag(tag, data, pos) break return self def to_xml(self, w, **kwargs): if self._Element__ns: name = ':'.join((self._Element__ns, self._Element__name)) else: name = self._Element__name with w.tag(name, attrib=object_attrs(self)): for name, formatter, child in object_children(self): if isinstance(child, Element): child.to_xml(w, formatter=formatter) else: if formatter: child = formatter() if not child: child = '' w.element(name, str(child)) class ElementWithXSIType(Element): """ An XML element that supports type dispatch through xsi:type. When a class A is derived from this, it gains a decorator register_xsi_type, which classes derived from A can use to say "construct me rather than A when xsi:type has the value I'm putting in. At this point we are doing *no* namespace processing in our XML parsing. Hence, we discard any prefixes both when registering and when matching. We probably should do namespaces one day; astropy.utils.xml will presumably learn them when they add VO-DML support. Let's revisit this when it's there safely. Meanwhere, use canonical Registry prefixes (cf. RegTAP 1.1, sect. 5) everywhere in your code; these will continue to be safe no matter what. """ _xsi_type_mapping = {} @classmethod def register_xsi_type(cls, typename): """Decorator factory for registering subtypes.""" def register(class_): """Decorator for registering subtypes""" cls._xsi_type_mapping[typename.split(":")[-1]] = class_ return class_ return register def __new__(cls, *args, **kwargs): xsi_type = None # Another namespace trouble: people can bind the xsi URI # to anything, *and* it's not unlikely they have another # type attribute, too. Wiggle out of it by preferring a # literal xsi:type and otherwise hope for the best. This # really needs to be fixed when we switch to namespace-aware # parsing. for name, val in kwargs.items(): if name == "xsi:type": xsi_type = val break elif name.split(":")[-1] == "type": xsi_type = val if xsi_type is None: dtype = cls else: try: dtype = cls._xsi_type_mapping[xsi_type.split(":")[-1]] except KeyError: warnings.warn(f"Unknown xsi:type {xsi_type} ignored") dtype = cls obj = Element.__new__(dtype) obj.__init__(*args, **kwargs) return obj class ContentMixin(Element): """ Mixin class for elements with inner content. """ def __init__(self, config=None, pos=None, _name=None, _ns=None, **kwargs): super().__init__(config, pos, _name, _ns, **kwargs) self._content = None def __bool__(self): return bool(self.content) __nonzero__ = __bool__ def _end_tag(self, tag, data, pos): self.content = data def _content_check(self, content): pass def _content_parse(self, content): return content @property def content(self): """The inner content of the element.""" return self._content @content.setter def content(self, content): self._content_check(content) self._content = self._content_parse(content) def to_xml(self, w, **kwargs): if self._Element__ns: name = ':'.join((self._Element__ns, self._Element__name)) else: name = self._Element__name try: content = kwargs['formatter']() except (KeyError, TypeError): content = self.content if content is not None: w.element(name, str(content), attrib=object_attrs(self)) pyvo-1.5.2/pyvo/utils/xml/exceptions.py000066400000000000000000000021061462331236700202120ustar00rootroot00000000000000# Licensed under a 3-clause BSD style license - see LICENSE.rst from astropy.utils.exceptions import AstropyWarning __all__ = ['XMLWarning', 'UnknownElementWarning'] def _format_message(message, name, config=None, pos=None): if config is None: config = {} if pos is None: pos = ('?', '?') filename = config.get('filename', '?') return '{}:{}:{}: {}: {}'.format(filename, pos[0], pos[1], name, message) class XMLWarning(AstropyWarning): """ Base warning for violations of XML specifications """ def __init__(self, args, config=None, pos=None): if config is None: config = {} if not isinstance(args, tuple): args = (args, ) msg = self.message_template.format(*args) self.formatted_message = _format_message( msg, self.__class__.__name__, config, pos) Warning.__init__(self, self.formatted_message) class UnknownElementWarning(XMLWarning): """ Warning for missing xml elements """ message_template = "Unknown element {}" default_args = ('x',) pyvo-1.5.2/pyvo/utils/xml/tests/000077500000000000000000000000001462331236700166225ustar00rootroot00000000000000pyvo-1.5.2/pyvo/utils/xml/tests/test_elements.py000066400000000000000000000055671462331236700220640ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst """ Tests for pyvo.utils.xml.elements """ import io import pytest from astropy.utils.xml import iterparser from pyvo.utils.xml import elements class TBase(elements.ElementWithXSIType): pass @TBase.register_xsi_type("foo:TOther1") class TOther1(TBase): pass # it's unclear whether we want to support unprefixed type names # once we properly handle XML namespaces. Feel free to adapt # the following declaration. @TBase.register_xsi_type("TOther2") class TOther2(TBase): pass class _Root(elements.Element): def __init__(self): super().__init__(self, _name="root") self._tbase = None @elements.xmlelement(name="tbase", cls=TBase) def tbase(self): return self._tbase @tbase.setter def tbase(self, obj): self._tbase = obj class TestXSIType: # Note: most of these tests will need namespace declarations # once we're properly dealing with namespaces. However, I # don't want to predicate an API to proper namespace support, # so they're missing for now. def _parse_string(self, xml_source): with iterparser.get_xml_iterator(io.BytesIO(xml_source)) as i: return _Root().parse(i, {}) def test_no_type(self): found_type = self._parse_string(b'').tbase.__class__ assert found_type.__name__ == "TBase" def test_prefixed_type(self): found_type = self._parse_string(b'' ).tbase.__class__ assert found_type.__name__ == "TOther1" def test_unprefixed_type(self): # This is undesired behaviour; this test should fail once # we've properly parsing XML found_type = self._parse_string(b'' ).tbase.__class__ assert found_type.__name__ == "TOther1" def test_badprefixed_type(self): found_type = self._parse_string(b'' ).tbase.__class__ assert found_type.__name__ == "TOther2" def test_xsi_ignorable(self): # This is again unwelcome behaviour, but unavoidable as long # as we hack around namespaces found_type = self._parse_string(b'' ).tbase.__class__ assert found_type.__name__ == "TOther2" def test_xsi_preferred(self): # Another piece unwelcome behaviour. found_type = self._parse_string( b'' ).tbase.__class__ assert found_type.__name__ == "TOther2" def test_bad_type(self): with pytest.warns(match='Unknown xsi:type ns1:NoSuchType ignored'): self._parse_string(b'') pyvo-1.5.2/setup.cfg000066400000000000000000000062721462331236700143530ustar00rootroot00000000000000[tool:pytest] minversion = 6.0 norecursedirs = build docs/_build testpaths = "pyvo" "docs" astropy_header = true doctest_plus = enabled text_file_format = rst addopts = --doctest-rst --doctest-continue-on-failure remote_data_strict = true filterwarnings = error ignore:numpy.ndarray size changed:RuntimeWarning ignore:unclosed =4.1 requests python_requires = >=3.8 [options.extras_require] all = pillow test = pytest-doctestplus>=0.13 pytest-astropy requests-mock docs = sphinx-astropy [options.package_data] pyvo.auth.tests = data/tap/*.xml pyvo.io.uws.tests = data/*.xml pyvo.io.vosi.tests = data/*.xml, data/tables/*.xml, data/capabilities/*.xml pyvo.registry.tests = data/*.xml, data/*.desise pyvo.dal.tests = data/*.xml, data/*/* [coverage:run] omit = pyvo/_astropy_init* pyvo/conftest.py pyvo/*setup_package* pyvo/tests/* pyvo/*/tests/* pyvo/extern/* pyvo/version* */pyvo/_astropy_init* */pyvo/conftest.py */pyvo/*setup_package* */pyvo/tests/* */pyvo/*/tests/* */pyvo/extern/* */pyvo/version* [coverage:report] exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain about packages we have installed except ImportError # Don't complain if tests don't hit assertions raise AssertionError raise NotImplementedError # Don't complain about script hooks def main\(.*\): # Ignore branches that don't pertain to this version of Python pragma: py{ignore_python_version} # Don't complain about IPython completion helper def _ipython_key_completions_ pyvo-1.5.2/setup.py000066400000000000000000000036411462331236700142410ustar00rootroot00000000000000#!/usr/bin/env python # Licensed under a 3-clause BSD style license - see LICENSE.rst # NOTE: The configuration for the package, including the name, version, and # other information are set in the setup.cfg file. import os import sys from setuptools import setup # First provide helpful messages if contributors try and run legacy commands # for tests or docs. TEST_HELP = """ Note: running tests is no longer done using 'python setup.py test'. Instead you will need to run: tox -e test If you don't already have tox installed, you can install it with: pip install tox If you only want to run part of the test suite, you can also use pytest directly with:: pip install -e .[test] pytest For more information, see: http://docs.astropy.org/en/latest/development/testguide.html#running-tests """ if 'test' in sys.argv: print(TEST_HELP) sys.exit(1) DOCS_HELP = """ Note: building the documentation is no longer done using 'python setup.py build_docs'. Instead you will need to run: tox -e build_docs If you don't already have tox installed, you can install it with: pip install tox You can also build the documentation with Sphinx directly using:: pip install -e .[docs] cd docs make html For more information, see: http://docs.astropy.org/en/latest/install.html#builddocs """ if 'build_docs' in sys.argv or 'build_sphinx' in sys.argv: print(DOCS_HELP) sys.exit(1) VERSION_TEMPLATE = """ # Note that we need to fall back to the hard-coded version if either # setuptools_scm can't be imported or setuptools_scm can't determine the # version, so we catch the generic 'Exception'. try: from setuptools_scm import get_version version = get_version(root='..', relative_to=__file__) except Exception: version = '{version}' """.lstrip() setup(use_scm_version={'write_to': os.path.join('pyvo', 'version.py'), 'write_to_template': VERSION_TEMPLATE}) pyvo-1.5.2/tox.ini000066400000000000000000000036501462331236700140420ustar00rootroot00000000000000[tox] # Please note that not all the combinations below are guaranteed to work # as oldestdeps and devastropy might not support the full python range # listed here envlist = py{38,39,310,311,312}-test{,-alldeps,-oldestdeps,-devdeps}{,-online}{,-cov} linkcheck codestyle build_docs requires = setuptools >= 30.3.0 pip >= 19.3.1 [testenv] extras = test: test alldeps: all description = run tests oldestdeps: with oldest supported dependencies devdeps: with development version of dependencies cov: determine the code coverage setenv = PYTEST_ARGS = -rsxf --show-capture=no online: PYTEST_ARGS = --remote-data=any --reruns=1 --reruns-delay 10 -rsxf --show-capture=no devdeps: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/scientific-python-nightly-wheels/simple https://pypi.anaconda.org/liberfa/simple https://pypi.anaconda.org/astropy/simple deps = cov: coverage devdeps: numpy>=0.0.dev0 devdeps: pyerfa>=0.0.dev0 devdeps: astropy>=0.0.dev0 oldestdeps: astropy==4.1 # We set a suitably old numpy along with an old astropy, no need to pick up # deprecations and errors due to their unmatching versions oldestdeps: numpy==1.16 online: pytest-rerunfailures commands = pip freeze !cov: pytest --pyargs {env:PYTEST_ARGS} cov: pytest --pyargs --cov pyvo --cov-config={toxinidir}/setup.cfg {env:PYTEST_ARGS} cov: coverage xml -o {toxinidir}/coverage.xml [testenv:linkcheck] changedir = docs description = check the links in the HTML docs extras = docs commands = pip freeze sphinx-build -W -b linkcheck . _build/html [testenv:build_docs] changedir = docs description = invoke sphinx-build to build the HTML docs extras = docs commands = pip freeze sphinx-build -W -b html . _build/html [testenv:codestyle] skip_install = true description = check code style deps = flake8 changedir = {toxinidir} commands = flake8 pyvo --count