From 087753cd42c406f1e060c1822dcd9b5fda3d60f4 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sat, 27 Aug 2022 21:01:55 -0700 Subject: [PATCH 001/348] sites.db is better name than dogs.db in this example --- docs/cli.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli.rst b/docs/cli.rst index d071158..ed089ac 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -259,7 +259,7 @@ You can use the ``--functions`` option to pass a block of Python code that defin This example defines a function which extracts the domain from a URL:: - $ sqlite-utils query dogs.db "select url, domain(url) from urls" --functions ' + $ sqlite-utils query sites.db "select url, domain(url) from urls" --functions ' from urllib.parse import urlparse def domain(url): From ecf1d40112e52a8f4e509c39b98caae996b7bc36 Mon Sep 17 00:00:00 2001 From: Jacob Chapman <7908073+chapmanjacobd@users.noreply.github.com> Date: Tue, 30 Aug 2022 22:40:35 -0500 Subject: [PATCH 002/348] table.search_sql(include_rank=True) option (#480) * search_sql add include_rank option * add test * add FTS4 test * Apply Black Thanks, @chapmanjacobd --- sqlite_utils/db.py | 4 ++++ tests/test_fts.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index b653cc4..27c46b0 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -2376,6 +2376,7 @@ class Table(Queryable): limit: Optional[int] = None, offset: Optional[int] = None, where: Optional[str] = None, + include_rank: bool = False, ) -> str: """ " Return SQL string that can be used to execute searches against this table. @@ -2385,6 +2386,7 @@ class Table(Queryable): :param limit: SQL limit :param offset: SQL offset :param where: Extra SQL fragment for the WHERE clause + :param include_rank: Select the search rank column in the final query """ # Pick names for table and rank column that don't clash original = "original_" if self.name == "original" else "original" @@ -2427,6 +2429,8 @@ class Table(Queryable): rank_implementation = "rank_bm25(matchinfo([{}], 'pcnalx'))".format( fts_table ) + if include_rank: + columns_with_prefix_sql += ",\n " + rank_implementation + " rank" limit_offset = "" if limit is not None: limit_offset += " limit {}".format(limit) diff --git a/tests/test_fts.py b/tests/test_fts.py index 477d599..ecfcff9 100644 --- a/tests/test_fts.py +++ b/tests/test_fts.py @@ -556,6 +556,50 @@ def test_enable_fts_error_message_on_views(): " rank_bm25(matchinfo([books_fts], 'pcnalx'))" ), ), + ( + {"include_rank": True}, + "FTS5", + ( + "with original as (\n" + " select\n" + " rowid,\n" + " *\n" + " from [books]\n" + ")\n" + "select\n" + " [original].*,\n" + " [books_fts].rank rank\n" + "from\n" + " [original]\n" + " join [books_fts] on [original].rowid = [books_fts].rowid\n" + "where\n" + " [books_fts] match :query\n" + "order by\n" + " [books_fts].rank" + ), + ), + ( + {"include_rank": True}, + "FTS4", + ( + "with original as (\n" + " select\n" + " rowid,\n" + " *\n" + " from [books]\n" + ")\n" + "select\n" + " [original].*,\n" + " rank_bm25(matchinfo([books_fts], 'pcnalx')) rank\n" + "from\n" + " [original]\n" + " join [books_fts] on [original].rowid = [books_fts].rowid\n" + "where\n" + " [books_fts] match :query\n" + "order by\n" + " rank_bm25(matchinfo([books_fts], 'pcnalx'))" + ), + ), ], ) def test_search_sql(kwargs, fts, expected): From 686eed9a49faf87b0f2d3eba5fb12caa0250988f Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Thu, 1 Sep 2022 18:37:13 -0700 Subject: [PATCH 003/348] Typo in release notes --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 6ac1c18..1245014 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -8,7 +8,7 @@ ----------------- - The ``sqlite-utils query``, ``memory`` and ``bulk`` commands now all accept a new ``--functions`` option. This can be passed a string of Python code, and any callable objects defined in that code will be made available to SQL queries as custom SQL functions. See :ref:`cli_query_functions` for details. (:issue:`471`) -- ``db[table].create(...)`` method now accepts a new ``transform=True`` parameter. If the table already exists it will be :ref:`transform ` to match the schema configuration options passed to the function. This may result in columns being added or dropped, column types being changed, column order being updated or not null and default values for columns being set. (:issue:`467`) +- ``db[table].create(...)`` method now accepts a new ``transform=True`` parameter. If the table already exists it will be :ref:`transformed ` to match the schema configuration options passed to the function. This may result in columns being added or dropped, column types being changed, column order being updated or not null and default values for columns being set. (:issue:`467`) - Related to the above, the ``sqlite-utils create-table`` command now accepts a ``--transform`` option. - New introspection property: ``table.default_values`` returns a dictionary mapping each column name with a default value to the configured default value. (:issue:`475`) - The ``--load-extension`` option can now be provided a path to a compiled SQLite extension module accompanied by the name of an entrypoint, separated by a colon - for example ``--load-extension ./lines0:sqlite3_lines0_noread_init``. This feature is modelled on code first `contributed to Datasette `__ by Alex Garcia. (:issue:`470`) From 5b969273f1244b1bcf3e4dc071cdf17dab35d5f8 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Thu, 1 Sep 2022 18:44:56 -0700 Subject: [PATCH 004/348] Markup tweak --- docs/cli.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index ed089ac..3adec26 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -2097,14 +2097,14 @@ Use the ``--type`` option to specify a geometry type. By default, ``add-geometry Eight (case-insensitive) types are allowed: - * POINT - * LINESTRING - * POLYGON - * MULTIPOINT - * MULTILINESTRING - * MULTIPOLYGON - * GEOMETRYCOLLECTION - * GEOMETRY +* POINT +* LINESTRING +* POLYGON +* MULTIPOINT +* MULTILINESTRING +* MULTIPOLYGON +* GEOMETRYCOLLECTION +* GEOMETRY .. _cli_spatialite_indexes: From d9b9e075f07a20f1137cd2e34ed5d3f1a3db4ad8 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 6 Sep 2022 20:45:36 -0700 Subject: [PATCH 005/348] Documented the release process --- docs/changelog.rst | 2 ++ docs/contributing.rst | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 1245014..adeb6b1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,3 +1,5 @@ +.. _changelog: + =========== Changelog =========== diff --git a/docs/contributing.rst b/docs/contributing.rst index 7ac1ad8..a1041f5 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -78,3 +78,40 @@ Both commands can then be run in the root of the project like this:: mypy sqlite_utils All three of these tools are run by our CI mechanism against every commit and pull request. + +.. _release_process: + +Release process +=============== + +Releases are performed using tags. When a new release is published on GitHub, a `GitHub Action workflow `__ will perform the following: + +* Run the unit tests against all supported Python versions. If the tests pass... +* Build a wheel bundle of the underlying Python source code +* Push that new wheel up to PyPI: https://pypi.org/project/sqlite-utils/ + +To deploy new releases you will need to have push access to the GitHub repository. + +``sqlite-utils`` follows `Semantic Versioning `__:: + + major.minor.patch + +We increment ``major`` for backwards-incompatible releases. + +We increment ``minor`` for new features. + +We increment ``patch`` for bugfix releass. + +To release a new version, first create a commit that updates the version number in ``setup.py`` and the :ref:`the changelog ` with highlights of the new version. An example `commit can be seen here `__:: + + # Update changelog + git commit -m " Release 3.29 + + Refs #423, #458, #467, #469, #470, #471, #472, #475" -a + git push + +Referencing the issues that are part of the release in the commit message ensures the name of the release shows up on those issue pages, e.g. `here `__. + +You can generate the list of issue references for a specific release by copying and pasting text from the release notes or GitHub changes-since-last-release view into this `Extract issue numbers from pasted text `__ tool. + +To create the tag for the release, create `a new release `__ on GitHub matching the new version number. You can convert the release notes to Markdown by copying and pasting the rendered HTML into this `Paste to Markdown tool `__. From 0b315d3fa83c1584eaeec32f24912898621e437a Mon Sep 17 00:00:00 2001 From: Mischa Untaga <99098079+MischaU8@users.noreply.github.com> Date: Thu, 15 Sep 2022 22:37:51 +0200 Subject: [PATCH 006/348] progressbar for inserts/upserts of other file formats * progressbar for inserts/upserts of other file formats, closes #485 * Pin to Python 3.10.6 for the moment as workaround for mypy error Co-authored-by: Simon Willison --- .github/workflows/publish.yml | 4 +-- .github/workflows/test.yml | 4 +-- sqlite_utils/cli.py | 54 ++++++++++++++++++----------------- sqlite_utils/utils.py | 5 ++++ 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 59c8a23..355f271 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,12 +9,12 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10.6"] os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - uses: actions/cache@v2 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9303965..788df25 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,13 +10,13 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10.6"] numpy: [0, 1] os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - uses: actions/cache@v2 diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index c51b101..767b170 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -953,14 +953,16 @@ def insert_upsert_implementation( decoded = io.TextIOWrapper(file, encoding=encoding) tracker = None - if csv or tsv: - if sniff: - # Read first 2048 bytes and use that to detect - first_bytes = sniff_buffer.peek(2048) - dialect = csv_std.Sniffer().sniff(first_bytes.decode(encoding, "ignore")) - else: - dialect = "excel-tab" if tsv else "excel" - with file_progress(decoded, silent=silent) as decoded: + with file_progress(decoded, silent=silent) as decoded: + if csv or tsv: + if sniff: + # Read first 2048 bytes and use that to detect + first_bytes = sniff_buffer.peek(2048) + dialect = csv_std.Sniffer().sniff( + first_bytes.decode(encoding, "ignore") + ) + else: + dialect = "excel-tab" if tsv else "excel" csv_reader_args = {"dialect": dialect} if delimiter: csv_reader_args["delimiter"] = delimiter @@ -977,24 +979,24 @@ def insert_upsert_implementation( if detect_types: tracker = TypeTracker() docs = tracker.wrap(docs) - elif lines: - docs = ({"line": line.strip()} for line in decoded) - elif text: - docs = ({"text": decoded.read()},) - else: - try: - if nl: - docs = (json.loads(line) for line in decoded if line.strip()) - else: - docs = json.load(decoded) - if isinstance(docs, dict): - docs = [docs] - except json.decoder.JSONDecodeError: - raise click.ClickException( - "Invalid JSON - use --csv for CSV or --tsv for TSV files" - ) - if flatten: - docs = (dict(_flatten(doc)) for doc in docs) + elif lines: + docs = ({"line": line.strip()} for line in decoded) + elif text: + docs = ({"text": decoded.read()},) + else: + try: + if nl: + docs = (json.loads(line) for line in decoded if line.strip()) + else: + docs = json.load(decoded) + if isinstance(docs, dict): + docs = [docs] + except json.decoder.JSONDecodeError: + raise click.ClickException( + "Invalid JSON - use --csv for CSV or --tsv for TSV files" + ) + if flatten: + docs = (dict(_flatten(doc)) for doc in docs) if convert: variable = "row" diff --git a/sqlite_utils/utils.py b/sqlite_utils/utils.py index c0b7bf1..8754554 100644 --- a/sqlite_utils/utils.py +++ b/sqlite_utils/utils.py @@ -155,6 +155,11 @@ class UpdateWrapper: self._update(len(line)) yield line + def read(self, size=-1): + data = self._wrapped.read(size) + self._update(len(data)) + return data + @contextlib.contextmanager def file_progress(file, silent=False, **kwargs): From 85247038f70d7eb2f3e272cfeaa4c44459cafba8 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 26 Sep 2022 11:57:11 -0700 Subject: [PATCH 007/348] install and uninstall commands, closes #483 --- docs/cli-reference.rst | 38 ++++++++++++++++++++++++++++++++++++++ docs/cli.rst | 26 ++++++++++++++++++++++++++ sqlite_utils/cli.py | 25 +++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/docs/cli-reference.rst b/docs/cli-reference.rst index d74855e..b88e38a 100644 --- a/docs/cli-reference.rst +++ b/docs/cli-reference.rst @@ -59,6 +59,8 @@ This page lists the ``--help`` for every ``sqlite-utils`` CLI sub-command. "convert": "cli_convert", "add-geometry-column": "cli_spatialite", "create-spatial-index": "cli_spatialite_indexes", + "install": "cli_install", + "uninstall": "cli_uninstall", } commands.sort(key = lambda command: go_first.index(command) if command in go_first else 999) cog.out("\n") @@ -1349,6 +1351,42 @@ See :ref:`cli_drop_view`. -h, --help Show this message and exit. +.. _cli_ref_install: + +install +======= + +See :ref:`cli_install`. + +:: + + Usage: sqlite-utils install [OPTIONS] PACKAGES... + + Install packages from PyPI into the same environment as sqlite-utils + + Options: + -U, --upgrade Upgrade packages to latest version + -h, --help Show this message and exit. + + +.. _cli_ref_uninstall: + +uninstall +========= + +See :ref:`cli_uninstall`. + +:: + + Usage: sqlite-utils uninstall [OPTIONS] PACKAGES... + + Uninstall Python packages from the sqlite-utils environment + + Options: + -y, --yes Don't ask for confirmation + -h, --help Show this message and exit. + + .. _cli_ref_add_geometry_column: add-geometry-column diff --git a/docs/cli.rst b/docs/cli.rst index 3adec26..5f85e55 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -2116,3 +2116,29 @@ Once you have a geometry column, you can speed up bounding box queries by adding $ sqlite-utils create-spatial-index spatial.db locations geometry See this `SpatiaLite Cookbook recipe `__ for examples of how to use a spatial index. + +.. _cli_install: + +Installing packages +------------------- + +The :ref:`insert -\\-convert ` and :ref:`query -\\-functions ` options can be provided with a Python script that imports additional modules from the ``sqlite-utils`` environment. + +You can install packages from PyPI directly into the correct environment using ``sqlite-utils install ``. This is a wrapper around ``pip install``. + +:: + + $ sqlite-utils install beautifulsoup4 + +Use ``-U`` to upgrade an existing package. + +.. _cli_uninstall: + +Uninstalling packages +--------------------- + +You can uninstall packages that were installed using ``sqlite-utils install`` with ``sqlite-utils uninstall ``:: + + $ sqlite-utils uninstall beautifulsoup4 + +Use ``-y`` to skip the request for confirmation. diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index 767b170..bf7fae4 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -4,6 +4,7 @@ from click_default_group import DefaultGroup # type: ignore from datetime import datetime import hashlib import pathlib +from runpy import run_module import sqlite_utils from sqlite_utils.db import AlterError, BadMultiValues, DescIndex, NoTable from sqlite_utils.utils import maximize_csv_field_size_limit @@ -2657,6 +2658,30 @@ def _analyze(db, tables, columns, save): click.echo(details) +@cli.command() +@click.argument("packages", nargs=-1, required=True) +@click.option( + "-U", "--upgrade", is_flag=True, help="Upgrade packages to latest version" +) +def install(packages, upgrade): + """Install packages from PyPI into the same environment as sqlite-utils""" + args = ["pip", "install"] + if upgrade: + args += ["--upgrade"] + args += list(packages) + sys.argv = args + run_module("pip", run_name="__main__") + + +@cli.command() +@click.argument("packages", nargs=-1, required=True) +@click.option("-y", "--yes", is_flag=True, help="Don't ask for confirmation") +def uninstall(packages, yes): + """Uninstall Python packages from the sqlite-utils environment""" + sys.argv = ["pip", "uninstall"] + list(packages) + (["-y"] if yes else []) + run_module("pip", run_name="__main__") + + def _generate_convert_help(): help = textwrap.dedent( """ From ee74bd5f8149b5e4403a4b56c74e9b94dbda2a32 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 26 Sep 2022 12:03:54 -0700 Subject: [PATCH 008/348] Fix heading levels, refs #483 --- docs/cli.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cli.rst b/docs/cli.rst index 5f85e55..ecac91b 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -2120,7 +2120,7 @@ See this `SpatiaLite Cookbook recipe ` and :ref:`query -\\-functions ` options can be provided with a Python script that imports additional modules from the ``sqlite-utils`` environment. @@ -2135,7 +2135,7 @@ Use ``-U`` to upgrade an existing package. .. _cli_uninstall: Uninstalling packages ---------------------- +===================== You can uninstall packages that were installed using ``sqlite-utils install`` with ``sqlite-utils uninstall ``:: From 9a5add659d87738a658d8610ee461b038e28d268 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 26 Sep 2022 12:09:00 -0700 Subject: [PATCH 009/348] 'just docs' command for running the livehtml docs server --- Justfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Justfile b/Justfile index 52e6190..4fe3fb9 100644 --- a/Justfile +++ b/Justfile @@ -16,6 +16,9 @@ @cog: cog -r README.md docs/*.rst +@docs: cog + cd docs && pipenv run make livehtml + # Apply Black @black: pipenv run black . From afbd2b2cba45cccb305c3d4638d18db4dd3d4bbd Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 26 Sep 2022 12:09:32 -0700 Subject: [PATCH 010/348] Link to convert command too, refs #483 --- docs/cli.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli.rst b/docs/cli.rst index ecac91b..8bc4176 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -2122,7 +2122,7 @@ See this `SpatiaLite Cookbook recipe ` and :ref:`query -\\-functions ` options can be provided with a Python script that imports additional modules from the ``sqlite-utils`` environment. +The :ref:`convert command ` and the :ref:`insert -\\-convert ` and :ref:`query -\\-functions ` options can be provided with a Python script that imports additional modules from the ``sqlite-utils`` environment. You can install packages from PyPI directly into the correct environment using ``sqlite-utils install ``. This is a wrapper around ``pip install``. From 6b268a1b3664784ed2267482d8c1d021a597d2b2 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 26 Sep 2022 12:26:04 -0700 Subject: [PATCH 011/348] language = "en" to fix Sphinx warning --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 1339477..62d5ab1 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -93,7 +93,7 @@ else: # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. From c7cad6fc257c178b24b3f574b8c6992002c6f072 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 26 Sep 2022 12:26:31 -0700 Subject: [PATCH 012/348] Documentation for Just, closes #494 Also added 'just init' and fixed a couple of missing pipenv run calls. --- Justfile | 8 ++++++-- docs/contributing.rst | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/Justfile b/Justfile index 4fe3fb9..a479717 100644 --- a/Justfile +++ b/Justfile @@ -1,6 +1,10 @@ # Run tests and linters @default: test lint +# Setup project +@init: + pipenv run pip install -e '.[test,docs,mypy,flake8]' + # Run pytest with supplied options @test *options: pipenv run pytest {{options}} @@ -10,11 +14,11 @@ pipenv run black . --check pipenv run flake8 pipenv run mypy sqlite_utils tests - cog --check README.md docs/*.rst + pipenv run cog --check README.md docs/*.rst # Rebuild docs with cog @cog: - cog -r README.md docs/*.rst + pipenv run cog -r README.md docs/*.rst @docs: cog cd docs && pipenv run make livehtml diff --git a/docs/contributing.rst b/docs/contributing.rst index a1041f5..ef5a875 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -79,6 +79,46 @@ Both commands can then be run in the root of the project like this:: All three of these tools are run by our CI mechanism against every commit and pull request. +Using Just and pipenv +===================== + +If you install `Just `__ and `pipenv `__ you can use them to manage your local development environment. + +To create a virtual environment and install all development dependencies, run:: + + cd sqlite-utils + just init + +To run all of the tests and linters:: + + just + +To run tests, or run a specific test module or test by name:: + + just test # All tests + just test tests/test_cli_memory.py # Just this module + just test -k test_memory_no_detect_types # Just this test + +To run just the linters:: + + just lint + +To apply Black to your code:: + + just black + +To update documentation using Cog:: + + just cog + +To run the live documentation server (after building with Cog first):: + + just docs + +And to list all available commands:: + + just -l + .. _release_process: Release process From cf9861216b8f2200535482f37d2f83f25a934493 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 26 Sep 2022 12:36:09 -0700 Subject: [PATCH 013/348] Comment for 'just docs' command This shows up in 'just -l' --- Justfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Justfile b/Justfile index a479717..66863f6 100644 --- a/Justfile +++ b/Justfile @@ -20,6 +20,7 @@ @cog: pipenv run cog -r README.md docs/*.rst +# Serve live docs on localhost:8000 @docs: cog cd docs && pipenv run make livehtml From cbed0807822dd3ba0e51b99c6b28125422f690f0 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 26 Sep 2022 17:10:59 -0700 Subject: [PATCH 014/348] Typo --- docs/contributing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index ef5a875..3a68b40 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -124,7 +124,7 @@ And to list all available commands:: Release process =============== -Releases are performed using tags. When a new release is published on GitHub, a `GitHub Action workflow `__ will perform the following: +Releases are performed using tags. When a new release is published on GitHub, a `GitHub Actions workflow `__ will perform the following: * Run the unit tests against all supported Python versions. If the tests pass... * Build a wheel bundle of the underlying Python source code From d792dad1cf5f16525da81b1e162fb71d469995f3 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 26 Sep 2022 19:23:17 -0700 Subject: [PATCH 015/348] Clarify wording --- docs/contributing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index 3a68b40..4a13725 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -111,7 +111,7 @@ To update documentation using Cog:: just cog -To run the live documentation server (after building with Cog first):: +To run the live documentation server (this will run Cog first):: just docs From 34e75ed0dd3091a6f94d6bd70150caa70660736d Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 18 Oct 2022 11:00:25 -0700 Subject: [PATCH 016/348] sqlite_utils.utils.flatten() function, closes #500 --- docs/reference.rst | 7 +++++++ sqlite_utils/cli.py | 14 +++----------- sqlite_utils/utils.py | 18 ++++++++++++++++++ tests/test_cli.py | 12 ------------ tests/test_utils.py | 12 ++++++++++++ 5 files changed, 40 insertions(+), 23 deletions(-) diff --git a/docs/reference.rst b/docs/reference.rst index 66b9d38..5b5fd25 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -101,3 +101,10 @@ sqlite_utils.utils.chunks ------------------------- .. autofunction:: sqlite_utils.utils.chunks + +.. _reference_utils_flatten: + +sqlite_utils.utils.flatten +-------------------------- + +.. autofunction:: sqlite_utils.utils.flatten diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index bf7fae4..5fcc95a 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -24,6 +24,7 @@ from .utils import ( chunks, file_progress, find_spatialite, + flatten as _flatten, sqlite3, decode_base64_values, progressbar, @@ -997,7 +998,7 @@ def insert_upsert_implementation( "Invalid JSON - use --csv for CSV or --tsv for TSV files" ) if flatten: - docs = (dict(_flatten(doc)) for doc in docs) + docs = (_flatten(doc) for doc in docs) if convert: variable = "row" @@ -1079,15 +1080,6 @@ def insert_upsert_implementation( db[table].transform(types=tracker.types) -def _flatten(d): - for key, value in d.items(): - if isinstance(value, dict): - for key2, value2 in _flatten(value): - yield key + "_" + key2, value2 - else: - yield key, value - - def _find_variables(tb, vars): to_find = list(vars) found = {} @@ -1845,7 +1837,7 @@ def memory( tracker = TypeTracker() rows = tracker.wrap(rows) if flatten: - rows = (dict(_flatten(row)) for row in rows) + rows = (_flatten(row) for row in rows) db[csv_table].insert_all(rows, alter=True) if tracker is not None: db[csv_table].transform(types=tracker.types) diff --git a/sqlite_utils/utils.py b/sqlite_utils/utils.py index 8754554..8916c46 100644 --- a/sqlite_utils/utils.py +++ b/sqlite_utils/utils.py @@ -513,3 +513,21 @@ def hash_record(record: Dict, keys: Optional[Iterable[str]] = None): "utf8" ) ).hexdigest() + + +def _flatten(d): + for key, value in d.items(): + if isinstance(value, dict): + for key2, value2 in _flatten(value): + yield key + "_" + key2, value2 + else: + yield key, value + + +def flatten(row: dict) -> dict: + """ + Turn a nested dict e.g. ``{"a": {"b": 1}}`` into a flat dict: ``{"a_b": 1}`` + + :param row: A Python dictionary, optionally with nested dictionaries + """ + return dict(_flatten(row)) diff --git a/tests/test_cli.py b/tests/test_cli.py index 24f36ed..b8df8d6 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2176,18 +2176,6 @@ def test_upsert_detect_types(tmpdir, option): ] -@pytest.mark.parametrize( - "input,expected", - ( - ({"foo": {"bar": 1}}, {"foo_bar": 1}), - ({"foo": {"bar": [1, 2, {"baz": 3}]}}, {"foo_bar": [1, 2, {"baz": 3}]}), - ({"foo": {"bar": 1, "baz": {"three": 3}}}, {"foo_bar": 1, "foo_baz_three": 3}), - ), -) -def test_flatten_helper(input, expected): - assert dict(cli._flatten(input)) == expected - - def test_integer_overflow_error(tmpdir): db_path = str(tmpdir / "test.db") result = CliRunner().invoke( diff --git a/tests/test_utils.py b/tests/test_utils.py index d397176..f728bcd 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -71,3 +71,15 @@ def test_maximize_csv_field_size_limit(): assert len(rows_list2) == 1 assert rows_list2[0]["id"] == "1" assert rows_list2[0]["text"] == long_value + + +@pytest.mark.parametrize( + "input,expected", + ( + ({"foo": {"bar": 1}}, {"foo_bar": 1}), + ({"foo": {"bar": [1, 2, {"baz": 3}]}}, {"foo_bar": [1, 2, {"baz": 3}]}), + ({"foo": {"bar": 1, "baz": {"three": 3}}}, {"foo_bar": 1, "foo_baz_three": 3}), + ), +) +def test_flatten(input, expected): + assert utils.flatten(input) == expected From eb67fc69a227276b8ea635b885e5e4baecc43180 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 18 Oct 2022 11:08:34 -0700 Subject: [PATCH 017/348] Run cog -r against latest tabulate, refs #501 --- docs/cli-reference.rst | 107 ++++++++++++++++++++++++++--------------- docs/cli.rst | 12 +++++ 2 files changed, 79 insertions(+), 40 deletions(-) diff --git a/docs/cli-reference.rst b/docs/cli-reference.rst index b88e38a..82b4b6c 100644 --- a/docs/cli-reference.rst +++ b/docs/cli-reference.rst @@ -112,11 +112,15 @@ See :ref:`cli_query`. --tsv Output TSV --no-headers Omit CSV headers -t, --table Output as a formatted table - --fmt TEXT Table format - one of fancy_grid, fancy_outline, - github, grid, html, jira, latex, latex_booktabs, - latex_longtable, latex_raw, mediawiki, moinmoin, - orgtbl, pipe, plain, presto, pretty, psql, rst, - simple, textile, tsv, unsafehtml, youtrack + --fmt TEXT Table format - one of asciidoc, double_grid, + double_outline, fancy_grid, fancy_outline, github, + grid, heavy_grid, heavy_outline, html, jira, + latex, latex_booktabs, latex_longtable, latex_raw, + mediawiki, mixed_grid, mixed_outline, moinmoin, + orgtbl, outline, pipe, plain, presto, pretty, + psql, rounded_grid, rounded_outline, rst, simple, + simple_grid, simple_outline, textile, tsv, + unsafehtml, youtrack --json-cols Detect JSON cols and output them as JSON, not escaped strings -r, --raw Raw output, first column of first row @@ -176,11 +180,15 @@ See :ref:`cli_memory`. --tsv Output TSV --no-headers Omit CSV headers -t, --table Output as a formatted table - --fmt TEXT Table format - one of fancy_grid, fancy_outline, - github, grid, html, jira, latex, latex_booktabs, - latex_longtable, latex_raw, mediawiki, moinmoin, - orgtbl, pipe, plain, presto, pretty, psql, rst, - simple, textile, tsv, unsafehtml, youtrack + --fmt TEXT Table format - one of asciidoc, double_grid, + double_outline, fancy_grid, fancy_outline, github, + grid, heavy_grid, heavy_outline, html, jira, + latex, latex_booktabs, latex_longtable, latex_raw, + mediawiki, mixed_grid, mixed_outline, moinmoin, + orgtbl, outline, pipe, plain, presto, pretty, + psql, rounded_grid, rounded_outline, rst, simple, + simple_grid, simple_outline, textile, tsv, + unsafehtml, youtrack --json-cols Detect JSON cols and output them as JSON, not escaped strings -r, --raw Raw output, first column of first row @@ -401,11 +409,14 @@ See :ref:`cli_search`. --tsv Output TSV --no-headers Omit CSV headers -t, --table Output as a formatted table - --fmt TEXT Table format - one of fancy_grid, fancy_outline, - github, grid, html, jira, latex, latex_booktabs, - latex_longtable, latex_raw, mediawiki, moinmoin, - orgtbl, pipe, plain, presto, pretty, psql, rst, simple, - textile, tsv, unsafehtml, youtrack + --fmt TEXT Table format - one of asciidoc, double_grid, + double_outline, fancy_grid, fancy_outline, github, + grid, heavy_grid, heavy_outline, html, jira, latex, + latex_booktabs, latex_longtable, latex_raw, mediawiki, + mixed_grid, mixed_outline, moinmoin, orgtbl, outline, + pipe, plain, presto, pretty, psql, rounded_grid, + rounded_outline, rst, simple, simple_grid, + simple_outline, textile, tsv, unsafehtml, youtrack --json-cols Detect JSON cols and output them as JSON, not escaped strings --load-extension TEXT Path to SQLite extension, with optional :entrypoint @@ -651,11 +662,14 @@ See :ref:`cli_tables`. --tsv Output TSV --no-headers Omit CSV headers -t, --table Output as a formatted table - --fmt TEXT Table format - one of fancy_grid, fancy_outline, - github, grid, html, jira, latex, latex_booktabs, - latex_longtable, latex_raw, mediawiki, moinmoin, - orgtbl, pipe, plain, presto, pretty, psql, rst, simple, - textile, tsv, unsafehtml, youtrack + --fmt TEXT Table format - one of asciidoc, double_grid, + double_outline, fancy_grid, fancy_outline, github, + grid, heavy_grid, heavy_outline, html, jira, latex, + latex_booktabs, latex_longtable, latex_raw, mediawiki, + mixed_grid, mixed_outline, moinmoin, orgtbl, outline, + pipe, plain, presto, pretty, psql, rounded_grid, + rounded_outline, rst, simple, simple_grid, + simple_outline, textile, tsv, unsafehtml, youtrack --json-cols Detect JSON cols and output them as JSON, not escaped strings --columns Include list of columns for each table @@ -689,11 +703,14 @@ See :ref:`cli_views`. --tsv Output TSV --no-headers Omit CSV headers -t, --table Output as a formatted table - --fmt TEXT Table format - one of fancy_grid, fancy_outline, - github, grid, html, jira, latex, latex_booktabs, - latex_longtable, latex_raw, mediawiki, moinmoin, - orgtbl, pipe, plain, presto, pretty, psql, rst, simple, - textile, tsv, unsafehtml, youtrack + --fmt TEXT Table format - one of asciidoc, double_grid, + double_outline, fancy_grid, fancy_outline, github, + grid, heavy_grid, heavy_outline, html, jira, latex, + latex_booktabs, latex_longtable, latex_raw, mediawiki, + mixed_grid, mixed_outline, moinmoin, orgtbl, outline, + pipe, plain, presto, pretty, psql, rounded_grid, + rounded_outline, rst, simple, simple_grid, + simple_outline, textile, tsv, unsafehtml, youtrack --json-cols Detect JSON cols and output them as JSON, not escaped strings --columns Include list of columns for each view @@ -732,11 +749,15 @@ See :ref:`cli_rows`. --tsv Output TSV --no-headers Omit CSV headers -t, --table Output as a formatted table - --fmt TEXT Table format - one of fancy_grid, fancy_outline, - github, grid, html, jira, latex, latex_booktabs, - latex_longtable, latex_raw, mediawiki, moinmoin, - orgtbl, pipe, plain, presto, pretty, psql, rst, - simple, textile, tsv, unsafehtml, youtrack + --fmt TEXT Table format - one of asciidoc, double_grid, + double_outline, fancy_grid, fancy_outline, github, + grid, heavy_grid, heavy_outline, html, jira, + latex, latex_booktabs, latex_longtable, latex_raw, + mediawiki, mixed_grid, mixed_outline, moinmoin, + orgtbl, outline, pipe, plain, presto, pretty, + psql, rounded_grid, rounded_outline, rst, simple, + simple_grid, simple_outline, textile, tsv, + unsafehtml, youtrack --json-cols Detect JSON cols and output them as JSON, not escaped strings --load-extension TEXT Path to SQLite extension, with optional @@ -768,11 +789,14 @@ See :ref:`cli_triggers`. --tsv Output TSV --no-headers Omit CSV headers -t, --table Output as a formatted table - --fmt TEXT Table format - one of fancy_grid, fancy_outline, - github, grid, html, jira, latex, latex_booktabs, - latex_longtable, latex_raw, mediawiki, moinmoin, - orgtbl, pipe, plain, presto, pretty, psql, rst, simple, - textile, tsv, unsafehtml, youtrack + --fmt TEXT Table format - one of asciidoc, double_grid, + double_outline, fancy_grid, fancy_outline, github, + grid, heavy_grid, heavy_outline, html, jira, latex, + latex_booktabs, latex_longtable, latex_raw, mediawiki, + mixed_grid, mixed_outline, moinmoin, orgtbl, outline, + pipe, plain, presto, pretty, psql, rounded_grid, + rounded_outline, rst, simple, simple_grid, + simple_outline, textile, tsv, unsafehtml, youtrack --json-cols Detect JSON cols and output them as JSON, not escaped strings --load-extension TEXT Path to SQLite extension, with optional :entrypoint @@ -804,11 +828,14 @@ See :ref:`cli_indexes`. --tsv Output TSV --no-headers Omit CSV headers -t, --table Output as a formatted table - --fmt TEXT Table format - one of fancy_grid, fancy_outline, - github, grid, html, jira, latex, latex_booktabs, - latex_longtable, latex_raw, mediawiki, moinmoin, - orgtbl, pipe, plain, presto, pretty, psql, rst, simple, - textile, tsv, unsafehtml, youtrack + --fmt TEXT Table format - one of asciidoc, double_grid, + double_outline, fancy_grid, fancy_outline, github, + grid, heavy_grid, heavy_outline, html, jira, latex, + latex_booktabs, latex_longtable, latex_raw, mediawiki, + mixed_grid, mixed_outline, moinmoin, orgtbl, outline, + pipe, plain, presto, pretty, psql, rounded_grid, + rounded_outline, rst, simple, simple_grid, + simple_outline, textile, tsv, unsafehtml, youtrack --json-cols Detect JSON cols and output them as JSON, not escaped strings --load-extension TEXT Path to SQLite extension, with optional :entrypoint diff --git a/docs/cli.rst b/docs/cli.rst index 8bc4176..1d67e88 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -187,10 +187,15 @@ Available ``--fmt`` options are: cog.out("\n" + "\n".join('- ``{}``'.format(t) for t in tabulate.tabulate_formats) + "\n\n") .. ]]] +- ``asciidoc`` +- ``double_grid`` +- ``double_outline`` - ``fancy_grid`` - ``fancy_outline`` - ``github`` - ``grid`` +- ``heavy_grid`` +- ``heavy_outline`` - ``html`` - ``jira`` - ``latex`` @@ -198,15 +203,22 @@ Available ``--fmt`` options are: - ``latex_longtable`` - ``latex_raw`` - ``mediawiki`` +- ``mixed_grid`` +- ``mixed_outline`` - ``moinmoin`` - ``orgtbl`` +- ``outline`` - ``pipe`` - ``plain`` - ``presto`` - ``pretty`` - ``psql`` +- ``rounded_grid`` +- ``rounded_outline`` - ``rst`` - ``simple`` +- ``simple_grid`` +- ``simple_outline`` - ``textile`` - ``tsv`` - ``unsafehtml`` From 9cbe19ac0547031f3b626d9d18ef05c0d193bf79 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 18 Oct 2022 11:16:43 -0700 Subject: [PATCH 018/348] Skip cog check on Python 3.6, refs #501 --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 788df25..e994c25 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -49,5 +49,6 @@ jobs: - name: Check formatting run: black . --check - name: Check if cog needs to be run + if: matrix.python-version != '3.6' run: | cog --check README.md docs/*.rst From b8526c434a3d6aafb4102f9d9f5da14dfc4e3002 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 25 Oct 2022 07:17:49 -0700 Subject: [PATCH 019/348] Test against Python 3.11 --- .github/workflows/publish.yml | 14 +++++++------- .github/workflows/test.yml | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 355f271..f3627d3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,15 +9,15 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10.6"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] os: [ubuntu-latest, windows-latest, macos-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - uses: actions/cache@v2 + - uses: actions/cache@v3 name: Configure pip caching with: path: ~/.cache/pip @@ -34,12 +34,12 @@ jobs: runs-on: ubuntu-latest needs: [test] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: '3.9' - - uses: actions/cache@v2 + python-version: '3.11' + - uses: actions/cache@v3 name: Configure pip caching with: path: ~/.cache/pip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e994c25..98729c9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,16 +10,16 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10.6"] + python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] numpy: [0, 1] os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - uses: actions/cache@v2 + - uses: actions/cache@v3 name: Configure pip caching with: path: ~/.cache/pip From 5133339d00252cb258a4217eda830ac60f43ee1f Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 25 Oct 2022 12:08:58 -0700 Subject: [PATCH 020/348] Skip macos-latest Python 3.11 for the moment Refs https://github.com/actions/setup-python/issues/531 --- .github/workflows/test.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 98729c9..ba88451 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,6 +13,10 @@ jobs: python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] numpy: [0, 1] os: [ubuntu-latest, macos-latest, windows-latest] + exclude: + # https://github.com/actions/setup-python/issues/531 + - os: macos-latest + python-version: "3.11" steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} From 7b2d1c0ffd0b874e280292b926f328a61cb31e2c Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 25 Oct 2022 12:23:20 -0700 Subject: [PATCH 021/348] Update tests for Python 3.11, closes #502 --- tests/test_cli.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index b8df8d6..20fe99e 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -737,10 +737,7 @@ def test_query_invalid_function(db_path): cli.cli, [db_path, "select bad()", "--functions", "def invalid_python"] ) assert result.exit_code == 1 - assert ( - result.output.strip() - == "Error: Error in functions definition: invalid syntax (, line 1)" - ) + assert result.output.startswith("Error: Error in functions definition:") TEST_FUNCTIONS = """ From 079bf1f4dc8540f834adae68c7feeeffcbc1d4f2 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 25 Oct 2022 13:06:58 -0700 Subject: [PATCH 022/348] Use tmp_path fixture in test_recreate, refs #503 --- tests/test_recreate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_recreate.py b/tests/test_recreate.py index 504a0b8..91f1ba5 100644 --- a/tests/test_recreate.py +++ b/tests/test_recreate.py @@ -21,8 +21,8 @@ def test_recreate_not_allowed_for_connection(): @pytest.mark.parametrize( "use_path,file_exists", [(True, True), (True, False), (False, True), (False, False)] ) -def test_recreate(tmpdir, use_path, file_exists): - filepath = str(tmpdir / "data.db") +def test_recreate(tmp_path, use_path, file_exists): + filepath = str(tmp_path / "data.db") if use_path: filepath = pathlib.Path(filepath) if file_exists: From 7ca497a8f5be24c127946813e3052a19b48be1b3 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 25 Oct 2022 13:14:41 -0700 Subject: [PATCH 023/348] repr improvements, refs #503 --- sqlite_utils/db.py | 3 ++- sqlite_utils/utils.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 27c46b0..0d9eaed 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -308,6 +308,7 @@ class Database: assert (filename_or_conn is not None and (not memory and not memory_name)) or ( filename_or_conn is None and (memory or memory_name) ), "Either specify a filename_or_conn or pass memory=True" + self.conn = None if memory_name: uri = "file:{}?mode=memory&cache=shared".format(memory_name) self.conn = sqlite3.connect( @@ -3534,7 +3535,7 @@ class View(Queryable): def exists(self): return True - def __repr__(self): + def __repr__(self) -> str: return "".format( self.name, ", ".join(c.name for c in self.columns) ) diff --git a/sqlite_utils/utils.py b/sqlite_utils/utils.py index 8916c46..4e5bbcc 100644 --- a/sqlite_utils/utils.py +++ b/sqlite_utils/utils.py @@ -392,7 +392,7 @@ class ValueTracker: except (ValueError, TypeError): return False - def __repr__(self): + def __repr__(self) -> str: return self.guessed_type + ": possibilities = " + repr(self.couldbe) @property From c5d7ec1dd71fa1dce829bc8bb82b639018befd63 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 25 Oct 2022 13:21:37 -0700 Subject: [PATCH 024/348] Fix for mypy issue, refs #503 --- sqlite_utils/db.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 0d9eaed..80ad026 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -308,7 +308,6 @@ class Database: assert (filename_or_conn is not None and (not memory and not memory_name)) or ( filename_or_conn is None and (memory or memory_name) ), "Either specify a filename_or_conn or pass memory=True" - self.conn = None if memory_name: uri = "file:{}?mode=memory&cache=shared".format(memory_name) self.conn = sqlite3.connect( @@ -320,7 +319,13 @@ class Database: self.conn = sqlite3.connect(":memory:") elif isinstance(filename_or_conn, (str, pathlib.Path)): if recreate and os.path.exists(filename_or_conn): - os.remove(filename_or_conn) + try: + os.remove(filename_or_conn) + except OSError: + # Avoid mypy and __repr__ errors, see: + # https://github.com/simonw/sqlite-utils/issues/503 + self.conn = sqlite3.connect(":memory:") + raise self.conn = sqlite3.connect(str(filename_or_conn)) else: assert not recreate, "recreate cannot be used with connections, only paths" From f6b796277f783fcb613136e5a230b8657ef6c090 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 25 Oct 2022 13:27:18 -0700 Subject: [PATCH 025/348] Try a 0.1s sleep, refs #503 --- tests/test_recreate.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_recreate.py b/tests/test_recreate.py index 91f1ba5..be161ff 100644 --- a/tests/test_recreate.py +++ b/tests/test_recreate.py @@ -1,5 +1,6 @@ from sqlite_utils import Database import sqlite3 +import time import pathlib import pytest @@ -19,14 +20,16 @@ def test_recreate_not_allowed_for_connection(): @pytest.mark.parametrize( - "use_path,file_exists", [(True, True), (True, False), (False, True), (False, False)] + "use_path,create_file_first", + [(True, True), (True, False), (False, True), (False, False)], ) -def test_recreate(tmp_path, use_path, file_exists): +def test_recreate(tmp_path, use_path, create_file_first): filepath = str(tmp_path / "data.db") if use_path: filepath = pathlib.Path(filepath) - if file_exists: + if create_file_first: Database(filepath)["t1"].insert({"foo": "bar"}) assert ["t1"] == Database(filepath).table_names() + time.sleep(0.1) Database(filepath, recreate=True)["t2"].insert({"foo": "bar"}) assert ["t2"] == Database(filepath).table_names() From ba7242b1f231a9707944a4c11a8dfede9ff9cad0 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 25 Oct 2022 13:36:17 -0700 Subject: [PATCH 026/348] Try closing the database before recreating it, refs #503 --- tests/test_recreate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_recreate.py b/tests/test_recreate.py index be161ff..0b98967 100644 --- a/tests/test_recreate.py +++ b/tests/test_recreate.py @@ -1,6 +1,5 @@ from sqlite_utils import Database import sqlite3 -import time import pathlib import pytest @@ -28,8 +27,9 @@ def test_recreate(tmp_path, use_path, create_file_first): if use_path: filepath = pathlib.Path(filepath) if create_file_first: - Database(filepath)["t1"].insert({"foo": "bar"}) - assert ["t1"] == Database(filepath).table_names() - time.sleep(0.1) + db = Database(filepath) + db["t1"].insert({"foo": "bar"}) + assert ["t1"] == db.table_names() + db.conn.close() Database(filepath, recreate=True)["t2"].insert({"foo": "bar"}) assert ["t2"] == Database(filepath).table_names() From 05e2bb85fcd11729db40c6452f2f7287232e2f1a Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 25 Oct 2022 13:57:43 -0700 Subject: [PATCH 027/348] db.close() method, closes #504 --- sqlite_utils/db.py | 4 ++++ tests/test_constructor.py | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 80ad026..b8e8d4b 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -336,6 +336,10 @@ class Database: self._registered_functions: set = set() self.use_counts_table = use_counts_table + def close(self): + "Close the SQLite connection, and the underlying database file" + self.conn.close() + @contextlib.contextmanager def tracer(self, tracer: Callable = None): """ diff --git a/tests/test_constructor.py b/tests/test_constructor.py index d8c8d72..8743911 100644 --- a/tests/test_constructor.py +++ b/tests/test_constructor.py @@ -1,4 +1,6 @@ from sqlite_utils import Database +from sqlite_utils.utils import sqlite3 +import pytest def test_recursive_triggers(): @@ -25,3 +27,15 @@ def test_sqlite_version(): as_string = ".".join(map(str, version)) actual = next(db.query("select sqlite_version() as v"))["v"] assert actual == as_string + + +@pytest.mark.parametrize("memory", [True, False]) +def test_database_close(tmpdir, memory): + if memory: + db = Database(memory=True) + else: + db = Database(str(tmpdir / "test.db")) + assert db.execute("select 1 + 1").fetchone()[0] == 2 + db.close() + with pytest.raises(sqlite3.ProgrammingError): + db.execute("select 1 + 1") From c7e4308e6f49d929704163531632e558f9646e4a Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 25 Oct 2022 14:00:53 -0700 Subject: [PATCH 028/348] Use new db.close() method, refs #504 --- tests/test_recreate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_recreate.py b/tests/test_recreate.py index 0b98967..49dba2b 100644 --- a/tests/test_recreate.py +++ b/tests/test_recreate.py @@ -30,6 +30,6 @@ def test_recreate(tmp_path, use_path, create_file_first): db = Database(filepath) db["t1"].insert({"foo": "bar"}) assert ["t1"] == db.table_names() - db.conn.close() + db.close() Database(filepath, recreate=True)["t2"].insert({"foo": "bar"}) assert ["t2"] == Database(filepath).table_names() From defa2974c6d3abc19be28d6b319649b8028dc966 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 25 Oct 2022 14:23:24 -0700 Subject: [PATCH 029/348] jsonify_if_needed output of convert() functions, closes #495 --- sqlite_utils/db.py | 2 +- tests/test_convert.py | 26 ++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index b8e8d4b..a06f4b7 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -2662,7 +2662,7 @@ class Table(Queryable): bar.update(1) if not v: return v - return fn(v) + return jsonify_if_needed(fn(v)) self.db.register_function(convert_value) sql = "update [{table}] set {sets}{where};".format( diff --git a/tests/test_convert.py b/tests/test_convert.py index 796a08f..31a9d28 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -15,6 +15,14 @@ import pytest lambda value: value.upper(), {"title": "MIXED CASE", "abstract": "ABSTRACT"}, ), + ( + "title", + lambda value: {"upper": value.upper(), "lower": value.lower()}, + { + "title": '{"upper": "MIXED CASE", "lower": "mixed case"}', + "abstract": "Abstract", + }, + ), ), ) def test_convert(fresh_db, columns, fn, expected): @@ -81,10 +89,24 @@ def test_convert_multi(fresh_db): table = fresh_db["table"] table.insert({"title": "Mixed Case"}) table.convert( - "title", lambda v: {"upper": v.upper(), "lower": v.lower()}, multi=True + "title", + lambda v: { + "upper": v.upper(), + "lower": v.lower(), + "both": { + "upper": v.upper(), + "lower": v.lower(), + }, + }, + multi=True, ) assert list(table.rows) == [ - {"title": "Mixed Case", "upper": "MIXED CASE", "lower": "mixed case"} + { + "title": "Mixed Case", + "upper": "MIXED CASE", + "lower": "mixed case", + "both": '{"upper": "MIXED CASE", "lower": "mixed case"}', + } ] From 0d45ee11027700f184383d5c8c25a26770fcf471 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 25 Oct 2022 15:21:34 -0700 Subject: [PATCH 030/348] Release 3.30 Refs #480, #483, #485, #495, #500, #502, #504 --- docs/changelog.rst | 15 +++++++++++++++ docs/contributing.rst | 2 ++ setup.py | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index adeb6b1..c3c12f2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,21 @@ Changelog =========== +.. _v3_30: + +3.30 (2022-10-25) +----------------- + +- Now tested against Python 3.11. (:issue:`502`) +- New ``table.search_sql(include_rank=True)`` option, which adds a ``rank`` column to the generated SQL. Thanks, Jacob Chapman. (`#480 `__) +- Progress bars now display for newline-delimited JSON files using the ``--nl`` option. Thanks, Mischa Untaga. (:issue:`485`) +- New ``db.close()`` method. (:issue:`504`) +- Conversion functions passed to :ref:`table.convert(...) ` can now return lists or dictionaries, which will be inserted into the database as JSON strings. (:issue:`495`) +- ``sqlite-utils install`` and ``sqlite-utils uninstall`` commands for installing packages into the same virtual environment as ``sqlite-utils``, :ref:`described here `. (:issue:`483`) +- New :ref:`sqlite_utils.utils.flatten() ` utility function. (:issue:`500`) +- Documentation on :ref:`using Just ` to run tests, linters and build documentation. +- Documentation now covers the :ref:`release_process` for this package. + .. _v3_29: 3.29 (2022-08-27) diff --git a/docs/contributing.rst b/docs/contributing.rst index 4a13725..2e883ee 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -79,6 +79,8 @@ Both commands can then be run in the root of the project like this:: All three of these tools are run by our CI mechanism against every commit and pull request. +.. _contributing_just: + Using Just and pipenv ===================== diff --git a/setup.py b/setup.py index 88b186b..2cf7af2 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages import io import os -VERSION = "3.29" +VERSION = "3.30" def get_long_description(): From fb8f495582f68d8d49f57b42d12a66802f9ac238 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 25 Oct 2022 15:34:30 -0700 Subject: [PATCH 031/348] Skip macOS 3.11 test when publishing Refs #505 --- .github/workflows/publish.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f3627d3..424e7ff 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,6 +11,10 @@ jobs: matrix: python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] os: [ubuntu-latest, windows-latest, macos-latest] + exclude: + # https://github.com/actions/setup-python/issues/531 + - os: macos-latest + python-version: "3.11" steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} From 529110e7d8c4a6b1bbf5fb61f2e29d72aa95a611 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Wed, 26 Oct 2022 12:27:32 -0700 Subject: [PATCH 032/348] GitHub Actions has Python 3.11 on macOS now Refs https://github.com/actions/setup-python/issues/531 --- .github/workflows/publish.yml | 4 ---- .github/workflows/test.yml | 4 ---- 2 files changed, 8 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 424e7ff..f3627d3 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,10 +11,6 @@ jobs: matrix: python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] os: [ubuntu-latest, windows-latest, macos-latest] - exclude: - # https://github.com/actions/setup-python/issues/531 - - os: macos-latest - python-version: "3.11" steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ba88451..98729c9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,10 +13,6 @@ jobs: python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] numpy: [0, 1] os: [ubuntu-latest, macos-latest, windows-latest] - exclude: - # https://github.com/actions/setup-python/issues/531 - - os: macos-latest - python-version: "3.11" steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} From 52ddb0b9ffa5284be668da088b7600b6ff64a2f9 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 15 Nov 2022 07:53:38 -0800 Subject: [PATCH 033/348] Rename utility functions to library --- docs/conf.py | 2 +- docs/index.rst | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 62d5ab1..8d49b81 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -187,7 +187,7 @@ texinfo_documents = [ "sqlite-utils documentation", author, "sqlite-utils", - "Python utility functions for manipulating SQLite databases", + "Python library for manipulating SQLite databases", "Miscellaneous", ) ] diff --git a/docs/index.rst b/docs/index.rst index 6793255..2bca2c8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -15,7 +15,7 @@ .. |discord| image:: https://img.shields.io/discord/823971286308356157?label=discord :target: https://discord.gg/Ass7bCAMDw -*CLI tool and Python utility functions for manipulating SQLite databases* +*CLI tool and Python library for manipulating SQLite databases* This library and command-line utility helps create SQLite databases from an existing collection of data. diff --git a/setup.py b/setup.py index 2cf7af2..faa822b 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def get_long_description(): setup( name="sqlite-utils", - description="CLI tool and Python utility functions for manipulating SQLite databases", + description="CLI tool and Python library for manipulating SQLite databases", long_description=get_long_description(), long_description_content_type="text/markdown", author="Simon Willison", From 965ca0d5f5bffe06cc02cd7741344d1ddddf9d56 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 15 Nov 2022 22:25:26 -0800 Subject: [PATCH 034/348] Apply no_implicit_optional codemod, closes #512 --- sqlite_utils/db.py | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index a06f4b7..e819d17 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -297,12 +297,12 @@ class Database: def __init__( self, - filename_or_conn: Union[str, pathlib.Path, sqlite3.Connection] = None, + filename_or_conn: Optional[Union[str, pathlib.Path, sqlite3.Connection]] = None, memory: bool = False, - memory_name: str = None, + memory_name: Optional[str] = None, recreate: bool = False, recursive_triggers: bool = True, - tracer: Callable = None, + tracer: Optional[Callable] = None, use_counts_table: bool = False, ): assert (filename_or_conn is not None and (not memory and not memory_name)) or ( @@ -341,7 +341,7 @@ class Database: self.conn.close() @contextlib.contextmanager - def tracer(self, tracer: Callable = None): + def tracer(self, tracer: Optional[Callable] = None): """ Context manager to temporarily set a tracer function - all executed SQL queries will be passed to this. @@ -378,7 +378,7 @@ class Database: def register_function( self, - fn: Callable = None, + fn: Optional[Callable] = None, deterministic: bool = False, replace: bool = False, name: Optional[str] = None, @@ -879,7 +879,7 @@ class Database: pk: Optional[Any] = None, foreign_keys: Optional[ForeignKeysType] = None, column_order: Optional[List[str]] = None, - not_null: Iterable[str] = None, + not_null: Optional[Iterable[str]] = None, defaults: Optional[Dict[str, Any]] = None, hash_id: Optional[str] = None, hash_id_columns: Optional[Iterable[str]] = None, @@ -1129,7 +1129,7 @@ class Database: sql += " [{}]".format(name) self.execute(sql) - def init_spatialite(self, path: str = None) -> bool: + def init_spatialite(self, path: Optional[str] = None) -> bool: """ The ``init_spatialite`` method will load and initialize the SpatiaLite extension. The ``path`` argument should be an absolute path to the compiled extension, which @@ -1182,7 +1182,7 @@ class Queryable: def count_where( self, - where: str = None, + where: Optional[str] = None, where_args: Optional[Union[Iterable, dict]] = None, ) -> int: """ @@ -1213,12 +1213,12 @@ class Queryable: def rows_where( self, - where: str = None, + where: Optional[str] = None, where_args: Optional[Union[Iterable, dict]] = None, - order_by: str = None, + order_by: Optional[str] = None, select: str = "*", - limit: int = None, - offset: int = None, + limit: Optional[int] = None, + offset: Optional[int] = None, ) -> Generator[dict, None, None]: """ Iterate over every row in this table or view that matches the specified where clause. @@ -1251,11 +1251,11 @@ class Queryable: def pks_and_rows_where( self, - where: str = None, + where: Optional[str] = None, where_args: Optional[Union[Iterable, dict]] = None, - order_by: str = None, - limit: int = None, - offset: int = None, + order_by: Optional[str] = None, + limit: Optional[int] = None, + offset: Optional[int] = None, ) -> Generator[Tuple[Any, Dict], None, None]: """ Like ``.rows_where()`` but returns ``(pk, row)`` pairs - ``pk`` can be a single value or tuple. @@ -1345,7 +1345,7 @@ class Table(Queryable): pk: Optional[Any] = None, foreign_keys: Optional[ForeignKeysType] = None, column_order: Optional[List[str]] = None, - not_null: Iterable[str] = None, + not_null: Optional[Iterable[str]] = None, defaults: Optional[Dict[str, Any]] = None, batch_size: int = 100, hash_id: Optional[str] = None, @@ -1545,7 +1545,7 @@ class Table(Queryable): pk: Optional[Any] = None, foreign_keys: Optional[ForeignKeysType] = None, column_order: Optional[List[str]] = None, - not_null: Iterable[str] = None, + not_null: Optional[Iterable[str]] = None, defaults: Optional[Dict[str, Any]] = None, hash_id: Optional[str] = None, hash_id_columns: Optional[Iterable[str]] = None, @@ -2464,7 +2464,7 @@ class Table(Queryable): columns: Optional[Iterable[str]] = None, limit: Optional[int] = None, offset: Optional[int] = None, - where: str = None, + where: Optional[str] = None, where_args: Optional[Union[Iterable, dict]] = None, quote: bool = False, ) -> Generator[dict, None, None]: @@ -2527,7 +2527,7 @@ class Table(Queryable): def delete_where( self, - where: str = None, + where: Optional[str] = None, where_args: Optional[Union[Iterable, dict]] = None, analyze: bool = False, ) -> "Table": From ebe504ab2103b0426b21162fc30691e42e8abaa0 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 29 Nov 2022 09:03:35 -0800 Subject: [PATCH 035/348] Clarify column types in create-table help --- docs/cli-reference.rst | 2 ++ sqlite_utils/cli.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/cli-reference.rst b/docs/cli-reference.rst index 82b4b6c..153e5f9 100644 --- a/docs/cli-reference.rst +++ b/docs/cli-reference.rst @@ -886,6 +886,8 @@ See :ref:`cli_create_table`. height float \ photo blob --pk id + Valid column types are text, integer, float and blob. + Options: --pk TEXT Column to use as primary key --not-null TEXT Columns that should be created as NOT NULL diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index 5fcc95a..d25b1df 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -1477,6 +1477,8 @@ def create_table( name text \\ height float \\ photo blob --pk id + + Valid column types are text, integer, float and blob. """ db = sqlite_utils.Database(path) _load_extensions(db, load_extension) From e660635cea6c32f4022818380b1e1ee88e7c93a6 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Fri, 9 Dec 2022 17:25:23 -0800 Subject: [PATCH 036/348] Drop support for Python 3.6, refs #517 --- .github/workflows/publish.yml | 2 +- .github/workflows/test.yml | 3 +-- setup.py | 4 ++-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f3627d3..a37907a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,7 +9,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] os: [ubuntu-latest, windows-latest, macos-latest] steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 98729c9..2e706df 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] numpy: [0, 1] os: [ubuntu-latest, macos-latest, windows-latest] steps: @@ -49,6 +49,5 @@ jobs: - name: Check formatting run: black . --check - name: Check if cog needs to be run - if: matrix.python-version != '3.6' run: | cog --check README.md docs/*.rst diff --git a/setup.py b/setup.py index faa822b..4098a33 100644 --- a/setup.py +++ b/setup.py @@ -60,7 +60,7 @@ setup( "Issues": "https://github.com/simonw/sqlite-utils/issues", "CI": "https://github.com/simonw/sqlite-utils/actions", }, - python_requires=">=3.6", + python_requires=">=3.7", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", @@ -68,11 +68,11 @@ setup( "Intended Audience :: End Users/Desktop", "Topic :: Database", "License :: OSI Approved :: Apache Software License", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ], # Needed to bundle py.typed so mypy can see it: zip_safe=False, From fc221f9b62ed8624b1d2098e564f525c84497969 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Fri, 9 Dec 2022 17:30:45 -0800 Subject: [PATCH 037/348] Try this fix for flake8 error, refs #518 --- setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 7a88d6f..6b9c885 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,4 @@ [flake8] max-line-length = 160 -extend-ignore = E203 # for Black +# Black compatibility, E203 whitespace before ':': +extend-ignore = E203 \ No newline at end of file From 6cd0fd2b4c5116889e40245f84a9786fb19f4c40 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 12 Mar 2023 14:25:26 -0700 Subject: [PATCH 038/348] Fix for Sphinx bug, closes #533, refs #531 --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 8d49b81..859c6b9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -41,7 +41,7 @@ autodoc_member_order = "bysource" autodoc_typehints = "description" extlinks = { - "issue": ("https://github.com/simonw/sqlite-utils/issues/%s", "#"), + "issue": ("https://github.com/simonw/sqlite-utils/issues/%s", "#%s"), } From 92f77c32620d282f8e15de860bead40563b48dcb Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 12 Mar 2023 14:28:43 -0700 Subject: [PATCH 039/348] Ran against updated Black --- sqlite_utils/db.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index e819d17..c06e6a0 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -2876,7 +2876,6 @@ class Table(Queryable): self.add_missing_columns(chunk) result = self.db.execute(query, params) elif e.args[0] == "too many SQL variables": - first_half = chunk[: len(chunk) // 2] second_half = chunk[len(chunk) // 2 :] From c0251cc9271260de73b4227859a51fab9b4cb745 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 26 Mar 2023 16:42:01 -0700 Subject: [PATCH 040/348] Link /latest/ to /stable/ - refs #388 --- docs/_templates/base.html | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/docs/_templates/base.html b/docs/_templates/base.html index a9e670e..43c2003 100644 --- a/docs/_templates/base.html +++ b/docs/_templates/base.html @@ -3,4 +3,35 @@ {% block site_meta %} {{ super() }} -{% endblock %} \ No newline at end of file +{% endblock %} + +{% block scripts %} +{{ super() }} + +{% endblock %} From 8f9a729e8aff972cb18de25b40f4113e26bbc758 Mon Sep 17 00:00:00 2001 From: Chris Amico Date: Wed, 12 Apr 2023 21:44:43 -0400 Subject: [PATCH 041/348] Add paths for homebrew on Apple silicon (#536) --- sqlite_utils/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sqlite_utils/utils.py b/sqlite_utils/utils.py index 4e5bbcc..3bd5db1 100644 --- a/sqlite_utils/utils.py +++ b/sqlite_utils/utils.py @@ -24,10 +24,11 @@ except ImportError: OperationalError = sqlite3.OperationalError - SPATIALITE_PATHS = ( "/usr/lib/x86_64-linux-gnu/mod_spatialite.so", "/usr/local/lib/mod_spatialite.dylib", + "/usr/local/lib/mod_spatialite.so", + "/opt/homebrew/lib/mod_spatialite.dylib", ) # Mainly so we can restore it if needed in the tests: From 373b7886d26902f54d72f1a414f988f79f0ffacd Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 7 May 2023 11:26:03 -0700 Subject: [PATCH 042/348] --raw-lines option, closes #539 --- docs/cli-reference.rst | 2 ++ docs/cli.rst | 3 +++ sqlite_utils/cli.py | 53 +++++++++++++++++++++++++++++++++++++++--- tests/test_cli.py | 15 ++++++++++++ 4 files changed, 70 insertions(+), 3 deletions(-) diff --git a/docs/cli-reference.rst b/docs/cli-reference.rst index 153e5f9..c830518 100644 --- a/docs/cli-reference.rst +++ b/docs/cli-reference.rst @@ -124,6 +124,7 @@ See :ref:`cli_query`. --json-cols Detect JSON cols and output them as JSON, not escaped strings -r, --raw Raw output, first column of first row + --raw-lines Raw output, first column of each row -p, --param ... Named :parameters for SQL query --functions TEXT Python code defining one or more custom SQL functions @@ -192,6 +193,7 @@ See :ref:`cli_memory`. --json-cols Detect JSON cols and output them as JSON, not escaped strings -r, --raw Raw output, first column of first row + --raw-lines Raw output, first column of each row -p, --param ... Named :parameters for SQL query --encoding TEXT Character encoding for CSV input, defaults to utf-8 diff --git a/docs/cli.rst b/docs/cli.rst index 1d67e88..dc0c072 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -239,6 +239,9 @@ For example, to retrieve a binary image from a ``BLOB`` column and store it in a $ sqlite-utils photos.db "select contents from photos where id=1" --raw > myphoto.jpg +To return the first column of each result as raw data, separated by newlines, use ``--raw-lines``:: + + $ sqlite-utils photos.db "select caption from photos" --raw-lines > captions.txt .. _cli_query_parameters: diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index d25b1df..da0e4b6 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -1653,6 +1653,7 @@ def drop_view(path, view, ignore, load_extension): ) @output_options @click.option("-r", "--raw", is_flag=True, help="Raw output, first column of first row") +@click.option("--raw-lines", is_flag=True, help="Raw output, first column of each row") @click.option( "-p", "--param", @@ -1677,6 +1678,7 @@ def query( fmt, json_cols, raw, + raw_lines, param, load_extension, functions, @@ -1700,7 +1702,19 @@ def query( _register_functions(db, functions) _execute_query( - db, sql, param, raw, table, csv, tsv, no_headers, fmt, nl, arrays, json_cols + db, + sql, + param, + raw, + raw_lines, + table, + csv, + tsv, + no_headers, + fmt, + nl, + arrays, + json_cols, ) @@ -1728,6 +1742,7 @@ def query( ) @output_options @click.option("-r", "--raw", is_flag=True, help="Raw output, first column of first row") +@click.option("--raw-lines", is_flag=True, help="Raw output, first column of each row") @click.option( "-p", "--param", @@ -1773,6 +1788,7 @@ def memory( fmt, json_cols, raw, + raw_lines, param, encoding, no_detect_types, @@ -1879,12 +1895,36 @@ def memory( _register_functions(db, functions) _execute_query( - db, sql, param, raw, table, csv, tsv, no_headers, fmt, nl, arrays, json_cols + db, + sql, + param, + raw, + raw_lines, + table, + csv, + tsv, + no_headers, + fmt, + nl, + arrays, + json_cols, ) def _execute_query( - db, sql, param, raw, table, csv, tsv, no_headers, fmt, nl, arrays, json_cols + db, + sql, + param, + raw, + raw_lines, + table, + csv, + tsv, + no_headers, + fmt, + nl, + arrays, + json_cols, ): with db.conn: try: @@ -1903,6 +1943,13 @@ def _execute_query( sys.stdout.buffer.write(data) else: sys.stdout.write(str(data)) + elif raw_lines: + for row in cursor: + data = row[0] + if isinstance(data, bytes): + sys.stdout.buffer.write(data + b"\n") + else: + sys.stdout.write(str(data) + "\n") elif fmt or table: print( tabulate.tabulate( diff --git a/tests/test_cli.py b/tests/test_cli.py index 20fe99e..c3ba682 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -916,6 +916,21 @@ def test_query_raw(db_path, content, is_binary): assert result.output == str(content) +@pytest.mark.parametrize( + "content,is_binary", + [(b"\x00\x0Fbinary", True), ("this is text", False), (1, False), (1.5, False)], +) +def test_query_raw_lines(db_path, content, is_binary): + Database(db_path)["files"].insert_all({"content": content} for _ in range(3)) + result = CliRunner().invoke( + cli.cli, [db_path, "select content from files", "--raw-lines"] + ) + if is_binary: + assert result.stdout_bytes == b"\n".join(content for _ in range(3)) + b"\n" + else: + assert result.output == "\n".join(str(content) for _ in range(3)) + "\n" + + def test_query_memory_does_not_create_file(tmpdir): owd = os.getcwd() try: From 963518bb16dc933694955309e7c9559e551b6a8e Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 7 May 2023 11:38:54 -0700 Subject: [PATCH 043/348] Build with 3.11 on ReadTheDocs Refs #540 --- .readthedocs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index ce66cbe..b092ec7 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -4,7 +4,7 @@ sphinx: configuration: docs/conf.py python: - version: "3.8" + version: "3.11" install: - method: pip path: . From 80763edaa2bdaf1113717378b8d62075c4dcbcfb Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 7 May 2023 11:40:47 -0700 Subject: [PATCH 044/348] Different approach for Python 3.11 on ReadTheDocs Refs #540 --- .readthedocs.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index b092ec7..aa15c7c 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,8 +3,12 @@ version: 2 sphinx: configuration: docs/conf.py +build: + os: ubuntu-22.04 + tools: + python: "3.11" + python: - version: "3.11" install: - method: pip path: . From 2376c452a56b0c3e75e7ca698273434e32945304 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 8 May 2023 12:24:10 -0700 Subject: [PATCH 045/348] upsert_all() now works with not_null - refs #538 --- sqlite_utils/db.py | 22 +++++++++++++++++----- tests/test_upsert.py | 10 ++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index c06e6a0..44d59da 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -2741,6 +2741,7 @@ class Table(Queryable): hash_id_columns, upsert, pk, + not_null, conversions, num_records_processed, replace, @@ -2778,14 +2779,20 @@ class Table(Queryable): pks = pk self.last_pk = None for record_values in values: - # TODO: make more efficient: record = dict(zip(all_columns, record_values)) - sql = "INSERT OR IGNORE INTO [{table}]({pks}) VALUES({pk_placeholders});".format( + placeholders = list(pks) + # Need to populate not-null columns too, or INSERT OR IGNORE ignores + # them since it ignores the resulting integrity errors + if not_null: + placeholders.extend(not_null) + sql = "INSERT OR IGNORE INTO [{table}]({cols}) VALUES({placeholders});".format( table=self.name, - pks=", ".join(["[{}]".format(p) for p in pks]), - pk_placeholders=", ".join(["?" for p in pks]), + cols=", ".join(["[{}]".format(p) for p in placeholders]), + placeholders=", ".join(["?" for p in placeholders]), + ) + queries_and_params.append( + (sql, [record[col] for col in pks] + ["" for _ in (not_null or [])]) ) - queries_and_params.append((sql, [record[col] for col in pks])) # UPDATE [book] SET [name] = 'Programming' WHERE [id] = 1001; set_cols = [col for col in all_columns if col not in pks] if set_cols: @@ -2846,6 +2853,7 @@ class Table(Queryable): hash_id_columns, upsert, pk, + not_null, conversions, num_records_processed, replace, @@ -2859,6 +2867,7 @@ class Table(Queryable): hash_id_columns, upsert, pk, + not_null, conversions, num_records_processed, replace, @@ -2888,6 +2897,7 @@ class Table(Queryable): hash_id_columns, upsert, pk, + not_null, conversions, num_records_processed, replace, @@ -2903,6 +2913,7 @@ class Table(Queryable): hash_id_columns, upsert, pk, + not_null, conversions, num_records_processed, replace, @@ -3112,6 +3123,7 @@ class Table(Queryable): hash_id_columns, upsert, pk, + not_null, conversions, num_records_processed, replace, diff --git a/tests/test_upsert.py b/tests/test_upsert.py index cef8af0..b7267ea 100644 --- a/tests/test_upsert.py +++ b/tests/test_upsert.py @@ -28,6 +28,16 @@ def test_upsert_all_single_column(fresh_db): assert table.pks == ["name"] +def test_upsert_all_not_null(fresh_db): + # https://github.com/simonw/sqlite-utils/issues/538 + fresh_db["comments"].upsert_all( + [{"id": 1, "name": "Cleo"}], + pk="id", + not_null=["name"], + ) + assert list(fresh_db["comments"].rows) == [{"id": 1, "name": "Cleo"}] + + def test_upsert_error_if_no_pk(fresh_db): table = fresh_db["table"] with pytest.raises(PrimaryKeyRequired): From 4fc2f12c88054a4bcc29004e8e9cad39e5b66664 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 8 May 2023 12:39:06 -0700 Subject: [PATCH 046/348] Fix ResourceWarning in sqlite-utils insert, refs #534 --- sqlite_utils/cli.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index da0e4b6..9f708f0 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -948,14 +948,15 @@ def insert_upsert_implementation( # The --sniff option needs us to buffer the file to peek ahead sniff_buffer = None + decoded_buffer = None if sniff: sniff_buffer = io.BufferedReader(file, buffer_size=4096) - decoded = io.TextIOWrapper(sniff_buffer, encoding=encoding) + decoded_buffer = io.TextIOWrapper(sniff_buffer, encoding=encoding) else: - decoded = io.TextIOWrapper(file, encoding=encoding) + decoded_buffer = io.TextIOWrapper(file, encoding=encoding) tracker = None - with file_progress(decoded, silent=silent) as decoded: + with file_progress(decoded_buffer, silent=silent) as decoded: if csv or tsv: if sniff: # Read first 2048 bytes and use that to detect @@ -1079,6 +1080,12 @@ def insert_upsert_implementation( if tracker is not None: db[table].transform(types=tracker.types) + # Clean up open file-like objects + if sniff_buffer: + sniff_buffer.close() + if decoded_buffer: + decoded_buffer.close() + def _find_variables(tb, vars): to_find = list(vars) From a256d7de9887d8476400bbe3753439f2e406134b Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 8 May 2023 12:57:43 -0700 Subject: [PATCH 047/348] Fix a bunch of warnings in the tests, refs #541 --- sqlite_utils/cli.py | 25 ++++++++++++++----------- tests/test_cli.py | 18 ++++++++++++------ tests/test_cli_insert.py | 27 ++++++++++++++++++--------- tests/test_cli_memory.py | 12 ++++++++---- 4 files changed, 52 insertions(+), 30 deletions(-) diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index 9f708f0..ba0a416 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -1839,40 +1839,43 @@ def memory( stem_counts = {} for i, path in enumerate(paths): # Path may have a :format suffix + fp = None if ":" in path and path.rsplit(":", 1)[-1].upper() in Format.__members__: path, suffix = path.rsplit(":", 1) format = Format[suffix.upper()] else: format = None if path in ("-", "stdin"): - csv_fp = sys.stdin.buffer - csv_table = "stdin" + fp = sys.stdin.buffer + file_table = "stdin" else: - csv_path = pathlib.Path(path) - stem = csv_path.stem + file_path = pathlib.Path(path) + stem = file_path.stem if stem_counts.get(stem): - csv_table = "{}_{}".format(stem, stem_counts[stem]) + file_table = "{}_{}".format(stem, stem_counts[stem]) else: - csv_table = stem + file_table = stem stem_counts[stem] = stem_counts.get(stem, 1) + 1 - csv_fp = csv_path.open("rb") - rows, format_used = rows_from_file(csv_fp, format=format, encoding=encoding) + fp = file_path.open("rb") + rows, format_used = rows_from_file(fp, format=format, encoding=encoding) tracker = None if format_used in (Format.CSV, Format.TSV) and not no_detect_types: tracker = TypeTracker() rows = tracker.wrap(rows) if flatten: rows = (_flatten(row) for row in rows) - db[csv_table].insert_all(rows, alter=True) + db[file_table].insert_all(rows, alter=True) if tracker is not None: - db[csv_table].transform(types=tracker.types) + db[file_table].transform(types=tracker.types) # Add convenient t / t1 / t2 views view_names = ["t{}".format(i + 1)] if i == 0: view_names.append("t") for view_name in view_names: if not db[view_name].exists(): - db.create_view(view_name, "select * from [{}]".format(csv_table)) + db.create_view(view_name, "select * from [{}]".format(file_table)) + if fp: + fp.close() if analyze: _analyze(db, tables=None, columns=None, save=False) diff --git a/tests/test_cli.py b/tests/test_cli.py index c3ba682..5360e56 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -13,6 +13,11 @@ import textwrap from .utils import collapse_whitespace +def write_json(file_path, data): + with open(file_path, "w") as fp: + json.dump(data, fp) + + def _supports_pragma_function_list(): db = Database(memory=True) try: @@ -1016,7 +1021,7 @@ def test_upsert(db_path, tmpdir): {"id": 1, "name": "Cleo", "age": 4}, {"id": 2, "name": "Nixie", "age": 4}, ] - open(json_path, "w").write(json.dumps(insert_dogs)) + write_json(json_path, insert_dogs) result = CliRunner().invoke( cli.cli, ["insert", db_path, "dogs", json_path, "--pk", "id"], @@ -1029,7 +1034,7 @@ def test_upsert(db_path, tmpdir): {"id": 1, "age": 5}, {"id": 2, "age": 5}, ] - open(json_path, "w").write(json.dumps(upsert_dogs)) + write_json(json_path, upsert_dogs) result = CliRunner().invoke( cli.cli, ["upsert", db_path, "dogs", json_path, "--pk", "id"], @@ -1048,7 +1053,7 @@ def test_upsert_pk_required(db_path, tmpdir): {"id": 1, "name": "Cleo", "age": 4}, {"id": 2, "name": "Nixie", "age": 4}, ] - open(json_path, "w").write(json.dumps(insert_dogs)) + write_json(json_path, insert_dogs) result = CliRunner().invoke( cli.cli, ["upsert", db_path, "dogs", json_path], @@ -1091,14 +1096,14 @@ def test_upsert_alter(db_path, tmpdir): json_path = str(tmpdir / "dogs.json") db = Database(db_path) insert_dogs = [{"id": 1, "name": "Cleo"}] - open(json_path, "w").write(json.dumps(insert_dogs)) + write_json(json_path, insert_dogs) result = CliRunner().invoke( cli.cli, ["insert", db_path, "dogs", json_path, "--pk", "id"] ) assert 0 == result.exit_code, result.output # Should fail with error code if no --alter upsert_dogs = [{"id": 1, "age": 5}] - open(json_path, "w").write(json.dumps(upsert_dogs)) + write_json(json_path, upsert_dogs) result = CliRunner().invoke( cli.cli, ["upsert", db_path, "dogs", json_path, "--pk", "id"] ) @@ -1767,7 +1772,8 @@ def test_insert_encoding(tmpdir): ) assert latin1_csv.decode("latin-1").split("\n")[2].split(",")[1] == "São Paulo" csv_path = str(tmpdir / "test.csv") - open(csv_path, "wb").write(latin1_csv) + with open(csv_path, "wb") as fp: + fp.write(latin1_csv) # First attempt should error: bad_result = CliRunner().invoke( cli.cli, diff --git a/tests/test_cli_insert.py b/tests/test_cli_insert.py index 45ad362..3c3b88d 100644 --- a/tests/test_cli_insert.py +++ b/tests/test_cli_insert.py @@ -10,7 +10,8 @@ import time def test_insert_simple(tmpdir): json_path = str(tmpdir / "dog.json") db_path = str(tmpdir / "dogs.db") - open(json_path, "w").write(json.dumps({"name": "Cleo", "age": 4})) + with open(json_path, "w") as fp: + fp.write(json.dumps({"name": "Cleo", "age": 4})) result = CliRunner().invoke(cli.cli, ["insert", db_path, "dogs", json_path]) assert 0 == result.exit_code assert [{"age": 4, "name": "Cleo"}] == list( @@ -78,7 +79,8 @@ def test_insert_json_flatten_nl(tmpdir): def test_insert_with_primary_key(db_path, tmpdir): json_path = str(tmpdir / "dog.json") - open(json_path, "w").write(json.dumps({"id": 1, "name": "Cleo", "age": 4})) + with open(json_path, "w") as fp: + fp.write(json.dumps({"id": 1, "name": "Cleo", "age": 4})) result = CliRunner().invoke( cli.cli, ["insert", db_path, "dogs", json_path, "--pk", "id"] ) @@ -93,7 +95,8 @@ def test_insert_with_primary_key(db_path, tmpdir): def test_insert_multiple_with_primary_key(db_path, tmpdir): json_path = str(tmpdir / "dogs.json") dogs = [{"id": i, "name": "Cleo {}".format(i), "age": i + 3} for i in range(1, 21)] - open(json_path, "w").write(json.dumps(dogs)) + with open(json_path, "w") as fp: + fp.write(json.dumps(dogs)) result = CliRunner().invoke( cli.cli, ["insert", db_path, "dogs", json_path, "--pk", "id"] ) @@ -109,7 +112,8 @@ def test_insert_multiple_with_compound_primary_key(db_path, tmpdir): {"breed": "mixed", "id": i, "name": "Cleo {}".format(i), "age": i + 3} for i in range(1, 21) ] - open(json_path, "w").write(json.dumps(dogs)) + with open(json_path, "w") as fp: + fp.write(json.dumps(dogs)) result = CliRunner().invoke( cli.cli, ["insert", db_path, "dogs", json_path, "--pk", "id", "--pk", "breed"] ) @@ -134,7 +138,8 @@ def test_insert_not_null_default(db_path, tmpdir): {"id": i, "name": "Cleo {}".format(i), "age": i + 3, "score": 10} for i in range(1, 21) ] - open(json_path, "w").write(json.dumps(dogs)) + with open(json_path, "w") as fp: + fp.write(json.dumps(dogs)) result = CliRunner().invoke( cli.cli, ["insert", db_path, "dogs", json_path, "--pk", "id"] @@ -182,7 +187,8 @@ def test_insert_ignore(db_path, tmpdir): db = Database(db_path) db["dogs"].insert({"id": 1, "name": "Cleo"}, pk="id") json_path = str(tmpdir / "dogs.json") - open(json_path, "w").write(json.dumps([{"id": 1, "name": "Bailey"}])) + with open(json_path, "w") as fp: + fp.write(json.dumps([{"id": 1, "name": "Bailey"}])) # Should raise error without --ignore result = CliRunner().invoke( cli.cli, ["insert", db_path, "dogs", json_path, "--pk", "id"] @@ -212,7 +218,8 @@ def test_insert_ignore(db_path, tmpdir): def test_insert_csv_tsv(content, options, db_path, tmpdir): db = Database(db_path) file_path = str(tmpdir / "insert.csv-tsv") - open(file_path, "w").write(content) + with open(file_path, "w") as fp: + fp.write(content) result = CliRunner().invoke( cli.cli, ["insert", db_path, "data", file_path] + options, @@ -233,7 +240,8 @@ def test_insert_csv_tsv(content, options, db_path, tmpdir): ) def test_only_allow_one_of_nl_tsv_csv(options, db_path, tmpdir): file_path = str(tmpdir / "insert.csv-tsv") - open(file_path, "w").write("foo") + with open(file_path, "w") as fp: + fp.write("foo") result = CliRunner().invoke( cli.cli, ["insert", db_path, "data", file_path] + options ) @@ -251,7 +259,8 @@ def test_insert_replace(db_path, tmpdir): {"id": 2, "name": "Insert replaced 2", "age": 4}, {"id": 21, "name": "Fresh insert 21", "age": 6}, ] - open(json_path, "w").write(json.dumps(insert_replace_dogs)) + with open(json_path, "w") as fp: + fp.write(json.dumps(insert_replace_dogs)) result = CliRunner().invoke( cli.cli, ["insert", db_path, "dogs", json_path, "--pk", "id", "--replace"] ) diff --git a/tests/test_cli_memory.py b/tests/test_cli_memory.py index bb50d23..41bb00a 100644 --- a/tests/test_cli_memory.py +++ b/tests/test_cli_memory.py @@ -24,7 +24,8 @@ def test_memory_csv(tmpdir, sql_from, use_stdin): sql_from = "stdin" else: csv_path = str(tmpdir / "test.csv") - open(csv_path, "w").write(content) + with open(csv_path, "w") as fp: + fp.write(content) result = CliRunner().invoke( cli.cli, ["memory", csv_path, "select * from {}".format(sql_from), "--nl"], @@ -46,7 +47,8 @@ def test_memory_tsv(tmpdir, use_stdin): else: input = None path = str(tmpdir / "chickens.tsv") - open(path, "w").write(data) + with open(path, "w") as fp: + fp.write(data) path = path + ":tsv" sql_from = "chickens" result = CliRunner().invoke( @@ -71,7 +73,8 @@ def test_memory_json(tmpdir, use_stdin): else: input = None path = str(tmpdir / "chickens.json") - open(path, "w").write(data) + with open(path, "w") as fp: + fp.write(data) path = path + ":json" sql_from = "chickens" result = CliRunner().invoke( @@ -96,7 +99,8 @@ def test_memory_json_nl(tmpdir, use_stdin): else: input = None path = str(tmpdir / "chickens.json") - open(path, "w").write(data) + with open(path, "w") as fp: + fp.write(data) path = path + ":nl" sql_from = "chickens" result = CliRunner().invoke( From e4ed37251746b25ca69b5ace0c8c7992024556df Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 8 May 2023 13:31:56 -0700 Subject: [PATCH 048/348] Show more detailed error on invalid JSON, closes #532 --- sqlite_utils/cli.py | 6 ++++-- tests/test_cli_insert.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index ba0a416..c3c55e2 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -994,9 +994,11 @@ def insert_upsert_implementation( docs = json.load(decoded) if isinstance(docs, dict): docs = [docs] - except json.decoder.JSONDecodeError: + except json.decoder.JSONDecodeError as ex: raise click.ClickException( - "Invalid JSON - use --csv for CSV or --tsv for TSV files" + "Invalid JSON - use --csv for CSV or --tsv for TSV files\n\nJSON error: {}".format( + ex + ) ) if flatten: docs = (_flatten(doc) for doc in docs) diff --git a/tests/test_cli_insert.py b/tests/test_cli_insert.py index 3c3b88d..f403328 100644 --- a/tests/test_cli_insert.py +++ b/tests/test_cli_insert.py @@ -43,9 +43,9 @@ def test_insert_invalid_json_error(tmpdir): input="name,age\nCleo,4", ) assert result.exit_code == 1 - assert ( - result.output - == "Error: Invalid JSON - use --csv for CSV or --tsv for TSV files\n" + assert result.output == ( + "Error: Invalid JSON - use --csv for CSV or --tsv for TSV files\n\n" + "JSON error: Expecting value: line 1 column 1 (char 0)\n" ) From 455c35b512895c19bf922c2b804d750d27cb8dbd Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 8 May 2023 13:52:21 -0700 Subject: [PATCH 049/348] .convert(skip_false) option, refs #527 --- sqlite_utils/db.py | 3 ++- tests/test_convert.py | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 44d59da..3c5d91a 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -2618,6 +2618,7 @@ class Table(Queryable): where: Optional[str] = None, where_args: Optional[Union[Iterable, dict]] = None, show_progress: bool = False, + skip_false: bool = True, ): """ Apply conversion function ``fn`` to every value in the specified columns. @@ -2660,7 +2661,7 @@ class Table(Queryable): def convert_value(v): bar.update(1) - if not v: + if skip_false and not v: return v return jsonify_if_needed(fn(v)) diff --git a/tests/test_convert.py b/tests/test_convert.py index 31a9d28..9fd29c3 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -50,6 +50,16 @@ def test_convert_where(fresh_db, where, where_args): assert list(table.rows) == [{"id": 1, "title": "One"}, {"id": 2, "title": "TWO"}] +def test_convert_skip_false(fresh_db): + table = fresh_db["table"] + table.insert_all([{"x": 0}, {"x": 1}]) + assert table.get(1)["x"] == 0 + assert table.get(2)["x"] == 1 + table.convert("x", lambda x: x + 1, skip_false=False) + assert table.get(1)["x"] == 1 + assert table.get(2)["x"] == 2 + + @pytest.mark.parametrize( "drop,expected", ( From e0ec4c345129996011951e400388fd74865f65a2 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 8 May 2023 14:03:20 -0700 Subject: [PATCH 050/348] --no-skip-false option, plus docs - closes #527 --- docs/cli.rst | 2 ++ docs/python-api.rst | 2 ++ sqlite_utils/cli.py | 3 +++ tests/test_cli_convert.py | 27 +++++++++++++++++++++++++++ 4 files changed, 34 insertions(+) diff --git a/docs/cli.rst b/docs/cli.rst index dc0c072..572b0e5 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -1342,6 +1342,8 @@ You can include named parameters in your where clause and populate them using on The ``--dry-run`` option will output a preview of the conversion against the first ten rows, without modifying the database. +By default any rows with a falsey value for the column - such as ``0`` or ``null`` - will be skipped. Use the ``--no-skip-false`` option to disable this behaviour. + .. _cli_convert_import: Importing additional modules diff --git a/docs/python-api.rst b/docs/python-api.rst index 206e5e6..4d883db 100644 --- a/docs/python-api.rst +++ b/docs/python-api.rst @@ -916,6 +916,8 @@ This will add the new column, if it does not already exist. You can pass ``outpu If you want to drop the original column after saving the results in a separate output column, pass ``drop=True``. +By default any rows with a falsey value for the column - such as ``0`` or ``None`` - will be skipped. Pass ``skip_false=False`` to disable this behaviour. + You can create multiple new columns from a single input column by passing ``multi=True`` and a conversion function that returns a Python dictionary. This example creates new ``upper`` and ``lower`` columns populated from the single ``title`` column: .. code-block:: python diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index c3c55e2..ce354e0 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -2811,6 +2811,7 @@ def _generate_convert_help(): type=click.Choice(["integer", "float", "blob", "text"]), ) @click.option("--drop", is_flag=True, help="Drop original column afterwards") +@click.option("--no-skip-false", is_flag=True, help="Don't skip falsey values") @click.option("-s", "--silent", is_flag=True, help="Don't show a progress bar") def convert( db_path, @@ -2825,6 +2826,7 @@ def convert( output, output_type, drop, + no_skip_false, silent, ): sqlite3.enable_callback_tracebacks(True) @@ -2882,6 +2884,7 @@ def convert( output=output, output_type=output_type, drop=drop, + skip_false=not no_skip_false, multi=multi, show_progress=not silent, ) diff --git a/tests/test_cli_convert.py b/tests/test_cli_convert.py index d26ab8c..97988b9 100644 --- a/tests/test_cli_convert.py +++ b/tests/test_cli_convert.py @@ -626,3 +626,30 @@ def test_convert_initialization_pattern(fresh_db_and_path): assert list(db["names"].rows) == [ {"id": 1, "name": "17"}, ] + + +@pytest.mark.parametrize( + "no_skip_false,expected", + ( + (True, 1), + (False, 0), + ), +) +def test_convert_no_skip_false(fresh_db_and_path, no_skip_false, expected): + db, db_path = fresh_db_and_path + args = [ + "convert", + db_path, + "t", + "x", + "-", + ] + if no_skip_false: + args.append("--no-skip-false") + db["t"].insert_all([{"x": 0}, {"x": 1}]) + assert db["t"].get(1)["x"] == 0 + assert db["t"].get(2)["x"] == 1 + result = CliRunner().invoke(cli.cli, args, input="value + 1") + assert 0 == result.exit_code, result.output + assert db["t"].get(1)["x"] == expected + assert db["t"].get(2)["x"] == 2 From 9662d4ce267accdc8f5301b20a4c7cd82b5ccf34 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 8 May 2023 14:05:30 -0700 Subject: [PATCH 051/348] Updated cog, refs #527 --- docs/cli-reference.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/cli-reference.rst b/docs/cli-reference.rst index c830518..8993213 100644 --- a/docs/cli-reference.rst +++ b/docs/cli-reference.rst @@ -633,6 +633,7 @@ See :ref:`cli_convert`. --output-type [integer|float|blob|text] Column type to use for the output column --drop Drop original column afterwards + --no-skip-false Don't skip falsey values -s, --silent Don't show a progress bar -h, --help Show this message and exit. From 39ef137e6760d385dc48d03eccf9b89943636fc7 Mon Sep 17 00:00:00 2001 From: Scott Perry Date: Mon, 8 May 2023 14:10:00 -0700 Subject: [PATCH 052/348] Support self-referencing FKs in `create` (#537) --- sqlite_utils/db.py | 2 ++ tests/test_create.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 3c5d91a..19896f8 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -813,6 +813,8 @@ class Database: pk = hash_id # Soundness check foreign_keys point to existing tables for fk in foreign_keys: + if fk.other_table == name and columns.get(fk.other_column): + continue if not any( c for c in self[fk.other_table].columns if c.name == fk.other_column ): diff --git a/tests/test_create.py b/tests/test_create.py index 7252e48..54d125c 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -288,6 +288,25 @@ def test_create_table_works_for_m2m_with_only_foreign_keys( ) +def test_self_referential_foreign_key(fresh_db): + assert [] == fresh_db.table_names() + table = fresh_db.create_table( + "test_table", + columns={ + "id": int, + "ref": int, + }, + pk="id", + foreign_keys=(("ref", "test_table", "id"),), + ) + assert ( + "CREATE TABLE [test_table] (\n" + " [id] INTEGER PRIMARY KEY,\n" + " [ref] INTEGER REFERENCES [test_table]([id])\n" + ")" + ) == table.schema + + def test_create_error_if_invalid_foreign_keys(fresh_db): with pytest.raises(AlterError): fresh_db["one"].insert( @@ -297,6 +316,15 @@ def test_create_error_if_invalid_foreign_keys(fresh_db): ) +def test_create_error_if_invalid_self_referential_foreign_keys(fresh_db): + with pytest.raises(AlterError): + fresh_db["one"].insert( + {"id": 1, "ref_id": 3}, + pk="id", + foreign_keys=(("ref_id", "one", "bad_column"),), + ) + + @pytest.mark.parametrize( "col_name,col_type,not_null_default,expected_schema", ( From 923768db2ee15f521fe49ce75002cdd02c82e2bc Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 8 May 2023 14:11:48 -0700 Subject: [PATCH 053/348] Assert on exact error message, refs #537 --- tests/test_create.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_create.py b/tests/test_create.py index 54d125c..6931f0e 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -317,12 +317,13 @@ def test_create_error_if_invalid_foreign_keys(fresh_db): def test_create_error_if_invalid_self_referential_foreign_keys(fresh_db): - with pytest.raises(AlterError): + with pytest.raises(AlterError) as ex: fresh_db["one"].insert( {"id": 1, "ref_id": 3}, pk="id", foreign_keys=(("ref_id", "one", "bad_column"),), ) + assert ex.value.args == ("No such column: one.bad_column",) @pytest.mark.parametrize( From 6500fed8b2085869b9714ce3a08c30f61dc829ad Mon Sep 17 00:00:00 2001 From: rhoboro Date: Tue, 9 May 2023 06:13:36 +0900 Subject: [PATCH 054/348] Transform no longer breaks non-string default values Closes #509 --- sqlite_utils/db.py | 24 ++++++++++++++++++++++-- tests/test_default_value.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 tests/test_default_value.py diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 19896f8..dcd3b5f 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -542,6 +542,24 @@ class Database: '"{}"'.format(bit) if not bit.startswith('"') else bit for bit in bits ) + def quote_default_value(self, value: str) -> str: + if any( + [ + str(value).startswith("'") and str(value).endswith("'"), + str(value).startswith('"') and str(value).endswith('"'), + ] + ): + return value + + if str(value).upper() in ("CURRENT_TIME", "CURRENT_DATE", "CURRENT_TIMESTAMP"): + return value + + if str(value).endswith(")"): + # Expr + return "({})".format(value) + + return self.quote(value) + def table_names(self, fts4: bool = False, fts5: bool = False) -> List[str]: """ List of string table names in this database. @@ -839,7 +857,7 @@ class Database: column_extras.append("NOT NULL") if column_name in defaults and defaults[column_name] is not None: column_extras.append( - "DEFAULT {}".format(self.quote(defaults[column_name])) + "DEFAULT {}".format(self.quote_default_value(defaults[column_name])) ) if column_name in foreign_keys_by_column: column_extras.append( @@ -2020,7 +2038,9 @@ class Table(Queryable): col_type = str not_null_sql = None if not_null_default is not None: - not_null_sql = "NOT NULL DEFAULT {}".format(self.db.quote(not_null_default)) + not_null_sql = "NOT NULL DEFAULT {}".format( + self.db.quote_default_value(not_null_default) + ) sql = "ALTER TABLE [{table}] ADD COLUMN [{col_name}] {col_type}{not_null_default};".format( table=self.name, col_name=col_name, diff --git a/tests/test_default_value.py b/tests/test_default_value.py new file mode 100644 index 0000000..9ffdb14 --- /dev/null +++ b/tests/test_default_value.py @@ -0,0 +1,34 @@ +import pytest + + +EXAMPLES = [ + ("TEXT DEFAULT 'foo'", "'foo'", "'foo'"), + ("TEXT DEFAULT 'foo)'", "'foo)'", "'foo)'"), + ("INTEGER DEFAULT '1'", "'1'", "'1'"), + ("INTEGER DEFAULT 1", "1", "'1'"), + ("INTEGER DEFAULT (1)", "1", "'1'"), + # Expressions + ( + "TEXT DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW'))", + "STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')", + "(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW'))", + ), + # Special values + ("TEXT DEFAULT CURRENT_TIME", "CURRENT_TIME", "CURRENT_TIME"), + ("TEXT DEFAULT CURRENT_DATE", "CURRENT_DATE", "CURRENT_DATE"), + ("TEXT DEFAULT CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP"), + ("TEXT DEFAULT current_timestamp", "current_timestamp", "current_timestamp"), + ("TEXT DEFAULT (CURRENT_TIMESTAMP)", "CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP"), + # Strings + ("TEXT DEFAULT 'CURRENT_TIMESTAMP'", "'CURRENT_TIMESTAMP'", "'CURRENT_TIMESTAMP'"), + ('TEXT DEFAULT "CURRENT_TIMESTAMP"', '"CURRENT_TIMESTAMP"', '"CURRENT_TIMESTAMP"'), +] + + +@pytest.mark.parametrize("column_def,initial_value,expected_value", EXAMPLES) +def test_quote_default_value(fresh_db, column_def, initial_value, expected_value): + fresh_db.execute("create table foo (col {})".format(column_def)) + assert initial_value == fresh_db["foo"].columns[0].default_value + assert expected_value == fresh_db.quote_default_value( + fresh_db["foo"].columns[0].default_value + ) From 02f5c4d69d7b4baebde015c56e5bc62923f33314 Mon Sep 17 00:00:00 2001 From: Martin Carpenter Date: Mon, 8 May 2023 23:53:58 +0200 Subject: [PATCH 055/348] Support repeated calls to Table.convert() * Test repeated calls to Table.convert() * Register Table.convert() functions under their own `lambda_hash` name * Raise exception on registering identical function names Refs #525 --- sqlite_utils/db.py | 18 +++++++++++++----- tests/test_convert.py | 9 +++++++++ tests/test_register_function.py | 17 +++++++++++++---- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index dcd3b5f..5972032 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -219,6 +219,11 @@ class AlterError(Exception): pass +class FunctionAlreadyRegistered(Exception): + "A function with this name and arity was already registered" + pass + + class NoObviousTable(Exception): "Could not tell which table this operation refers to" pass @@ -409,7 +414,7 @@ class Database: fn_name = name or fn.__name__ arity = len(inspect.signature(fn).parameters) if not replace and (fn_name, arity) in self._registered_functions: - return fn + raise FunctionAlreadyRegistered(f'Already registered function with name "{fn_name}" and identical arity') kwargs = {} registered = False if deterministic: @@ -434,7 +439,7 @@ class Database: def register_fts4_bm25(self): "Register the ``rank_bm25(match_info)`` function used for calculating relevance with SQLite FTS4." - self.register_function(rank_bm25, deterministic=True) + self.register_function(rank_bm25, deterministic=True, replace=True) def attach(self, alias: str, filepath: Union[str, pathlib.Path]): """ @@ -2687,13 +2692,16 @@ class Table(Queryable): return v return jsonify_if_needed(fn(v)) - self.db.register_function(convert_value) + fn_name = fn.__name__ + if fn_name == '': + fn_name = f'lambda_{hash(fn)}' + self.db.register_function(convert_value, name=fn_name) sql = "update [{table}] set {sets}{where};".format( table=self.name, sets=", ".join( [ - "[{output_column}] = convert_value([{column}])".format( - output_column=output or column, column=column + "[{output_column}] = {fn_name}([{column}])".format( + output_column=output or column, column=column, fn_name=fn_name ) for column in columns ] diff --git a/tests/test_convert.py b/tests/test_convert.py index 9fd29c3..8fffb4a 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -147,3 +147,12 @@ def test_convert_multi_exception(fresh_db): table.insert({"title": "Mixed Case"}) with pytest.raises(BadMultiValues): table.convert("title", lambda v: v.upper(), multi=True) + + +def test_convert_repeated(fresh_db): + table = fresh_db["table"] + col = "num" + table.insert({col: 1}) + table.convert(col, lambda x: x*2) + table.convert(col, lambda _x: 0) + assert table.get(1) == {col: 0} diff --git a/tests/test_register_function.py b/tests/test_register_function.py index 5169a67..05721ea 100644 --- a/tests/test_register_function.py +++ b/tests/test_register_function.py @@ -3,7 +3,7 @@ import pytest import sys from unittest.mock import MagicMock, call from sqlite_utils.utils import sqlite3 - +from sqlite_utils.db import FunctionAlreadyRegistered def test_register_function(fresh_db): @fresh_db.register_function @@ -85,9 +85,10 @@ def test_register_function_replace(fresh_db): assert "one" == fresh_db.execute("select one()").fetchone()[0] # This will fail to replace the function: - @fresh_db.register_function() - def one(): # noqa - return "two" + with pytest.raises(FunctionAlreadyRegistered): + @fresh_db.register_function() + def one(): # noqa + return "two" assert "one" == fresh_db.execute("select one()").fetchone()[0] @@ -97,3 +98,11 @@ def test_register_function_replace(fresh_db): return "two" assert "two" == fresh_db.execute("select one()").fetchone()[0] + + +def test_register_function_duplicate(fresh_db): + def to_lower(s): + return s.lower() + fresh_db.register_function(to_lower) + with pytest.raises(FunctionAlreadyRegistered): + fresh_db.register_function(to_lower) From fca3ef8cf2a68b7a5fa1d740c4439adc7f83e431 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 8 May 2023 14:54:24 -0700 Subject: [PATCH 056/348] Applied Black, refs #526, #525 --- sqlite_utils/db.py | 12 ++++++++---- tests/test_convert.py | 2 +- tests/test_register_function.py | 3 +++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 5972032..7cc1c99 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -414,7 +414,9 @@ class Database: fn_name = name or fn.__name__ arity = len(inspect.signature(fn).parameters) if not replace and (fn_name, arity) in self._registered_functions: - raise FunctionAlreadyRegistered(f'Already registered function with name "{fn_name}" and identical arity') + raise FunctionAlreadyRegistered( + f'Already registered function with name "{fn_name}" and identical arity' + ) kwargs = {} registered = False if deterministic: @@ -2693,15 +2695,17 @@ class Table(Queryable): return jsonify_if_needed(fn(v)) fn_name = fn.__name__ - if fn_name == '': - fn_name = f'lambda_{hash(fn)}' + if fn_name == "": + fn_name = f"lambda_{hash(fn)}" self.db.register_function(convert_value, name=fn_name) sql = "update [{table}] set {sets}{where};".format( table=self.name, sets=", ".join( [ "[{output_column}] = {fn_name}([{column}])".format( - output_column=output or column, column=column, fn_name=fn_name + output_column=output or column, + column=column, + fn_name=fn_name, ) for column in columns ] diff --git a/tests/test_convert.py b/tests/test_convert.py index 8fffb4a..5279bc5 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -153,6 +153,6 @@ def test_convert_repeated(fresh_db): table = fresh_db["table"] col = "num" table.insert({col: 1}) - table.convert(col, lambda x: x*2) + table.convert(col, lambda x: x * 2) table.convert(col, lambda _x: 0) assert table.get(1) == {col: 0} diff --git a/tests/test_register_function.py b/tests/test_register_function.py index 05721ea..5b92631 100644 --- a/tests/test_register_function.py +++ b/tests/test_register_function.py @@ -5,6 +5,7 @@ from unittest.mock import MagicMock, call from sqlite_utils.utils import sqlite3 from sqlite_utils.db import FunctionAlreadyRegistered + def test_register_function(fresh_db): @fresh_db.register_function def reverse_string(s): @@ -86,6 +87,7 @@ def test_register_function_replace(fresh_db): # This will fail to replace the function: with pytest.raises(FunctionAlreadyRegistered): + @fresh_db.register_function() def one(): # noqa return "two" @@ -103,6 +105,7 @@ def test_register_function_replace(fresh_db): def test_register_function_duplicate(fresh_db): def to_lower(s): return s.lower() + fresh_db.register_function(to_lower) with pytest.raises(FunctionAlreadyRegistered): fresh_db.register_function(to_lower) From eebd1a26ae626cdaba6e568bf11f32c76b60ad09 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 8 May 2023 14:58:28 -0700 Subject: [PATCH 057/348] Removed FunctionAlreadyRegistered error, refs #526, #525 --- sqlite_utils/db.py | 9 +-------- tests/test_register_function.py | 20 ++++---------------- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 7cc1c99..2b2091a 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -219,11 +219,6 @@ class AlterError(Exception): pass -class FunctionAlreadyRegistered(Exception): - "A function with this name and arity was already registered" - pass - - class NoObviousTable(Exception): "Could not tell which table this operation refers to" pass @@ -414,9 +409,7 @@ class Database: fn_name = name or fn.__name__ arity = len(inspect.signature(fn).parameters) if not replace and (fn_name, arity) in self._registered_functions: - raise FunctionAlreadyRegistered( - f'Already registered function with name "{fn_name}" and identical arity' - ) + return fn kwargs = {} registered = False if deterministic: diff --git a/tests/test_register_function.py b/tests/test_register_function.py index 5b92631..e2591f4 100644 --- a/tests/test_register_function.py +++ b/tests/test_register_function.py @@ -3,7 +3,6 @@ import pytest import sys from unittest.mock import MagicMock, call from sqlite_utils.utils import sqlite3 -from sqlite_utils.db import FunctionAlreadyRegistered def test_register_function(fresh_db): @@ -85,12 +84,10 @@ def test_register_function_replace(fresh_db): assert "one" == fresh_db.execute("select one()").fetchone()[0] - # This will fail to replace the function: - with pytest.raises(FunctionAlreadyRegistered): - - @fresh_db.register_function() - def one(): # noqa - return "two" + # This will silently fail to replaec the function + @fresh_db.register_function() + def one(): # noqa + return "two" assert "one" == fresh_db.execute("select one()").fetchone()[0] @@ -100,12 +97,3 @@ def test_register_function_replace(fresh_db): return "two" assert "two" == fresh_db.execute("select one()").fetchone()[0] - - -def test_register_function_duplicate(fresh_db): - def to_lower(s): - return s.lower() - - fresh_db.register_function(to_lower) - with pytest.raises(FunctionAlreadyRegistered): - fresh_db.register_function(to_lower) From dab23884ae49f1497acd70d855105bf9701f4e36 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 8 May 2023 15:08:02 -0700 Subject: [PATCH 058/348] Better error message if rows_from_file called with StringIO, closes #520 Refs #448 --- sqlite_utils/utils.py | 8 +++++++- tests/test_rows_from_file.py | 10 +++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/sqlite_utils/utils.py b/sqlite_utils/utils.py index 3bd5db1..06c1a4c 100644 --- a/sqlite_utils/utils.py +++ b/sqlite_utils/utils.py @@ -303,7 +303,13 @@ def rows_from_file( elif format is None: # Detect the format, then call this recursively buffered = io.BufferedReader(cast(io.RawIOBase, fp), buffer_size=4096) - first_bytes = buffered.peek(2048).strip() + try: + first_bytes = buffered.peek(2048).strip() + except AttributeError: + # Likely the user passed a TextIO when this needs a BytesIO + raise TypeError( + "rows_from_file() requires a file-like object that supports peek(), such as io.BytesIO" + ) if first_bytes.startswith(b"[") or first_bytes.startswith(b"{"): # TODO: Detect newline-JSON return rows_from_file(buffered, format=Format.JSON) diff --git a/tests/test_rows_from_file.py b/tests/test_rows_from_file.py index fdc7a03..5316b86 100644 --- a/tests/test_rows_from_file.py +++ b/tests/test_rows_from_file.py @@ -1,5 +1,5 @@ from sqlite_utils.utils import rows_from_file, Format, RowError -from io import BytesIO +from io import BytesIO, StringIO import pytest @@ -44,3 +44,11 @@ def test_rows_from_file_extra_fields_strategies(ignore_extras, extras_key, expec # We did not expect an error raise assert list_rows == expected + + +def test_rows_from_file_error_on_string_io(): + with pytest.raises(TypeError) as ex: + rows_from_file(StringIO("id,name\r\n1,Cleo")) + assert ex.value.args == ( + "rows_from_file() requires a file-like object that supports peek(), such as io.BytesIO", + ) From c764a9ee8fdb2c55785cf1f538aa5a462cbb292b Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 8 May 2023 15:12:39 -0700 Subject: [PATCH 059/348] Avoid negative hashes in lambda names, refs #543 --- sqlite_utils/db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 2b2091a..ec4bbfc 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -2689,7 +2689,7 @@ class Table(Queryable): fn_name = fn.__name__ if fn_name == "": - fn_name = f"lambda_{hash(fn)}" + fn_name = f"lambda_{abs(hash(fn))}" self.db.register_function(convert_value, name=fn_name) sql = "update [{table}] set {sets}{where};".format( table=self.name, From b3b100d7f5b2a76ccd4bfe8b0301a29e321d0375 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 8 May 2023 15:33:57 -0700 Subject: [PATCH 060/348] Release 3.31 Refs #509, #517, #520, #525, #527, #532, #534, #536, #537, #538, #539 --- docs/changelog.rst | 17 +++++++++++++++++ setup.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index c3c12f2..9c104f1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,23 @@ Changelog =========== +.. _v3_31: + +3.31 (2023-05-08) +----------------- + +- Dropped support for Python 3.6. Tests now ensure compatibility with Python 3.11. (:issue:`517`) +- Automatically locates the SpatiaLite extension on Apple Silicon. Thanks, Chris Amico. (`#536 `__) +- New ``--raw-lines`` option for the ``sqlite-utils query`` and ``sqlite-utils memory`` commands, which outputs just the raw value of the first column of every row. (:issue:`539`) +- Fixed a bug where ``table.upsert_all()`` failed if the ``not_null=`` option was passed. (:issue:`538`) +- Fixed a ``ResourceWarning`` when using ``sqlite-utils insert``. (:issue:`534`) +- Now shows a more detailed error message when ``sqlite-utils insert`` is called with invalid JSON. (:issue:`532`) +- ``table.convert(..., skip_false=False)`` and ``sqlite-utils convert --no-skip-false`` options, for avoiding a misfeature where the :ref:`convert() ` mechanism skips rows in the database with a falsey value for the specified column. Fixing this by default would be a backwards-compatible change and is under consideration for a 4.0 release in the future. (:issue:`527`) +- Tables can now be created with self-referential foreign keys. Thanks, Scott Perry. (`#537 `__) +- ``sqlite-utils transform`` no longer breaks if a table defines default values for columns. Thanks, Kenny Song. (:issue:`509`) +- Fixed a bug where repeated calls to ``table.transform()`` did not work correctly. Thanks, Martin Carpenter. (:issue:`525`) +- Improved error message if ``rows_from_file()`` is passed a non-binary-mode file-like object. (:issue:`520`) + .. _v3_30: 3.30 (2022-10-25) diff --git a/setup.py b/setup.py index 4098a33..3964b3a 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages import io import os -VERSION = "3.30" +VERSION = "3.31" def get_long_description(): From e047cc32e9d5de7025d4d3c16554d4290f4bd3d1 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 9 May 2023 14:08:31 -0700 Subject: [PATCH 061/348] backwards-incompatible, not compatible --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 9c104f1..347601a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -15,7 +15,7 @@ - Fixed a bug where ``table.upsert_all()`` failed if the ``not_null=`` option was passed. (:issue:`538`) - Fixed a ``ResourceWarning`` when using ``sqlite-utils insert``. (:issue:`534`) - Now shows a more detailed error message when ``sqlite-utils insert`` is called with invalid JSON. (:issue:`532`) -- ``table.convert(..., skip_false=False)`` and ``sqlite-utils convert --no-skip-false`` options, for avoiding a misfeature where the :ref:`convert() ` mechanism skips rows in the database with a falsey value for the specified column. Fixing this by default would be a backwards-compatible change and is under consideration for a 4.0 release in the future. (:issue:`527`) +- ``table.convert(..., skip_false=False)`` and ``sqlite-utils convert --no-skip-false`` options, for avoiding a misfeature where the :ref:`convert() ` mechanism skips rows in the database with a falsey value for the specified column. Fixing this by default would be a backwards-incompatible change and is under consideration for a 4.0 release in the future. (:issue:`527`) - Tables can now be created with self-referential foreign keys. Thanks, Scott Perry. (`#537 `__) - ``sqlite-utils transform`` no longer breaks if a table defines default values for columns. Thanks, Kenny Song. (:issue:`509`) - Fixed a bug where repeated calls to ``table.transform()`` did not work correctly. Thanks, Martin Carpenter. (:issue:`525`) From d2a7b15b2b930fe384e1f1715fc4af23386f4935 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 21 May 2023 09:19:30 -0700 Subject: [PATCH 062/348] Analyze tables options: --common-limit, --no-most, --no-least Closes #544 --- docs/cli-reference.rst | 11 ++-- docs/cli.rst | 10 ++-- docs/python-api.rst | 25 +++++++-- sqlite_utils/cli.py | 19 +++++-- sqlite_utils/db.py | 54 ++++++++++++-------- tests/test_analyze_tables.py | 99 ++++++++++++++++++++++++++++++++++-- 6 files changed, 178 insertions(+), 40 deletions(-) diff --git a/docs/cli-reference.rst b/docs/cli-reference.rst index 8993213..c108138 100644 --- a/docs/cli-reference.rst +++ b/docs/cli-reference.rst @@ -564,10 +564,13 @@ See :ref:`cli_analyze_tables`. sqlite-utils analyze-tables data.db trees Options: - -c, --column TEXT Specific columns to analyze - --save Save results to _analyze_tables table - --load-extension TEXT Path to SQLite extension, with optional :entrypoint - -h, --help Show this message and exit. + -c, --column TEXT Specific columns to analyze + --save Save results to _analyze_tables table + --common-limit INTEGER How many common values + --no-most Skip most common values + --no-least Skip least common values + --load-extension TEXT Path to SQLite extension, with optional :entrypoint + -h, --help Show this message and exit. .. _cli_ref_convert: diff --git a/docs/cli.rst b/docs/cli.rst index 572b0e5..d567cba 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -730,11 +730,15 @@ For each column this tool displays the number of null rows, the number of blank If you do not specify any tables every table in the database will be analyzed:: - $ sqlite-utils analyze-tables github.db + sqlite-utils analyze-tables github.db If you wish to analyze one or more specific columns, use the ``-c`` option:: - $ sqlite-utils analyze-tables github.db tags -c sha + sqlite-utils analyze-tables github.db tags -c sha + +To show more than 10 common values, use ``--common-limit 20``. To skip the most common or least common value analysis, use ``--no-most`` or ``--no-least``:: + + sqlite-utils analyze-tables github.db tags --common-limit 20 --no-least .. _cli_analyze_tables_save: @@ -743,7 +747,7 @@ Saving the analyzed table details ``analyze-tables`` can take quite a while to run for large database files. You can save the results of the analysis to a database table called ``_analyze_tables_`` using the ``--save`` option:: - $ sqlite-utils analyze-tables github.db --save + sqlite-utils analyze-tables github.db --save The ``_analyze_tables_`` table has the following schema:: diff --git a/docs/python-api.rst b/docs/python-api.rst index 4d883db..4b143c3 100644 --- a/docs/python-api.rst +++ b/docs/python-api.rst @@ -1115,7 +1115,26 @@ You can inspect the database to see the results like this:: Analyzing a column ================== -The ``table.analyze_column(column, common_limit=10, value_truncate=None)`` method is used by the :ref:`analyze-tables ` CLI command. It returns a ``ColumnDetails`` named tuple with the following fields: +The ``table.analyze_column(column)`` method is used by the :ref:`analyze-tables ` CLI command. + +It takes the following arguments and options: + +``column`` - required + The name of the column to analyze + +``common_limit`` + The number of most common values to return. Defaults to 10. + +``value_truncate`` + If set to an integer, values longer than this will be truncated to this length. Defaults to None. + +``most_common`` + If set to False, the ``most_common`` field of the returned ``ColumnDetails`` will be set to None. Defaults to True. + +``least_common`` + If set to False, the ``least_common`` field of the returned ``ColumnDetails`` will be set to None. Defaults to True. + +And returns a ``ColumnDetails`` named tuple with the following fields: ``table`` The name of the table @@ -1141,10 +1160,6 @@ The ``table.analyze_column(column, common_limit=10, value_truncate=None)`` metho ``least_common`` The ``N`` least common values as a list of ``(value, count)`` tuples`, or ``None`` if the table is entirely distinct or if the number of distinct values is less than N (since they will already have been returned in ``most_common``) -``N`` defaults to 10, or you can pass a custom ``N`` using the ``common_limit`` parameter. - -You can use the ``value_truncate`` parameter to truncate values in the ``most_common`` and ``least_common`` lists to a specified number of characters. - .. _python_api_add_column: Adding columns diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index ce354e0..0c91a8f 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -2639,12 +2639,20 @@ def insert_files( help="Specific columns to analyze", ) @click.option("--save", is_flag=True, help="Save results to _analyze_tables table") +@click.option("--common-limit", type=int, default=10, help="How many common values") +@click.option("--no-most", is_flag=True, default=False, help="Skip most common values") +@click.option( + "--no-least", is_flag=True, default=False, help="Skip least common values" +) @load_extension_option def analyze_tables( path, tables, columns, save, + common_limit, + no_most, + no_least, load_extension, ): """Analyze the columns in one or more tables @@ -2656,10 +2664,10 @@ def analyze_tables( """ db = sqlite_utils.Database(path) _load_extensions(db, load_extension) - _analyze(db, tables, columns, save) + _analyze(db, tables, columns, save, common_limit, no_most, no_least) -def _analyze(db, tables, columns, save): +def _analyze(db, tables, columns, save, common_limit=10, no_most=False, no_least=False): if not tables: tables = db.table_names() todo = [] @@ -2672,7 +2680,12 @@ def _analyze(db, tables, columns, save): # Now we now how many we need to do for i, (table, column) in enumerate(todo): column_details = db[table].analyze_column( - column, total_rows=table_counts[table], value_truncate=80 + column, + common_limit=common_limit, + total_rows=table_counts[table], + value_truncate=80, + most_common=not no_most, + least_common=not no_least, ) if save: db["_analyze_tables_"].insert( diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index ec4bbfc..850a3ae 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -3419,7 +3419,13 @@ class Table(Queryable): self.db.analyze(self.name) def analyze_column( - self, column: str, common_limit: int = 10, value_truncate=None, total_rows=None + self, + column: str, + common_limit: int = 10, + value_truncate=None, + total_rows=None, + most_common: bool = True, + least_common: bool = True, ) -> "ColumnDetails": """ Return statistics about the specified column. @@ -3430,6 +3436,8 @@ class Table(Queryable): :param common_limit: Show this many column values :param value_truncate: Truncate display of common values to this many characters :param total_rows: Optimization - pass the total number of rows in the table to save running a fresh ``count(*)`` query + :param most_common: If ``True``, calculate the most common values + :param least_common: If ``True``, calculate the least common values """ db = self.db table = self.name @@ -3453,36 +3461,38 @@ class Table(Queryable): num_distinct = db.execute( "select count(distinct [{}]) from [{}]".format(column, table) ).fetchone()[0] - most_common = None - least_common = None + most_common_results = None + least_common_results = None if num_distinct == 1: value = db.execute( "select [{}] from [{}] limit 1".format(column, table) ).fetchone()[0] - most_common = [(truncate(value), total_rows)] + most_common_results = [(truncate(value), total_rows)] elif num_distinct != total_rows: - most_common = [ - (truncate(r[0]), r[1]) - for r in db.execute( - "select [{}], count(*) from [{}] group by [{}] order by count(*) desc, [{}] limit {}".format( - column, table, column, column, common_limit - ) - ).fetchall() - ] - most_common.sort(key=lambda p: (p[1], p[0]), reverse=True) - if num_distinct <= common_limit: - # No need to run the query if it will just return the results in revers order - least_common = None - else: - least_common = [ + if most_common: + most_common_results = [ (truncate(r[0]), r[1]) for r in db.execute( - "select [{}], count(*) from [{}] group by [{}] order by count(*), [{}] desc limit {}".format( + "select [{}], count(*) from [{}] group by [{}] order by count(*) desc, [{}] limit {}".format( column, table, column, column, common_limit ) ).fetchall() ] - least_common.sort(key=lambda p: (p[1], p[0])) + most_common_results.sort(key=lambda p: (p[1], p[0]), reverse=True) + if least_common: + if num_distinct <= common_limit: + # No need to run the query if it will just return the results in revers order + least_common_results = None + else: + least_common_results = [ + (truncate(r[0]), r[1]) + for r in db.execute( + "select [{}], count(*) from [{}] group by [{}] order by count(*), [{}] desc limit {}".format( + column, table, column, column, common_limit + ) + ).fetchall() + ] + least_common_results.sort(key=lambda p: (p[1], p[0])) return ColumnDetails( self.name, column, @@ -3490,8 +3500,8 @@ class Table(Queryable): num_null, num_blank, num_distinct, - most_common, - least_common, + most_common_results, + least_common_results, ) def add_geometry_column( diff --git a/tests/test_analyze_tables.py b/tests/test_analyze_tables.py index 5795a7a..a3e4e2f 100644 --- a/tests/test_analyze_tables.py +++ b/tests/test_analyze_tables.py @@ -24,11 +24,34 @@ def db_to_analyze(fresh_db): return fresh_db +@pytest.fixture +def big_db_to_analyze_path(tmpdir): + path = str(tmpdir / "test.db") + db = Database(path) + categories = { + "A": 40, + "B": 30, + "C": 20, + "D": 10, + } + to_insert = [] + for category, count in categories.items(): + for _ in range(count): + to_insert.append( + { + "category": category, + } + ) + db["stuff"].insert_all(to_insert) + return path + + @pytest.mark.parametrize( - "column,expected", + "column,extra_kwargs,expected", [ ( "id", + {}, ColumnDetails( table="stuff", column="id", @@ -42,6 +65,7 @@ def db_to_analyze(fresh_db): ), ( "owner", + {}, ColumnDetails( table="stuff", column="owner", @@ -55,6 +79,7 @@ def db_to_analyze(fresh_db): ), ( "size", + {}, ColumnDetails( table="stuff", column="size", @@ -66,11 +91,41 @@ def db_to_analyze(fresh_db): least_common=None, ), ), + ( + "owner", + {"most_common": False}, + ColumnDetails( + table="stuff", + column="owner", + total_rows=8, + num_null=0, + num_blank=0, + num_distinct=4, + most_common=None, + least_common=[("Anne", 1), ("Terry...", 2)], + ), + ), + ( + "owner", + {"least_common": False}, + ColumnDetails( + table="stuff", + column="owner", + total_rows=8, + num_null=0, + num_blank=0, + num_distinct=4, + most_common=[("Joan", 3), ("Kumar", 2)], + least_common=None, + ), + ), ], ) -def test_analyze_column(db_to_analyze, column, expected): +def test_analyze_column(db_to_analyze, column, extra_kwargs, expected): assert ( - db_to_analyze["stuff"].analyze_column(column, common_limit=2, value_truncate=5) + db_to_analyze["stuff"].analyze_column( + column, common_limit=2, value_truncate=5, **extra_kwargs + ) == expected ) @@ -164,3 +219,41 @@ def test_analyze_table_save(db_to_analyze_path): "least_common": None, }, ] + + +@pytest.mark.parametrize( + "no_most,no_least", + ( + (False, False), + (True, False), + (False, True), + (True, True), + ), +) +def test_analyze_table_save_no_most_no_least_options( + no_most, no_least, big_db_to_analyze_path +): + args = ["analyze-tables", big_db_to_analyze_path, "--save", "--common-limit", "2"] + if no_most: + args.append("--no-most") + if no_least: + args.append("--no-least") + result = CliRunner().invoke(cli.cli, args) + assert result.exit_code == 0 + rows = list(Database(big_db_to_analyze_path)["_analyze_tables_"].rows) + expected = { + "table": "stuff", + "column": "category", + "total_rows": 100, + "num_null": 0, + "num_blank": 0, + "num_distinct": 4, + "most_common": None, + "least_common": None, + } + if not no_most: + expected["most_common"] = '[["A", 40], ["B", 30]]' + if not no_least: + expected["least_common"] = '[["D", 10], ["C", 20]]' + + assert rows == [expected] From 6027f3ea6939a399aeef2578fca17efec0e539df Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 21 May 2023 10:19:16 -0700 Subject: [PATCH 063/348] No need to show common values if everything is null Closes #547 --- sqlite_utils/cli.py | 8 +++++--- sqlite_utils/db.py | 24 ++++++++++++++---------- tests/test_analyze_tables.py | 26 +++++++++++++++++++++++++- 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index 0c91a8f..46af7ae 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -2691,9 +2691,11 @@ def _analyze(db, tables, columns, save, common_limit=10, no_most=False, no_least db["_analyze_tables_"].insert( column_details._asdict(), pk=("table", "column"), replace=True ) - most_common_rendered = _render_common( - "\n\n Most common:", column_details.most_common - ) + most_common_rendered = "" + if column_details.num_null != column_details.total_rows: + most_common_rendered = _render_common( + "\n\n Most common:", column_details.most_common + ) least_common_rendered = _render_common( "\n\n Least common:", column_details.least_common ) diff --git a/sqlite_utils/db.py b/sqlite_utils/db.py index 850a3ae..997c2a5 100644 --- a/sqlite_utils/db.py +++ b/sqlite_utils/db.py @@ -3470,18 +3470,22 @@ class Table(Queryable): most_common_results = [(truncate(value), total_rows)] elif num_distinct != total_rows: if most_common: - most_common_results = [ - (truncate(r[0]), r[1]) - for r in db.execute( - "select [{}], count(*) from [{}] group by [{}] order by count(*) desc, [{}] limit {}".format( - column, table, column, column, common_limit - ) - ).fetchall() - ] - most_common_results.sort(key=lambda p: (p[1], p[0]), reverse=True) + # Optimization - if all rows are null, don't run this query + if num_null == total_rows: + most_common_results = [(None, total_rows)] + else: + most_common_results = [ + (truncate(r[0]), r[1]) + for r in db.execute( + "select [{}], count(*) from [{}] group by [{}] order by count(*) desc, [{}] limit {}".format( + column, table, column, column, common_limit + ) + ).fetchall() + ] + most_common_results.sort(key=lambda p: (p[1], p[0]), reverse=True) if least_common: if num_distinct <= common_limit: - # No need to run the query if it will just return the results in revers order + # No need to run the query if it will just return the results in reverse order least_common_results = None else: least_common_results = [ diff --git a/tests/test_analyze_tables.py b/tests/test_analyze_tables.py index a3e4e2f..f4559ff 100644 --- a/tests/test_analyze_tables.py +++ b/tests/test_analyze_tables.py @@ -40,6 +40,7 @@ def big_db_to_analyze_path(tmpdir): to_insert.append( { "category": category, + "all_null": None, } ) db["stuff"].insert_all(to_insert) @@ -233,7 +234,15 @@ def test_analyze_table_save(db_to_analyze_path): def test_analyze_table_save_no_most_no_least_options( no_most, no_least, big_db_to_analyze_path ): - args = ["analyze-tables", big_db_to_analyze_path, "--save", "--common-limit", "2"] + args = [ + "analyze-tables", + big_db_to_analyze_path, + "--save", + "--common-limit", + "2", + "--column", + "category", + ] if no_most: args.append("--no-most") if no_least: @@ -257,3 +266,18 @@ def test_analyze_table_save_no_most_no_least_options( expected["least_common"] = '[["D", 10], ["C", 20]]' assert rows == [expected] + + +def test_analyze_table_column_all_nulls(big_db_to_analyze_path): + result = CliRunner().invoke( + cli.cli, + ["analyze-tables", big_db_to_analyze_path, "stuff", "--column", "all_null"], + ) + assert result.exit_code == 0 + assert result.output == ( + "stuff.all_null: (1/1)\n\n Total rows: 100\n" + " Null rows: 100\n" + " Blank rows: 0\n" + "\n" + " Distinct values: 0\n\n" + ) From e8c5b042e49c627aefd620c8d4b1c84eb8677f73 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 21 May 2023 10:35:48 -0700 Subject: [PATCH 064/348] Validate column names in analyze-columns, closes #548 --- sqlite_utils/cli.py | 9 +++++++++ tests/test_analyze_tables.py | 37 ++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index 46af7ae..5a88bd5 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -2672,11 +2672,20 @@ def _analyze(db, tables, columns, save, common_limit=10, no_most=False, no_least tables = db.table_names() todo = [] table_counts = {} + seen_columns = set() for table in tables: table_counts[table] = db[table].count for column in db[table].columns: if not columns or column.name in columns: todo.append((table, column.name)) + seen_columns.add(column.name) + # Check the user didn't specify a column that doesn't exist + if columns and (set(columns) - seen_columns): + raise click.ClickException( + "These columns were not found: {}".format( + ", ".join(sorted(set(columns) - seen_columns)) + ) + ) # Now we now how many we need to do for i, (table, column) in enumerate(todo): column_details = db[table].analyze_column( diff --git a/tests/test_analyze_tables.py b/tests/test_analyze_tables.py index f4559ff..dc90325 100644 --- a/tests/test_analyze_tables.py +++ b/tests/test_analyze_tables.py @@ -281,3 +281,40 @@ def test_analyze_table_column_all_nulls(big_db_to_analyze_path): "\n" " Distinct values: 0\n\n" ) + + +@pytest.mark.parametrize( + "args,expected_error", + ( + (["-c", "bad_column"], "These columns were not found: bad_column\n"), + (["one", "-c", "age"], "These columns were not found: age\n"), + (["two", "-c", "age"], None), + ( + ["one", "-c", "age", "--column", "bad"], + "These columns were not found: age, bad\n", + ), + ), +) +def test_analyze_table_validate_columns(tmpdir, args, expected_error): + path = str(tmpdir / "test_validate_columns.db") + db = Database(path) + db["one"].insert( + { + "id": 1, + "name": "one", + } + ) + db["two"].insert( + { + "id": 1, + "age": 5, + } + ) + result = CliRunner().invoke( + cli.cli, + ["analyze-tables", path] + args, + catch_exceptions=False, + ) + assert result.exit_code == (1 if expected_error else 0) + if expected_error: + assert expected_error in result.output From 718b0cba9b32d97a41bcf9757c97fe1d058da81c Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 21 May 2023 11:41:56 -0700 Subject: [PATCH 065/348] Experimental TUI powered by Trogon * sqlite-utils tui command if Trogon is installed, closes #545 * Documentation for trogon TUI * Screenshot of TUI * Ignore trogon mypy error * only run flake8 on Python 3.8 or higher, closes #550 --- .github/workflows/publish.yml | 2 +- .github/workflows/test.yml | 5 +++-- docs/_static/img/tui.png | Bin 0 -> 238641 bytes docs/cli-reference.rst | 18 ++++++++++++++++++ docs/cli.rst | 22 ++++++++++++++++++++++ setup.py | 1 + sqlite_utils/cli.py | 9 +++++++++ tests/test_docs.py | 2 +- 8 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 docs/_static/img/tui.png diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a37907a..d5a0b69 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -26,7 +26,7 @@ jobs: ${{ runner.os }}-pip- - name: Install dependencies run: | - pip install -e '.[test]' + pip install -e '.[test,tui]' - name: Run tests run: | pytest diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2e706df..ab30b2d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,7 +28,7 @@ jobs: ${{ runner.os }}-pip- - name: Install dependencies run: | - pip install -e '.[test,mypy,flake8]' + pip install -e '.[test,mypy,flake8,tui]' - name: Optionally install numpy if: matrix.numpy == 1 run: pip install numpy @@ -44,7 +44,8 @@ jobs: pytest -v - name: run mypy run: mypy sqlite_utils tests - - name: run flake8 + - name: run flake8 if Python 3.8 or higher + if: matrix.python-version >= 3.8 run: flake8 - name: Check formatting run: black . --check diff --git a/docs/_static/img/tui.png b/docs/_static/img/tui.png new file mode 100644 index 0000000000000000000000000000000000000000..04cbeb1ed36b4e4155708dd04194f62a1f52c795 GIT binary patch literal 238641 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%0Amn6rzH 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ȌoRICv8*~Rf z0Z`PSIf3|q)(t(Oh=HP|K%A~{g6A#Gb=!fZMD&F0>x;PEh!!3{_Q<>V3_pQz0(uw= z+aV;|;W3E^;$wUYUQg*dFAS4yZi6j6Adn+o^RBEeFe#)#GEY7K-VbNO3DkUr6)fj(1nebV0oKEWIblK#zGaiGU{Bg4 zjY&@c34j^K0&+W&C16{-Y=;)Le}2k3C-iOM1hF32cCae`rL8};UvK|jNvqoLpt*x3 z>j&!u)?2NEH|rvMvV)!N&=QdIMHvm67!)Uf4}fhkP?8IND<^~@G=$NDbp-8gOqnAt zN9HnNW1e=mz1~?k?DYyxASBAi_!N@;;0=1BCnOu07?SV=0<=Q-YQD?W1SNc%w1fj1eL)K`LAc% z#|Y#t63By@8f5&pjYDFFCV3IwL9$80xq4FuGhrQfkI125_Tgi!6+TA03vIzPZE_)W z@su?^!DvuJX&SH{q$kq0bgRb^tCnm4ctD50k)#x`G*3Wc!P2EHS^77K!i|<|DM|lU zaVTBK;%2F(&dE`o(@$B~4)Sx>x&*QOd9@_9D%G#tzVtV={?BcfKJU(R`hE51)iuCT zOO}?lJg;;ePV9J!=7e;E5(qknEUnsh_#%1&mh-LtggBW?#3}F4%5zS@bYu@jU*u9r zaT+au6Y(X=15*>}iX4Iy(B;USK*mCk`y#{a^aTA4y#e<-P*>zx7KHJ42my~(i$1JP zCPBLhB9>JH;%OetGT)yE^Q&9p1R^<1ZH;T=*-SG+94kr)Zaxzwln~v;Nw%%0A&&F7 zyBQ}0ld^}Eag1>IsSqgiCK|QFI`uJjA=Qn8rjlxn@yP6t%&rMi=o}hE6Vex1JFMk> zkp~8y?N9PM&=*o;EW$w+ba6-trE%L;FH+h6?B=CHiiggPm{L{Nr<3KhU zfg{M{EFb!;Ibl*vX#jtMax~KTqT}vg+Z}VlQm5Uq>~4#sckRyD9Q}<5;qOEYJdUYB zr!h8(F6@{#0YjA9??}WQxR*}!DR|0kxABTN0neiYX$pvuJTCxo7fx41b5$zC)1atCdAfo)7>>{7&KCN8D} zn-1g4hijyiyT*ErB~?DE&b>7^;7|mSzx6_;o*9Lhb?(w zNxz`d6<|^+4Jp0G`{QBQ@OadgfQm}ZK3vmMX}{P~e5XG-ZTMJ_u5s7e!x#Dvb-<;(lbZyX z0gQIj)pygJutiVMw!;=q03m2S!Qq|agapBj42g`#yr(XehJKI)e&9O3>vBv}6!H8e zfET5-v6vF>$)9+7Iyf1)?0s9G8k7(PbmYm>dxO-&saUB9-r<+|2yxZ5T&chRMTFB*`%Jf=speHTw5kC_%<2WCE2Smjk?zyAp&Tc$A7f z6DM4z61D0&)SQ*0F~_+Qzz7`Y49IIlLUapqpxbahw?FtQUWG#PO~JBw2TFkXaOt~% zgEe2sm0Sd;`)FDJs>1L23@q#L@`DILB(ZENc^AOpuz}~HAfggFQZLWy#7_Za=Yr-P zP|sPbu0#3z2vn+Ku0JvnRO7Q@DvvB`mxqKCJo!P>76BZ`qb9gxxE=%%bKn*rpM(ilW6>8v$RXdlhU0qDv~c6? zIwvHni~v1B+YV(;pjGJ!4i;dZa{@3=3Q~yE&<%VyjQwDaq)0B$p-^Oe#=ovk@suzE zf5`p>C&AA|mRyL`>wv$|!7yc_NVcX2z}W~}i4026YErCSj-o0&rorvhzadVT#YvXM zK!Puhufu|CseRx6m$$R+Z5!9t_7uTMX9~0^f*?SEK?c~!eCWmrf~Unyu zH#ABFI`X*{;S(061ZCz_Mgp=SbZMOBFA`Pg4`RUc?M=RH=|C`nZT1Xx>g_~&3bmvbTznNExzwMgeH$|VbrCSA*D zGh_M+W0R43WWeNoVAADS3X`Gx(9bb}wrjcdGPgIFC)89Vz36cg;xfoTj6cH$wez)< zEcrV_s-@J+NFFG8cbpXGRpC9e0ZS0N#}x9m?Hp1T@_4YkL`ty`5la1>IsLacJCnlt zd8I`$6M%%jX(1*|bVJeN^!@H`0o~Cedpx-K^8NA7 zB=d*6FE}gci|ppuVGqsa_HO8&Cp2os=2RM8hN(14j{-3cMrK8+(4`RhelLFM8v9}-4t zwKy?C0D_9Ff~?>Io!~RJ;pI~7=cxf;<%ptdQiG9B*6)Yzg5 z5E+=F3taH4;vjgm?u0ayiL+mA(1fs0F#$Yi`Gih9L7`CjYjr+O3X@r4g2O~hIsS^` z>i9Hkt!YXWxUZoM7a1X6!K|qi3xQ{XZRH2MWGpTsdy6+kSn4h zPjc6*HYgKxDuekvr`4vmH_!Z9?Z6C7fSPt1)vLA$AiEyBIQwtP<#Niaya=1F{zfE6 zI4{z*Q8$h?p(0rqB`vIKN+k@7>fAepHY6sINr-%cEtm#c;Vg2d0<*Ub1th2MGQrp} zeTAM;2$hFR?^M(%3^3umL$fvwu4(1N)Jzeg7#lx-Mt}moEhi5vpq5`~4SThiPIF_eFhQUpZWo#R&?Lzo!Q@S2XUkP& z5~P0u6ebj%&gav7Zkzx?^;O3}!Y7C*QZ!D!jHh3}Oc7nW+b;JM6yo^O0_%bwTYn{e zx@3K#g+pwMFZhbU)@Qr`FQW%3Y{x0zKT-7U_H$p;&wbaADWxhnR8f_OG8X%U78%Bo zONgmZ_#&oUKq_{}dH0GOuG-m-QOJ5m0SOsiA{AXw-)i8%SZs%ohW;(y_Rw22v3tu+Lb5P5`|Wg_tTvM3Q`Ct--9O$5lz z>bTY*lg}N|g2|5y*odULyZ;5z7!m&~T{|s#?%%^i}Y$HqvDuD%D^7E-7 zRR}6zuy)TRC=);g=CU;y)?spw1XU+TYpK7M8Gfdidka2Qg~m_xx31*NZu9b6OuXTL z;A|Zw?v+iDFx`^q0oo_yG@~*#PA*63lbWE2CF9GaDq5P~_K5%z$Ob7BR2h~?u563z z>qG@(3rL_K7fTq0Ts?GVNH7YC>83NDgn$B|Ffz{i46dXI2SYK&(Y4;TjY8G^u25Z} zo=^mZ=kx@BR#&@`(3^V8ilWkOGB*B%ga{%|5i+AyLX0X6?YamFw&r9%J&z!ICY}Hw z{C!SLP$b;kBEj|$TO$sPT zk<3zJ31h(;B&2r`eqgy7CwWdtn9`-zJOu;-32hX1uhb?$FbYD2xQKng8v6t+5f@CC z35739*5n*GtER^0Y!&jPYZPD)41Ig8Jg_FgDB!z+7+^vQCj4ALVba$1)+ksp3PstK zh8ta>bL7M)4NGYN4VVN4f>s-LL=tSvCWP0_GHjxBYZCfIfRKl_J=+x4GRQr_1PL&n zi%$SlfrQI{PanR2`#PQL8&+R#(YjkS6IZ9Esx#y2(q+y~jMYo16#vtgFAlDl33T-K z0-zWW&fWX3z5hC)le}66q3;uxtR0D2!wkV8*OESbr&K6|Q&`m|Hx}s)lnMTXh6Nz- zd^Gfife4BO`Yww=gldTi-gA?~B}A(6pEA3bc85^LrIce*%Pe(er7GRP)u)+^f=@8W zg&B}w!Vf1iKmu{$a!hF@DGv9HPe@R6m{#I!HOh$c6}3CaE@+}e*dn1To4Kf9;1&^S zkug3^xkeccTtZ;N$FT+(qksY`4;D`+yWz%wM&Cx$0${Wu@!wS6xuUv7bM!B{l=aj&d4q;n#KEFA?9i<_pFeBxhkKP>|_*Lyj2l6Fd_dT1wK^ zusd4l{J;d@poIkZ1SUe+g#6IW>VB8_E6~JzKAxZ|C#2G)wy0FDWET;uw(toolZIlL zB)%H}EhbMG31dP+a``cNa$2VZE`e(fB}tJ`Oh3ip1i96_N$RD+gh-Z45G_^b1M(!7 z4+>210R>|-?ARt`mWw(LL0JRyw@R*|44VdqC>b7cZl%0ikw?-M`-jxh>JlS^Vk{_uVM zbvpIL$6Q)yD4Ztw%9NA%V+0gX43JGii#)jt=ifuolpafuxAa_)wMC%Q^s31}C!l@V!qF@wqG(D(K@bbZT?xK)r zkg*j|7)cL>k!b}P|95?sR4FPCx1sVd)>Xcd!W0GXZMUC#V|q0fA8{t4Q$`s7^<|yY9HkCh}`=-cni;#}dX=aY#&} z*YbgI0+0}{j~&hD$ul-VaU(?7twLh(TBan-R!mLqnPA=4C*)E{42es68iI^zuiMhL z@-WIA;|D=UEIXSa_3uz1bPoa(a{gx+rh;{8ls?7POpfif+9j#2$Tw>>jfqx{y8S7PpCW90s2`wOkv-l!GJ1M9+(NEypXH2v^UU(0yQ3U zc+nV>P$@C#Q%VR4Hsc|PvO71b_yCEn0$bTI})s1JpP`obBRu`LG^00(0= zo{)!DNYnQT8cyi>gcOthb1kETg!6Et+XwFxVyA$@D3u4JNZ_qhY5Csm7mBXp^Pva` zA|(Qwg#K&A0*bi8fPKURt~LyQR+cjpN|jn+H)}KDFWI4h`nm&%K&#K?ex16<&pn@D z&F$0=NXZLS9*ik4+`*NNP4mU%;%JY??^5jGa7F`mU*qUNkH|Z)E)a6ue|2(pdUW#s z&ECsnkmT?}nef}*+lvdl%kdr!4c^~>^>QDHiuaF>i7Xqt2#^L8?{_NL1v+WT zPzZrUTP*9PGL#4!IfboH5FqdYOXq=lmI=^T^$DsHzx6sxi%K;fSoLr#& zobZB#>6Bao7cy=d7*;|qfeOOqZRmfD&!3ZIKUF9w7gVEQ6BGxrY^lLiImk_)2`(@} z=Zp+jPSgncK}Lx&;=2I~DfnAEy(Px}N^9pDq#P&Mnv&1UlnO!)eJ>k|?% zVG1Tx>5^6i6c873O_iCdIs0w@*V8L%K{&j6z5kMqhy;8z<9Nd95$gn{fBy*6&(OIj z|7XAMzdGB&gd;lb_1>%F{rwY47;tz+&m3%W6l^7eB0+RNC_nK#TLo8oqhKBFTVchp zwqvtA8+p`wN^-gXBol_>300H&D5Jk$K7|BVO~ks7sPf>l(Dsm7j|59dK>t2z-5;(v zB*7(&=aqK`7%IA(_cw{ZG?2NURl*Lw#i`{Wb$UY%VC6MQ42 z@4F<5A_;?Bz_~;LZxog#Gr>n5d96|3lFZLsoq(xeCUkpGLH{eC(CqXHwVPyIc~Fn= zMC~+BM}GM4D0eQMWGv~Yz%BtMJWIJDFG+_*L|~gRMVHNddYf#=5GSmgBzdBojg1Bq zXc>^Jf|LoVV-!@L34(-Ns}45v;RI=79NR2zcpxD9x`Xvh(4m}h1tKtQvQyWo_X#(` zgkd5?RQOrhrz;JqalWk~?zbp`#n5w6dnG6mGO0EU;zz{sgi5b*MX(7x2JuVVC!B!| z$^;?8hx1=HE&Zq-zkUDqdAh(TVqpSlo*P4R*c;nqrYejj($!>wHzz3J7boNsP7zbQ zz+R88&Q6X`u)hB(CcFd_em#Bt;_w7MVeifRzy#mhK#{PGI6lJz5=toMa4XOviBLkZ zWG0kO;K`O@+b622*(cbVnGmP|Bn&d)j`WOAs5_>5LPfjI(s~#Ptxj-;D-UCeDx{^e zaK2u%RFHr+`{YTRASPjqqyyusnos#&l5i)?qGE9zc|1ggQPLXZfK3$*cfh7iCnhOe zk()w>k6M#=s}5PK@{r);6B~_pz~JZ(6Y`!5ot;{Nq<+8982M>k@zjk5*Ki=m-Jd|!kCV@-E$T~nF^IezHvB^=kGME5h z1DBxT=pyovd?cFyBKRMs@2amypn@zdU5-$5=!6${%a&1q1l^SoMW~9hRdtvewF%ac zq>`;8BeU+f$cZ4~qr?-eO@_xxD;g#P5}$xvE#s=(9WLwh{*6|6dnGyl({HkjbuoIt<&1TE>H%ivJ?lPf96#}IRfjFDs zCzRMm%D^&pE-f0)kWtiiTAxxvK$?$?$nc(~Bs)H!Ny=JB1c5UWw?yvKgNQL+yxH5| z#u%xpaDwMw@BhZ{a(FCEINg8QVZz<2KM2aJHN$gE1UyFD>Q10gZ<&C<46O^?Z-GJ+ zl{Ks>DW6x8T(JQWymTI@J`*~#R1WwAE^n$Di9t%67O_t#SY;atmdiVk@NrT&2`O+t z=Sh;wWZ!AZV+xZeOaci=_;@>oYoEt3s=Uy8ffYNya0B7fHo@}FDfhO2jj^xvZcoJX4rT5cd+=Ns zK|3hLND!cCngm+0C=mM9hUl}x$?6l>1xv47x(QSW@r1L#*dWMG=8)ZdD}T`M8x8fd zZr!25o`3N4oFOKdV#x&nJM><(jWrlSRyFyAuJj~sV?4sq{{D-%GdbFO@s^k%1xxA- za^C50+!pzk+ah1kd9T0*v^sKIBpM+PC=PM*`o(MV6VyX`lWVnMN4JE#pa=hc?#}ia_K|f4ltBa8kN4PSZ za5Mp67$IL(I+6?aBr(RoX^XcWKtkf1Ab|v2g%g7|acOze!r+H9BNTochyV#7#zudd zKVuV0MS?ZhB$&3f2}W%KJOVb7G%&JFh}s5dOhj}EEN(Xr$hJ!u_rane3Z zuii?ZC3;%=ES((6FsO^8H?R2>r*!WaH;1Rk^qv?=_4{U_^#=6`T_%VrP*Jl)xZf-> zxgA46LI)+MVPc<9lLM(tVmLz~>-L7Qkdsv?Y-AGHCn$47`0Oy}6x6Rf=y(Se1sQFb z+TJj_2nlj2O*p_v=>=~tN49WwGU7`{BIAlZ5-xAeZYE6FBydv0k2g~gA^sew@Iw88 zq+A#Q2|B`IShj*XaI@&kjK>)Xa*s!FO)U#hvu!4fEe?0)PvB<*6E2@~38P7H1ti(= zD&9lu4v_D2IS^koPM#?Tsqm~r(3Xasy&t4kYG)AjtXFGz#w0`y6h`KnZ`T?0pu>a< zFPUrJ&Sv<+6npvQ$B)OyA71O48Xq3jkcT5lTCy?)Di78;HVWn0`>TsE=ou@H-s6Z4 z=qQXTZ1-)(OK=+oRO1c!wWJGvfVa?JdUiEiY09-238)>&*vOViXqNKT`4|C8fkNZ9 zOmI+5UdXaLxDnmT9<46*7y8av)g%O;z#Sfn5Rxt%gfP{;4E0Y?)mb*l>NKmnOo)*n zIsPs=j(+)xpfEH4q+n}h5wFVNB!Cp$z;D91E2N6i_3FSGUpi-9rA`IFD8#*lM z-{MD&=KLuB9I*)|1Q>;Q5JwZnm)ft{H$k7_cMaE1>!GzsNa6ujkX{b(L0t)nW zN>G@jQf=@^xEW8fxY`gb!WcJ6ka071ebjD)GJ z0KOg)rc#A~EFeI)>9}PR;&6g=+u{)83{2=FK##QMpf(|FSg?vu(6&hFgB{d&06HE) zsQ?YxgSan3Q7QdnkI?FpFv}PVP@KNC7;NbDH?dF9`U39(1T?y@brvJ-qsvSnA`lQr z!~{-h2qdikQ6@xCK=G@yl})Rq7=^k-$@PO7F9HD|DbJnZ&%+Aj5XJMcEj$sl;6-B` zI!tJ6t#_8>6RL*VE%5g&g$Z@TiG^1{!fnz5!hf*)EKsO5mT;xc@>C2%5$kWga%1#f={4F#U|KH)M<4Pn;=7= zW|{ND9aJ$OLi_+qdke~!>GV%i{xYwfdy%xg&DLPg* z$_^%BkO&>&6=1^ulUK}IyRzm4<}_S46g4fOer`A+0{S*DXS6UBlnLU;`?|lwmUA3vE;69kU6@! zn-dV|I?6P_id$?n`vFlwzl3ULCF8bC1Q!_Y|7(E-?-Z^?|9@vZp$1mUEYqJ#Y+MBi z(%VSqEFdVrc#kGR1i6M{P-J3y{T8K10vB$=f+E58Wk|NG=}m-$=VWau@_#{Y4QdUw z&quWtVhP*$qDB}KZxf8h)LI>uG@M{F=Zqp@C=E>Np^(arE?B{YTV;ZJ1yK}XEL;5+ zgxJF#!SA)k(2cpqafCP*+pJe@fP@I~C&oYs7@;(rpn$grj&TrDkdaT&WM-g3e_Z7G z`T4tFX8+K_v&65zDHCF$AQtJ$rNy$sUz9rz0`S(+hpPcyV2zh2<#Vs~+8h)Lnr+Qz zMYFIl5>Ftl>gD|kFK3k0Bv;pVW=lfC4Uixt2)e~FZJ%#D;l(Qf)OD7z4>l$VnvA(? zxN|d5L2S&L^0Q(g#qk6%!GZ~8HCS=*M3}HX2o$)F0=GKKH85O3={rOwLAou%SX?p^ zF7qPq|E!FcKWH)G_LFFw{45b{(XqwWm?Dgiv@C-iw}j>)yhw{Ek($HhR2w)nRorN6 z6H;K|ez_&1j;A9n?kIHkU6${Q z>0-V9?zLXr-SvVA63A=p>R!D7eL-=kw98qpX#A4^0ym+3D>=6UJ_q?2B}r=(Pk>=4GK;#gM&x27 zgdS@sYi2UySo%k-42BWpyB*Wl=v5j*V*_FWa0)1H0SQKwn2xdz77Q5$wit=|86aeA z86mCmax~N5Q6xycAs|7-y_%T`iS7TgHEA>L$0&)`nP)?c56KT654Vq)2NH6iq0& z>X71GRn)ina*{$_8py``5Kj2SRfpIo^x_E-5>)Yg54rW7#p@mr;y}d=D@hH4?EyhN zLgt{sz(s5;L>YcHp(d+5kkrNZ+#sk@CX8sT!@Ivv|9*G+OGyQOO8+Zq=9Ym83z1u{2ii4xzPzjQIV~76(@+U( z(yBu3p|%PIQ#FgCr?`ZZa4HbQr>_Oz>gEY;*YH zXp`YD=^$a7HDL)7WSgTGe{M=8f=>mN6BKB%Q=9XG_EJfLvaIla__H`^N;X?f0}^g- z7t#qjgeeeb_>V9_O~QZkR1}ljM1U{_5fF-G1uBfj8E0!;&1Mb$TXv3%1QXWhhld}& zIrRx0B-paQJ^riKR(&q8KtFyYr~vlf33tHbcg`ejr31Y zDHE#+51&4M{kFE5t%V6baalf{Pn%UWUr@~fWv;p>Ig2o3-Ef@IWlVHi zHD)p?KI{_{>e@_l9GME!Gd7??N~QSFOwK2bv-q!Z&?I#!sURjGe=jDLPS@-cI8^8G zr&{7f)>u-@Cz#DK(WP3Z6GKEulVoD1Iqh-+f#I8}m5m^Qzyd>I#T5_EN|)7hwA6iq zOSoMz5LEXIF67efLWDzF&L%e?0t0~>C+IY)Mvz)wA$2ZujU<5izAJwMcIoDiH*fy6 zv1M7LS%I>})vOdk^D@rLOye>x2Zr1Jt(6*tq*s^3iEj@#az%uYJWq%KCSd20AUtrn ziq`HV251V7M@GGy(UA6d)kCdW1D> z+UlSh=&~$vQm;NP1{BvB;@qn<&7x$7;t1(%APIbwV?_lXKwtvqL?iWppz}(rr0>AP zdi~=A@qkv+FJGF~!^eMr|M6{Uont1*`!x+lNlX911(Y`&v@>~QO)gN#HY|dI#VwfN zOgJKYH8n^9ohO^O`=ibLTUBP?@T|*9*U$})5y-*qx?DCJsN_nE+px-TniJ^Vt>6=m z$tPs=%r|TK(-y|i9io9*Z#E`p$AGXF8|E6($W?==0#zR16Ve*3_dvqUXCR@gdzgs- zC==ikq<_`uCYgc=0t6m%oYF!9B$x^l>oSM`g@N%7!i1wYZ^#anCH>)+|G2_sKQ@qY z^f%Kv`~>|m_mtaWSyhgsF%)h5{N>{<`4jdEU;>HIxOupHcua%{Y!eg-h$RduKp`_- zC2SL(m$6UFmjWR|982JN>c(hajgg?i1mtfhEtRvyt-}7%HjupeTJ>1~&&2R?*bTP#H1djK@Q9JOQ)l zrP-{xU;pyBzFYj~^TWfsDN9#@2`kcKo-Y>jiakBH#j2|AS%s;Z+#tt*wVQt(lK$9C z^39)gKsAP}2}h|h#FROj2sfM8ujoge=h^1`=&e)Idm+oQ>2 zxu%!{d;);P??S^J?vIb@>4QHuZiz61_yjQFZgO<^?mmCOYdjb?bGU7fW?F6gNKPrn2niBE1lH&?TLjwQFc0uwGl7T- zz6&JJg*co)0sFEU9lU*eFj?8!QrP8>qoY6Wt=*jee*8bQoeOK@NVdl(1d>j`!U$n7 z7|b=8Cfot84Ye{gHcjsVmcTMZ$%J}M~ex3;C?=T3V{jnI(8P}Y&5V2 zqj{|kC~vWL90-j;Hx$B!%R-@LK+NDjWr89BKb(3FG|8Ka%f~d--gX^O_Z$PMLC#*d zNBK&f0Y+E)9P*(BhVRs1negpFrYC0IOh}4c1$^{if~>!vothRP(((~dA*f=D8VxCs z@QKgBJW%Q&&()}DvTib=wUF#dEqBBL+aJ*F50#Mi%h%_hU@z+m6MAHf5rc#4GB0a` z{<6xUyRc9QXoZKe$X+d)?q~`}46t?HY()jHS(ImD(7U)W3}Y(9^vhZA>a6c%ue0-h z|Ln_IUw8-e?5cNWl+~L7Wtk=*3AQQU1>W6#xw6h+R^>#bC5In25i!B*pP7qrZJvFB zIh2kV{I=iGxtn!KRkJN>+VT_+|EUK7ojh+^_X3vBtU+;+nfCL5ubf+M0 zM+)?Q8=#YLsuUg;51%6e?)LXBXrv`@W)*xzJ-~)4LCOS)gv9^2>KY?~&1tn9)$~J_ z33;ZMSJeZIU>|!9r#Lu`52dY~#buN*5opsEa%s(f)RGY*4IOAvbmW0deM?9ubP(Z$ z3CFxVVgmI2w>M_O|9riF+3(_@!U|=?1h^Hhn4Hss>NOMcoPdB>D3J$gkl^oY}}A$yzHC(sn{FmV?lw$a`xr?auQwi1>D6H5O5fY z$bm=!FkzdB$vNBwqY@c#Vj#wFiT5hV_T)S447f=sMrY;$K06e`fEYvIeCdfGAR>f# z4j2nTTrm_d%D60icR?8m-<}W&WuC`mDS)A3D6E(Xzw1oEg{Omf)1I202M$1i?9p8W zt+~?0smuhV^=6aaP8WS@P?glfcODQU>p|ZaKHRUcCuCj%!CBB5kwg6a|tAk1P|)Rg7{54de5G1){>EWCA0B zZ2>Da!)4`>1!_9hCU$EofKq5NL32C{-!%PM-^`N&m@pA<)&Q{90~QKmg1JjduWt#& zhGood;gW$d-Pks~2Gwp%C$4n@NePIZ{a^RBf^;K1Fuq7t- z{SM$*pu@6opX@L4v+f=-VqD&CgIc6A3}S*p7|`!_8eb(QaDWk>#UT;FaTjo=vSb8> zHB*3Pf;9K>VSz;8(oH7?UbeA>!b7R~4n%UMADM75qf4Bm%y#zW*C9eSghf* zL;CF61)*Reo*bE?OGE9n*GYO6_T=3rTl$M{#Dw*B9S3z9l7l-yA&Ljf}jF}H8~*5jN2aQ}4((TM3iiUdRgl}w9|$(<^H;AG^H0}8GdP;i6K ziNN=&)$r$FAVQPTgbYs+p)C{S;_{Tly#OSn5(yni%`><#kcD5`=I}|G|G+7XS^7?9 zNNax52lWopIL4h)+T}ugeXzIN@uBQ(h1Tiolyzf zT}vnY!6%&wSgX`?ytE3O=ulvd)>Vi|keS65AYrq{+#`4~XXq4|s7n3+O&Dj$gg&&) zGm}~eYI!?BwhHnRe1|=VqS|+`FRy)O0>%vYy$eU6r5he)+#nMSx*sv&P!bb@y&x+c zxawwfb~O_Y_zt%MEr73leSuQJ4MN-q2xN*3<&N^3lK^A_x(w zSn*jx!FixswVMJb$*~w~MkaR~cX7gmrOt$8Z}lxO!+On-36$sLnC555H<4iTqVt-`geUJqQvt?f@Gwd6+#O%k!VUZu zT4!0^w%X82D8Y^vX-)$sBxGre2)aH<6@rxM-=1|swbAAe)0vn6N6VI{Ojs@x-|_R< zynb@+Sl=ymZ87Uw|C`eBvKz^j9X{BNv_Vv5!?ltV7wN@gs7x7R--x+ zIXLSrUO;#lw{bWc3fB>%bMzr<48X>FNGgF(IYOaIJY>SR`=@OfAPJ5#fsCQB|5xog zNW+%5$C+0Jm)YTo97-(kAm-vToe+tcF%UbkH~YXe6Lmrod2g06E{BF;?J;(cK&!=s zsbS8_ays6M0ghp=kqPsK+5h@uHl6<%kLL5|DKG(vK!>{mgriDmU;)6;u7k)zfs+|M zjR~h%2&6>7Rsby(v;0eO1UmDqLHtVZIn5p}g|DV^xLgCC5)+)x1V*e~Elte>g0p79 z(R1L+tVz}-_qD=_no8(&8?=HczDh?}whAP84I)4zZ4*Jwta6-VHj)kl-O`le30=z^ zD4n20!h1RazpDpgdw%=&5Wc;<{G^=4BND*Em`aRo*^#EqzuXN1K?^*N&J7`k&^jNU z_n~zTHh`ALsh>ETq7+wG`!dRWadUNc1(jDl7>mRqOp1l>&9n3COL*#I)-n!2I`v&H zO|k!SEr1DdKc8fJ8O07)Gl_ein32)H@%Gpv==GYN!J~6B-x_n+%!o@2b}=>#(=g{hhS!E^ z8ph;)&Pb@|0jt_c+8XAKsgPmqJNTbR7~yjD4haq1!Mf!_b0Czb4az{B7FPG%HW6az zc}gX4DQP`h3GS@L>S*Jg4i!vb&*2zNNZ#+uT3Mtnm7t)Zo0g8i7QLHx~2>e=#= z2bwz2>S@aQ+my$+>om!P562zKrO!zCx)M(@tONA|!jqv;LI+!|aQQ?zbvg$^XL;*Z2TnaHht^@QNY{o)UDTHypZ870@JZNpWGLcB&hgA9yRqeQi zi^YdbNDk9cBEam?REY6J)OIr*-iW>httqtdjoVQ!CKFMNZG3w)#OuX$3@3Q`=kxP3 zUOD{w^XHEVq&tmfFVEiy2@6JotZ$-(tlnAc7$bI~u7SuvHE@GnJtJ#I6S%3KsVD-E zRmA*FS~8)b5$q-r@;sA+kSdm-xeZ#)frfi7j|LL%Ty_hX2wH?u<|uZngccJ%jVw|u zp}FZ92}}eEBq(6BK!BqzYNu6Vz!=l!h5A}czzEqw85wL^N16>7txQD)TALng#8RMp{ zN+Oxy5)%TiQzj?|{1pp2tfQ)F{Ta8Bj>JN(kRl=Rodv}bVAF{Z=(XW$4)}ahi(Ilw zh}E)e8Db&RVgT7kBM$l;r;nD9U8-yZtvowz8WD&a4S#`npiHp&>QiW^KKKCTP6b7YE7H~D8zo`d9ml`NrGI< zaL$su2>d8xzJO3y0_>OLW^K1lx`j-S<*c=(T3qIZ&G?sh6}E({3GpIzIB z!uKmMKiXpTgaSLaixqo-2KEAbx*H^Zl)aANazhk#k+v+`!Z}qQgt8cDw!k6@PcD#v zHa;B>3^cA-Cn&!GZ5gBfR@QJK3Ppv`Ezp+F|NO^qIujz%zq)?MdT2)P`fph8^y%ao zLJ7G<0`WlqLDiYydamlrUP~qfbVBQgVz?*0BIN=78J!a+`PKl0)C_8hKDTUBE2${A8gsu@jmI!KEL!=jD)`y+MOLvT^f_>KYK`E+#}pKD7np#nBH1Jo z?3xSea9v=6hJ@dpRnpSP4Hd`nMy~M|${MY}gr`#`B-MEIo>_fJOC0?T8}i0&-oJjE z0}`|#6oR2M^0(mBCTRQmpaWAu^ChIuz#lr9%%D$$>a>6qf+WHnD+I9Pw@)d$h$_J* zAhe0VJx(`?1=#VRbKqiGtuSvEKEh2OaA13!s41Qkd?kAZ=Z zYQ+~w8FYd`XToorZHKjEjIy;nVZt5Q3>=5oKmYu5Nlb__KVcOm6;4gY49QivW|h#; z30sCjT9#>)q8!LEv=;#zj@9V6dWS?p222P637e9|LDsagQmd>%7vG8P?X4r4NWgQ7 z$@my1a0v;}zh#xVojtRqe*w2Ci!FbyiUSv+ps0W!3b49bNNxijcZ)lw0vrBthQHTL zz(=`wjki)nXfc7QAU%lfKiPi$^|$XU&3Ev{yO!Cvw)i{tT$>Otb?fKdJW#8;#LaL* zq#H4SWJ2J2)YWy*fnPi7g?7rlZVDQN3fMm|$pexH!~=?9v9_1>;87$jPTdC3hh0Eu z%X8&OxGt)MpvYRD1DF;<>|BPS7Ux=b?TRdN!lG7PSqT^~YG{O@_88g%AyE+njRoDd z`s3e!bm|1f1Vsj2C(Ho}^Ov_h5MlYf1STBP0y=h(R%odGpTZR&0Ui(ts~QR9o!|__ zd{aeU%#pq0*mWok0KtMGLy-`!vC|frW?VA?4mejp7{3zYHA)&)*U%zHf=ftP1p$f# z+NurwouId?Lc}=&jnqd&~uqG*dc*jc3 zq!Kv$Pcb1o>CPuAphb(W@=5yW<5ETvbRM)MLYm8@Mc;4op#6I$FS=-uzHjgbOlsVA z35DuI-{BQDWC9G-ulCgv?ECV2hVg_L>ni0{NvX^$i3HRLk02H5YU&l72ns>8*~DH@ z;yO`LPNn~F)H?=S<+;qTOwmO_a$q0MbSt^uTke$ zzYa(U@zoN608%AT^>R~q=EJib?+EV}=xz`Zq{RhHS&U&wm3H*uIBR)|Ls8e=PIqMbr;?jvhIp&EQdyMUE56D&yJ@LX25U z3JTQ0VyU}HpMAE6WIKQt3nl!+=kGC}SFg@A(Qe{7Wph>oxE3TOV@+Rg67w z`La7qX(le%00cKm8aY7GhdM!e?g16;XCyEa2ndqgD5UHV0?SP6=5QZ;XBq21Z;{5RDkQrwe36H zrB09m1>%B^1gz1DsOeyW3M;IkIBP8wzK4EVH2uKD4^+W(2?XsXIT>)&iPA*=|2I?X z(lmAfW)cXDgrHy~$Xmu>bn41B1_mtdQ76d$-6<11Wsck&esmqzqwQ#c+c3%x#Sl(Urty(E=rLHAS8mvM+8Es8%zs{k3fGP^C&!2C2qq>3HHaoO(#sYxv=T&+o zq}yz))B-F*8r(5=C2U#30Xx>|qalyhLrsKeV%PSd7F`>lKjEUc2@^;g>obS!?)>#) zD>uvpjWJ0}EeRrs5rN|lx}^exlM+xeg~I0>3=t?)KrrA%J_HJ%yxrO#OaxY5$p8a5 zrA+XACnm5KRjDQwMpXtR7)QG>Ut`^;0tQ#g4x0SCS_K7&g(m{daHwLGU{H^@7aBoK z$b>Sn+IC_KSlH-F6Khr4@ zN?JXl!XrWGVL%}!4s}voM9s`_csOyG%xnk;dQ(X^mG4nDS}7AE1Z9vf;S+YrtV`UC z2zXeAR zgfS9`5L8bvsqD0>&|IU+&7aJK+6}TkOi;cvCh*Rio%Scz$;G`;hDfl&@Obh z_;lOH_478^ZnZzJ~9O_oL>gHl&s*F-9n9V%iWP6eR;;epG>f;fV&@I5VT* zy|y`_?P(@ldnTYTPRJJ}vf;MRG6TUoK%qk201G}>pW%cdLj?;v3$?~Lo+FGM3t9ry zxO1ENh+jX#Mjazb!qfjN}6SICJJ$adNgkcr37Ybh!bLJkQ}BjabB#a_oqx?6QyaTBlSxujrKnj zfIvK$b#+W+tTDKHP&dQ`T!N+G2oJNl12e(L3CkQVzI{9FKj@Zwo;NF9fj-merlJ{< z>kS*PIb)iRh_G1!5_*!_%G^7%3N2w{4_ien%#`HOTK8SeR-}l8uJ0Xgo5rvMsp+~N z)pQLa;l4-fRtlQ(D5{J-T6cZF7K)x@r8KY%OPi~{7h>C^sLr4*j)q8!k%Gb-25E3x zz0uin8YX0vOu+aT)e;y9e5QKBe3)QujQk0P%~y;QvO=0s!%`tRca21t072qv3Wc&b zfdo>P@!_C|3Bo51nkE_zt|@VtAk7?yN&V=K^>)C}MCiI!G-GP`uK7;GE!!~BO?mET+ zO%t}pwuA)W#_DDrJX&Q(TXa;1;L(~7mFeQh9<5Fm?9tlY%H816T5idsb$L*??9o~` zZDUi9R(3)XLNgO?3-D;YYwzB?fX8m`-kzO*72*y(T4D79UPN|n>y(rVs30_@53Avj z2Bjh>T&)B_VZ3~x<^Oz?fJ-=5ZlTqDbP%`e(Ax~-4p}iOD44P|xRX$2k$^D%){T7w z0SYKnl=d_fBo-MLjD*Z_Op8F^eaT`v&-KNG3XFsbhhfpRJs<&pGBHPdNaZXR@TurN zqC$WIJ8-OclTo0;Py|m@7sN8HQ&Rv|Qcs{j0Z*x<&l{uzO=|`Ani`w^h{AnKg6VKW zynnu!fM>7d7%riT0Ma_93a#Qz(tu9w{(G2^T|%Jn?O0u|@||Zw zGVMCZ5fZShuW>Y@Pd!?TxTQ;)45!uI8GE$8I|h$d;qET}7Cc(_CwEegD(np1=6y8s0e z?j#f{RKA?$Cqi+QkX3(pM-?UlU;ec+56R*wGJ)fSJQftJEGS4IA{YWf+>-(p=vaix z3{Z%H!hNSk@`tE$F8v~YEKX4B(IDZz-+>`95$;D2^>`!#AK9E31e}@O zz&uDNIx!lxQsuaDIhI)KM0Un4@=0;RNj=1b8VubJbb8jWm&@JZc=$d69qxB_vwB8LKty}VFU5z|iU-zZ=Xl=?<9<9r& zCkbT2`&;yA)%e&i^w=Z#soaU>PruyI)qZ&)Djt#OwA8Cg=nkRaq~PGCR0^R`UE#!u zX_x>5VB!Xlz&xOZRjh`Xzz+!q7Q~B$2}Nm#%t0ANK|(kvWbGO+LPY6{3CcquJgUAA z1p|vxF6TJm@-lC&B_t^4Y$d#|2n%@OcXt&fwYVfh*Q}DLEU7OhG?-PHU?EV!?+=p^ zRvI6|19W-}xF&Qf&3`G*tlJz96@aMXhfBf7P|@AtUn7Vk)M=e$Sjk$je9U;OyovPY{1k5)H$v=)lJ z*PIC+tuIF&t=Zx&>fKTVk5-kf2anb}z{Zh0TG40aT>2!wC2de*kxu~8QnZC)I2nF%wp z)G-W1sBS=2Px~=4ft^{mKmu}2EB0fL)Y^7?2%H(6ui44>Ba*P$O2735XpZ`1=u7g$vb?SnJ+-2!X5{09C;|jtfSQ@y;Q&lPAnb2J&vx*L!|K;d zTa?G!cEW@-bHE@0dKs~QL6b*o&K|A7nU%JdN8FO$qg8jVK|jW}_8zV37(80#cE33c z9<8@c!Eu5ed9?1I|GlsGNQVrb@BIAzxsc@1`ZIX6?(*Q#y2{|W&k#=lvV2P6Wl5DL|es31xhRT5%7fg5W`ra`9$N1T`> z5hEsiT>64St)#+>Sk4Ab!O%34_31kooGRp-W&MEs;-CT(^A|c{{RXBTu`H*vvrYcjR`b` z?Tovkz?D?An@BYp+rVK+lF)>NkWIy*GfAf?!KZmTlqaZQq$u#2Igf~~dMu`bP9pfH zCl4U+`x0XWg+xeqIit*o2zI1MgvQl#F+!3!BuG#pPNMrcLZFl(>d{fhr{Ew}SrHSO z_O!lW?(hdMQzqEkz1OoHK%xKj*DIWCd?I()vNqdRyY=d-0wPp6H4+}Q`0>Z{cVCggk7nR$NI4IN*!FoC!e!rw+gbSg4fkGt~(beq$z-SbLBX+h;>#oL3qu)P9HN z?7>DRq)1SlJ1j6xsOv&jJjbylC`i6giZ(D*06YxMOXyO9%CxR9wvZ2YDCOftQ~ThAL|L*sj&UQ z>9rP+HsEAwf`y&)XyvWaIUJ1!X?^$e&ugcx?9bVwRkb~Ow3gdV+jjlnvzkBT(VA6M zGjwf19<4|8XuY``9;_f^g@FhI z6%@BNMko*!YFazD(o$re3J>iQtofk_wszuH2PQ%~$x)pO5$Z87mYOacX5GZXf(S2Gj#-*(@`ujiM2e+W$2 zPQ<BlcqO5fY|I0-lKKD>Vq?@!44zZQb;Aob1V0+U$3>&T9QZWo5QWyW6zSs z+@amGM{B-mmG@{h-Nd7{*{WA>?nFiNnA*mmM=KEO6}+OpV~2L7&rC69&70NLbjE&;rf);GARL4Cp)x&W@3=!C1mo#nt z@l}NR4#8OV{qC=DWk&7H#=kkfP2wNPOn{I+bDF%QI{9`<3QwsSB}f7j#_*ZaVg0Xx z2XTX#VD>u}<_`OV`1M`Y?+?d4F=5OD6#1Syy}CjqtYc2mZGtoF%HbBQ+FgrV1@x;FbB#Ym_JC^#t_XI%kkP=)sbtWW_88uxOV58i57`2c}Wc#d&`^-e&5 z-p*==4h<&$JKGTxekUdXf=WPwjmE$8YMLel3gLSVm~isjMCQv&P9DHr1p?YciWDsK;r~{NCO*UN487?DVG4@iH*yT z_HoAwj$)1b6F?299&2DiM1ntgCTw@CXAmoV)4!gJ!w%*=@UiCOr0aNeT3uDpl(5Ej zrlHQzAt2jAu2x%Pl-rgTZYBpdprmbx2kcWQ%Z{I_#`i&9jfH|6ng(yB57*QkK%pq| zvL1P~y0U~vHF+jvg%LkKA7Gd|5(~+Gt$Cq#b9cqI)%0wA=K7VR8Gp)+7_VD zNT~@5N>wPD)C)3gw?+pU4X|Je;CdP-*cwGqLDO_vUP%`QA8?@+e;{A}_1kaEgtAn1 z?c3%+apjWhTxTppm9efvoG|KbGc2eu;zCdWAr$D1%h)OdYZ)s4LTY?s$wnoB1W8Cx za;8s-Q--|2vnK&dgi{C}rb?l}`2#!=Y=V+02X1IsV6xh$g$l77qtcG$$sK`UlL*0Y zMPQ(2ngwV*)6!nE=>FKcX+03)`ZN#f?*>9tfg0+VX$w^FZDwtF2aYKbA6FWdAy63N zgk87oX!m}0JnnZ#Fl1D_{bA>sK>%OMgfa&c!U;Op_rwUkc8wnw;SgPtbJI);_g zZgQAgIHPzYyhzuWj$gK^H_@ZDrF$Em3oS*3PbaZ7Ay!zp)|ITZF^WA}Z`BZ9+mQhY zLqTF2qXHq!ISz@LR5$otnpKv>!x)=T#2gX!3P5;I@e!W zz=Xem34)ID6GtGUPG4`zBE$)nm>^9Z)9_dj6PIL5Mj6BnNXy+IDnYq}E{t!LgBAJ5j89)D$PcLfhU1qN}%k=RI2a_$dyVppXIZFfEUY1{abGQ<%Ix)oNdjVf$w3Pn)hxoQj3XqVrXgi|ohk}^P;%Z|n!tnzIg<2Y0!CKRFJKuYRZq?sL;5Ge>ig3xMmY^Mnkc$dc&k$_hjoWfPUWFCl_l7g2p z2nC-#B)!4JZ%+lidP3Kd8X^RFB4;o$`x!qPN#!#A}6u_-lt47TkY5CPqk zPR0q>F8o7lC1u?r6FyxDf$z347Uk!lkbeKiZ^(qKD1F}WovJxdP^BRz7|EYKX<&lR z5C&v35Pcajf5?kccnc3^g2WXvLCK8m%m@hrkpPl0N{~fV&RF0$A!A2 z=|stxKByuulR!bCILUJ>pu#8mP#^D6h!ZLWJi<;J7U9gWnR74X2!e+m6CN-(!WBA$ z94;WdN~lPph7cGMQs^gdF?Rq*VC;_s#X6(==kNdg{U5MEoWM*-kT9P_G_tHib1+Y+Ri(AQ ze8vCF2BZZd0f<0~Hs=dAd-erGMNB9QNld7`O*Q;DY-7I?N_r@a6^0f#XUITkix>n{ z$(G9)FFZ6SN{wp@d6GT&I|J}mSXlTZt_TbBE(N-ZHN{GE2+uHpQ{3NcmztK1fMJru zI>pU+b8 zhzMu}FW+9%WjNLYGQcwd*wB=wMKKe|Jb}vzb)!_Hm4oYQC4|-(&(mVLq{yw9#)k;_ zyq*deI~T1pXg%L(sVw_%g|8i$6tLN7j|3glhN#j7BFO3rs}Bw44xeO4V#`mC6A%c1 z1Rp10^6;pXYAA8&`$iQLCMbGyd$7Rij5Y0{!_0v#WtYr_v8y{$Aul`=@IuP+nwUU& z1G$=s3R)PQQ7D;~7xSh+U~v(v1zF~g-%2N5KwrL~2|HIBaY+M(w9qJN>_ML@-E(FH zY;d}|5_oKGto+`?f-)KexgGt4Kp>?3%lxFAGQjmTZ3sZ{AW1QhUXQtZ8q@JAm6d{6 z>P2l64<`+C0m3jjBYEhIR0BI_D&8C8yhwxrE5r#+-CQ?_1&9+63X6+&-|gBOm~a?& z-zY36Y**FV@e!2$i$eCjilM{afHP|g+q~r>p_&jVAP}P9SBL;^C{W0Q`_LPFW?bhFi5`CZOnvOq=5|krq2e*JHFzP|#9##(XBnJ+Kk`eMlf5?n^%tvN_=xS-U>|9mW^pfUovR9^uSuAIFeCdhz< z?)}-jT^@+QA{NjJFFw9`gIBX`R8+`kDhf;mgHT{Mk#sNc^?Pmy)gB81xiA*Z{6gp%xSKM1D#Jg1wi%$LycJbZ1cTwCkye5Dhk6fQOaWzy(nyyYxH}3I+mJ4-$i? zhlL)k-JGWW37kJboNz=Y)YsSbHDcjekGfsHUmgc0fSRc^GGT+3?rjhfL5Hg276{a% zGpmBD9c{^WJy2oo-0+MvPKvM0Zxwr*thNrZfPn(XJ-g!F`=f2b#0@0`X1)Yqi#pT98^gc*~d@vEs?^X~j$oKrBhGjEv*ioygV zEnKwcFiZUH#U9??oAXqNC82^Hqbk=@H4PlfpJOKx+(OhX~>LP!IOt9$vS{n-wXv$pq;V_*SZCU2Ir9c6p0H;8P zru9sij}z*Kq~w*Xafp;{TUuDGX*t2G`Dj+y#~7F}R#OxXN39%+5B=1bZT{omvEd4s z@N=&6JZC0=lybG%FW#OPCNG?_5GQn|b~Z0B-Y)jkO>c^vsBjsvu)x=~*mwK!-z?w( z@6Q=bfWp68?q@XTD7cz)B>~SD7$@X_1bzh3fD|r2{_?YbwhtkL7*nAvPSqJriGji_ z6PVsZOh@4YV1O1>qya;9l;!*9vIGhnhaQGer=e`|U&R4?(QB};ib#Nks1Udy^b8Xs zp`Vm6D1TB>o(Uja^F@cN8UKV3C*W8Y1sR?R$A0^M(E}4^;sgf(*b)?~8x#{^)AXcv z*BZPeCOE$(+rX#|+)Fa8t?_cJnnoRP3%(4#_MiLy>aOp#wu4+BDQwiM0;6A01@rMT*wS%96={ zkh-oL<)tA?NK|0mlgc3QU`%9yE0t@!<-Ac%V?lntLqo@q6|m1p<)Wx!Q1Y+bIN6ho zX^{!JGQfnNmOZHSOt8`-6W+gF?oHR2{Sn>@v7+gb2~{c7haq=pg8Zh_SF0^(eSUVZ-h_Uz*9<%f4KUVqN?9o>6tZXx?P>p^Cp!^2*`c=4ux3zn=(_3!a% zjHM|IVkLVtWIcO#DEbAuzp8_(b7q36yH~G|s>@%!fnRfR_Hudt;+5Ni=j&Vae3h-n zio!L83XFsi6AU#cByqwu9=(D7B-B%Blx+to)wE4yLQ6eM0u9O4ICHSaSNt0wuYwb8 z-S2*UK`4MY;qaEMYssd|VVF?EU-w;kyKPK|RfJ_nZ4!HC0%*4YUy0li6)w5Bus|q~ zkIMBYLINfMuU>zFzvQcrz~Q$qU%*V2LKXe{S1-@bUYED8UlpKNuQ!`FXJ;5E2nc}v zBR)X3fR8x;1R(g&`Ps{}AD{b(feUC!nK20zPzI}vO3g5Vm6RXjK_Svz(||%g5byPk z1NVy+@dz1-LO&Q?K0)@rC`dpe{Ab7XR@u2K0`;BR;k`TPsw>$ZKaHR!polaLLW3Yz2H_m!Ub%>x z8G<`8IcY1NWG5%v$7!wa{~cyMq*6(zwfBBCE3I8d6Jy%^daYHf{sn^0Xgtr-?wN_7#x}8S5*X_Q_)2z{DE!S7L zc`m82x~^|KzU8NZ95$cKE{@Ec7_`5|F>rGxB*$h)y60J=CtsfMJHGs3fC;;jpTPD9TET<_b1-bE;T8x!^?09Q7b!F~6Ua}XnU=P(HsC-DRA(D_ z1aP2qUb=GJ>o!~6Zf78Y33Pm~(kA_uAdJJ~bUYf4$Kz>sfV9GJN+b~HoC)D^G8~PE zBNqa+1=cu|ssb_rOF9Db6Br6DqyN-un0xC2!%yJ^PcN2yZ9cVHPeve(7GBdWL&|=m ziPAUt?QXSk3-AfvUK+4P>mTd+9<~5wD|24hat_?X=)wI83MEG&_<{)m-8f(PnI@6K zfrzxYGTen_7zkDUSv8&44nMPB=7BbJ5~@UiI}L<6-&>zh(I+6}B~)tf_hN-FtgN4q za6?kSOnTGKdDOh^?I$W%h%NLa{+QVaQ7(P*U{0JmlsMcgFW zA&|p{IlkgLt;f3Q!q}g7-J(_uBYUCI{wJ_h$ujR~XyrcKBW9s!LjA2p2eaR#4g((g6f z@Oj}QpdLj!YIKPSvB1Go2VlYom@qpCKbs*FLd^tmgj_=afyCr2lF*(J5V#w{*<$~r ziXE)E*=PuHn?pA+%|vz#WP;I=_97iN2EYXPR(EI%WBE2}bf&OH zf4>|zo3HaFtec~$Zc2xTMcfl((~d&Ugdq453c*fI^<2*v78M4nAc(iqG|(tuJ|lFNt2GA>cijYBEF5oF^AJiERQGuB$h6E8`3V(Izs!V#_7mt%Ga+y( zr-V#MFwIXRr{#Q}5fj)dOh~q>vYBt-Fr4E;qxA zvmJ7R2Z$c|2}vZR2?uk%wN0DOOBTRU3Mp0W(1TBzT@5&Lp*kYN z3}OdyJ*6TP=h+88L7c|JS>}18F`^=I7s^w>^dC9*NCixL0TTxD((Ap;{?qV0TNvi$ zy$>XHrD>+j1RI%P%h6N8gu(m1*In-6b*t&rFjs)_ft>F-pP;Y+=6oOmE+zX}CPZ2S zjpb)*L{J3?TwqW|0kfdU&hd}3dlik3ALo|06&EV67i^3Kaue!EAaW{z0<1*|2uy@X zRXhHouFM_%gcz2jVkx@}hqBHRv@{cvtFSrKj2f+3j)k+erGAH+QAS}K6BKeV98%V3 zy(x`WkCa)tWowPr_Ds%Dag}-{W=fA0|4Ydw3ac((8qkyL7}f5|?qaz$XXu97EvKqOoH3GluLWCFa0GxvovrlG$&vpCsigmPFPl76w)w`u`A8? z!fth>8@X+~{ugkp*b^XotyyLtj1Dp(3`9)KI*vzfgx5`Q`pASZ8x4WHaK|;c0YQP7 zKturh_OEZ0{00atH$EV=!{^y_-p$s0V*wK~U;_3cY9>&BnqlteXm9xz#&m7A{;`~Y zBPNuoz=_8DenL(K$EC(k`3d#03ZdW&A6ZZ_ogOS*v~`Q<_G|WWf2vta0B#-1E`Gb? zBvgK!Z7F78ReueHpNkjni|(U-f(T)WN>WH5IJ3Z}V+UWWCNLA21g1m{dYg#p9;q)<1lSk35Z)PHgM%p<3Fr%q z+*IITFJ=PnN97ZM2~)IL$U${tQ@`X)IrbRAa%kJ`NGDEH512sbc_?hd{BBv_4fYdk zAd_aoYDrAMJs=ZaUI&B0>wIn)*!5tP{RDOt9M^f|C$PCLH@{>;efUx_UMf`aL8VI| z{O_2?5_UMOF>X*)&@d=D3A|XPeg+yZ`yi)ymj-%hO4S@-LLe#@vE@vtimH@#o3FP6 z1`xlc0E8X83R#xm4D{KjOh|B&;8c%{{HW1-V~tjB!8-pe$zt*y_*r2#=g^e$FyYvt zX-drmiO-^0&O1rHW9-08a7b#^Y@(~+i44_R<;}53b_J`M!CUFd;1=lEAraB3)urRB zFrj0*B6fiZ=z3VW`4LrX6n=v6|M+Ok1UkFBG|HLahmmj`Yt31Mljyc4{}k1&~_nUDnAFs9hyihCj^T+v?`4;S)umQr0JF+zc- zl4PS!F}U$gg$r*vmS`d}+dgJ+IzIYOCwQW(nBc6yhzVHA4AF+%?7f*zg1NGPcsFa-eTz7H)uSV^0X59(G?)~<^0S3&`O_w{4m zUfGts1o){`CaBnIt_wk_;pC+V9?5fRru`xhbSH3$yMJ*$fp;qktLP{2Qrs~UP{{py zjcgnJ1o&Ytq_KQ||C9-^P@hnes__)bZPsdz7hTk7)yk}P(el%n6B0+)Xbe4CM>9vQ zt=ED48`N4aZ+*-$Hm8?1Frhi3XE*ED!NvM}(KM!VHa=Z76Rcc?RR}?{L6+moNwe|l zdXo|77_T%{J!!ZG3lYdpb8Pn-B|o8wOz?u#MkbK0;7^HAz;uGnG5Xd=B>ax~1Y`m` z3a|vu#v!vD)(K9MJ4{I8YV2UT0mM}knVP6X%b!Uat%+QW$xldt30>(65tf+{qjwM@ z6Al5kkO@D6ID{PI$WGJQG>zEe6rg~?17E8O3G0>7rRq+aWowcqqn8NI+RFhtg*qGX zxP}pQBQX;igY6tQGdsWp?1{u|=yE=MneSnXh?mr(Z;9B$T){r`dq z)FY<`py$X?g$Sye`9U^-ul!nsQGQT74u8t(w5jgiw>XyoOK!Q7My9Bs9R&}KW&UeO zc&xNkat`psDlLcH;siBSO6tm_KN9D=uIVT69Wg%@FApkW^ZSHiXpV-d<%dSB{`oXS=g0*sL~+Pw z&~E=oJZyKsZ$M@H)H}CK=>>T(5QUgWbh8MzzG8lLoMCGUt&#mh94GJ_IxCN1F=UUz z2jwWN*h^TywM>?+^(0`xgudV#Q1Yqq^lX}M@1q9TbIXu624e6$Y_)0`=qDf)U<)|C z=JIVwTNo`BI}~Zgbv4cCed;LqcbQPRn(1qPU*KHY&Yu_N1x0 zaIDJ|)LI8`8>m^D@1d+{{5*ZjpPjFvFzTI(WNw*_a<75o_|C#P7-kZL*n4Wg}b z0=wawCEQSg&e%f2^{TUfMX|KUiUaig(gIx2*ML3KLIY} zKkCM@gP`unBtdz03L7wl5U}>HpRoPNgx2%(%Txw&h_y@L9lh9x5QS&qa~w{uFRt*? zmx1&PB!t^^Itfx_LUh0^%{0Zf;QH7rau{4nv_}0^Y1Yf51)y_QUaw`-4U3 z$`MvindaVlA7bleaCP0|t!7?me&t6jf4pcRDkelZmF=NP+T3z#oD@Id+V&NH*7!vc9H9qysY-}4N^1`?acxGWQUp&YPpJR z3rL_{yh=Bu@rQteZ5SnyrvzEOBfW7DiF{omM8Ms9B=j0cjMq=_2!nrl!^>o4IU$h1 z!8M_P){Z1r9L)qSQ(#lEL$sq2os?Q!Q^x}QxQrUs>^_)f}D1-}Q0wV!_uh!c*q*8A( zK10VqxYRJsi)CTMmJ&xR{LORqE}^iQulT&S`Le=3i2Z&Kj|w8_?#S|xhr51)|0NT& zI2H#HG@Bm*;ch}P`c)j%qK1b+`C49iCv1emH|lp_3@>>E*+MZvfq`s+bJEn1@K9(; z@c^BJM+I*I-L9`n6g^V}OdMFW~!^Cc0%axjhH zT3|F%RCpXYtkxO{x=;cAgdIa65k8UwN9sAyQigQ!`RwiX$b_|JZQ_av**4F%ivdLv zqW}_)iAq?8(ROio7AhyWZW!x|y*LSlE^q*=wqgoxqq4;TK_N%Ne6>Mm1&4&?M!6}x zO!1p04ddOh@DBrzaG;nqUs!Gk*JANyeq^fCbFC+fs;3w|W68P7Y*)7h4mEV-lZR;K3ITp=Jq4CNna7~>R89HPh}VV2xtOK@aVC~nGjH( zpuv>EH#NwSA58_$K8$~r6;c_-lKCEmiP)dlxJyF|Wo)3afAg7pPjNou_ zxR}(=$%J8DaHX%kPLc$4R!_r)^P#&y0`r(LB?!_&#H8Ov+#jQbx`d07VnO4Oa%D|g za4<&EwXngLFhdDZB1A$s(Yr6=&;Rr1H88Ie#;g!@0>f$Kgc_tk10v{z9oaxvQ?yo`!E;8Y)G+62=yme$b_I1KnBzaf8h4rc2gqT(Rq7fotHDP9O%HKpucY z?4GN&Eh3HxC!p~(@y2w0O5E{49*W{pQ#S2|l& zWrr8rS|kIwYA}sQY9J}w#uoX3P5>oDAhBp@IbhJ@%PNl%jR4~TG6Cbc78T$)-i4_4 z?gXzEfJMRBgf)9cu$Y9EjDY=>F=Pwjgg<`A@|REIvuF-5^Ytp7c2Lw>|DoN$?P;`Z zdYS`|0)>Kb!osytCQK5G;}on3)W5+mGsoMI74~7YAJ77y9fiUasuNCg0!4fts>e9)CmK-q2+agL0!ch+H1PDYheV=%6@%4;skG3Z)KPRM9KE94V# zj+TR*An1fYgb}bJC**KB>_q~)zSAY?87?se2uV2Mufhq^ruFx~N}MIS6Iy?r>_!M6( zMwY{o=4*bE`gZbtIk|s%bA1P$@aLqb3Mm28E8Md&?rB}!fA{qc6bqqy0(NIuL4n)R zW^=tjuhhkLS=F+8i4qBKJ18e`bS9vL%ZWo|D-X!6)2w&;c-Z)G?}UN{;cSVbx%^Nk zI7c_4oLp8wvI?ZYfI@;Lr4zxxmyo3fKE)5*+fW013_5{)KuXwif}^znrpXJVI%I{H z!?P00FQolu*uxzalNnazdLT#aTFE8BB-cRyIi?wVAbu z08J=?XpFD)H8D#6HeLQx2A?ssy=A3L_(}E7(QzF@U}#e^L?pEa(Jr#QIQj(N&qEH zaFtUi=y!$6h&n+!b3!8=|}(q zIoCkgpuL2AcwyYh#W9w#6M(3k${STnXtpnm`MPNT{xqm6xVJ=A2IbLLKD<|7*B=@GRXi59c>hd{@_!KF zC9&dP72y8$IIwRj&k4r?oiLUNT=fzJa1;qx$-L6?4aWGcMVZyg2_B8dkQR3221OHk zueQGPsgtvCgDpt=UxgFu>yBMqaq~Ip6<;w_zkU1GoBVhnHBVM~q`|sHBBQHfQJccq zHL{!X(xWJ-{Q<*;Rs^$?=uJo-mmyvPYK{0xv1?x zp&&cd37yYl{QZx2tKyIon(4GcAqXkh>vlH>pAk4Ud#lMLLFryp_T-STqVC7yPFRj76v#v7{;Zw_z1- z!>(>G=xWwakKk>DS`o7*#4M&ASQzXU#IlF@iRt7TiSsoRN8*pE0Nz$CKSmWmP8frn zFpDqIpAc6Y^i;`|5XcDt`X*r}_z@>Gx(4c))4UTXEi}8(qqUNnf|tnMcU6bPeH?Qs zIiYQO>B8D~9lEQ2{`D6?Ha@J*UP|}R1=!*UwwMN8QIQj@HO?Cj!#);h>bvIze5I5R zIUe$Pj^CQ8t*|fCzXu0~4~HM|lvjP-ld(F2m#0gK0hUB)Ayr z1fSas+hJWdhn&#jf@?snQHsO2_cCo6ashU*6kTSP! zt@qdEafS&4Ikj*atno_|`GXw~dG6u^5Zz@%gp{R#>^SBkkxFgqS^O}ZURt;z5CUi1 zBK`RMz+_;g%f?A8AWC6N5zen8*bQ;Ut)RjjHv>09m|-IB6P&r*I~n6g7AN%7ABpHr z7!iVo8N&awCC2*=5dR+hQx9mO1SdeM&gz5-*k|c`x@oy=>U+p=mJ5BAzL$GD2EV9H zJ#;eYoc8-3t?9$Z&!rae!Ry|}W0`V-_6l%L3Lsy7esmx0deBd0^^E;kZ*DHHkDx=^ zv7*99Ul?nhFMk@L$W5?h3r&5}D+GWM;0GK?TnSSFerW#Kr{DxqI$1y#VU`Wk9C5Mq zDo%9*Jqd-JmJSQ-u|}5Jj6enjv5Pw4Eck+>@pp(ZC2+<|tL?mr`XJ;462XuDek@G@ z$3Qs0Ll2?vSoUs#TBxoeMxLy=q+kPCvP?N)TLDpogix3rw+%X|{)KyTkgkN)7<{dz zouLE@k(@?fV1!5rFz!eQp^lZ}WDyo70dP~+vNiqlusWm#myOhdXN42a#*@%V;k++4 zl7e!U{418W4U{m_gQ17J++*@z{IYja?Aw}oPPp9l=+Ss1-NnA4LEyKS_aD7keSIMfKUvgN zULV0_%AfW>t!UnLCL#Jbdx5>hoDO1))9qq-B|V+=bh z_!6d^z+$P&Bu4WuVJsNRjiPHSi+jg9;nR70pc5vkM)f}8P=|Pfb#SZ(a{%jO5r!~o z$LEV(%MUoAE{B8=z|l)e&|u`peCz+wqyIdclzY6NEWT8loM1!HVG6lx)^Mm0Z1_M% z@Nvd!tKo-ro-5yiguPH86>w5*Fv16atK=|Fr5e)$$%Cwb3zqX^s%~wnIh4eh}y6gg9&LWdz(@^PE8A0oQx#J5*XwPPh!D zFotTS_y=COM-Sd=kJja5X+_+l<6`QMMNZgBWv-IN%YJc|pqb)p=?f?wlNO6Nem1!& zY~%!dy$e)UFARd($&I*h{Hzhr0GV-xuL^5$|I~erL;=;(J2^q`Y!x#YB zKu)lA3l=b71UeNBq=w} zpyg<)KEWa_+pd{Vf)|OA5O_|Lfs4y^+tc%guY(qFi*chDPw z`V)kh9G)Lynl9rLY+JMY!qPaG3_0O~Kj@<|APg5JR}~D00()8oiCZAH6Ce)df}XiN zR1k6c)ONWLPFTQ)AtO3&oy@&(knVzUMu6XltYDmA=un8HFo(Meh0?J~ zV(ZeSPW+85k<JACsezgV|P_)i)NC~Zy%$LLL!{t`?2qfNVuI`Hh^C|9901mQr7avV|(eVm&7S zA&*gWn~}~ac)zygrc5bg)xnr%$~QSYGjee${*Hm|a8Z_OUpqo}u=WF-l)%xbj6h1L z_gSyiuxAGlo;Nliv`{Ai&kSE2PA;2h5>z!+2{xtKkP)(JArmkR>9@tC&f%L^ticH5 z6rAzK$F-LU*{n>k)*XfBD6Q7TmMKeu9E?ChASI}`j$}|&v|Eq$6A6sDPp{IOumB@g zu$Gjdmtoryx@rCi`=QLrV0$&Suiaq0Uoi|e`AHq7G|!&oWUF?d>2m8 zrh?E3=fK6eOzw1beHr};BeYXAjy+m$*`qam^z@s(S;&6u+e%J&+h5AtZ_~#QAr-Rq z>ZwqJ{d~4M3;kHzYhYd{a3pw6*weug>%sbh<($720*k|xA!Wlmx6Bx8cKF(Vwv1Wr}Ylg-HlcML)T$_NdO26Zf$yZ|E@5W=1gyi`37mGt10d=Y$% zaT(}@$OAdQXt~WQ8)wC&`-RCy50&37d!C9POA9bJ^!G?VGZb>w2r3W~*=Os3 zQ@(h9ZkbI6tq%{q31e1BY-EKIDU{VeV?R{rygEfB?UGrJULdq59E#D7oSFMUz?%qi zLPX3nj5=YK6IAcslL?3amn<{R}_d7s%s2_(?w2j zkO>QcBT8(86==80oqI}%oHACztM6LO{%VkR&-OriCRVJtf^1zACMO#?3`f(VcfcwPe@AgB`J{0{1{)N?4M;3T$W7?YeZ zvK{PFRKnuZfD_(vpe|EKgEIB51VC=QBbuW>z(&&nByva!o3T)clO*!BmW!;d+iJiF zS!O8`tj$0MAy#152t*ccpp;cGY;&`UlpvU)1bQZTJ<198I3v(br~o<|RU)i31>uN3 zs1Nlt*D?KPX%eA=8S4QEx$D!g8JKiS+jb9V?H2)x6T2 zFA__*VEK1)g6qHtz`bwZzKb^a@&%XcRvZMkR|E574s5E@_5$1Y*r?lP*Y~9!%W$kM zd!TE4ch!tdU5T&BI!Q2XZU6nK<#0s<0Pjzb7cy>`se>uz0V^aiK!ZNjg0-m7Z2_wT zoFGabq_9kKL=9moaQp&j>sF^sQz%>_gi+$;CdIFotC-;#IU!I2IblP!;e(f%lL6@% zMc2&frgV0!6S5^`!ut=7D5cY(BGtq-t0F<6ERkj72qV>I$f$8?8m2??E(LUX8S4b^ zQgDQmu|YJj*OIiaqO_cv%y z^glRwm$QU0k2r^{4pdwM;(U>D(fJ*|d;urWq2M<6zIfco0#2Y#2VAQ$mio6Z3XLQxq)53`n6^ zuM3h?BO1Zg&Y054mZ@Y2BP*Io$ZPruOrj~wpo^40@aj*XHpo&d2P*bqZT@r53A*;2 zz`sDfi>WI&0yzP6g2nmk(#h>w{}G?T38_-To)gS_0X2Q-sU;Zrp)>yKPwV4(H~k5s zY?TxAets^8oB-5X?U1%jTe<*_PcnjR#nc4ZU%_&40po&-3amhx5LXIruZ<-g@LV&L zU?+@Vl@YdRAX9mx!6$=yhhp>|9%9^tEr$A+#y}-Z!VWRw5cheVk^-5HLSYVLP9Ub7 zKvI~&JUz_3)w}UQ#wBn9(D|(6xE1G%)Fs?_PN+BDcDNBT2)H`ngj2poFx(;Jjf}GS z+Z*Yw`pfc5iF>Hv?N$bsmLnV~i($s^T-H|`W4qo2xd(W!YKc$ zxMn5ODkFtxN_GbV9bd$7+Xb;>PDOPWgZ&gLV7&qmw$E{)=mCz$Vm)zLml%SzjJ; zFyPIlMiAuCSsx!{MXfi5*BXgUu5;ZNiA#z?@u5rSVKxE=4%%Da?lDB8)CHv z8+dLx;a`l5U~QhWe{R(j2yWmDAz0@H*-s~Po+~~)^LbrXUF|Dn3WQ{=9o>9+(Z|hF!z*}t9|32n~u}<)mz@!G& zTe27VMkygV6P`wo*1!JcukSn~B$|`UIGLNE6fsBnSrAN zDQNZLaeD!eKSYQ*o*;xw7X>#W?8Vp5v7GV{&AM1)MChb+aEZ|&sF4td@bx}cj z$}_ER+K;A8a+=Gn7D}_qAus}uM!8=XCV@XW1fG@=t?BTjt`hTYP64rHQn`#dFkW3|F z;Dk2I>N-u^VNF$~VAL}Q8<|`cf~8?EAQw<4P&JqjW0f#379bVC1z|;%044y-9ETOS z5#&ITa~>C;z-SV>8aYNvKyU62pQpX#J4wh3WP`{Aycz2Ry#R@uI2p6$yim|k?L1so zMK92NLtUZ*0vkk1n7;IkJiuxSG+_1>^!rs^>n3ypK|=6X;RMe4vbP;%-RcgW$y({? zR6OcPs0E~g4KCaqW7MOS59eA@UjdOM-t3AnH3a?Hj1K62Q9ATu4V*xM=p5BNlNMwHTi^skXSj7`?KweKfbK1~ zj+_9ct6iLw6R0jUPASKYZ~}TZ|Kxib+-)~=yr#d0J5&-mQ?cq;QWS9pkf}9X7g$W(-v;@h3#30L* z7V3SERz8R%vZ3`{(dGs`DGNgn8wx(H!aqIsqzyoIpn4_zWJp7%F?WR6_8x8(qKr?YkR(G5UU#FThe< zE&aDT#qyWme!IE(?YC>3Wt^X|e?=l8DH!XKat#oUO=KnYFqL9-L|I>}HN_m$OcBAIv;7$uI?HB(Gt9|WI} zl`&w{s#BqwwtjB+Ypwe7IL!yy9RRo448RFvkRtlX2^lzHfRcTdloO_?_t;>JZ*&8j z$EqRT_#UlzfPjxAClrRHfQx4Z{%&ly#dh)A>vLKbDidngWHJqX>a1Al|AzCu`m8Nd z_~exe1uGkuIiuU!BGEboS()xk-f#x}Jc5C+L7j_Rf(Qa9IJF*1S^K661yhw2ta!l9 za!p6VN?R%^Y-M=f+K~~!C!S+3|3f)}!Pli#FKNP@K!8HQfe-@9{^uvX)jqS@mgv>% zD|yzK6zE`v&!lc@%stu5Y>&59HI}1|Atl68#>6^L35+<97+6WJ<Q>HP>B;99w? z9w#H{fUgH=4!{OE2kbM~ci$bbFODA|msn&yhl-pKxFF60b)XXj4hwEPCmg{bZA|t? zJz6zCArsK)pwris>DwiGw8G$BmJkYVnaL)w_#mXPg6j>Gz#&GQq0*Z3CKQEPqM>z{ zJ$%+LmzZq!VK};P_@W z+Jpv7)*7rao_z$tH(WJQC;U5sjpYiZldPiz8EAPlgB7F%rZ-B~RC9Ty%`21>NC}B= zC2B&TOudZ8g~47~d9zYon&KxyMj+~N7(^~6tN;xABf#RY@V~}) zHfu7RG;7{LkJWKkA17yhEz&7m&R zq;gvC7fZl8WtwWR!f6o&?(;oW+ON;QNME7Dtp5u6bu0S}>rWD&_sCC7|IThFZXu$SepJg#WCa;eoJlKXyA< zc}f`S1g;1>*vzcA83G~LoNyz|4EH-S0!}U}p`kMY*OBRf>!jJzGzMV~Z3nqH9qsc) zl2cANkO|Q|C>Sm|suxiJzC$~x=ebszUsB!K{k^7GkUbXKf@3dtOaNN zTj7MS{go}M-k&8EPI&q3Ic$ax;Dm2*9U^ zR?b=6sG|U+0J2E4jFZ9`Yt;||7nr&>6fM~CoDeC2I>DfQn;|7=?}cosoZyrZ3ivrg zy@PTBDIxg~bDM*r=i2zhSRv9kzdrxv!P7o-{cF7QmqM~P!U=-1&cZP#kLNJ8&gwJB z3H@(UIntNvVx(~vatMYFx5O$;zBbxIbOlKkG0_F1{cx-@!EnfI1c`yj2JBF{AQ7w} z7uX-c1r+;6=&>35JS9Lu+2n&Rgwn!X(ezj+V70UvF|-}B!Dz2$Ie{nxqBmiTssJk} zrecBM6pH*5`IaL>)X|;fgf!}evi_k?7^#FmhMd6BaSxP`Zgk0}oWK)-5T^NtyL~@a z_wX@lARoN?vb+e#lH^u6;hQn+(VC{{(c15Ow5Au&*`qaC`JUVIxK~e~-2eIM(}{kWmG6&3{yT}{1cMkT!J&vG=Y!N7Dn5+wXKEf7;r)Y zN?3f@bAr$V(-VkfValpxXuW^=;3x5yh%V?~@SQ*H-#mNz;GujPFZ!iFCMqIuf=k0Q zz6&S(a$oRUpV4w?KnYleq!5*YS<_~KUqc-U7RarK3%X#qWLCWx1a!hGw^L|Wc-{~; zb9#OsfGn`ixda$%CKxcpbS?+3$H+`#gc1N%3b+u_pKutKoRmPM*$5;Ba>6{0#tLH4 zp`92I1s^!2hKBGV!_sDS+RjLXSxz96rC6tXPN)v~0Lw%tpyDCSD-ER32`M^~Z9b70 z$}uM}Vyc8NMH&0CrqZMJSN3R?-m6dEd`6$8Bzrp?`V*@!Ph4 zp@r2iL^1S)`S$ABv-^*pKYR9D-@lZze19YT7#2TE_t($Al2ynFrm-NI&v@=Po}gd6 z`LKdas0+ps859IWP!uH!5LjSSSfeG3o)XM9PmDDyfEGg`oPhJt`WPiP``e|Ei01^P zU|RHJv6WS}K>-owmo-H@-~^MV%f)+g0)d(+l?hx73yfVVt!N&;YO|k37reUv==JqN zw%$B{_TcB|&!4^Iu~+RoIhJakoWnC7JbcyEVr}&J?7yQr-JxA(<(57qGVP$awybdk zMEyrcf^`F0I3^UVjjWKb)N+Vmr6oh0dZvtsGf?52e66A^^gv;UFj!Jqj=}jKdD2cw{0QeOy#gm+n zV!?&)LOB7ZipHSyTFVi+f)gexA#y^{37OErC)wCwMV$~C0cTS}nBwwF^XS1l?Z=v; zN9!e&|KgGKQ#u!(h&mymP!JN3-~VgJ9<6mJKl zzQ0pWc+r1;|KRCWMNasdxLW!^zy9$4&4XvR=*;pekupnDvs(hK1WyVW=6O{r9U$(Q zSzp(jQt4NnN%%XZ!3yC7$Bs!YlMjYm5Vlj{gjCLg7s)jV(%3T7a_g#2v%0lH31#Gj zPc|bZypNoK3b6N~jwnepL?dng{3p@=@F&Q7d)0TPjQ_Il)pkhxXL8Kh0Vmj$&v5i7 zJdxH_H~r&B68A+b@ifKQ6By46G5DFVk_ij5JjN0=j?l4AV3RUs3ZPg+U<5Yc)eUcW zo>6(Uoiu6oXau8qAe|+QBK50)qugLp$ClEAuFlCpHsf&dm$-X0Zz;`U;9)ifS{})= zbguo;%+t;mAw4qE!bCD)w9U68$GY;gcfLcvZlLKMwp`0eyrNJ`O)KSnS&E>EV+5R*9l(@;?Wr5Pkvrr!fr;7 z*5%E+eUH}uw+Fz(w_lcIm1T-^O0zlQPo+_Qg;CAo<(6y*tR`RY$KoYS^IyPzjv?eF8 zgqf__k$kdH)yz>y0AImpAXo-zV4n!sw9I4!%VXE}lE0p!WUad;CqNiOamZQdPe7MvSsV2+ z+VW}{`nztE)#q2wAN=&_4JOLQ@Fx<7fr@r(IH;4pfD=NUGZK#S&SB4B_9&lNz)DG@ z8498krpe8piUb}7gOq3(B2XFN1OXx?a=5i+nbm<4{`&i;WZ~uOz8~Xeu}HxQSU@F+ zVBf&5N_VBK-#ic=xP2$<^ontZQWz4)Ivy)4ZJxGU{4Ctj?0(i*q;<`s$8GD%SV+Z* zg4)W)&>R3pKogVVn0MPwqypDA8V{5StM%B2KmrJiz=v}ub3ej)(7M#cn%AA}y<5r0B8&j^YN#V6$BhzT_e zTCbF9M}Qaz&j$pF3%p3fAwvBLP)|gkiDF8JZ z=F!A{tl)&tYjQ$7W*vL9ULZpydBh&A>GcjhT6g_*dGqAKq6b`k*%NK$zQ2Vs_}k5fs&AACCfBr^-9| zD#EK6Ibi{h!uurgaR=4USE=M4-O9Us+P@V|@I~CQlBsn`weUr3C^>ec$4YnG)?YH) z*hi19`3%M#s{S{4T!aM1T}wIBrXLXi)0ium7GQ)4IIKg)V=S)*_Lc)?{FYr@xQg&c zT&A%HcM_vfQF}~gH;8@Oi`-hH1*U{G>X_hk5R?j33cSF4IF+anHUZ;;LeLxUPpC-= zGnnWEHEaA^eTxgowxq$HBP6m=Z=hA)^Q36Ksc1d>Kf- z>Q=g|6l|Hjqt!5vT`#}&uhEZ{obVN#z#gssm~+qx)$(eaC)%Uc*js6?vUvOU?ZS$C z;n};ce2Ms)BkEcdz=KEc`nRw9h41wGT(+L&A70-7`Q1%IPH>AlrEhb!dig?}6ffSZ zyJBJMT8BTnDEUs!8$scFyn_^s@o5C|;Ch(=&|VPB__z%21Z>eFlOxImZ##741ZM;c zaH4#Nm3_`A3VhK`;Do;-KOxCd@gFRL?XZB;@dJu=w5qVysWP7a^x{UM2T%Lmhsm{g-F%m0j>l5_6#Cm`q_<~bxDYeP6s3Nm}#(9bxfgQ-;D<*>I( z8hnf%CnFC7)zh3tRanx3`@tC}G+8r3Ma@8@3de(Ti7+Eh*f_5f#`y{ATqNXXU^`@j zd#Xq_I$1{X?aQ}stdcxM2~w{V`>~pbPrj}`zn5RKc&yIYqxDJkrOVIXqep9U^={vz zHN6f!TGM(DbbWpBVIBu@GG@%@LdKeLm8 z)okO|vA42}UvR=Y)J-S0b<_dcmd&Mju$H^jp{GhhBciPjIe|F}%X8s`bhF{Kv4AnY zn+m?cD0~377_%F2!V+Jv=ugO&pHh?}XpVDY9F?W*f4eXE_2FZQxM1tW&p&rvhV3WF zK=8*Nx@IW^2w$x{Lq5u%e*O88Z~{J|2QPa&vSsT%T0?;llu0pLjc;A3k1}HMMOLuE z-B{$L1dT4ZDByL2i?=wi|My`!HbOA188buMh^=73-^h?Rqybe5r?e6 z%kkpK)UQ%|iW1x~!MUvFIH8>$nXtlXULgDwf0h)mBy4$9C%AGS#ue~eK#&tcrjjzk zfjGdVlQ8b*(*Gch_aQ zH9tT8yk4ItzseE0NG$ylNlw4~mNp{6VgOaLZ=U_~`b+sm&hlE$(v`m=$?5Gk+vLet zWJtY8*1o^%%Z14oFMpAwi zPj_Oy0t&pwri1P**WdvJT#iB7ux{sN!So1W)t!vLxWKtfN* zpG>MB=>(^;A1z{XyS9YeBUhC`xWEDe-6BFr9jku~M+Znvj7C*%3^Uz~K=VPjs=mg4 z#%babR@0K{r8~%4N?oG^ex4Iby??|Bf*%n=J7R^LtT2ybv)jj}wCVz$+72efB0Upy z9)S}gBLJ+B+}jQt8V&cHRQLaz;Qhz5tLw&d!f8rieRD5Z_Px#fesy+s*7uzrtNQ*5 zkqO}X%J*nZ&erF>hil4`8<%4ybb2rDWF~a0=QvN+q8gL0*j@qOuytWpXFc#G(b^}G zNI+(2Ai&g5%d&uz(P*P38VroXcId-KB$p(u008J5VB;och=5zv`h0Od;);?n)X4~h6z65um69iE(1f39Fj5zSmvqPYU z$PU_m0hyRC))unx{&*3$E~YNNg?7laFiR z1XJm;_S!mOjO!Y*^Xkh>^l1Giq<*$emPCqd3YtNx5+ZOgt8r(VmCLkkWdo4_lQ(t4 z(3-D-lIIp{Y#5uCbQ)+B5;lPP3D`@GVm>b_=WtJ(dQM;pv?8(K#}da-AB9@a7QNRn zAKVyAJX%wn6voidaF%l{c6C~^!jcTJ37n8+W{|U#f)UimD8@{TJt6vK@INFcvg!(YSSx0Jf z60K*o9JHWGno;cYawp8s7dh#u+5+?^KqdTVR0&uDC#2K~-~_23_;&l{T28Rh-fNK( zPD_PMY??ejgG@NT(qkiBUiR$K`pvA_5Tz>XSax0qRI$9A_Y~^!tyqKUTdm;%TU0&* zrk1;NZm#7~xi4Na7In)$g*i*O5xQ!+P21v7hB@)rsfp*+%tkVT2n1JSgZ_kA@!*#< zcdV9*i&;7v9Nf-Zq)Nsft+X9dK_Vnj5>>XOMi9W5(U$G)H0v$y^ZrWU$^vBoF!YN3 zht{%xEiee9JIgjqBQ)S8B57tAfzT`@Mp}`B>;iJDqYn?^GRFv6YzJHV{a8lz&tXup zybob+q02C}yXME+B!ncf8pkAON}?vVL+UTF>Cs%)($Jw$$4ALt|a6f{s5kyW( z&j=BI|L^7m%=z+rw>#Ec8Rq%+&uBOBcAwjH*2LA`sJk6Dfz^y=I?*OxB7-mXjXyWYl)M4xc}R zB@|#$C}_jhaG7^3s7@I6ZgzNq!&u|Na6D|^9RIXgbuirqD{!uC2W;F7e#{B}eg_oN zBY4UOp&f96`5i_wA?SoG>Vz*hSCAO3wl$VJZ$Sxn<%C*fYmuL=g%d6Y;sE-9Z3M6d zo4C8a-okj0fe6g=0j>@ObY-oN!#olY2k2N}sTT5;&fZYgo)hv4^NDS-$84UiG6A@Bf*l#5!7b3W9eXb1D`|PK9dQCT$KdS10PgXm zP{6%q5PQH-W?e&$A)K!~DVQ7|wpnYZM7b~*fk?{`C`hWdEtg5El;DF8);Ks9xq}rf z4d?{*ERJX5Uw5|gG!)7L8p&*Gq9c$R$A9=j(%B#qhTHyE>#pCe%K$u;azvP0?!B#lMOd%1u3-f2AgL4)a=C z{fuP-m!a(-v~X{S&>Z2|Q>E|kEE4)2ro)gEE^inIK;xBGZoA6>-nm|Z6Uq@093*@5 zW_&+LfsI>DzD0k%`Dh@7xCu5B^Cw^iXAHYWsbmd5yM=%U3?v5^um%As+sEx`zK zEjTL-Z3hCSV^Jr7JLiRJ@VQ=u?*Hww~&xWg!>P*yWKAupEu&kV}E;`ad9Z>qo1>4Ij&k3XHBKJB08bOxjJq!+d>y;C3@5%{RH_gje z{SusTwwC2mdZy4@A)?P6J(hHI%kY(PYSEGVHZc) z@}xl90kJ6Igqou@Z#wn{OY*I=CLs*7mdFVm%K@yhc(|fPzeZ!6>ICLDEc z6D_orl0wNP3zkdBFgOki;>zBwq4xB)l(DrY& zGi$v=PU7pn+$p?P5Vym5QH*?yRagw3qruMhi%|NDB89lCuskKTvL?fu%Iq6 z9tTuI5Z~#*!DA9*xwa*tgjyf?|%0PZl|kEh?nhw^~3-s0)}5 zv;7$3U#khx zt|lW~qD?5cU`8i=(&e7hhKzbqPEefY1oSspERAqNvbqqJkd@cvTlY)g1i7u+H0$=f zaJ5|v=w&1kOab#pWluapbRY$?0xkr`e_~oem|32-gcd$mEi^#43)4QJad^k}^K#t? zBL-S!fN^(Lp9(0x3%*nHjyd2n|rB@OQcu0_xHP3h<&Q*vs<%AEA2~L3` zO3g4XwY&V(EzlnB*MSl&D4{<2c&UrS&D=}iO}q>uf1=NI)RAOJa|sX5Onw?=|lIF41DEudv~2c zPJsKNH^^YzUhls2eSLkcT;Mq&6Q9HPHfn_&$vrU8#*u~yEtMS|@o3=RCod>8fym;~ z$_cW-@y9z10*akTh_&qJLf9?*r~=Z$CR;AEnmQrPI5E93lnE}ufL~#L2Ot^`+*=kX zMdqJTz-qv?6eemwMZzXN8!FUADkm&NCBQy)9`rDmPzh-^>nd!=rbDpw%UA$VpAe!0 zQYdRqfmxY}PMD(waNH=)@Q=}hbs&YxWr!iEO0bR?PT+KW&ZxN|5Sd}j3V{@~UDJns z(;B_J9~VVUTfHZK(#~KxRDU-oOg#!HFCGv9Z%4rX3@xlJ1Rfi(cIZ@ZC!*?66>QGTxQandtEMp&69 zBysV_U2e)HySM_Gp9aFWVYW;+NNlZTPlBKlWGn6D$r^dIwP3@pu$U=*B}yB$4=t2~ z)xOPv6BbK+*T!*X2V2dR_1&f$c2*VrBt$dlOESw2Wy!x&ZdeY0L`FM>8v(793cl!R zMu-pDV@wLJDZvFYMye`ycK5(O;h&!pnLRET9Ir>(n(s+LM~|Hfh1iNM+;SCk!m`W= zWd*edPB|H4RG7jyBp(OVzKeS#g?frW@guasj`044WwJ!~6tZ4>=)&F9FLvN`_6!<;&eTfwZ&R_fLJ@zejCv{}xq_st?LeyC*K6{`9=o zDdsRzuD0NWW441o((FZortdXFA49V?qW_y_o9AK(G;G_BfI|T-fT1pMIya?rRk_y; zEx@03o(-8x#=u(WDD^Fa1}Qh4%P178e;*kSNOVTrp>C8Ci~%EHDeJ(YI1hrcl=j<200k?67RKStjY_~;Tc-auTLz=bRDffUBU}*d3IYNPk z_OAgfp0AA-4J5rOg%H38rK8;2w2p0@GtEaZoT@%au}RQ2n9TXYJjTkd2yd&Dby<)k z@{+lciA9ox*$p`%g*!J%oJXdtAh&pS7zudW$5A(>)!ae-%x1Id<_y#pG|7}KRn+fo zh1g+JR2a(yB7_|rL_(m16OqVhI}n}`${~N$`z~_0^E8^^WSA6DvBXa2fAAI3 z35j7L%Q7o1HgUlMB*F>p?otOU;Y9)vbwZR0(VwtPNelNV88&jscKw%={sd15ffHW# z*KZ$4$3*FdWT%|)3SE_4-39C}yY-5kFr--fc+h`8IdB8v;= z9c&qFaDfr(+L<+ehc)uB7QS#b0tMX1x?zg5XoiLaBIHmB#a71H7L>s1Xp2-Q$G!(3 z*}5QgfD6(rQF;JYo(4u$4@<6dKbg@jI3}hO9B2&xzvez-Kx+~-KLoz(kU<2q)hr* zQzo1&Y2YI~VG(v*Hj$|sz}(m%1Rs&q!Jrx2Wq5FgyDG9GIW9CaM~#PO+X!YD!CTH0 z7&+(NSOzy^v>$+RwnD6dpy^O%`;@5>CDcFUgnC{l#Q#L4tPlucgnEKKeKbb_UWkwb z{3{wkJUHNlOOM?i-8DHOEJK~(9gLqA!U_7JrpsO5pR^sWB77{ed?UVu_reM6)*3j$ zwYdG|!K1f**0l-<1>uBkp&$07$|1NB8n#ASZ)F4V03R(tPyx0>n+pYCTDY#wG7f#E z1RUwMYvwQ&t-zR~xuHqI#L2Pkkgyq;aU)W28@EVHFv5G>yI5!$ggs;h;lY%YAee?By?3$psPLdk zolOu5&c`B?rieo~jaq@*{E1yrCY&%C1Y-u^B_tfhJIc}u*_D(DMX;3$*Zf_3gFB}5rE1&h%}I)O_SWx|vc zswsxGfK5fxJU}xKazZmk4Nkbc0+bGhjL@~GOf7SB##ZQkHeHJ(Ykl%4T;)IyKq-Z#>ja&%jV#m+iDYT-Tz-i%4ngA=0B+1ALcAX>^oDfN2 zZ$>11A)yzr3pqg>J5bF~C;<6(zzN1zMcT3HP-aH*1eOc_n1K_Lj~XX5#W)OEdUZVv zQ4Af}qhnRY2To9KXsW1AFiq(QP{Ys_U{ovEHK%?%QOTeipnzHILk}=~LaA#Let4ah zFi|fQvK_6Nea3QCsdWWjaNg4}%Lz&fx$&f6EQA7e0^ar^I~=$Z5{Gy^D50E=+`Dl? zWCWrf_g@4KnP3c_rr>8Xiy2O+$D+}5!bBio5l*-oZ~`x~j5uK-IzcwkOE~+||Il`r z)IoP@JG{96=>6xj#}D7IGpnDiDOkb)DU>pP;EF?%JYpX#BM+rCRy{2DBW1~(0DU@l-sJT)C zIDubZWrgkjCtheUc;hAwlu7^_V;ceoC-wT8GHKLwpp#FLJEtN+`5={bYGIVGb9Vw% z1SjD;0Du)l^!W*)Ler6|3ZsX#P{bIe1FXL9!m*P%jzLN|dx8{_Jt<7R3CBw%Q2+&J zXrP3caqz?b369lh;qf7ZJUqMsVzF95RL4d&wGqE>d~Pse*BsU$20ssUjUX>a0g@Qay4ZpfAQ&)YOb&`z=NAoHO*erP z+HTX98SDkwXmhhNTjn)d>W*Aat+s!n`G;xg%rbMbmpeJ=EI1*8Lc!~VZHJtOCI<_E z1j@FyzT84yGPU^M4CaR8Ff?+~fleq(lfVZ?Mi9_;h*Q2MdE-$tXPC{%3}-DaVN`OS7$_4g!$DaVwW~q}z)Zr@xM)4d z1m%Qk#Nd0WustZA9K*Q@Tbu?tVgEp${#-fXC?!Ol;6;L7BPT3AE#8SvSb`E93L0nQ z4p&F8yUDbFD)JaVzmQI;UnFPAIql3^;`ZJ3mHSnCt-8HV*s@F6z-p>=-DpwR3X7To zVMZrrC?Wd*nL!FUvL*Yj&3fAvE7{dTuq{1+=WCk$HNJm!3pav+15!3>YaN0WzP&1X zb}=j2ZG;n?1ECmB5@$Ro?0t+uC#V$yfnyF3}FPCF*|4r_5&ZK1t>OiqnC0nBzrK3(L82(rQ zwm?b00nD64ijthPX3@0OrcLFD6A}mMF^EMXPR5LUQi77gVm~Dd*SU7~#b~M9Js-d^ z`iU2XCYZ=UAqM1xM^fNkyXS;j9zbt`z*Ys2+oBxL31?q>>hFK__U4Jc0`XNrdKx`<5_vFoP_}!)n*bEIp!9(nty@NLQiY>&;^6G?5%8 zDfmF7E90+}YWW`}C^%qb%i+QWj}Nw=qKWpyloiOo^rzs7^N97AqkRqXl3T|87`wm- z2VjrZXgLtzgzsNc+6~GHp!Lm74}3pjJIpWwBA=N6{4Jb-Hpy-3I{5ih0Sx+7G^Z82B50nnV zZu9|%#DWuA%qR~Az^W-YT?<}N9=&hCkfi&0q-<-G)zD^${scrhou70zubCMsC#u@I z)LILj*P3mdYvlyvYyvn1oSV?o=Z-gDoF4Lho4HKU3MY^X#yWwV@M%Gn z@DBcjEOYH`4@`2KuW&sI7C+0SO;Zb@kn2lEjFc7U9cU3#INvI(MOL7i7c1amn-{Py zgcFdH#2;~p14;*Cy9GyVo4Q>mo&rqKAvg(~fg>)r21*1W2NpGJ1;R>)v8*t|ygUx` zIN&JoH{ZRv$#4EQ}D6S*grr?@Z@E{^t9oQyCRHf??}{r-h_>R5e8p=tmpokNHT=e zPq`kYeR7XC%rN37Ub_FcG40!|JzA$|sS|=qi13^MnE-IkmyJ_1Ao>$<0nlP~juLzx zb>=x?i`Q&DntECU)_P9ZdW{tC%FSAGncyt!I{$kwXS>X?K}JxhV94df?bcfqMV_OJ zLKxcTVn%@!a>#=Yldv!^A?NQuyET#;jFGEzh8&C$R&6 zhR=11f6u^9p*$eW2njEGlSEL2+#RGIqWnitV8LoAjx5g`sBJ8o!X?NAf8jCRkHFrv zM=Q~`ytcL44!Cj&obcuQmn9fMG8xm&J%q07?;}nS1WuU4C0QW|g)<=qa6&+%b;%P_ z=yGl6u7QVe2S)KZM|OSzBH-_Rr(uNkSs(;1@F(VShK`R$4MkIc6fmqo2{^K4=&_}# zZ{;@V1R(hcM$k#2oyF6z2yCf&jzTk}iXaoh%{#d+&Km0}0k9M}>js8{pD%*4zY(7H zot8dv#sP3bhN^z;JDwPw?sN|+r`DMUA`*n`O6_ZbTB#pnlZj&?<)eJ#>#`l7S!<}1 z*8rnVMAX|8YtEgK9}^sL;um{3r{WZuF5EZ(OlGJ>&D}35*&sq-vht1uW;Fud!CHZ? zOr8gefT4-0LqUO_({M8i*6K>YWd>{~QSzJG>|6g;Tr@;Z#+o`59MEdM*kSOYs#{>6 zHWI>yL_ut;Z3QR+!<8D9kPdp?`1>&E(fW0w53nZGcHouZ1~~!lgmkl!5p>UON|+8) zoxmH#yiV{!0b+m52@{CoZy85Kt#k}T;)62u15h4ZZYd5Lhykw|U`yor`Su)6L@koG zI1yN=W4J+|e0F}ZLrukH4o*NRgE2;b!>JQsUBCgw*@=}CA~?}zv88nas7@H;!&z$* zo+!a9n!2?sF;lc=+A(NCPrx64up{H|zyI+GK0A;?ViM*lIII&Q$b$v$LF8eCb%g^(9w@UB*+b46vrm;l@GFHkrYy{bWvpEb)2F( z!Xb|2bC|(2WQBrZDA_7}esigf7D8B$9{>wm@*LyGDJjgm84+-xR$$3=!V6d7L*SKw zgHb=Vh|@*V>Yypf2-CoW&mK6Asu-ZE7;m$Aal%B~3#qsQ;{K#Z>xd89h>=c+ASWP( zwUfRm-R2&$;dlKZClK>|K%qdHu)C_$EYB$vM%`kkXxbd*4n?6^@0ru|J8}X>HRF`I zthSd0`k!`NjK8(eK}Y}T4)ssq1!D{JX${H*LIHF_8wzEU3^K6d!EnIR2r*b8o)Kn+ z&KwRArYHf+yCAavMotI;Yc9b38)he!B}K@ zb8`bm*lg~>hs~(NG!{8W3Aidi3Okp1q0lOn5vGu_3wMHYLM1O%l2I{u=_uG$GICDvC22NO7 zO`BXGQweM!+e?ESg-8mBpcMrs9=>z?#f?OgToAt)N{7`ExsLmYkGTa5yd2!0{kfv& z0P74Jgdg)Wyx=Vdu5gXOp!aXfcLv}#=zx6SZ+UKYsK%sV^GZq4Q!mf_&q78ueS5e7Q=KrT z1=c|IU#c4+1TN^&s_ujscet9R16;`pYBpT4DRY};&Pe?4YgzXC)6qQWP!sSP2D1L82&{ZrgNPW2LT#tpW1f80#A% zV2b!i3D5%gCBctn$=NMRL3fG%&|7$C|I_oq@Fwv*VB1|VYw{iHIB{{T!>pZayA5&e zbC$x_&@y`;lwg+&Ey>1O_SEU6q#%oOh$tp>PHygqT1X+?b3$1jHgRI6=$IA~^*Va( zu-+DK7FMTyzzWboKLg$yRuQqMXvNf2&sc?n9MGOZK98x4v=CWAjyGG*!4*hhboO-Y zQApIGV8I0BW3KcvHlRkeHq`{>EGHBPHV0eW)gkG$z9TCP{y!Rg4LxT5ab!9ACv@#Z zCdBz7NBB>A%MG&xJNIuppTCF5#t z>FT<~7o^;xgR&`7*{R*_Tj~0|)Q=DW#tV+=X1(H=6n5}lg1p09(OlAPj;0D^7oUk> ziyHBT?PZg1cQTZ7t(-=dBED%I^Ai0;v6dqm&OOitejaln0i-9xCBVt%yUWJf4m{)M zci>IHYQ<;gpe$^!f=sAfS=$6rQVY)$Kcij9V|*3BPoI#MIG{-P#H-Tp$3Gt*_0GyCvt*ib*q&H0s*w3Yh4nq3ENEiQJl6MbnqS< z`DGp4KNBLDnh#L~)8cU-;on`-~(9) zyTSEDTQ5K3Hm|z6?|IObeZMQ~)_uoz$t4|!GyURwQB8l*1+hBG{#t(h9tAj!)bC%lH~ z9qJCO!(M2evn5g+Fo!JKt`Yv$_fN<{U4{`%coUewm>0qc!dKQd9qJuAt%3UE(;8}k zT9urTUUckn@y8Z+8eSzr zWz2euQ8)|Bd}bRd;Q&iqMwnp){g#uRN|s5|^~90ysz3{LB&cfGvx4*4jN3p8Zb}M! zz(3;C(ntkZkq&})0r;qtKtxWsV{=w#-NDGGTBJ-k&IlvwiIi~Yc%WdIWRJVMkOLkS zb;20&HA-@Vh9V=B6L#%Rgn|IJ!_5IF{4m%MaaHzjetG`z4C)(JC{Nz5v9-=Sx*WGH(5d@Uknn^+bG0Lyw7I$@ zCzKFcOiymxjA6VDb%G~SB~-a)=U^0h;UeTMZ9xf@L7>DYc>$HsRW{T`O5CA9^gs1x z0$qt<1&pDEk>jZx1uck7SjyrJ2dtpwg%wURLfm%CPu_T_4vhy5MWFxlQC2W{b5bN^ zUe=BVpILI@wrm^_!!+&?l|ro%2N(LtaOhi&NmL}EB9a4cc|Rp2`^GrWHF4h5Y(kxvA zxGBg9wRiT%i~x!razZ47IA0_=VWYSwX3&>@>`$0;B}7WtVO91oAKZT|U0D16_0K;& z`T3`3$yN98@pGw*eDF%jB46}rDut1+`ttctkDmPW^lbI{?N861{Ui_UUp;+t|L3Pq zpZwNmO`B8j(?4L=ff8i&$!lEXCRf?B<(TH9&L9&yL80yga>7N9yU*~g!jX=gkS969 z8j8PBClotHXIo*1+R=CrAP=CcopZJ71W*E&sIfK|#%Kvva8$NTU?h7P<1$AYik$Gf za00nIwJI0r^+tRWR)p1xR4h~qJ~ZH65bFl{Sa(Oek#m&;PN;@KF&x28Au2r^-unLsr#l?gmLDP1_(xwVQ;hENNB9B#15 z9qNSIb3$MPqQ6z=e=sNMOR(KAc}%bWKC4AV3@vq=1|S^mhhlAV!%dX4}@X zOQ_7Sov#)v8_x;s7}?fk-R?@+t<|3(Ac=B=%f?zxDFtCFe=KAIDM4_S8|y&HLP+^o zCuBgHLm_b7&MPtSSt@koR!?`3% z1hG!af>H2!P$3Xm=pnfz5$KehNZ`aWr3G9ja9JP)dJLkVDGIIW@kLN%1x;fZgzU;9 zkb>2LlFk_q3@mv@Nc1Kwg2XhjKSJdRVYn&Nv49p8V3c{UX{9G#<_`3T|6aX7j0fAy>xC#5jxa(b1&j`M7is{=l>#eQ1Sz5v=DHxj) z$085H-4F_Z4@G)~kc15w4)w5QN@%1KQQ|s*!{TMaBtL=5Y}Rs^Qo=k&Pr`WvvkQ*f zOR*vqI-v|xsL^xJF&jD@{22*>H&G{eMi5TexU2qdIsw-x6LwAiOuDSqyEg4#ib6>3 z#nT6Gzdm|=<*wd=6JATB#^ukC+naX}?>`WT#c-p+25YPAnwy!Umx+B8;bzpyPX)ohM!=6t-z6)49y=x2RC9$F>8}=zhh=d8y!>(P+b($OXq~!CEyPC=sXhE7=G?%%|zP`!Kj^Go2> z)i<_wmLKZE_8NC^+F2nFF2I!zV;Sn&PdULDx1@FQZBXN&}^V1qnf}T!R(@D<~cs*Wh`fl z2BW|7tf1aTkU~+UDWt>HrS!mf03{%q9r)lcffn*+F!INo5N^tuP$Z~{pXDTp zamooi(IIMJjuNT~C(L8!KK5CX!5U+QU;~1j5a>X`4dn!-gp{_!ji-d$Ii0{fW_g(r zc8+eu-Ho>$oLCHx&(9w{{cJAYkrRIV>G{)#5`4IR{NQ~LC=sMhRW4=Pi<~%$b`IcCI{Eh2WgTV3;`4( zKMY6|{sYCv$;EBBQR0>e0o z@LB!9swl%7bv-5pl8dWenHloha5p%elry$8V-vlg9zWiJ-AZ!16QF(*_Lkw52OEP_m^M>t;SP)(skFo)Rmy`JHOQ`8Xq zHb*junCb+qA}4T8IME3q>@ej7a6u?-IK-|L>)^p}P5b6`-#-<%!FN!EiJVXrkF{&7 z_!ytPU$0-j6x-o}9KcuDT?vD{6aVCw5@T1}CgeFx1@A7{3i2E`=Q%h5&6Maj6rBK0 zxD<^*U2%SONl#=4$6?z*-k&#CJ1cFK6goN;u|k4l!+Zq7ZT9u2$_V~u`|n4PHD%TC zjEp{L+7fdn#hPbhIkT-?Q4Bf3BmlmUU<8Z^{fQ$Pd@!lAwNwSd=!LYh=M^CkVFrwvg3Yz+A^&bAGlK(_t+{uw&abH5-H=tdIi3 zlaLPTc@(bUZP9iCZNYi1Biu$8Qh`(9K%Fqfrv~B595DwW$*mU*-EO@qy0#NtljI$K z>;fnJft;l^Oj?~c!or&fQczBiQK<$mghTEVc?;(`VcXtOA$VlVG%eeyKLOjC!#L;% zjV|dHKFz`rkr>De$_fI^Q030hU;vzS~H zEQkgZN%_1c+4LXOItT@u?t4K_IRPIp@j;mZdMuxa1YjH)nCsW<%EDS7?wQyQBc_;~ z52vaoR50Avfj{quptEtxCkOAug+5DTU~mLZh-9GKVP5G?29>~^rSD1!Q76o~7k7RY zcB+uG~2L#L{2~+BkcqVg_W_G#ssQ8 zF)m4+Gq{f~$8}*%YJDz%Ss`U94tiak*soK%6Cx*+)xaI)>fz(*z)n+htQ3;8fZXpG zZU`s@xgjeMi^1H?3ojLv97qb33Rvt|CvX)IT%WWOxTi%#V1=S#Vgg7ZWGxkyORbht zv(v80W`u&ZD1d|P(U^m*C==K#KaSKXC+JN*liCb9i77aa*baf-W@SP-p#)-%6Cx|% znC?gXD0^bukC7V@-4%5LF9$jSDgi*;p}qwp0LM5Xco#h)HQ=V~iSD}6Td~yp9xAl& zF_h2yezj&NwH~-?n#;a-9drT=#U7|ypu;aZu~z#5?t@(yCgW#15)UFYd4}CBEE>)U zg$JStOxBPNx!4o}--wAu$zCV4Oo<$71ggPcJ#27{N}-z732l&NmpPn`a!PQ*`8lLO z-c@B$RB?WX#Ti<|YuEwR*(n?1%8px_(it2@U2ks9(zu}4je*5`N*#>RmLfsiP$qC*&f9f>A@I%Xi=RYO- z$NLBO6Xh&oqpGn^;B5!&4(E3`7I(vyztzg}j7Z?M1gjw>tUxevyp|)>14@|Xguzz2z~_vgk#TXZ4%fW$fIAt29%81SNvD+I2F2?1TuP<4Q- zgk$*$p~G54R0_nXpMpnQ2qa!`4u{~mQbMaWaHIv@E4=9lbl!TP$H3&sT@v^G#xp{$ z1_(`yize5o1lzZ7ulS-}#eiDb33B=(WEEP#v-}!R2hfOHr%nZcI)NUAKMwo}kO`d@ zmJBr}=XvJt1~`Jd9D_0RwK^Z&rd2u?^+PS*l8yHMM~5WHVGK@4p(!wIk;=u~io9Y9H8G{G9EfERzu1*BjYkThN? zxIH6Gg+f5Bf36+`Dexs3q0~AkI2T9?>QUGm59EePFf!yPz(Uh;JXhd!(sICuVoV(U zIETWwZ9IzdUxTOy%18uZi`R(QqxBdk%+4X3Z2}@EOm%_|_es=YydV21c(Kw1LO={r zR*@2v4g`%C3j8?;VCj=Q<4n^L@N3_-2uPlxA>+2u@WWQ>8l*Z(aDjrztDT|&CsZhK zq>s`EA3QCvSLA_8Ag#(;wnokEHr%iOJ>rj{PUsNF3^kYAreKuBqhJR1`b?WFNjt{h zKb@nyM`Ac9TuM%m4fQ+v9&A1tD3OZ~>I)j;?oxp=p>7U2!T*@lW6PlqoIvBDaC0bx zoRtr#)dfy3<%kr5P#_zS9v0YFrJ&XWo*avy#;gEqVf=QWYXU1&u252_8fOd3O=ln! zOon)5qC(+VX{2JMgn-IbAkG3kK@~CYOfbO+&=mzgj~rSP|9Vy!x7|M=f{lyFVvp9D z`5m}~OY|ooO_;3&!3m5yj5uNHU!+%Y!U>_D0*ONzAt(e6ZTJK)nr5qn&>G6*BcV9y*Zl19d%&riA-a6%`K1t;usv%M&^#j~(f;d{Tv$WbH! zw+VhlUDLBr0`vI^HJPOa8e{a_q0PK$WbmOgYK`XMugg@O%2Au5FtClL6o^O&4b zpWp=G2b5r^l%RQuvoVK%RvXMCiy|T8E-zTx%N+#s6tH@!6Cy|oq0f?d0^!o%-E%ut z2|^XO$_bNfrJXK2Y>pU$_s0V2mW-f3?dK?ILi1GoD|U!EHl3#fS%Oe#ypwU~UcTzf zwv*f=h8xeX`fm(1VBUFt+FdJ;s5_4R9DNwc3Aq|9Iyo)a1d}|_T?tMAbC4E_DpCUB zqAVabymCbx^)YUm+M^YysPP@>FFS+EY4*-O3!Ly5a00qK=f-4Nwjw9sTl(!Ar9tMP z8PFtT`|>xZ=0o!G#fukjzn9G%uCMCm0435Mc(lI09DNV@qaInL{oCLE7AV1GYCM?Z z%@Z=3@1M~TCv%4o;%U~tMbpcd14kn+q4q&Z0YX6*n`)!iu(&yo0?|4f=0;!xQ=3~` z)s>IcmX&%LjVnUr;Yjml%eaG$i~!774#X)+D6O%2k}>}0IKkZwkrDg`8mNuX5>zkj zsX}ou#GhL06mT!w$$_a(xQreJWd*@Oc0#z*ue+islo9fCWF&42vG}(rXuf1WR&O*k zKnOkf?dr=?iYT)dLzpRuXGWfp=a@NiO_@;d>Ict|+mPpE1$z9!2^~+UeH5q;go<>} zXn>%_HJX4r?t?Oz2nKS+oK9$25F7moD2hnh(5BFd?c#!9-%r*PkC>nWT_MEgs!}AbH|hkh%w1N^Sjav8&^Lv>do4_jY4O zC~;Itp=M{B2aSJ^x%Yxz>&j$pb`Ks5u3?o zb5tj^Q6^{;Cwf1wp`db;v+HfQE7s)7ox~y~XX%4MUs~7*mOp8U48q03r~M*HAroZe zpq=~8qbE0uWC^Fj+r+ElUmv80Z9I zAh_CDnLudSQ8|agm}y!<1XLCpFl?zFkgeDgX0`4KkOwzEOV|RSuD^(?2^jy2#>T=X zTsYC{yiOomA3tSZSayTDYLZpq!@;@tV|4da8lnM3R?{thR9s%-4 z98WbB1cx$V#0iu0oir!zWGuuejx1v~Bqy{;?tze>%n?7&3Ee;^Y+yCq5@Sxdbl3hi*KdoBs8vQYN@Z3 zhp`&UKjZ7Ve=qwYUxWbU*z;e0dHF37PLQ$*x*hbG3?2Ld(j2xp&(FW%%oH~vW#pH z0T!G3O+LN(Mlfvp`O;Q%*Rr9cz?EyRraFO~KzyI-1X>D#4X^;%1!2S51K^z7+R zsE{=0&)ARk%dYwM%l-QgAILHGV}17Y>2JNg)^4p`xAz!rr9cvo6AnlYpYh~npZ))w zUE7N4%+{q5v<=vZECiuJ2*|=KFNEJ;I~Q&{(HN7gj^{(nR_p!WVUIa0Wf`O0>-%Oj zR;n!1X|0iYs!>AK^-6DGe|;hDHts>ZP5U;uVXLKbe>dLMUS2CN71$j!DdQ9q=C@>- zJm?t%vN)iaxUq$JeWE9y^DkfyM$6ENXqO??~umZi6sa`zmlkmG*XYBkMPtJR5vLmQ>0Idq&> zvwb|ao9%NyZEbHe?tJ+(^;b7~LaAJPDHV+MfX`8Q?VN`+#}$8z5ecK@VAPh%Le${f zsEYxdqx;v_U+7UjIQZWesfTc=M-e{&o#8xCXpmDBcq!`VdIB3kSw4u6!jF)-Ruq`g z*~KGb8v?=(1@{3axC#@LpW-J>@Pcp1l{^UvdD9_}3AfqI&j8?Y{S!{i{OY|f$~Rcx z$)ztya{LJxrFXJPzVRnqF3NWJbI$1rIuNe`}%y51|;Uw3tU0N3Gt zlK54KVP`r?H*ZG8jMkt(o>5k2-NWH4bxPY7vgHemwsKj9Ua zz_R1H(iYE*W=wEB6rH$TtrlN#ZH-;hER@Y@7nTGuP1L82!ke&q7hpnS+5M#X@~xx~z!Gs^h6Wq zIJAq!rn5AR4Xgpe1fwFHVQdx}z=V&{6g?VfIfz!F(npV)h|%MH@L_0wLQfXdtCBGj zITA21f!iZJ0pCn6?&|s4q3|UYg($@YB4YFC6N!u~g9*+ZCTOlIInCfFHQ`wl(z5dz z%_;*yR?YKvLm2$MdlfdSG1cvQsJj4n%qiYBPVgyK zpOA&tkA{%DbH2K@9C)3coDfpT2DIz-Ii6zzTzOhnB$8Yj@y}13S?dWB6I>6GNWuh* z><4_OQ!#_>kcgcn-U_DhhlB|g+>iaZT<(tsg7gH8siazH&b$5BH!;!q1?N4igZO+1 zj;&^QI~khhCOGuzA30eD(Q(7J!_o`Fd;l)^Gg|@?R^uR?7zJQ9?18S+>`2e! zo&UgQn9!D)-!+6V;^EKLc%Nm4X4_$t-qQJnH!5)H_+QBRzh~?(azjZ7g$X32Cy2*- z0%O9}pTKgH(yTaIX}#A|hd^LzyO)%YwNRB@&PVnx#=LjEWB=MJnnjXQC+QU5b|14| zk$O~5gB^1;Nd{MUocu0S(VyxD7(4FRDj6pl`PPiE!y45JFGjtSjICGo$;_h+j%K-l z<-PG2=G8c*0%A{Pe(OVjg0tfL!4(hW#?GAe+9zhnPgWH)A0sef8v3ryOe&sZF%jFS zgDXM2VbLvB)_t#!qrFi^DCJ$A@HcJ=QcdjUOSo{G zW;gb5X%n-xpJbDObc(m|gbB%j zQbG+wpw^j)H`PN_N}Ug2g4Po<`hu1bCr7h9_9rkVgjqg_tnm=0#)G16j_{;U^+DFI z)`QZbF(ISy&%c9&tLYFvf&@$H8_~U*;Cq^c`Nx@+-1gUiz-9tz1-uc#w~dPQ+s&Ra zqo1&zpkY9XI=+ZU!kBPYnDA#33%sHt>rY^qNoPZ9M9{KXNsiW)Ymb?Gm6D1N-D1ke zDoj-MMT^it#v%h3m{5|f@n+a=x1r|_62gu_(!pppQR*|6SUU!pv6bk$0g%8sc8hPK zMQvPXZ((K>U&`&1^;&B1C!c0CbD3xx)LN6-$!l^KG3N*s@u~nmh#<218$E%84jLAe zmO!&Fq|7w;8exJ@KTpC0&Qm0&MR-O|Joe2*|8W;XvjOi+stA#K8^Uoj~G6K_hGQ0H@a zxbts@lelcGfC-3g_DjG7M`6M=CQQH`(GhQ|hi8z6@L9%QOHUV2)D8t5j?DQJCaLj| zOWAh)Yl;GJbW*_j0B@ux9P)YsLjvuzyJB$f8h`^BG%M~esL3CnDyyXjLqxX2`EV9u9vtCJfJ@=v!X}li^QV2N- zFUIYpX~>p_BC;kge z@TZm{xXJ4YzU`qxVS?7*pF%>eB@YMmZvq-{koa5T|^sKDBly z!}rETrpyrOvR5uNKWc?SP4!?sVNfi?tZ40es2g(Kpj3_B_d=b#f^7s%sw*8 zxa-VD?+rou(0iT|IUD)8i7E6VaaBbbwvZ-)2YG>1p8 z?=8ECIc!$l&OGin&2VTo`*9E+OtX*H?C`o}=}A|Jl9(_Wz{gl_t>Y->cNqwIB-T!TGW!@8d!G9=hVd|u9)Uu@CC!qX?zGJRKc^-z zCIASs{)83t!>SeQW=uV!FoAiNEH}hJVD5271W=dlaK#mPQBHj2jn`LuQKoAvA_-$O z0B#~e`Fl3lVPG1sFK>zEUft*6p#wX?G$+oo-6SV%D<(Kt?Fk~bK`dLhQQn^bxw^QQ z2s92|7fFn}I-jF*6lU?I@bb$r_QK@v18=b{;1b%uJTJzf6EG;q9>yu{Q7&m7Hh$_N z%LieCmdAQR5I&sr5UMgT!DdYGrOgoBU_#Dzz|-?dJL8+U5>C_j=a67aNXUc2KZb;S z!_RQwhmrpN^78mnzEVthEE|mpmwQY&YCYl4Md}F}6XXVY8i-_TB}#La4qwE*YSZfI z?)sAQv4XC(hTN8V1I%bE(X7#dcM!65Lp(*SYli_z&B=)M1<58fdFN$0J-56H>4*fPn?m+L)P<(GE~60%T`#{@+?fj#FZ zEVZamq)Zu|j^wJ=Uw*K<1K!uL8?YFy!09euP?xQH~w$ZwY?jYeVrI&J+9G7Xy zTB&R1ROwi2I@|CF9QbBS27!yW0OdwQ1tOsI+|HyR6=MMP(So;!ksfxgzujS8( z!14nkC_KPU3a_yZ$-zP1TM^zROKLsg9u2oxo2UL7 zyD!KbQ=SwbD;HcL<`6GUOO8S=9F|O$Ksf}E^nJ3FjOe4#DoCOg|3&D&{^E*wvV>5{ z7UF9yFEF9GQd2JO@S$LGzL^jf{Tm78I*~a&A%_VpywokIA~Ds1C|Wfp{AV2z_b}%u z+@z&Cf&>U5Abl!v#RBdIY&$CRfK5(`xXe&sMg`8l3MR|_Dn8JU;BbtO$5pr&afpVa zI7Df}bBuzLD2oYxI<5um)(;Z+Z0OsX$dqNmgbA0hv+%dfs4RPUDV2~bNM_tMNd|a4 z6|E;Q8pxMtdO|qQ*$%!cYD|#hM-L=mf;Y*DkA^$bA$*DnIQ_%vbhQq$)_WQQo?^{-n zAb|vTSy`0#Sthe4g-898)P2oo&s-!e@l^Q^13s3r%;*X77el>yg438h8#qVLwt3PM z1;2$+jwMkib1uRoib}Q_9ESrJQwpZCB7j!xdmlX%zrh2E2~ttO?vb8=jql0#wu9@2 zs^}}*K~lOCDFo{YQ_4@sV*(IFdlpt{{O}1L zFmTf22g%wFRLCd|qyPxs&LS;bBqk6ZoB;`p37d>R;lh}pA%QU==VfHsPH*K4n-5rc zCYiCc9*sE3=W{00|1g-bp+GVv!JU`L*XS6nl3H=GVN8&e5yZ*(MfkDWZWqkqj@}e* z*lrah$XL|B5Jm!RK%{y?77=Ju+^_iJx6tRev=H_#VIfTTiuisa6$PMy^T%jcgJe`CcKF5P}PqEf~q!E(yMtlzp3vje5 z+kuDdRg^y=FBirHttViq2OW2~)e};j$zXyKsU{Gc!3-PiA*3*@=TAkAB*>E3jwKvO zNWhNa;giNc&$z~O0k^m@NMi~BaBwQU4Nm$mrocWF%$@SzW(@srub-f0%qU`k!ATT)&m~a7a3ULl{NHBeS>5L4J2KHx<8T7-aW~-vgWOSFJGqhh1`136#Pknv z!ljs>u5g~2`N9g~J5Z06lzVuwh&<66%H}or5LgeEJUs>YR;UrU@-@mrT~}5XSG6$Ls_TRBgEb=r6REt(AF~ zVLRxwmMi514KdmUhh{=99Oh%c(&-#VGNBzyql&d<4{fghDZoS zPDaq~g{GQZ%D5zESDr^a2X<<#_tJ}v1swi8r34)VwSMS1;iCi;G(*8SfvrAbZ%Bd( zz|MJ7O6sF+tqL;>Ewlk)2!E}o>UlK@VFRhP?e0*|xP{H#PRM0PU1kUC8b(09)A!s# zLJ0& zG5_O(mbK0lbJ_|l3zfTBhI2Fe4_X&pD#u|W3x>v|}10+&fHzpb%S3-ph z9Gu|savSc@bAq?40L6e4vPN&WUmm9fRj`~=f^cP%2r@T4!8mX<_XZ3ZA^k<4@lJ_h|o9fD@|Qo15F-PFP-dX*>ZqOyddINaG2O z8+d`LC_ww5-eMZIotBk6Dj)v!XwS6_Rod-v`u5P=cb`~5mSq_C!Ef2Q9GZ;W$GvWz z%WCVzf)(k*+HF8fv<$!|luCs^f@z~6MnfuvaUP9+37eW+keUf8Cu}2Bq;|(FDSW8> zpCARH1lERg&UyaaIx)ZjEU^;{^(TUlP9~5NvsR4<)zYzG}HjD?#}{mQp6Nq4grfK;YjgRsl}9s_);tcz0LtYW}`!iPl{$v4PibKDkH< zbFQ~#;as_HK`U-2whE(@JEzW33T=e8^EGB!GdED#6B|6v*$UG{JHgBZOeZ-( z-6zyWGB=sO(J8j%svOfXK+ zuVgPr)|j0ztmSfmMr&6EGh2wkd&j`xMq&W7P6jL#JJ;+rEigzwZ~jCc<7}r6k>fZc z8F^J=#aB|>y;cWC;tsSc)J)KZxsRM+)>9;Xu5d#f1>U{RvaPGJDh3{wv$Pu$0vAC* zfu-m3OW`DN!pDy-{-jqNE6oPa9|Z;Y%xuUq1$Ry1IR96J1SLL0o?d*#>3hJhCS`Lm z6@@JFf@=y_{(p$#D@Rx5eTgN%u;w=ymSj)N;QutJLqI;KH52RnoI44e+E;gaBzUS2)r1h#xN7<5JZD1$d{ z$BjJzql0KGL_0x^5Gf-LitIVwVN+yfWyG$bg!2DA!f)h%87%F=NJahh z|5gp>e=r7KO0TmNDIl&Kj1!LIwndEb1~ndp6Qr8qKD869{$n79W1Ijqe$f%WIdomy z0jsvEn5Zb(MrB8=R;tmuYQ&6SGGdD>^ewm()W`(%U031l??tqz3c9VcjBEMKVTp!! z$13)OuUSI&0NC2qMr{QZV>G1UmbF<1Yh{Tw-389NjU$XFj9CGkVB$(#L|pkfGb8Z6 zy?#PVPN>T)TUoM^a)R@-OdZNnGUl^kX=JY85vrTH%y?6(Fr@X$E9_xky>#RG9+kp+ z8^)dU-rF@{)MLuL_MNMGG7!7(fT$o)^3n;cE}GMlj59ts>4v^R?)am7%5@-C9Z;E%^x*#L!;3fFOG%S2RE728%c|O%Mr&&ttxbEQN4r*nay1J^T_V?C;t4L_y@C+v z^Iv~^$elCY*37B)>IOqAUhVR`_b*;|o~R`qfyJhFp;Hp8hYeb#Am7ovPYJ<5FJZhV z!8THX%@$+aDw2RgKG&2h?LD#F=KuYVf4fDI#ik&2pBT=^PA6=@PKQO0i;PP~6pBJB z%`(V{Mb0!vz)EpzghY9v^s8!!zT8$j83=`VdIHbW82?pTSPzB-M~Ij+{lxfFEh)E7cG|vJ$01&8)W-wRO|yXQw1nJ=jH)3Uq!Ag9 zxRAZ74B}wxUUib3U|f*$!FfBO?~wRG%mh>@>7t!rk}7BN7`IZ5q|1iXF`)#zWX^e4fFKrnpIwp;ls;Ap6^ z%;q7u$Qi{VhusyG@ym2rudgHoR1nUk$*Of`5yhSnbdCYWGNpuNnZUZEB0MFQLJ(Ct zYzh{euc+g`F4+ATIwG-ZD2P75sO^(__FPX9*l27d%;lCFV$z>mJIOJtTitc0Xnh)2 zEcFlPN+vBE($}Z(_IU<&0cLNF>i0>}xZgvB-;1BF(9&Q2gi zKj9Z@v>rNYv@TMO*4Mp8>#tLd)+Oa$_+A$vfH(JZwCAT7qkAW+q}?0nwnBxCx~)Nq z_2I)WkKVilFBCLm7+gd^FJwN;1Zd|27$KB^YB8Te)1HBnfw2K+`VL32-)n@Ec~i}9 zpr7ERibLj$tPB7Nge;(3NnwVCLDnwlImjtRnX~6Xg5ZN5fWQk{liQLN?K@rxF(^g_ zkYwSr83_Tm47Tqb|&z0wfX%Gak1a8!4%|#%~R09ov5{TqT9NYiVon3 zx3fQZz3z?ZE#6%^*Z@1$o_4s4jpZ^@zC9Nkf!z~=mbCDNdr(|kbS4l1J+oK6vElHG(0je;A%%k03stW^8qW4zjiWl0Muzp zs1hr|+p0SF8JLItkW2BML9x7QY9?4yqUYevn%7I6%uGnfMQiqE-563v6(b?@wtaqd zv@t?yj9{a4zuZD}bFM$(2 ziJjn=MJNPCXdP+n$R#5`!xjw%mF8ZsdW#Z1azEkbB!}j)^Mk_eMG+Mu0U%VsAih6Btx*6gLPoCY)$qDc9xPYJ= zIKf`AY4%W6!gXf(hPYHNxN=yUdOfq3G|6em3V)juFc~$3euvZ8OCg+EbX8J2!3|?; zMV=R>*TkhcorkgyGG>%|&%$sc?=mzvQbK+rb6JNzC$ipjBD|rR$^*$51%)eD;G;G@ zMcWeK>EMtSLT969G;&rryOP-jc>d!*e`3bOcNm4p$|;JfDihD4irhnUC(m5X2v5od8dv-rPTVdS44CP`9-c-PZfd zr$N4LOL_{q%kx^;fYH>e9RQo*l6Ol^Fnm8J;3d&K;L4eJLYmMhVoL)} z74g2EFZy_bcNytGbBJ{a%q*kJ!0Iq~kP$+-i_I>ZFhbl$t6|E%v4Z^uMp78ZwR%!; zIIXxS+k_Hg=)7S_ZJv`ALd&Vrcc&-vA4~v^1599qu=vZx#wttQNPzf2BWO2fod9p&G5_ySLX5$_edOHCp#os?mBIG+OsOdcDQbYQJua#cI888*sv- zH+u+<-adKu$+MQr0=li4=(gTTr1HaI|N7M@)Qf_Q`%jQJo$IKlO-)h|KGq^EE9&nLu>?& ziBdvvlIA&s-=F~n-<$(^z*r%Y!pcZtYPTvW%$%H7SXGoHjbY3Ryx=j7182cRqXRs1 zL-;KIgfCCn2|@`jUbSuF`bsGw_jOfacd1Uf7q&Fd!J|RM_i!-5uos`pJckmL0F>p~ z_@eh>TVN{cqr*q40s{QAoCm)iOE43m0DPr{pqMg(5rV^{(3FFu^Z;-+ZT$!*^t#P_ zu(jGVIU!*Jn^TQeA%t8kr<+7S;i#d&9iimZb(4)WTAOxXHHUK=t;MZrv@UTiaK0oA zUC2>fr0jlqa+mG?cqF|IrrSDu2(JJE{rr9hQhEGg1Cts~f;jM5jVOZ4Wg8O}7##Z? zA|e}1X(6>CxDjTufX@1a>ErIak+7|Oy-PWvUY2x4^B7}nD#j8d`+!kKdCcY#v&#Sp z=X)nwMsppq?9S>X@Ud1_&~o0PloPDJ17LjxvLxs`7#rARc0wZ>OT-UF7F0ayE^4`_BOZ?ECx8>`Jnv8!Ipi!M zT?w4ftggGUM(fo;qxD9QE^*0&&%75hEpU~WNJXk{U%h&0uCkY}?{hi-&E3n_cll0V zfnEnGaXjC=!exX6G_2^{hE?Fe1$^X;GNg7wJM4bYwc)lM-ww9hUE~5zTs_SRd#X2U zesjL*;|c%k-y1egwDms5=vShsf_9d%u346=%fR8%u5L&O$OW7`H>XC?nQ_o@o8iTk zt%qO2W9~ym)XY89Al9Nt42ALVe=iaVLJF1vk%}BRvlDFkOM{C>2}^Sy&QbyqN-A+R zFoG9KzyWDtSW(Aev829|lR!bV5*!#ohc4-7@OTj^q3~5Xs?$nJFt0&O1a0(Fc;RNS zc4IiNs-%n{y*7~$#*}O!NP&-ZFeuz&Xr^f6)yA9vIx(d69h4Iy_BYA|2`|Xz?V37E zpKm|k{y;tu1&#GhH_Bt24tR)m!U3Fcnca5+&!7TsSH?Y>3Tx8_?Nu2Cwc^e-bDM!1 zI3b4uoX|+Xz6l3goQwTkhsGV$T5G(EzQT3geWo(5Tckklb+8vx7-jGgXH4XUoyHRk zMhSe1AqL%P$4A>1T~t;u-{AxyXfY_fF)3k~pRkcHKvh|v8KQbvTNW7yBhOW4sbCDj z(OAwiu~g^$$rDDX&+8?`ZIXhT3T!Nq6^@P1uQNCg^FRt^n{Yx3UppZMA4wtcAL_Xp z3PuL|mS$G34s7APTf zLy||ZUKiyspb*A-7;S(Nm=WfV80BPynJ_{%Tlr41U}%NUg6FbbEL*`+Y7lOLLI%gO#$uM$cowXs!XH}Jaw*p@W|!1$ zonK}ix8kTq1pb*hBp&SKiM924gFBiABGeS*RY(e(iTeQHDe0RW1s~1NQ1o_!5yF%c z_MB}{s^>6Ip{R|X)v~ViM&f8pu?;BrrOYTILoR@Ir9IzfA!NDqvkEgf-+VLLb!CY~ zn(z2gNuf-#Wn!5)A%$EAEiHU|iW5i*IZicFs8(JR6%09L7%+1|@kl4MXHybc98&_0 z_})m5lmKoNn@RW#C<_`1RS_uxAsU?4=lcyPp|+NXD+9PJIAxqHtY_$-dp%qi!(q@ zxVgoI`3F%qx?*!!-xwzd_9TTdCn#_vqzj4cns;vFHnPS)N1}R0Xk)aNWbBe6~AUGZ2mgyyOaB zTTa5T3$iHWKt;%rU?h02c|bx5I4liJA)y2hN|+lZRDlxk?4y}r#*;SAJgDMkZsPbi zZB^nZj^|#*nnoE%Tq)-j|BFhs(WaUz|6}+&*%0^n@5Fxkkyf+$%R>Db2U{?a+Eh2{n zh$8F{l;Bk*Ib*9^kP_yOAJ7l{hFba#oKb?t z12g{tH0MYI!)7=#ndT}VtGj&@MF z*$pF||5TYJ(P*tT+$G_Hf)ePVqd`QL*z;dio_&yW9I}oFYvA8%Aw)z{s3IviKaQ8P zrHe0JnDwHK;zWHk?pCD2GH zXe4+%l|IkdwZM<3JX<*CB@ZdVS8{$WpG!C|x((iEuJ%DWf0nyRd(+6GxDC0lalvNP zbWJ$bNMX?P7+B3IhDY#8N#_OBU*Vms9 zffEAOP0tBN3#TFq2Talg$O_vYWPu5`1H{V|-rl;Y!3d-RrXw-JaVZc>#O1P2_`xq* zHbC)L2qy!Ioa31~@e(Lelfd zXX97n1E3&r2D!Xc0+cVBfQOFY>*@=rIq)5vK#&t|t^rw?ogmn+*CRWDl#teqh&Z^M zt+sCZMn7~J;;Lx0rgj1^(-h5>GQuvf%Z74EN&#jph4_Qs7-|fPei1oS=4s6CH#Z)0yY|Ih72i z3`_4tHjss7Ge=G^^(V?dscqR;IIyP*pFp=;mh`HU<7rHRzog4xC>#ln`HV7;MhcAM zo#F(a;|BE?*Qh{IPCyw};Ix9uibg`k(7_;#pjkeK$^wu=1=Nca!z$!8Vp~uuBh(Bf z_==F%p~03)3E-6ha1Eq=%!8xGKAo|R;Ckn6^n-p>F40mECXL$c15Z9MLyCNWc|b!6 zktBmu&(gu_EFfd{bx!CeoUjr)_`L5qL9uVw-GgyL)M?$10t)ir@jCo%dmXa6X^!Rz zqN}o+#1r<1#&0Rd3P#A45%zK=cmf+5;!d#Xy8VIbD|gUnyK{^psLOD(c>U_t>-QuB zm14~QgEcv?o7xHcEkkjO6OKSFc~ae^@msT4@C9ND73L4!>z$ zZ84_|QnVB*9JY)n>UIEL9zZse?gZTvS)m-EFk(3AG~i+@WrRuti!Dura_BRJZ8=8c z0H^ZV<-thMdn(BYnuh4u4ryD6JI3~3&~A$Ge)h*-b0U)zCG+?PaRd$Qh>Z}gw>IUe zF(Y`3Dy~e^FChf-!H^K@iAj?93z!Y3{e*9%gc75izuQhQPS|7KCw4;H7$=YwI0=%F zg@Kxid0qEL0!(@lVxDLeM2%K*Li8Sl6xjUdd9V@0&kqp+*@N)Jp}=b!2fHDomg}sp zEsU`3e*fjsPZ(8gQOTSue|0Z$-g3+d?ez-dESKAFLrhEHk`rT$`zZoP?rQN{+&ufZ z20rejFLDPmaQNb222>oiz%B+8g(q?sw3{V2ex(JDL@R(n!Z&ZfB#pl89zW?M3|{RQ zPagdQC7NaYVIi~?nAWmc3eHbCfri4|GLW(1n#y8~_D~K8-b3bPyHd8Wd+~^8h1GtoWk!a8- zaE=r9hdmf!FLnYOs&N7ilM{>AwQba4uAg}9NGxneFRhA z0as>gk0t_}P>dqPZ5;EwLL)s9nzE5)v%!L+f`<^4gYFhEdWYP@Dq$sMLxiHI(iHP3*p;48qczUWo1sW5l%RX z_U7SBm@p@t06tKpoUj&7h?IawLJC+Wb?AIYAR^dxjgEJ0Djn{K=5|`6wb2VAbb!br z+Y{d(l8>I-+gyS}3(j=#D$q*9QlCyCbrCgYi>n{}$k&6T(9kuHWLlc?0-( zyccjnsnzaps}=?kbYyRi#~Ai$wvw)VJn5!cf=JCSkHL6{&8n=tYOfhq8i`}devvV_ zu=vt#V4mTQynL#{OYQzeLr!3|oU$X9-~@(H%BJ0SzbPlQYaV?lw`t!)6-mKqtFinT zi;p>QLNpY-^o{sc1ZlzG^NfU{DL>&AQ^7#2O=Ah%;ssgegoHpR(<}nP+DrzsB6u!# zFy~t>1x4ovp@*WhGqKFI6>|h3lC^5 zU9Imp%`oIC4gennLXf?qj*Fw6$j$c!56ZJ};DlhUALoRKNDQjwzE_On2{>s#A%&@U zGxx0^ocjFvi#MMx|1gc#@+N4sR?QKO)~45JVY6(0u?W+}+>h8)~_B+bfsnc4eP+FX1~h2i_hS29d^lo+mB6gXZgRa#O2qrJAqs z9S*#M4-caG+N`_ZU%cu`!BP$|T^+R(_(z)}cFt^TxIJ#Dy;hbCBINydvqZ|K3`EU} zSuVeTvm(2|&Aos5{QbLMU*5fW@wS`Iu77{=8pH}t0IFuiyT>kYLij|@qI<2Jux_yH z`HS~msgk1764t@&mGyCy5wo@POwtOh^;S;$K?4<-Yd}`;GT~?y7zu%0D$fDcLrU;ka?1!IA}|6g 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ƎzZqFdsrzY{H&ZnnWgBfuz`@m-+ub_TebN8^8!x9R{`9huXm#AZdQXQ;)C%h zUMns;fcGLtaFznnbHwjsf9FbW5+1gYLyh~5f7h$<>_711HL&XAD?cWdd;-l+q%CVN zWTUG5hX^;2uNK`jEaWg%v-o?!6AQUiHdbhGA69}&UiKtH!#>m?@Gg3(F05&dmF=?E zv1julE^KKFLuKy?aJyQy(esqb-L@XY=6J37>zj-DdUtubfr!HSy-nF0cQ8?v9yZ<# zaWoVdzN1EK=BUw{HA9TF6Y8?^8m$cLol&FpYK4y6q=b4!Gl88EZ!HI;XUkmXPf#z# z74$f8qUyW*sA8-Wr&azyib45)EsL6+oV^u}!-`-)fk)L$nBgNa*r9wOg)7k5*0RwU zZctMJt2slf==$qi@2@_|%7mNgJz)1Qzx?lC0QwHB8a1 zkJc+R?Frbrdzqbd^o0ISB(SzNk>rlopJzCL&6Dx*R{Y5u@^K3O=(Vjm48F<0kvre7 z_olslME~!>*8qmDwv9VJi#2OK7I$+3GaC1o#r1Ceg;OKR34J@E zFUJ@(8L@z4pR?P>->A_#rAF&CgcZa}0E7`PCdfxj@;r~J(JE!-SG7j;gxqUyCNTMs zW&&Mys;iH`s;VnAoEKXF=c}`zC1PiR5^9~qU`6JIF2pX8xk>c@E6Xe(pU}<_1A!fz znbD*VkOK1{g%biPKs|w~Dsz0!-3}Kq!`hdV&Piah2Fb97W?g>yg7O(!FS3YX~n(Ei-d_FbEUs5k8=XhlX53Q zj>1Yt6S7JCk#nUlel$BFnSw zbd}3n%T75@0|Pk+6;^5;gaz{xXqE^k;9T$m3tXx86Se`?w2kwi(RwCDilkiKa*rOSk$nh$1z}0%~732f;iYlH_D2>Xh z(wgzA7|?is&0@25E4e2*!N>nrIp;&k0W%ZOqw$CjMPq396dd}%NlF8(^%CYv!y2)v zkqk|ur;`c9Y{0FUGJ(BibG}35BB>*v2a%U5sqC0eKNDPo}#@(ZzA zU_;)8fr^G6hb-b3r-_kW6EeAD;jy7}mWrw-!l(Fd=wr>*Q6 zn!Rm%gr~iT-UqXv{hQ|mP=R0E#tE?E$qD^%f^b5zal#0rtNp!lU0(Yk%SIj}UbjJ~ zOdm8_ooTc>(`Ze$M>JaZUbopHx_|ZZ{$psgNVHW(Pm(D|=2E5<=vb08 zGPW|?)`cSJ0i*u{9d|sOBRBkltMClS;Ej2VCVuEK42fktp)(-q_>grJMQ8$2lEO+$ zTZ%%!Zz%mR2?IcTpKGzSK01-X`x01OwWIKUqw5-zb)9SU+CrOgTHZhfl zjz;41O~M5!p7zik9$(TsuuLYAoMwNR46DxXGwmYL3fBPonV|$dQP~56`0L93L34Gk!r66 zjn*P)v_eVY!Gkw9qD4B=Z~ZuTqS1QAlcTFW;r2f~yn6lM;g6F?Z*lFj_czp9O7zYw z;sS(XP~Zera%M^~QybAKRl`8jpl})o2sNP~26QxIQ8B_ubDyCMCK`aJLuv?LY21i_j3rfo2w8!$xL)^Q4otM9Pv1{*D*);qbEA z&K-S)pzV5qFAdj)s-RikU}gY!-#*153o!mqrLB8#dH6hcOpve&xzPfgFwJ#h?3a`3 zK+#Sx!Da$m(z+ad%Fz|kxV)EFzJlyc%Xb*!gxZ#s`zji(SLci%dQOO_sS0LEy+-Q? z(`dc=?cwupd8C|BRie>4IT!V|;!MQ@&%h8LUp{*H>H`c4x%Nh)mlNd#@!Yqp?8M1Z zRvelV?f~{xGnH|+Le;9N44^@CLYDZM)Kf71bqXt}24j`4MSg)(E^NMo`3;IMmBSS( z_*R*vz%fX`dXLm;Tr9d0_K=Fr9JP zy`GH$_%j*_%}vtEIyi+Ei0msk;T+7%iBi2fPyxDa@q{4OYL*hR~WzP%*~+v{IxdMdST*?^0mr4z2tUW0W~iT7j$?Pxwsrvn(ynCz&hKbX}Z? zEX!IlzxiSQD-Nk7W5{$~AE5-KPa41Y|7lrb6E{H%j{_;7@1$xiSaP$=o!JP;OB&sLcys*5B7MK(LRpJS8q*K@ci!rW`GlKdK z*Ihoq#$Gj5uqAfFB{`uRPw0_Bj};2``VoDHB=VfFDYi|G*0X~~>!+`x(VBTXAqFRS zjn z@HL{8Pc(IBlLEc+ZVSPX0a~!2aS^si2Sip3Tu(|E#od4AWnm37H2CERLZGF9wNnI3 z;WK%GPgp!n*TJSgDI#Tr`2Z(~etYVcE?ozQ2+eiRP9P@~9p3=}CoCFv!W@rj!b?{E-1_X*oG3k&H<~8Yg_Iid>j5@(3}; zvH@EOzA6fTP!+4E2Ef!2ZQbHY(wl_u7-6Rt#~)i?qDsyL$2`Wx6FrbcU| zoPg20gGQ@ct@xp?s>|tP6d!trm?f{h;D#e5}ZYoV1-&;%f@l+#2J*$3y! zCMT>WtY(I%k1_u3Z@+#;$8%AUj1Ud$&dI2PKvUIRW02nMq5&iNx@=_!U~_;{sV&v@hl2v!co^DEmADV2}wp0FirsaoWLL> zyLF2bCL>5hxj+bW&j|=9j7RblSP;Qh+&&ubBhWe`xE{G-PFSAMb!E5SU>Iv5#qM|p zuDhEQ$O-Lw*KS4aH4oz#U5NyKt=@vsL7Nl63K!%A;HEzoIT&QLbs98UCljyHI=WI# z)yMC@bv0TazuQmZk!rL~E;a{^))B&iltOZ>9fbXF_TkzBca|%r*Be!O+G*~x7KY5h z5j8$2t4Yg};XYD=BsvNWpnsci3m3Uhb7b5>w$RNr;K(wtajrB%DhSR48|PpP&dsiQ zUCwZ4U5I&6ZUL9ql&{zjgCdW}T16WsYIJ9WQTBF;Iw(3eSfMYF(2ZaY zazfkRq0SQfN?CUP;@rB}8yoaEfs8;-*ip^B#R>Et?m}H3G+N`>YqZAIN7ZP(!gRma zs?mCtP^0y?{baGy^Ip5Myxdcxbwbr-hVKg;`&e-@Rej_2U3WX|m8;_&NWjMiG)o8> z3(5pK(Gj;1ZUL}Or3KEI!ilQbhqlLCw|ElwJ6Xd_Lv0J`GT>qQ`8e1sC5 zzEVZzj0BK(ncH3?1^>!oAq6@RJxhV{#aIb}U`IWIEc2Y;O>;5dEu5ew;(}-*g;LtS zL&P>YA!^wPk@^mis5ca5C+NbEbu|;)I3j1}qF$AlCBcSu-9b*!)35}(axUFa5=V_% z$a)yy1pPRH6Lt-3iJjkfxj%**YP3?3@t-^8go_(;!b$oGq0O`1elG_7UQq7pJ#i&H zQ{HNZ{N)|r^y`FrY2Rd{E~~=v#IKrRBs$DHks!^nxT))W4~!@cS@Jw20T45GdVU5K zMvXf8<8U`AP~xSDX6=@y($SES(CPopdxryo0@E&rSt0%UlW~GU zIYCK)U?X(s`Zi7=BZzMEgYC2^7q|Fk$eX`WVc1|uggSo-tr0%fuR*% zRfJ=sn3>6{#kCPJg-OH;ik0fGVTE0BjjPICl}9d_#Ze+3t%z{_Odxc?=ss{rcJ@h$ ztog14;0c2FBw#;aQy}81FcUP25a(<2q$dnieXHSWPk)% zC$grj>^62gk%R)gMk_es%Pd+KgeKc*JRx-N#OX zPEgFvPH?mn#yMSrB*M9FkP_JAhB={V84AZM$7ktnoWPYBgjw!@JU?nkS!JK&zIsOB0t!4m>rcY(1rCIm8|?@<)xrlO(|^o+4y z&3GLA9apOUto_sS3|Ey{;Tks-0Yxq-S_H_#uD~HB1hhyAa*Zmutw=3_ALac$ftjXz z%r=Ej>X?I@I;YzEE+vj?awE3QW%y4s7ks@)@C;9m7kI_KdSzMec2!hii@wb_g1OD{ zRITBqPqqK`Z-4vaEC2rAPY48sjEhW`%M;E=U{q7v4C}h z7d$1%Bet}JK~8X)Zf!WbihjLHeoXe2xl zdVGfx`qGX4Uhl(<>j7!SKnndKNM=Rm91Ah9s)o`)|9-xBQ`)D6OkTaQPq7pJqsa+{ zX|(qG?g^Z5DK5kIpJFE{mt1%C6Yek+Sdmp>@qZ5R16Xg$b)lJtTG_esVv42%Ra3i! zl%NvIXtSszZTm_V=+eO*;>x%1WOWI;2Q2@>X$Mw{iJcsvaE5`pwWs4=B)igIH4e_ zUw;0FJpRtH$nR$aLndqhwyT64A^ zOh^@{L=-YQ>&s)19z4JaK(2HkGS&^uW}pS0-lv#5jD@U?)z5$bd#bkz)n)$p-_U41 zl@o5-TJHbc0Qa=B)fYYegh5WIb=?HhgjL9-hT%-{Mb%AIWz-dI1Z)^`f$5;&IxoRo z$IMI0Yt?3F5>VX|lJ}IbosCysz*#5(Sm`CJSgntm!9qsDf3)NVn_F6S{Wd5)J&)*ER6aFG7 z;!Ho*Irf-aC?t0?VT5M2f}?zp#0eJ79e5zh$TX$nASr+m)Jl*SDIuES6>IKdbeEZs zWpo`Fe$_Hi2G@2SoVX5dDV$(htN_*{L=AM8*$I-z7$<5c#2INsC<}EaloW0aFB+CT zu+Zmn2*FV37n9CO2*Lt<222K6fjrOG5G%BYDVMNE(QcQ55slUX)VFH6Bbc!H=VnJv z0P5XMUq4}p6u6QH8_9APSYcHuH1zLTK*kI?@z78KPWr0!#)+%6wp#3xXrg8UXDZQ2 z*nkF<4P-;w`fjtanh8QfUcb3gg9A5d_0Y;rVBEp0qi^?1XhQ!RXX&iod%|Ng<<^ ziI&tAPS=^BK?t!h!k7~6h9)0mdEtap+<_)oTiPii?>txrqcsh=1{91I@J2=lF`fx2 z;SMtaOVAI^gpk*~Rx=?4_${2^vcL(6dJz&%m}H4ze&B@II3dH0C%=!#3KQA(KE5%+ z-G)M8hJy30TS=j7CKN6fMv%u5%v_rk`u9)Cpo7Q+^ZA!%o(f;>CX zkQ8S1D})Zvuqh9;NQ@8U#ifipZ0X}@=_)kiypBbjslc^6VHHZou8b3Y7CT`Q_w0nf z?mMit7n2$?=VOM&9=K)!X6@PR6SU^lYcTks1~e302RMKg;*wYzG`QUm;;|VDOjM9S z0;^5Nme1Vx9c;2P>%D0GSYZM&7ue`U6U=ppC9QJVK7W7NPX96dLzz47xH^ATCB+hZoFeQ)@Z)b%xEx%To!Psu2#Rk0$u}{nd#_p7^7+Vxd*rC0n*m^aN$qDw=y+U(^E%E|Gl$v)CYnRusqNy$j z6=~Fw4ghy!NoZxsZG#dlcM0PT5_-s44#HJ974L;N7e5Fe4O@sjO4h@#zYyev=)ehl zn4J*7sM4w1S!M>pvT&Xh66oH({&17(2`-K(&mc1(^X8R6X!hXbB{Qg51}Y{TG3Y&j z5ey3$2>=ZR*XDtKOT3i~05PPJg+*T?3m=s25MdP8K_aWJk)6QiXvPqu$DD}CYfVm= zjShL&!V2D|H#RsiqL8`Bg|OftDfB6Uq!9Svvw!_=tZ>YKDEhQ;ix)wS*7itc294HG z^4W}f!)=_fL5xuCgbm0+wk{g^2@EIjH9Qo#kwv)|jn<^LgB&6d7#p-H;bQs@0x5ar z19hIB>x1#`IWYTKpA0-_BEl#S(UPDda$}LG{)&Ke6TotC$jvH9D$tg6v^Y zNUJT&F$ft-oUL|1HRB$T4M{?fQ%|j|lm}XJjLc8)^RXFH*n(wtmAD(i5H*6H6;+%d z(F%>$>JQ@tU_Jd)5dU&ZJfS(|Z2;s1=Uq-6fhfCG<7lV*T1X^+;&!fgrD za$6&j(MU=@xwf5vZN?KUk&!Ag2Czbu7s?3%r$-cSb07MgKuUmz(6JDV8^{WyS(_9F zM>Y~g+C{>7kwtrGVkH`_TQd$$({d*#>^6$>9!}eA`M5MY!C;ocAnI(ol^M;+W}eka znq)Y=jIu;HqMoN)>{@Dd_L_@=dI}Tf6hf5@KH=m_I04}zgba%h6VYQ;GhyP=XtVQ- z!j%wCxxtHL1hX0XCVhvMdL9doF>Wg%h@82RIrjL7C0n%*UFO>y7i&n7dDXSbwCrT1 zQyi(~PLF{NfSM|P`b6%$VL|!=DPRXvtEuV`n?`H!9o9=`E;v62s_zucr-k^#+?Y=j z#0Rizjk2+FsI>F5EJGPtkrY~k4W?+M*!kh?wQ_=-4os){__t4!xr2&RR?er(h<7H` zr4Dd>V4Z4^JaT4jv{)g*qm3Ab0zpO~A7J64j){=={0FB2Ml_@}9LF}BV-9LEtPhkx zGa(}X%kL#+~?wCj{ zg=}?xGzS%!zeH^%u@N-&X@nF)asxa}!n=5jgXFc2AK-b|RyKE>^k$Y~@e;nM`) zAq6Kim0w1%6@*`IM=0HFnMXyG6wBL?HAq2!eY3y_0iWe*IiMtknCAZ7w7|sY@T*nK zJtwrV@>=fJ>)2##bCR7(@6o+vTgbIv*mdjkGYy;UhQ!>CJgAGap`xg6jyJfT<7f_Qh(XoFKyqUct5SZV^@# zN2=4skPZSUy)RR{S-QoiBTkjT*Vs2iV#kqW_vC{2vcY;cUaO9 zgPjoYpSGHiU+5gkn^B%Aj_Jqn!tIW7PPK6Y=SsVEYraD_o37k%H+q4{}2J zMIQHX-cFu07P`K}@tX5~wN#B(^z!Qu_g}oY{}{6z_dhA7s>wo!E>+7nw2gk z_V1oQd+_-A^Jj1O(T%CF{uUHmRjo0{xBgdEa4mNHX-EmIutGBdi%kXho%sn&aAt%N zwg>iAl?Dx!6F5$w)1}8WEYM`y5yFL&nNY+6mnkRI`PNsP#vA_8AO-UsCNKg_vw>lZ z8LlKLFewt?Z>Gr!i%9|W?1Xij6r%mJM?XA3Fy-xDisw9fe*ekG{fmdwvxoR~JbAvK zu9XssNPfLfoI+SU^j7pmnC>zZa8ffR zD((zHe%2*f01G~~HJVxAlZ!jc%?5J;4dsOFxSc@5fNL&^TRCA%;gv6I;e@Sc1&l-# zKE@a@Jp7OZjn>N#j~+b!F+pql9RJuX3U&5a{u5t8Nnw7yC)WEPU%q(og|Mhw10z5T#K(rKb%rgV!385$sKl-GQxm0NP+6^WPnP`!^KqB^d-Cwz z)&AqV>HZ~t#UgaOpHDp}B>QJici;q&$lC`G-#x$o;w>(hJJ&xxc+Q*j^aCAVE;Z2et_ptb?>?H_#99vm(*#SP5z58dHY@B~WI`JO^jdgjNz` zVw{jjxN|bIit`+d3<#{?(qSAELR&JTa2r~jFbmOyzVqN&!RR3_+BV4`iZ(Aq(R{u- z!U;oK?swLK+N@zC$nzcvf!G=+?6w_7z^jLPvloq4RBsSYc!=-$>9beVOPgcg)0^{0 z_>ZI(>74LdNiJpxgw&P%%zwFXuaEh{P^e@c;Uxa48cDwQK_?JQYre9zVm5AMnTd@#Dicyz|BW>9ai(bUS_n zmuY|#(0$;RA_gstXM~R`0!buYI&w*$49rC6a07_LQ$k8gh|tVijzf}KGf+a#@E*35 z63AbSCM-si!gcHfoJBK%bD#nzjGL-2o)jkT_Tk9B3Yz6D4I@maXay5K2ZzBP+%W%P zlJ_VGxQ!bG^dra#Q`ZhYuKqeFh>-x$caRq!#s0>|A`L?`AIkklL;~iQXXFHS-*n1< z+UXh=>&FsoFyEd(d=3v;RaignqibrmPGah|O{v!Y8_r9%^GDBiJ73G82A`OMt20VK zzf&`TJ75q|fKl^|m}<-d^yvID;gNDGGcJCXorMsiIcKLOxCgWTLoy|B0(-i{Uv(5UZlHZFj{c!EU^ll_ZFAFt-u;DpUn z{mDg>^3Ag!UOajG;KzMb=p3OrOeeS(%XEsP`2E4rfOl|CPM9huz{-6BJ`tzk>CKPA z3BnpUvX7k0fvku07%8+7D;t=J;3Y;Zu|)?GWQMbBWjqT~06rfob3lQs~Pyy5tT{D3+EyoyV4#&@&Q( zfe<(%E>03|?lXhVm7XSZ6S*6<69R@gq5MKD!3lz$*$Hxi>L=XgKde=mS$zj|br8Ts zrkqd+C)DWg(;Mmg>+|Myn3P{~Id91x~mi z0db}@HC}Z<18XfR!~X;jLx?AwEv*0FGVXv*vVZ>MyhyIV3EQU+U%vs~?#>@SfBg2% zgBMTEc9GBhos$EmS#pdMmcj{}IV_0h`!^tx=g;=!gvV#wNI1c38oNAL3vD~$kjt#3 zU~L7x1`>i32H+yG6_K{7K%e0lzi9}98BjD6bQ}^JG!(c**Hlzn z({08c%Yu}C8&^Igy6}0=A+SId6s9fJ^gwW9*)6iGMU{gGET#FdOsgW#b<#II2Sy|% zir`B=kSp*;jrtCi#tzm)b^^PAaKhvBI=Q0nApRH-!GC)6;r&BI0%S~({~UA1S?YSP zzQY!!vmSl8QQslO(Wg&#^7jxk(=crgR3HVI4ATnWy{NpPR!GfEN`@A&OAveqasn}wpRlHe2;vD(@KfJ_ z6C`f?VV%Eyy_3#;vwe0S_CREP?E2&52d}5e2a7SjI>Z=vXUQCV@^1IT1F*>3mYr}B zNj$-i{JU5cu+4E8Accv_4&Z4EfkY7iH4Ypf!5RpWgf2mFL^B~JL`n&47Y$z_W0MPZ z?TB1}wpj@-Qt2cd9F5>POgd&lG>vj`9bye9#6I6R#1lS+CmzJaxghu6pfc>}5CSsSdvo$7bp$E}em-P!x~2&q8@L)vQvP7Uz{P{BdAr=k9)TJA;! zTr@cWbnxZRFMl>UVYl_1Al^FAQFPN)K+tFfC&)i1_GSC`Pwqo{^+h4$4yw$WvKQCH z?CKRXT)_mY!ulHjzvrgln(ZRhW1U}=SEk?^DJAH9=bHD|>KiPzWEGyQh#u2TB zAL0ppPB^BYz_G}4q!;14e=1crpFYNSJ%?;Kg0tRxTu%4z9^HTV_`%c7^~dM;A3wSO z?88+eL7WG$DK1b_19*kIAf5oDVgDnj`T0Y!6COX=N37wXw4kiuT4qA(0`z;@2*Cly z3Vz@bjW(=6V_335I6}3?lT7R)8*eZK)hM4W6=xi15lToIBuFNctRN+bzEaF_g?7oz zbDW-dGl85yxC!;3I`IT!1sS>^A9RsseYmFcV0OKKwKyS*n^U7dhulRKsx#4RKDan= zf?o{y5lh98_L{6Rizn3W%CYTwk=Gp)fz3;?5dNG3#tAzvj1!a;0wsjkagEm985T>q zB-?*{B`GqO7w3UkdQvqS8>tT{EF8iDJOgN1J5gD{yEig)`FQ^F)?>E`@3a>LHAqIH5ZU;Xgv<2I5@-|aPL2?pkb<87(2p%jR)attmxL1`6L$6nGHYnpqme*9u<&A( zV4sBlooAL;6h)8!F&g!17igG3l>Z0 zKDzPFJ|);{*+yTFF-up<2}8Igo`B2OUzY2#X(xD2(EHWf&b$W=J+k3ZGItqXe|?Ds zcl+Gta(O*{_!5nqvQzM4C9zgBl8^sC;WjXeTPM? zgpiYkNC{hk?9*4()@^1vj&&Qt?Gn>hpm(KaTj1ZF8?!S)kx5(sP$#U7O29ZltKm042E_1_Ah$3zg z(hA3Xo^gv5Qod|qD+VEG5-=t}W=JIaq05R^`Yxy2pr;8}C?iDiB+7AI1`#NtPZsp#ds!na5)Yk(kQF?!WiL?{|s}c=d1Y9OR7Biwn3jb*10THz)(9B0D~N)57|G)O@f25SZ{0`P?3 zJ#%h((y(!kLX3Ksu|e&G?sQNqhM9Xt;B*Mu1saVYC4^uCJO@S-g2WPBi$XsWM;*W! zX*b5$cOG>3!6nDg6+h%wl6LF_V}*_{n8oxREJrDfHS|pcIe6fN?5IRSztpWPiS&p@ zYvy&EYv4cxr*MLUZ>|wLa)JWi)4@(KQ^64U06;@gZ+p;m%JO3V{MA0))P@b3J`T5{ z9+Q~$RBTN{37Z(kg01 zf@eggNIG@`*LGGD?Jeprn1zsNOKyA1EN}}XWO|zUDPX50_mVL)P(rL_qm!0m^Lau7 z@uLnP!WJit`y&s043k3IbmR^ASY6!|S@-oY@|KOzU;uR=^wG2vvUCVM(&YsUJE}%& zz6|JSv>Gp*#0eLyp(dQbD1;gcZB8&+u<20NXbMF4Py^;I;91!gm()2iR>&6;kgIn+ zp!+Un9QW6Y@>)a_H-)+pcx>3J4OK~Zp)$GxM_`8)msX`GwG9|?TrI|Nt+N9#T z(457p)0)RZ34dVm$A5~Y@CV6Zx{&yV3dZoBk_QoZ3HrV5uoh zHRnp3IIE>D%^6Q%k^>1&evrIrN`YUTbLjuS5I&yJ&|doqubzIwQnp=zr@(p-5T+t| zMmWd^W0f>HVXcKzS~hHk4u)>e!uCfPAq}M9t%O87eM2!pIDyc$UpQTaJuWk9(3iE@ zZ+(B3X4cb=Vz3;$Esg*WFAE1gdz7qSs`*s z4@bY8k8uL_953uY_6W8?%VVIftzXYvdK&s;60k>$PM|^dZHYcJ++imeCzK&Sp>HP; zIJF;C2=E%MbqBCM&~dP?4fMh}Fu0KK_1UKxg;;x#oWxnII*#H#&{Ciuk5`FKYp4nR z$~T>N9bce(z+?s0R?_iCmYY&BMP(A&2v{Rgp~^v0D=^B$;RcK+kQM&3G48;6GdWcz z#etwKY!w2rfPCbyFc1i{6YkP&rbq3B49t6Q2@!L=orq~whYPZ#Fiuz>B#zrR22avZ z7-QXos`9v*kwRkjK+s8`_mG7CZ;K@O5C$IUA)z-z-KNBK3*ni0VEsMT3Rj7bFh(jF zn>#_y=py`viOjtKCA0&KNi=clIVAB67&XEP#tFf3uvLgDjN)UyL#OvKb`Urr_!Rvj z@@6R*4Vc%^dH5%DmeTJ*Q+p98=NMW{wwnjkBCKKG~ooE8u}`QEJuuDka#} z)k;W(l((Qwf+d-VqRq8TdN6GimZ>Kv$XktlrMDQLiiFTiW<;fjFFUC$&yTZWN`#iE z>3;kRaDqJXFgu~I+iX~~Z>XsN!azO0U&z{;nTE^+R84KyAPq{Uk+YpCHP8d%i5Lnq zF%&w{vM>%Vfkxm!1s(}uU-k|7IHEB?;DxXq_R+*q-=QRUdbqN0X2)?xKIR zEd+aFI}AJ62*`JM5v#9NOS73l#*5vpoX~^!9WHk$`XXKXU?<$l2}TV4cp^1g3mh}B zNlMVXfEWzGfp6#-G)18}A&IVnyciH< zLJ^mEKQIU^YA?m8rD~X3FxDz_Ip&2LGC49iQcrXj*$ZG1iaKvSZf(5 z1QICH+S5;1o1s7_I@EO`GDY8Cm2j6fXeaO{X*ZxiVPF~xg&7AxD|D5!kb?#n*_Y7_ zaC{QY$xLc1^z8)OHUvIs{0OZwXpTVywIS#+HOvZt5e@Y$;5NvR&Lql62?6sd#~jFL zHWE22j1zKl!ly78$wkk9h}nPCZ$ z&#VJ#vJKopxxW!9{XHUI*xC zg1HV8rs$2}+XL-U&lo3gjuI(B`z&n(U2yzi!$6}_LSCEsBG(J^EGTp`v%BE1u3|

BjD?^CR48;}tKkK_*Az!n){64OYaJ=CFukwq|~SUJHJsfQbXYWWU^ zKnuiZm=l7bZUF^s^(n!ZneN6Ev@pLBfh}y47_%Ua;$&hlUZ+ND-g0v|7FlnHFw6<- zJs5##*a;_b0%1u=n{-->Mr%~gT^XfGiv7hrjiM5rFmiR2VAlcYfB4IatuV<~qZRV> zt`;ogfFDm16cQvlMCopYyoHt5T4Pxlv^VNJ5V?&;Vri5>E}>IjQ%v5^e?XTfBdo;C z=e(s_g;*`}C<~RIAJ1wkC@Fa?NC>9o?i#;B{%@}yT;>NG z0=@N1&+rg;=tl0uO_H6O&B&wbHN=c0I8#?KOs7dSa#=7F#xn{Mq#kZW8v&+5Yb?^1 zY%$C@%n4)!gXe_4m0-0cTUf^JR0LZyykH>)dUng6y!=VDVA^Xfzi>rPs89DD%ud)} zd)(UuY%RukH)ti4scN)x=Ho`%#a4DAmdr)J!n`C z>-Cz)+vi$<)}=VtY4-w zQ{j2QZ~54R_ZcFzNC?qHb)K-m9y1bjh1@O|OhHG`(D~I)=y3v^iPKP; zM(etz(fZ=$>&vUk1&vmZ>f4(ie)}57SMPE5Yh3w1zOvSf*JHhS^=4nbdh_+`>o@zj zVkJge38iK-hL}Quw(`W5qNxEi6?7^>C9j2_)Eu)K^de^RfsjH)tA&bf9Ep_elDW_I ziqooe7*bSK@bKQlR7Ii}2w(yw&aKEM@lj4tJK=8qgun@z)__!5mn%O1El z{1F&D@Ua845{w#VaodGFP{V09Lvlp7d8vAg0yS7qwG-BRoep&)c0zf&?+^exAvaEg9H7?jgaq1{1;7VXQ_rsMKfLJ3Pg8CNi*T^ey? z@g7**QKF6MkfER+ZSq|pBlv)02r94%sPhw4;syHY2_|8gyW#{q90*~}_CX=9{0+!XD6IyC^R%$FAf^5)h!yWoBfAJ zPZ#mG4^UQ^oq;*Ll zDQs$%qHckluz&-g`9~_Ipla_D8l-G1Cr-wA2lyA`g$lNoc`iz9xt6K~DM;obh8;NF z!I&WtHprWtP~lEBUR_QwJ;qb?6KtS9cn@qjI0#D|F?e!W>j=J;C1-u2Mm%BN<%C)6 z)Oi@LkjRL_sZ`Kx9nValb`w{Jm?^0M>wu+283OHPj)aiuZplXgCSt_ovJ2fY0waJu z&|qwll#r;Ez~M_X6P9T+oFK7;N#KMQ9oYI5P9U0`U{l#zL=aj5l&%XN1EKYO*X;o) zF52G6Ff$~F`VM*0N@_AYAwUV@1WxeHdw~%ebD&P3g|ehZ>&;nHqm`Oq5ww$@UImTT zOVeo0Ko($(S6GaM3*KG*K3;r-9_vK(Sh4@Z%SR7ieGpmq3Wzm1p)5%X6;TH(p{5yN z2-X5U2j&=RHF=7uR-88}P4rq}h8hGwr=nhnLcGuqyEw0}I9Hk>1;9omr*a1X8PF!_ z#v=9cYS2zNNk4%V+hiFk$devYmSX2Ig2poy2<+c3X8DM~2|YVuA>KobZzMNd(jcG7 z|5;S%))^-bV|gnz z;Cb9m7^nlKH6%B8*$L!?zMXIq3e{-6IB2w<7m?R!EpDL4xE77pT}z|&ij?u`@zcdZ zYB_)tphokX>9Il`=d17}YJn%4->LNF77jG#fz$l1hBjW0&5u8&gFff5q94j>7*4&xABY_LH~ zv^74-PLQdQzGaa_3^g(HA3BVkDgKZ9Mz>o9VHB`yUTqJbM4b!xzuKO5n~H znyvDZsHkV8*iecMy_n?JHx$4DIvS}WOjt^z!cITavjQjJxRjgR>FF>bIA>D>kE`uJ z|9Nq7u>&ro@y~x=;<2gZ79b;@!+Vnx2JD2>^b@$SaFzVqG`t6mvinRUCN-u-ixMKe z-Z&w%`vu=&0aD=XNX^RoSKA2=i#{tbz8KOFEOChf2nL?e(Qfxyn~vvuJ`lxn@_;cy zMvjP_H z_37J3Erw*66G9>p*X_E}!{ZJ_d)#4pIwvf<8m-Z`E~wEONqvX?w6D=xJA@!!K7aVz zw^j8?e1|={)mPDDy~5$er^gSTe0%#49OIlA_ptEAQYcEB3~8*uoMvocbtF@ZY6hMY zSjJ_y@>$K4WERwwoF=(P;GA4jsWt5Y%pj2heBwTESw&sv6*bm4DMO1?4~#nu#}oR# z1JCkf56(zI2j2{-xDtd>0?!K}Ms5b=2qWaM6yQ8i-s?D=8~;^10nfgzkTse*;K-;* zAukInq`|o$vRsZT)(TcOX?9N`2eg;VWEv&T#vsWuG)2TTr zc1n%b3*`jF;jf+}Sa8+WXe~GT4=){g{8Y}5su-N`;N9Z4#SGl={I}KO&FgOqsZVnD zM2=G$J*FN?S?CA!q+p57IF8j(QrNNbymA8KiS;fgC$J8F?YpgPR4aX#Ek_s8ra~LP z`U59~U*7djj>fT=C?d>DIoPTo>kM!L^%Dm36Nq)-gueHnU5fDiBvb>E?@8`&h7-7L z$S2^0TufETj7g7!AxQU^7=f!~xB#o1;dI;kHrQAb&EzE-Kna`1kQsOLYFx@&(P>x; zBTOSGvcc#&r?av+^G+F~9 zT=Ve#jYO11j}@ZClf_rlW93jK3KzZEM>0Jjrrcsxmqk^Uffaztr#;t1-+9>9K3FJW zKy+MOf(v!_rWp!2FUNxE&P-!i`C$y91aLwhQul&$A!wv0C5cA}0+PgZqfI3t4U7OfbfM02q*emZ^c-+L#9Y-((AOs{fRS*5#Toe= z~bU4A`74#V8wHc^2Pwg(OTAXkhIDxXRjX6-K z3_FlW+8V7+COEjBMr*chexh$5e|Yl^e?25KiXQ75(8CIPthi({#;m9h7qa*^Uc@=P zd#CmyiNgRpbs#cBp;QvG!XK4Zy4?j!EP*C6QeXav0_Wtsg5+ zi1Dups|0M@y3PH1kzbZgr1H`UcUX#bidMqyLw~~|1JBHoXv$LplBByMiX{k_MHBQG z{L5R9;^4}AhWAb&Mo zaUH;YhOEEOTFZX{j1#^zZKJMkGtuV+zV_xt^f`fOjYXaaSQUU1jaK9&OMx`_=_%i$ zEDjp2+q#Krwcn4&qNhCG3uFp&&eqo$>zh1A3&|X!mYOr3AQu&tuoQy|1VelcO_d|G zP~{vH(E8H8*0h{#(kred zAPm#Ps=MR!=g$jSvN>?0&p_+i)#`*6o1yVvg9RR3_1|~#8CZ;M({>Ky_x`T-Px;>k z)M&Ly7@ZhnK+|^^)KppCTwY$|aVebeWm9@d){V}U#wMS)LOQ6JZ8Fyht*jy^)b+Zf z(b_>Z`h?*Cp@kUVP>wkR@nke{=!|Xxrve+XCJg7X^jwu>to$dc3?vKV1g<)PMqG<0 z?k-|TDyYPh8l|mRRl24COD*!x;2!pOa001RpSHO6*Kyu8{TDIPW`(7Ayuude5I7D2;FehBRqKtn zFcS=0z5*WvS|FKlLc`uMnmw=o!*jAuZX5{T_Lg7Jiv z*$H1|4>el<{bXsQSm>|L5O-{<~Synp5QU^Ny*%!w+Vbc(s*RyGYbg;dIM)NPK)B5Ma6-4!q|Mfrr=l{avAO9%7 z{ny~@XI*~=5W)xt7)2nye*5nX{q#N}(*0Sm$3q*#_;3HaSa;{rf4lm2_-Fr~nlIO& zgbP49!Tbl$3Epy$Mc*T-N;(UoF|^%?eGoZy9O{mlP;UB^V5Bfs*8${!He3l7R;t*X znKiz}ZW;>fQbL0zoPa{qo4^?C#XU2GbO2|dhdw6|IMtV?$5NnOoaY|R^a{1MP41c7 zeLY6Q*<#`f;@bh)mnP=AoHw@S09v{eEvN*bbsMHf&X}K{^I}xkVOV{1J~d>Ubf3YqqX)p z(M}*CkQ3HM3V{=(WltjrX;d3_Mtb=&LV|UV+%)4Lf# z&Ix1%+6j}WAigAD@RY}L-5!VpvQ*bB@`~duooaFlOju+};}B$`U^mUSh$nF&E6l&+ zKpcC7tgyi;IF=9LQwzRu=uuzI2Nz)&UTc%tkSm}neO}>b09lBrJ zK)uT}Au&M2+~q;oPsf}n=Tu?A#bZt{lrdLL+sgEJ3WY|QX*8NmW@D2s!Geu3XGC!Z zL;%wm`Li1MV|0cgxz-`$FH6h7;GxDza#SA~7B~+;doa(j;3w8gY-Kkgbgw>5Ow$f8 z<<3PHbwi`ICcc*w5F$U2g5@@I1DoMa;xPcU2h2(!3tU9dXf44B>O>5n-G3SD9|H@? zCk7SFWJ`FWDP&f$LQ^eL$rMqM@I@)P$mFD|pOCRtGvQ=T(DY{3X&}r#0qUE*JS;A& zxHy3l#P0|it>=q);zCtt7zVFRor%6DE@4}`uKml~jAGJ(%E0>s z1uF|@SvGx7<>U3_4gNB6hsh0|5u&`6-zSs+Mi|eqvU$nY&>51*dG2U4&N@G7 zuS!m;LW~M)X8;;}IxGXSP&}@w?z~>Eh4eY84QuXS4&(T-tbbn7%vKXIP+)7&Zh03c zP`BCagmwAFOoje@2eTGx0hypGDzg%>D6go|+Ei%fXlQ5;+6ttF0Escwy9P>`wSf0? zLJFLPNKNIOGaUd{nmXM~r~`I{g$whA+|HtfffEd!afe}8B^>-sAkxZukuyh9Al7^} zp<0D3>t=dOlt5tFW$bgZTMs#t7HHpsb0z0Ve+I>5;z*1 zE-K%*ZJeQEgGR%)m=@G%jUt(vJx_P(e4Qj^D$=a;WRoP)IL-?dYeIRfSZ%G{>EI-& zs)5Lx=DEHtU$LL`@I7XNau~5`zK!X!)?04jc?&1dc^JmwtqvNkvnZzq3ef+Gw|PIB z2^=_qSA zA8Z_fXM!Mh46-pe-PJ7iawriSgj%zPWSfa2JDwA?n1~F700<*Q4MtGL!N;3W)Rk@n zUo)iyKq&zfKruE0+Os5T848&|J%9$9j9{$*+tb@y48ofU`VwUJQa`fw=-fT&o5U!}4*JzCpQ=nQy!oy*wFivSzsqpIj%4!mlLa*6e z-yGuvg2d4!gB>44Q$Jxb$%`nWm{<>)S_fL`WNCH+8~ie)1K|}K37XB;ht|AllyI2vYhPEoOAEvaoUjEa*f%vnPN+)FK=5^71z-e@Gn77* zP}dS)$W5d59P+B!wM=?=`|9k6m)W})ulETxT3@P0YoLX&vAYA+q!MNJl}|rXGLl>Z z1{g>S?fW(<0b6|a21JQ|m-g@%8z*q7^T@~v9oRL7J}0b3v9Fv?tiytoz@|fADFP>~ z&3P#B3f=>;Od=rB-iP)aMnbqLm&9a}TO}xiSqii*Qeg$ac!5EHSb9F5fdjZ<)&jY~ zj&>NqYRxwhdjFCy^H$_NsCj_HrUi4D7D@!2caNaW#&HA@TybcEAB`RDYS~Rj@CBVc zB?J$G;nwl2XC}09a2T?HV6>xc>u^w4w$YV(=OQ4u0ze2pgqt*phfA{vY;SXCK;Q)8 z7%OZphkXb8X1@1zyl=EZlXE4N5h}~$Ht_pi(bxy5v7kv$JyKxPV3c5X z!ai`qP2hxP=}g-Ws2lSlR~iz}q}y9+NvAY?#;U1HHpvMG?F8r6<~+D(qgC|R=4kzZ3D8fV-2B7q2k(o6Mr+dPyVzj#{|?l%xF#vY!bjdx zsYwYe7fJu2Zh%tk=FuA;gl!!f31xA+*mgOAyl}NACjk3hD}mY;dg%ZsRM8~n=z{l$ z*8(y^Y8^p37R~^cO-^u)%24t$P=n2j3k)dw!DY~f%iI5jc(a)I0@qL-nQtDd#j~%| zHbJYBgIYKuc)J$jpkuX^bHwfuZxy@Q8#9B5b-m z_v4jLbQtW?zzBfLVI&Zr8XV<=<6X2L=i#Wka0cCNtG#R%@Lz@*Sh?icIUHSw}HW(9V)__<_XGLa?Zh zQ^Lgm{{bs)fqCNm}rL~FA(%8%#$z&qUSyIv>7u%*K8?x;?nz0*mMUZ zzDnfMMK}dbL9V_;p5?NkPL9by2PmOX<2f*b#18-;R8$Q%brjTdzzH64BxnRwTY-d@ z-vC&1Ah6&!jvEI`DLmXBZtRUzg!`XN4jyUMuLITN1Y+2CxYz|w*eNG;5*JAXq=cNk zPAS1rqcy6856$N62lDPs}n05f&{D{toC(_om%dMZ|TauRRbO9RH!N8!&Krqp+G5#0WeLE}^Koa47_%p=AoI3eJe zoj^_ihB=}3F-2eDng8-SkXQtbR&au}eiTl4Xq<3u8m)eIc(hrWt6kAmC=NWs4F>ozCcsOvz;vAg&H&sIF4fHiA_1UC8}KuU1M zGH`;kBQE1L`qJGe(Sn;8B>;9Xq`%}v+#rR-h0o#44UR+M1EB@|4pnLhqmg(9m~UWi z1DZmA%0z*gEBJ1|P<&;Zv24;Uku&(NxxYnO*+5u%I0q41_3A`S=}Cps0&Tk1s` zCj_)P;ljrg(7=NT@dN{m1X4n|7D^}&lmM)QMk_c0t&8Vq1v}yTpwSu~%Vyl&i8?AR zn4sYVhbBiLv|OaGUmeg;R5$`MT3Kx^@uIe@#m*qE90VuObD)_(*Fo)sTS{}xXlR(D>tM|yf`j-qKv2Gki#LAZJc1hjy#G&7FEn9NBE2t z;Cr;x3zZe-WQGuV?7^9-Aar21f~^hwKxQwg>TH)MP3xIHLmcDAY7XdGjqaIxwYCYv z3%M}DOcLg`K;tZi7omhe!ysfdV$pIOl1MQ3c0z=8X6V`iX?9#`EmN5FV+fqg&aBds z220>{UauBwAZ=O+X+Yp!oYI2JItaeQG3@#^UjipsPq>E_H-s0uoM6GlvS%frZ(UBH z(K@F_YxeZf`?D7hT6V(aL8Ep3-L3<%jp7=X0$4#oMgXdnS_raiNKxLNT+I&+AEu~l z&Elq|U=c#L0ws_WNC`9($O+ioR{GJ0QP&+#h}TXyAq|1`M7?$$U00JGI8Z{!%Ly4q zSnv+Lk*)rAOiG}Wpa^5&h9VcqV}nbq5zUkp;Cux6(l#q-1Wq4l^u^W=aYVWdvT~N& zm?(nE`%;TZYFcxv=5%Z$k=Sl4zpL~s!Jb6jMk54ZA=TYKOHQmB324oNN-MW@9Y!3y z83fE_iC1bydN-0lN-%_W7-?;#o*N-FDZq+l>iNzo6$GBh>@tIHn-P+EesGosMz|s0 z;qA$u=Vhjj78o*lO(1$ z_kS#dM(bbXgj`~bRUQL$8U$V)b)&3MV_6$+r35g^uFmnX*ERW|1n55a67?Yzz(ecH z110DyaKcdvHm#>FC(J0GT!b$WP=O&fM({bg`uFu7G&#XYf#Vi;i++<5VlxXY@x?Gi z3WuhWR956_Cj_bUPs$2&zATZcA5T!LS6%jG25YCplwVe!MjCVCwO*J1PFfkfk_8SR9N=^ zC}9|O*`x-m*qdg7A#eTjt{c1fZZ7;LLm0y&_zrxXRxeWLI|weo360))VdD;g5`+d= zd#R|sQW57tp+8kc&}c38uV4P~{)RbA(G8N7-oN@7bv0Tyr;a)p$~+d~L&~WdBjmZI zCu{&&21*zP1Eq|V;**P^Py%R~@(K}*1pEyyjS|dE04G#c7~nJ<(}`$uf&(WwNfWYs zc1F)_!XJ8zDK`_r90;5sgrKjxkYmIFcefEQ^MFY?9^3#=bxA=Bm_>lg$qAilgWgmy z_n|u`nF8Jfa@@!SY%G-&mKI{&Pvx z#|fv~2?RJnOoJ^sfuI?EQ3pFA_zj>0dhI-*O_jT9OBf-jz@la>@zIVZ41=N4y2$a#5mE5qJo7dq5cnfpGnimzf(9Eaz(zIz zt_*{mfFPrEXRQ)#knctQn;mvoH#%618vf&XO#aQ>UEzd$21+1#`2XeLKD5n+Ors74 z2`~7p1)2)^xGQu{QZVb94|JV0LW@AC{hqZ8u5^YBiw=A$Lzr#W9A&YL zt-G+a^&nL&z0uL|B~o%i{r#L!Twee?0GvS3iVmDmdB33&$Dw2?cC!+&*l@k6<$t4! zQrHq`WjY6HQ_s_FF}*a6*6&YZG;E5xa77&$K}*77Nid?&==Ak4WTHgHdz3x z2z*!ejF7GCtD8$w!WCYOCZNH|pLav=^5yFv{{SZ>#bm}rfJT+3r6T+zY3E@Vusubi z016f-7zz+<|2=0TYm0W`;=|NABZb&VfjJAVFfi2zZ&|BK9B^yEl+IENEhn1iQoFUgM^CB0xTu#jKaA+&Ui6D#*KnNX1u;B+X zf?>|5(n4pV0@ftB_9%lb0(-PT}^eeoWO&_rb0ZC)EuXzpd=8Y;oNK>%j3z+{`uCf zO$sq?T(gTxm8+L6IIDVT3_@IC$kek!chi z8QRHB&k1s#RD>}zZfSJS(2@>NOe}MA^a@~X%v8Qg3}&B zgIdk&Y}j{jQS2~?h5NhKO61%(xpw|wc#AO^AY%x5dhMzG_W(q(m-xrl(t1kWZw3XCGm08a>?$pDL) zgc<`w@cKSD<`BFFJo{FHg=q#%1fHM`j;VyQ-$N@$coW&Mmi6$)K1<1hj0YGyB{4hcj2sXkH5+zQe z@dsKOdJ+bm($7W`e$XWlg3AWP6Z)m8ZTjP_^RP|csRwy$$jxpxKHOzXtQEJ{nr8&< z@?OXTG)^d-4=(1g5yS;2YLimY@jnuUbl5s^$G@RrjwQZtZZiZHHsR=roWSmkPP~J@ za*jp?(*b+J)X0YG(6>xBfTmW|a%tu2ZC>oaD5~hDA`l>jh5*vKl%FV$;!4?oiz}H3 zg%vmRs@1O;YqVsJ0^pvESDMCxL2SqzVdppoAJ9lJ=K+`zY+HOGc1hF&c1^iH zFanl+Mj#pq$h~mi(t<<&!>Y?L@KN;K^B$BL zPT~X|V56NNtb&JXxnJeEk0ekyy$CVBiYZIgS|WV~&Zix*lW~W>K0MwAk$H|VMtw8Y=mtaP8BZ)kTKPqx7ZpfIG2|i5mYO6 zjbEasL(Fho;|N9xAcb#NtTpdN-wgE;wfpTcP7u+>C2Pcmv93^8aJdJz$$A|;=s3!Y zC|P@-n}!1ayP8HdmYgNY?|dR3krc9OSA>6GfTNz-=4RU<1sD~Fbh{`DIfS$mLU_?$ zVMvnWc1HsWDxs!=5m=H76Io?~BywsH+4!8N1)Jt!Q4k9(Xv-)8Ta4D3n1WgeEsPJ) zVTeVn`z(bqCL{DQ)pST0OMN4OW4WD5(WJJfbc23r2LmAtvZmRvzhHErV>U^28RxcL&=D<5zY4|F+!MVB@M0fhp@15POAE!mTiKk3UwRs`Z#r05 zcUvKl;6+;hTtvCd)>{V=6;1a|TUgHVyL$cMx)Dwgw+T#|t?4|dx{7I_n0ajzm?rV; z)5KFmF5{6|{qch_`f!G=0`mk!GX{S=tvVd4X^qnZP%#_XD~3LKd`BUbIvXol#Bi!TS*leT4A`@_`(6 zy^bf_(LoVk`yXW5L--0mC=`(rqId5}3breN`21)e zQjOM~)R0}o1vw#zttl&{O)DX(&z?MZc>nEwZgT)7#26LnyC1JpY&)=lk1_^vGAsq@ zWI{R8QI6|OZ!Nq0@!k2l%?ZvWmml8Xh6kh7)Ebo_A|Wi}giwmTJ0uz6jCkEb3Mm~S zua_V+fF{u{_OWPuJ5C6R zZkdNqgudUVgfwdt0yenG^G#%yKOWkBSl0Q$GH9hUuH^x6P3T(RAdrK%uG;gWdOAyi z7D5fQQ|$zD0x4iM%S42QQv;ZoP)I>l{dx?PQ1MMPijhq>)M%Y2VlzyUsel%hS-07T z4H#I|xB^LGwbkqh zt#7^QL6j{K30?7*zxhT^aDfwo0^?ljo6P0ETS}!Q&P5;2U*CVXpND@me4_gN(ewQj z?qPJ!!NTNx;-=H}bV@@%hXE2z)3wyyl{Ef%?MobhBOEwk;u4?pU^zvRQ#*lPC|P1W zigRxW<0wCzaYEZjuoa#|-a~`p)Kc)IAYFjra3uvY8at0=^abfv13QP21&_AOW9=RK zj=F2@{rRk!(sY1jKSYGCxH%ONxXC6m5jjYGFDT`Rf9dxWg zz!)R2s5sw38Pg1fiY8SlOX1`lgrGtmW8ehE(uw~N>20UUR1-m`F5B$ifD@u1@gB#? znw*eiDb9(qwX660$sW=*z(gEiezc?%6_MDDBC-9m^5*hlwTQq8(=CLv#2k^7)yrj) zCfiF9*kTQdw8TL4)y3@hnY*~YyxG78iL%V8z0Vk zaO-lN$EcFADz(-;J%_cYgsql_Y}AkSW1gFJQ=>JdM(gy|Tb%`wZKlczkTiOYvoxNN z?%)6LQVy?o`*+xjD|YZcejrb5u5sy$SLge(G4e25i3X!ocg;x#`qmteL={&hgi2t{ zRm!q*U=6vD@dAcI#1E3+jgZ2M+;2o8FW-uIJ{m9ZTTODr+BF=x&Mv0!fBfUz zeS{JG$_T;cLy}2!D}WzYStgJeJ9m!;PTwXkcy{w{L>Q|KoDfizr8gDs;Dl@13FIm# z(?;Se>?bGS7VuANGD7V`c>(606kenChlWP$Sx{!3lM|Mk&E@k45AJh*WQs}D51u~1 z|6-r)e~@c&#k=Rv9z1^W{Q28`3N3~w&+b3Ryo;RyCF7{3B;n32BMGua#~$4eOTzRN zDnHUd2Z2EYm2yU-UMnX^V1%YZB^B>if(0r;#ds}ZP!Lvf(<>l|w4#meYUW9yT$MBx zN(DDcDQhVt*5(8$$RwO#SmM|6?$P}x&mTOyzWMO<{?n)TpMAVZnw*fPQqpAq{>Af$ zkDfn&_Im&B{_}k_#oW`^`$tcHcmRNV5u$nY{Qi@VR|v4>UbSU+_U`SwAK$*4R6o9Z z|MuP6w;zyXj&1Bc&$ej1eSayX#_T*d8^8bmcst+L#I@~@&tag`DRd}h7$_73!3Tu{ zK9Ix3A6|amIck$M2{9%ylW4K#{lAC%UEj60+cefQoq@a5xbx4db$znd+H0@P#&k_4 zq-26`?Sx}6*t;_nPTL8wYHO@eu${16aMeJ8oM@#v`}Xpw;=ZtUw;P2OF5Rh49=l?9 zSr|x~gqD4siI}mV4hgpE1Dc)6ti*)T|CWK^20V3y+l{**Y~=Z;R6Tp|x_S~v_JWBn zoUWfxk_u!3NBMSSn}P{)+wDIrvn=sIgvn~RtF@Lpf91)#I)Q{jkx*568n_y*uJ+pa zP?^;;On`MTMQZ>iESram%ZH{J4r=JY^{^S@yut0w$E(}hn_h!$w++s{Ya(YTfC)mN z#7_89Wq3cT<(vja?%MGnSYQKQ5HyqZp~wVXxzbg~01l!T&MZalA{YN11PChT9DMw6@ez{PX zaC>uciSOmU!EpkCtIw0>8e`LoyN_QQ0fn6OwVBNPl(Ni&es5IP^Uvw71$s=SiwwCn}IV@2^qF~Tbh*(`VQFduhbV|)^(cW zF7`o@r&(51ii9>3GV}wq<|PqD{?>KWejZ14AN>%) z223c!IO1_}oY&E2&>I$y0gj99EK#9v${G^`F51Wy8VbjyTTjIkAi)6&k%NLMsix^j znwKe}8l_kWM7Z6B94!@ebGHPleAMFSInGRCsa$fgciA z&Ux@TBNddn#zS~y%!I-(%7h*STF~#Rg2tlp1clY0LvAXy>7oo1Iv+rXS_umuZ5jqo zIMX~44B!J|j9WY)L;|27Ou(rL8AB=%Fge(O2mV$KQVBNNOjt}LWyy)xe1-PUW4H@_ z`3~mzP6o}?GC`5x`$-l>5@Vb`2AL-SXl_0}>~~j}VLsbkeH!kr^t-y&%p+yOLvOT+ z@MD~<&=*-oWxu~00}eq@V^8f>U_#-=(T2z2ho4mNAt1dz=b>ZOR$+qaZor*jz=97n zMUFC@Pcnhoi?%;8P;Fll3XD7kX+|i-XtoAAkOO8EHFFXYr-(|0I6OObf-%y`D&BqH z(}yQAv%>mT>exFVXb^#S1;cCSh3e-b2o4C#ww5{`nCdZ|;6cXwS*sv>=Q{IG6$??v zPH6W*FrLum0+_IWf$UzugehcJfC$@7Q5<{+>(R5UfO8!3N`-ZsPt|B0yUMKe9av=v zE&Sqxb&4PMu!-Py@YredfaRR8;N|gt`svfXNWUo^6QITjNt7#^2}D97j{<9DA`tN9 zF7d)dq5{|=<1A|BmUSL@Qks@R7GYfC0|FAgG`4~Nlt2s+7BDqRS7r%fIv-X%!7sFX zHpLlfnihCybrwMa%#5T}w|P1H?zfCe!-UA2k_l7C1R+JFNbtnAuGsGofeASB1|b{q zAFiIg(7U`^{J6TfyNC3Uq1<6Y2qw((W!3L*9TO@tq4dJ=zyyggJPaYDNMR(!&^d%q zMa_c)A#8Ku%mgRxQlf$Fa|a!)rNDVv)pWp`raRh-WVXJT8xH{q51kGg*gFdZH-(Jr z{)hN{*okb+i(0Lxi{R=zIxF8{#!&7+2hFxRBO0MJd09VMS@-vaucTNwUsJ{87t?>} zGJ&oGm@u16{SD-KQ#dA62P7PDATc#jHcD0`;p-Z$yDw;s50zOTK>`q<7ADY6DA%G7 zcg=c(wTSl zkvE-cPN!be;cD=#JQXvc=IHK%S+QaxsI-WX-~dfl(xNI5HdZM1o`}m5-NT!nRqa0a*Iw zV_%uzH$6lkuw~GQd|#wS)F;TZvJRgH{mD9poltoF{u;i6y58_)A_++d%j>pI#F zdjHyOaOGGE6OiiQr=hqG?nVdV@T8oWowXCF3t?b&N3C;zptCrzQ-bZ-SqaQj zbOqOVB*pXsXz-ykTKLpuR&~<}VGvkq9p_T>Ku?6J$RZ>_%V4lUymNZ2G;%X(|L;Yd z2w}qd?K(>v;=lwN_iZMqq44ry>g7_L@wqB~Jg>-vby3wu6bY`|O_4zRJK4@JKlbN0 z?Ha9PTbcDGqn!{<%QczMUr(#J8C+cS;`CtvKjET3BZH!88Pm7Gv3zs_f1wg zCg4x4#Zgc*L6;IHtT;{*&nC^lOh&stbUv=}qyfg7HU=FKMo5Gu)PHh;LVIChiLfrV zrkKhxP?-}^u!^}OF3#@T!nt-yp7 zcETVjT#PXgCggbiM2?G6#2D*QuRkvG(NN5J$#qT;6aC(?6YP_Q7HlDfc1iara7?ff z1?ov)i_*(_pioeq=enz2>aMabmgmMP8Xf!R5+ly5&)n?xPWTsEv{1e=vdB~18CJ7x zIN_BWa9p+B)kJK&5?uh2KyAM?oM*-Fp+rKCo`23xXvY}Q>@Z>fvilHMK*IB;dKTT% z7-Q9S914~?wy!G#kZq0DyH<_X(V;RcGn(P>26aBi5B~x?gP^%bKjkI*BC!Q$+#`aX zE@kf*i7Y}%K3;v21FLo}BOb(pdJTpSRfs1|W2ztqSaff2m}o1HHqOIJYziJ)(oet! zi7gTYk_|67coHD+Gp{ya!fx00A8Nb;sG#*-j>l{;^>O8k?^3*;S9;>N|KS!7e))(uM++UufX= z5&-rz#euHdZFb!O!;IVfr%=HuS-!FVmtK!`ma_6(-R3>SBB8?s7z!Wis(|NEJo8dZ zCLI0NU2rik@)Bl)F%z_S>!C(#yq6lS8??rzGHWEg4gIg*zTTx}9y}t;|^} z<=hA4;B4x$ZhBv3CEYgn*x3u(qa@Wvs1v^`2?qfZ_1-U8VI{%Q(9kkhHmGd?1HY6N zg#tc`T;a_e)dd7rf?uexJAp|{1rr|(5<9A?B9I2b_y zKjZ7yZx77``YXvxxA)@$@!`bUuJx3;NO?GX0E_bBu=Eq=EY0&E3bHA(mWB~jxzyvb zWxB%N!Em0*v<^BGh5y}o3ziPfWZ;VDorx%@f`m4L2Mn0vPgxi&X&JEE+fri+c38L3 zz0i0OY;qAA(drWaSdz@O->!%3#uQj;9Mgb+aHpS@SY`Q3mv|DbCB`%QJ+py%jA@d; z%>)-t;KDIs`=aup5Hp+x_%rPUnN$6>nZUGN(I@I19Ca$5)@Y64|F3gtVRfUGS$9y? zH2@$YDFfO{naD^1TjU9A@~W9`tMz4?m#R~430Mdd6JRW5jruN+I;B5Q)_?*yWh@Cn zfEo%MWQ})C)h}rB7oydR1sI{TWmmXiE1ISs0?(TrYcbjh9^4$Vqyhv_oTkVW_jn=_ zNaxZqVG7+%UyIFcj#H0cVD7Z=!nc;fdcWQ7H>tOHp7>e(vc=!@Img|3~zjt7-DgN)1gP#b|k1XEHn(p5Zw2Z(%wktf=33xYTajo7h*eEMf5 zbfD0wY{i9Opq04jY=aIBy6Z=UP1063Ygsn0G z$054-yrQP?NBY`x$VD{IS=*sEDnuJ%AGBULxQQ%N;_fUXXogi=)6ZF0psk^ zqly=qB)#+zos}0i!yOnBH8(fU5)0(iXPgU7@Bk=~3Qn*?n6RX-FG5pRg&dH-GXoU# znrr8!Clg>NU{~lSk#+e>M1qy?w%ehD1ZO9(K&z@^rV#~Mi&%orBNWhFDu~*plHZ(3 zndmU`zH962t=cpZ$TOaKd$vbeI&&f8?N`Es%U_hAwH6B0XsviH&04lX*z{v)pS&>ZM2%jp#x5E72Spn`Y!T_lzXI_s04=xl^Gv=;i7330lZSeAs~i_aM%{B|jK z7hL3>F-G!$Pp6mw0TW!0oEi$TZLv-?XT6Vg?h`XLjrk6i-KmZE-b0tPv^?iUxE3|o zE_ii!YEA^56h;f}VQD*z*(S;IWK{5-On8eC@u|SV?kQL!z|pQ$` zA<6_^^gsmc+dAKY(Q^(2ZmX~w@TDe7Vqwa#nF&=G<*VpM6kf=!z=K>)^45$m@03c) zaAKL%)Jnw>b)OjrzW5cg>L-T`rE*o&8b}a^u$RE{g-HpL1uaR;RY9TcV4(nY-!)sq z1UxmSZZpxx5n^}%CMx8kN=aV)rShlbCq(gY_BqoGkHST0ORdovf!1q!7W zE-CB^WA27Nso?Ac2Zhu>>M9kVBuSMCD&0BaxLCA`lbp;?Sdt0K!Tqj?R}>s@NSRcj297irE5LB1tRpD zIn7~Yuge5S1z8@LfV=n(w3dsF?6Y=)BEgLLd#%BRm|Oj}wl)TNMl<0@{^ArT4(S`4z)jkMIEkY*$# zgoCJr2)Hl)#xJBFS`<@5wU! zFMDU(+cvVK?F|AX69X}VAUJ>l0|NvI7Wot`2G1O1;=u$SStjWwPm3Zc4ki1@;{X2- zPt{%BEVlUKmzmk!4_lJRZoVkCqrOyC-F-J)1t<}_Ar@>)d4LIVj&vLveoVQ7nBxbB z1QUS?nMJJN%`7M*Y7j-JQ}Dan_cu#jcYZXVpus~9^9$R75l-1T$_hXMoU^sdr(DUIK_( znGiz3Ghrq91bY1kw+tbI_Y)9wZ%UD5I}ZLEr|3@mQgQ{|YRj9Pw%&H;{G(_!--=r6 zZK;xt+b3IXY)x$8nMbafvxm~;IW_zx6yKLIjx!6Wz!;#tT{lknxm#Vz#`U%W6E5B4 zXxfnp66kUH`*K}j1ZW4*J-{IUbnYed)zn8o-t}iV32grY6Q1AQtZTVNI(pSI8RFbi zfyZLv0UqFa!)^itfu3*_tXVExCQ~eyPN*hqgOCO^{sohDk)9ibPh9{~e7hNc7?aKV%5 zk@h$aDul*j9MS^~*Hg>6VXB`n0)+!Jfp7pS5EBqJ95JDRl)D{Sh=H1+v-h z?;r;yU?qAbQ+k8KtF>me6K)>y6PhM04A0iKT73MxkD&&M@k^Dl0D!DMT@e!~im)!j zXfe+L0K*0PW~||683dP?K!S$?LI-(VlHbG_@EQtkpSAwiyRI7OcPmF6e*P%BSjV`O8ZIQf$nN6Tm#Lk~IPS1cnv=|B=C|7ZW3EDf7V$;w*sX3$sCQ{P6x=IdL zJVP=`vbtG)Nk53<615!GxhS`x6;YNYIiaXb(llXWBq?p0D5Fx+q-jcp{J;t<7ZtlG ziv`^f#oX?}0ru%R^^eqJZW}D{0yaQ!$@ZC0V^ZWLk_nPS5G+vPQjoBu>fL@XDAJqUDkdPz&uq&HQ+pf46Q2;>&wkb${m+mu_Xv7wLKQxm;axhR#ejYIT^YCeXSsP@4F{&qIk z=O^#e4@vkO_Avc*vi^Y0G3Os=A7Lr3#U3|F-9M*I&v#5vm0D)P@T_<#Dy*=~ z>$WECPc#wW?6$49^nS(tchY;^ojFy~^d9rDi3WAN|4bsM@6YTuXC|yY6E+*>!Iq1n z&9-JKS9{D%TUGSY7aEYUa{wA#QX&ES<(jgV&ONyMLl&66rMirOUc(rPuGd$9fO!z| zrzt%XG@qccgI^z2UR*{otfSV6d|{QQ?z~;Zd0`VO>#A@TV8DYQh#R{Ymp0bPx>$=& z2{ioJSbH1+#}~4$;*9Ak?PEv?8iOdc=JHGoZ%yVij#*AL;2J|A2jt}U@O^|COh+8! z_YKrh)ldtvjedFDAkSEY`9uAi`@Jp-?f&>&DE!?hVBa6en7Ly`|Ghv4{ZVu#!$h|O z*y0f+_u@5cxQ3vBvN!K14BD=bAJk6>p)kq>?RRJ@x_hbvfKJKC;ku}+chFS5-_@^A zNSXB|DYK?tdzEKRhia`Er7dqog>|>BYgvM~*|Kiy3+T4CpWnQA`{u>F>$Z4#%7ma} z+aFGNb|r}id^&ym>Ia$}blz5_u45K&9waZ; zOK$ZJ3CJm4f(QZy+ChNrZr>tu@Ov4@$QW=~5XA+lpyXhA69<|qxtMP|PtGv9W#W3a zT%vbRKC>1GP_tCcJPSHAa#cp>$uf&AETDoRHvrFREc^n^vXND#)%0uK=L#{FkxRgY ztn6Tj2H-#pPXWpUe1QqqQwC-X8jQ43%{_)hPqc=ze5!J$(zV|Sn*}!?;;8; zYU;22Wd_CTV9d8NAvEYQHZx3E-A~632MCi)7``3R+RCUs6HV3gc0t;qXHX2K&FPo+ zGn5%st<^Ch(L0hu3)>Z)ZtHz}N)ltTWYlX`c4lR^{FGElSw#d|u5aGId-tQrxV9Hx z@*U57C~U)B32VD8sOINVV=_Z*t7-^0dvQt3;I)T|2m%Ch7SI^DjErM*+sF^-k_z}k zf`pCe(LqK5`31I0fFN3K>rLP%6oH@c_~|U#4;h)@e7v;G1oyRNjJs5{iX2CdzGiI9 zjj^kBa}H?}teL+21|qn}s4bJA%V97&K^=Ib%d0CB=x6lv93bHAmdUrSqawHT@~tP! z!w_iPJUYESVZihF-`{`D%S8-QDe8SaBsfhNam#{t$L0}1#Ok=ddKMF4%7l?g0&xGw znSj-#^EoEe&9s}q=did!S=1G2ek{JTV$#J4HC#jsS8qsr)hjrkx6o%S9TUpU{o7Zc zuW(2C^&>1vfrxa|wnP#pLHhcn-oEloC{9^-B&AZ*UelIrL1a1on71vDp>EhgU?yeN~zsI@kZ3B|orVP$9GBkk$t zpIYd)<_&@4NAfAIMYEBZkb()@lM`yjpv^7k7{8}Q6({dszBnPVfzmO7CqY*+rcPoZ zIE-h+EXXmli@M>CNMV9U!VR?oE>nrp2^ZDAhtT~k_B?Uabw|3l);~RmQ!yq-R@G)E zpYUiR=!#HL9-3LkV1kQM85z3n0StR>Ou~c=P@t`DO#kD$@{R&Ou=|}h!?6i-NPy`A zsi{s2$eoUg&7|o>OJhA0Y_tRvKn1!6zt2>|nLljiW>n`-H%&uwr3mvGfn$K*%SRD~ zDmXHxH8HC87sn22!J03}Qw^(pdd-*UkG(MA_DM{rA)mkrwvw6f>IczOy}Nls?H@V7 zk3@qhwbtVB)vK@`cHck4vJDuJ)x|LruE3Yw3HcfK+=p=uMqHOgV=0gM{?&^&Us@AG zWrdkQFYa)#m5r-v8M98~cGMuHN*RTwS`q@72E4&jLI-}QxZ1U9dl}2PmMK*}TJY`x zLWXrrwyvv!3}Fqk??=8>RpIA8*T2S$?%7>ukc%asMyz%DlM&ie{FAu)r=i7{j-6jpy76p$Bm z!Md#U(x*2L!N63Z2vC5+3I(!W_!FQ(>(8}xzyzn;>8eQdU>OLKWUTqzh@X&kOjtRans1MdS{&K#pfLdx5DFmQ z;703m9oaaw?t@70V@;ZSAo%u^!g}IiOOFKg7hr+~LIP|ibkN>G&x9gi0zDBFAz)ao z+^|L&do9Bs1qyUdICl(zT*_+U`LypTo zckgjii+sJll;bd|K9&h9^%IUvyC1fbYs3AqBq;1OcF?tDTV_Jlj3L4IIY6%!ImUNi zcO_}6#>9jStH1n20-~K->mg#b)htl=Evs~^kq^QQx$wu?W?BCBrpeHH+WhGg2%=BsNZ2V%7n+M>zO#@gJS}c z4Bk<&bl5kH6_B8&3%f3jvH%;yNKkV*1v5m#?4HlDoZwn?hw zS^M?Hd(~bgCcI#^R_dN?BB-^VBu%j&s<38Ww-tSnCsasD=#k%r)n9i%oG=rpl;rK% z%U5ry)Y3ou_U+5ppIgg50dpa45HHZvU~a>#<%_DWmJ7HDc!{5|q~cA*vmzI9yxzBJ zZf4|Gq+TUpz&Iu5BU{tJe-d4Wdd^Q6osX{;)-w|mCiz21K>F?%V{MTc@eISZ~kH+k6*&_;DZao%ZT| z^p6ivYt5QEb84+kzLPty`|Y;yx~)n3l?vzFq0pZ30XJr;-sL=osqtE_+pp&|$mt(n z+MEzjSxa1CaYddb`6$5_o&m9lYm#JK0|-bd@D^`!v*cB!G@qcV*V`?fk1Z3^Cl)I% zR%F(xv|55`m`}hbNty8I@RnBs1QJ3fz)!GdDft9A(*}TWwUq*)Zzf(c6AXBOvqQt( zR?>`k0uIh!c&wKo{(&nq6Lh+?WIr-vR%CD#qzN+=sK((#CJE6vHVg$x(W!-l4?7N^ zE>EUfm}q=R{asgPd8F@#fIxdE3%yl-UcC+|!(QJ_vG8V4T7lJ8Ii> zV!5E~Az9SAhXh?8O1%2%`%=wf*F2xd9Oczp-e#Vz(wY=qf=re zdT@1F9k0JeQ$E1L|4B>3a?)++0g>)vL zFuz`;XM##G4wxtyBo6jq)IR{5~|$hC1x~5QC>GuLeQuq z{;FKWQC3G0dEhL@Q`E4OG+HAxl_W~8lOvs$B1zMT=Rj=|vSup^uDP27#nmXKv!dl% zZ27V*O-lGf&#`*3sH({DAO&vGP->;t&RWj#WlXeKUsMxJ;LHV0PPPzdS~3|#n$ayG ziJ!fv(OS80HDyBIW)}TGqD>ERgl(4wr#KAYv5cC?B1&*AOiQJq@m(fGYcauj69OU= z5hpyD3aG$In6haXSee#i0{;k|qmawznxPOa;3&YDy6}BeMIo1M$OJX*GVGu*DPW?1 zWu=0C-#-ouc)$J)T9go{M};v3hg8Ba^LQML!y%pY6Y6Hnz=TmKK(~3nUoP)%`%L)u zpS5QKL!pE+g6JqV-b?UqLQbb~O0*MdPy=(1q0efee(bH}o78H4!}+<=T9~}<`PP`q zn2I=XiEhh^Wu_L@wxMssCcdOf6IUaSf=s<;p{AQ^%b`MCbNnDo&>yNHfmVJ>KEavB z4n600@KNilmp-RI8fX!x$4(X{xatN5Cxr2YqgrcfbDpE8A}pJFM6=ne3B+*bK)g88LhO zy%ZZXd@{=tYfF>&Bzcf_4Ens&Ar#^Tv|0yHVAB$e-rvUzWE|)xPxBHop+v7KWx@oP zWQ1|JVDijEL81re_UASdC}=;a|I3Am)L{0IYm!U?jA?v=2J%=gVYGXAaBJWxXkSCn zx1@{LyVII+ymr&qpKsZbnO}eV*$FER?Exm-v!`&=W5VqpzgWJylLlxi6ch<$+_aHl ztjbE2lUS}8SMo3cw|2@JRw+y79EJa8MAVBl4`tkPHgSo;%uQvee96g5>W6O*?Kl&3 z**s!`1ry+A^gDY5R1r6o~pX?sjyKYVFyj|n4Y%+fQ# z^sX>!bWW@BQr66qW;(CNU`CjLZ9n*lB-h$w0vrWl0#XWgK~P9c3MfEEdQ59ce5}E- zPO+_X#7y>hD1@DgeoADa=OV}a(2?fd>*zMMmYp|eH!tz_(R8*K^f?9*sCtbH>aANn{ z|05TH41U5e`gQ$=QhtOLm_zKmX+vnZp`s?>@Lo1r_LVLfMARHo(N0{e7cuf;S`{ zRa_)6O(>X-0dfuM0SiD0DJ1+zlUV~+Cau5TdSYCA6()$qmmC8*CM20%I4GpR96}3c z*}Q4&t|mE!Nei00mbEgYw>oLVh(ZD#t)&ttgg-NW{p(TIqV%*Fg4>X^+fmasUxrK& zIY!MV+*SuZPF4@CQY0`FEV~Jo9fj_KQe<4PVJ1{LYqWCp_UrlCnP{|v2eb==hqB$C z)6%G4+Rcu^@Hd+at7soH1lDm}*#*g)BCSv@zAbRU^O;bKpYWUd2^q`-L9Ne(qHVKc z6bLcL_wAhsEDI7YCBvvpkRd>Jzy#APa&M$D{j{PHa|Ny8HZBnq=Io*IHu`1hy(lai!!e^n;vIL3epxrf54NI!nMPrLav>L57#_-UVsv@*@c=4BX; z9A~s3tDTS|W`qgO3s4qdD}_{v;J&2z@H7q*Ts>u3Pu3_)T;T1Ppuq#u^4?Kc!cjR^yq|(v&p(n2-Fh&gpfhhmtrhPYLCy`waVWteoKMQ;;63PltAd84@C0!Cl6{8H|PN7EY zs;w zl?(6}R)U3Nd5Sc-b{5SrK`n!R!e(r_f#KS4$_CY5>-z~+wZ~lP{l0=Z9C#r3-b4_g zv{-H?FGMPdhaDO&W?Qe(Dyz=pSlzHjE12LlTJ!!=vT6JN)z`MB9|}ykDmOgd8TL2% z30koii}ljdKt{`bZo~u}onXSQdGOs~z4;CUCIlVsevV+Cl-%mSC&uO`!ao(oRon7( zOWK<9`wzr~WGOu)4hf+by~?G{XW6iYYLZcti)4N$FAfw04GE60xvfSJxF*rO<)cFf zBR!GoCul-}_uf%3U_y-OLHr1Y!tq)sPw?h5$hJLw?)2lbzk$+gs0hhLr3{zRUb{Qp zIH|0QBm^a>hcZ@mdvGRno?`+GOb{nQyCmyTCLDaJ9o1etE(caB2cwx=i1hds1kUw)9$&Fz;Dtit+N_fa-t>gI1E z8(&yTEL0eov#@;X*-U8guEE&m?3f9C%Z&`yOY645?C{pamLzH4efY9(zI^z{#d`hE z4`)@?-V!lrvNj>%k}S?gG`cKQTJ2=yV88_R^%JE67mR--M=^x)c`k#kSmG}aZ2}1z zI$+Eah7Q0iIvjoHY2Q(BnI|a-_Ugy>TwHNyjFJ7O% zc=OZUnnfrM?`h_^o?T$7mSvE1Oz0X|Y_a0o$QL)-xO6-Oo`T7|lb}5Cu7X_Sy@8}d zL4uQdcFIv$^#=SDUFfjNJriJH!m9HVk_=1`D7fH4p)kdT`Fd)ypdQ+^enpy1>M2Y^ z0ozb0{z!~TatJOa;p-BvL+m-FvyVtMZzkl}$ zDy%0|`cW=vufCLjlb_IgTetdbT~(+)l?j?!RO{;}$VX*@2?lCO88PAgyH`KZZ+_mA zEX3&r{*pJBZ(p2q;ZDxJdw0I4afRs84@5*#&je#7jTo45&D%4fgG3T082Kq1kU=8{ z|B||AgW4<-bZNMi7kgl81xZHlCXgAkSVUp;a3TGbtH;~(!-pa$KQ4j@hFVWe)mja> z;BzCm%}9mT4Ltov#gm#!t71+Opu!!yi|-40mrzX;IdECOjyywG;J%f!xbsdeO&+a<@M>^ zFL$4)Gv@6Di8{1*uTSaZ9pW3C_j3t7Rn*v^`!0Y2RWzN*#0YSqRB%L4j{#+_pq3gX zjG|%y0G?@2 zgdv^&dszno{0$6GfHIlCzwbHt>_Ru|@oM`;kZ|5A64WOAgtFd@Kmo7Y!MbqDgk4{I zjhV3C-tLNS46pV&&|@R11n(x`nL(qK>;*Jh+k8Z$^%@$jwbyMvIE_~7rBsd9G9xCG zS+d#Y_nc$=o|er{R9L@03ks9%K1jCAzd=3eCx8psNLA{v(b!?Nnq-2FV=E)z6Z4gG zj8{>%JCVQ8+ndXOwr8}i#o-rf6nvJ}c>)sfVH;}j7)YxahC-hT+Ui3l7;zQ6rGA&V zm)_dD!OZ&xYC1a@1tYVA+`Pzi$OLs1VsR7_bri%k`V&;hOga-@%nw@3%>4Ob{4kbT zfIr|-1EvIV(r@`Bs?_+MJ&dLSdE(`MnT<=Sm;sVQu4?z@{CM z8wngLbUBo{txI?b*^x$THm%VrenMKRM(h37X|(3Mx*;aqZZ|x3iJ1_U#kSs3AVDj^ ze0c~8&G?~tHvx_MIZRL(;C`vYrecSOnXuxLsnOe*Txa`}S7+yBKic@>?)}S`@9!3I zTwPGV!!MYzY^Ruzfe9E9Y|)Uzz(ARxrWd7ww}5RT%)k|>t9$S;7{Zp8r8>0FMA?uD zxZm`c5M#Qf!QTkW*r})R^t=LkB5K>fB zbIjlxdk%_)ACZ5TW81h#+1< zy~$yS9io=autsZs`1*2IqjlHQXkFDi-_9B^VOKK~etPxcNQE^S1wrGsD5(FgCr7OB zB+OHxsb`oVAn3&*BTRsKKVh=p!HPzJFyV@suxlfV9X_>e*%gadp1z>8AG3|>CzxRg&oHf~~9g}*;!&t~deFEnuIISpm;jy1!LO(&I1zdmR z1r(XaEPt%0kj-G@0lfrc`t?!SmG1o})TD9F>o>3Dden{Mg71F-7r3_a^B}yV@P*uj z==z~?rHT3puo@QNVU7u8ogU+X%*Jb0GCDXPxvszjAp(FTC68uoeFExhzjde`^L^yCQpU>altr_ z=P^ML5O83|Pnc(diM55B_6^BPe*F}+AIUqaHb0*22(Kr9{O9YJANRj}`{T{4Pjt4( zlPG{fFrVJ|xzv}j30t``Vc12<+u&mL3<6}}vd4uESs*J^q(+AcBb~rD6+4)PPbef# z6p9^%i=}yHmNCbMkvZd0%rCit)#tR1I|e-w%Ae4*?y(>Pi&XQOWfZ^#U_oUkq?&No z!xwTB+C*nX%$WBRHk}_hLG_e>CjE^?T#14 zRwU#GOh^MJEF-o=GYT}4j8Y0n3Ow7gc{PP%TrWY3#$?ASgt<)P2pQBT5iIBts{79d zJOqs*q?Zzo!k7ER_fc{tV$MP)z=Q_Mgdm@QoiO3ww`|IUhDFhH@e@RUlp}}IS;a)i zS-H7pAn@O+@`|fb!y2u{?us;3^WB-#Xub9tt#z3?jn=Jdw3Zv44Aq{U-Q(XJs<4(* z`2v?0@FaLloe0-d4>JKgoM3|5BmIPVCiL2OLVu!nA3pAroR`slfBv=oMB~09V#Nod zs^PQm`D`9QA()|Ti3uJ2OZs)_$9wDl4?6LCNz=A=I16z4>v!;~`IU>{U|=0q*ieO4P@$%Mf136ud$Jga020%#kV8l_d80Db$q>Q6TR%`vS!xXfhBKecU z#%EDY1Q5fdqw9K*feJbxvm4fIG>q`wlxdmu`y*z-uvR77x{kmA zEK^?c)t%M7$xf`oTB-_b$OQk@uhSb*eOz67RZ*x1OsK;HRrM#BP|KfeH^~HK1dVeO zIxJkVj4glc78l95D8hoHb^HdO~8bt^AjRrLdsWt<4GQLXY#!G;rSD%!yt&WImh## zrsD@?cEE*xz_J1sG>E{DnM7p1_cn7}D1uSY%7tk^fd!Ao$N(m&1+l}<=NP+_HeUCb zuw^Ka<*=gkjd5G%VwSy!4Xo502KTS|@>=8=i5_5-j)nvguyOUK-$18xg$+M79(ckY zC=evTTR6%d;PqEAVRI)o!vvEdy3fS8%3IWP%iV7=huYx>kf#F}W7!wS$`6^EET~|k zh4rezGKDfGb4~(U)mZc&)?tsQf|)^rCKJ@+I5R3coC_1O9us2CCy+TN#NRIYPr^_z z&yF5I-zP&MG!u0Z#OR$XwfVK(`cZ^&-a!8l!%+t>xb%ZdFl2!e{5TW-%Q1mB^Gs+% zChPzOWx_7DR+!L@Z>Y$ioYLr2;L`GB2YQrc<+RkwTCK`?3kI+lBbTkMYSd|``Hl^W z3WX^_BoxWQ`t}L+2IgjjAg!PP;KOn*0F&K6n+bLE>#sla^S3?|daa1c9to;V=nK{u zW4Zkf#s9KI)>nm>z)(oT1zxgvVR1v}Di9U`h9wmR3G;scpK=w9PVbpQf@T;sbifhh zGhFNtDHIIeHH#^D5HBZ7p9F>AS}^x{Oi)8#(KHoSz2ir{LqjMi1NfI`E;w^TC=KF> zR1yq6!;lwb0tzR(GC+g*_2-{mw*b%(T6xR_RFw(hC+z)s3noBQ#WzA7z1w;1rS~1I z$51&oY6?uiezPVA#MeOr-f;ov;eo+2{EQ zTheExCz~>1)n@~e397UVG+<{9R2ShWh^^G%9*|j+mqlH~W6`*1j8hvAxKR#M+>smp6A85|qbs=K?A4Y32Zcq$V{A_vC=GM7)- z<9XOA6Y8cjv@F@8mfOOX^k4^~>S6&$1u4odT2wX6QjC~v>h(TehU005qb*sAdYdo`a|9ZZ%WVL>cXOjN7y1Kd=)26|$WRoG!^1wbdE5kQv zvhxBa0tsQ4fq3PgVj0RzhCbAP?A4?~zFaf=jS@L8k zJVWaNwm<=JvnK8yS_;sJxnT8n$A#SLt$5Fg3nb~PENw6%)gtmqn_gbzlMbu|ykkrI z5fi>SB(%x|0s`y=)zLBG&_Hqe5#I2^7DCDD{0U$Ic2Mw`Xxl=*fF(yOrjY7939=JI zp~Hd%Io{DBU(HEexTK6DDn&xgo`iZEfsx* z-bUz@uEf}13ll~xVVkEU2L-era{qK@Lh6}7D^wH)Hx!pQ227}&*LnHY?i>;zBb^S) z1Oqd{+X)=R2qutH&jc|P)_9V#DwsRvkNFL zc@8xG1?qjJxtIq!Z7E0Vo=#Qg&%aU#|EF)xt)QKdx`X|$J~r--`^Gy8olaWjt(H|< zRyB<_5L@==TnF{M7%sCnx zlxf1ZyM8{SP$@nD4cGFs(j>sPxn*6eg+QYpV^$jPf?j+8b~|8U#}L(IY7r z@RJ=GHdh;=26E3iIB0hS7dap3m;eW6f+B%dE+`YuH^Kxg;${q(fI1R-{2?%bErq)D z=1HFjP$l!j)8{CCj@Gha`3MTU^`Rj3xsp5HwI#~F?E($UQ2+cW5@0ACn_caZ&}EWR zC_rEW{w9|&0nhj1Jxn0e%f!m36wX{TJIpv;!*vih9bzV|i3#4F05O_86EI~Zu4O1pQz1SzpP%vR z<^mHH8vO9P^mKguA;i zKdUkU0uulPY|eXO!a-6s!x`u0zXsu66g#0td_qOnZ?Z#1I1>z&Kdnm0%LA>$ma zTcCiBZ1#oGRNw)AnDj`FGC`1#4u&J7WF<(p_GOAUDoy@>XDX!0DlvsXLFZKh z6Jj$#qnMwD1sys$v=xlC!~6n@gnr9`;naP!LfEKGFxkrRlyGP}=y*TpWbBw=EZGuM zHCu4VD++_FB(h_k2$w+tOz?>&6~d#YLfrolW?5DZW}~sWz)?cO^)j7<7FVFH*( zF9aqq0l|cWGNFS)#Ds=4`~kGMwgVU9I7V7b#{>iSBC3Lkv|b7Xzyy{|6|Lfe{(ol@ zqzMUDtg{)*WF=o1+a43($9pOjdVJ_KIk_E(P;`vwkYH@6QrKt%CZq&8{7wo5gOkbx zXDD!Uz6VM>tw$LO<;C`bNsZy;R?I~p2(>=EuX~<)8mDcA{?Fg<;a&h2;KEg3rL5jHqO4%aghUYoju(v5f}&Bb~xWa%!F@@1iV_w$9QZo7wmd>47&n$sb@l2!%(Q~ z%^*i>ULE5ct*_s;_HZRkVBdn=0Y`(l8TnRA>pcyb$o;+BjWA&>Ux9WjbhMZz0whSl zJKG7xj!sCHNuB5M=%_%~;00fTpfX~DRQ~w!{K7GTLlcj8h`quSNZ`O@^feI^))-BQ zPK24!c1Q(p5|%N!pOm#2RFFJZC_GWt*bWO+5=}<~Bg}GOC9D+*LCe9&vkfVOC9zI` zx;QZaF~Lu6oqH%0b0ioZ*T^NQPZ#MSzXB6Jbw&}M4muC8pimh1%M9riba5&eB)@CW zy-C-zy+Bv_V<1FIOxUW~R_#UZLl>tu@-0p@7Izsv8v9k2t=4<0xSif!7*XSNLcS8XTlCl@NI`na^Q6HHI3v3>%1G*FkzRT3HsHtv|3`%X;hmfkhWgXiIb`ZNYoBE;HDoVHc5 zi323)w6^4(X zMP7I)95_d-WL*6`mZSA2pQCku<(M#%qxCZp;^XUo{P@cUGALf~y&uPzpizpny&MQw z6$#kmy1%;y6x2%KZ5=S-z)Zl)nF{ynPmony2V>>FKY?Vm?GWcV!S@U)MPSilP2!M4 z7xm{2MSa6MDaJ_r^~pc_8D#T56Q+P*M~Gn3zLmgji8N$_mqbefSfFP(BFb!eiw=7k8;b^@?KTi6NT&~RY8;MgGnn2-o%K|-9PHK!b{+Z$#A2%$MzkI2#L zm@t;3^#f%MdHd5Z|9biBkDoYq-~r{dAZRJ}Fy_yrk?bJ- zt_H&@Pyu2hB;a1(nXm#9lnK%mOPhohCmJ+?KOyrW9xS7uYFc_e7z_1mP=E^l^hlEH z637gx;G<2#5GL8qMl%hCzqbI`xfxbYi3t7#3qWTq@Sy3>`Jy!FG2sL|0Yt#-_PV4x za4TP;%jM2{_J9P(ghR&!&C%L6lzT0|AIZ^rj~uO==^U*aYAd|@$NOJ?eDyE7i+BR6 zY~&96bRuY8nJFghietf027>i`=JfTOZlyqRR`9-V}fOCV4Ml)fCLi|t0Z5C zg>Q_bhCd|N9+mVXxB#I*UcaN)>F_)ngyY5r0!s%0C=o0VVHhBR>%L}`VSfkbO)%Va zU}u5}m|$j@;Qf}-6E7z)!MhZe={FkWdkw9ZJMZ1E)0hbY zg}Qn1W7e|vCt||(jwmpiqje)WT2F^&n4|S@{`}Jq|2+TpW0>F7GvVgxM9^VbeJ0f2 zPN<_P-8>U&zST29O$A{Bpn!Eov7LbSLC1uA2nDGfWy*%apPa7UmZE*SDyBg&GJ0W* z3C|}_gsADD{U@j!3I!B-%x}R+D5sFHg~!84PeN%lU`gcizNE|q_8?>)3h7#8VmMy% z&1817!=$-iSlT?@+DK+_ti3wl>$@fWMUg`eCBkCAGD+yvI z*t2sd5EE4Qw`Io!vZHI)bl;&&D_5*akWfT9TK6wrw)Sq2qxF8nIa=?0j@CnlJ9azd zXq7*9Ro%bhPKtlh9dz$+pXpv_C%oH!Zk_-HiH{T=PzDl|3H7z8);}BemnBrA!batjCx@M>%Y9u|WHQV*3B&No)ZKk)?Vv!-Sz+^rK#8pO*-b%zA5u24eX{ z{qdoVkRc41P7IJhcUd*0rR7i%7?4c`j)TsfpiB@XbWET_D>Ki&VIv~ydYB-?6C;_( z)}gKZzOD)3u`tmydGY6l#y)SBWg)rvC&0&en*6$CJ3(;}GeOLRfC+c#clai42gEQ6 zWp-;Sg9!d7)8adcE5~@Onr#kWtw(P?Q+mASsbUf;vY_MS_*1XdIvoMccqe2$(>; zl%@m8Sq^JxxmuY=eCul0!{sp0`u4C2>4QLP%F#22`b`Kcy7UjOK$~QO3QU00Th(w` z^EQ*lnLs)*6K=KfaDT&bjNhb>;Rv}<1?Q-*!32nqP@cmat)=8>J-ZyOXP=`rZw@X; z>-Ok#w60FxF^Y2^Z)o$Ot>`4~Z5nq+e&Ra)6d*u{Arm;F*&~jzr}I6+Oz1H|#eWy> zn0O=;u9K8y0w%yzz%ZtCWy|%#;2VZuP^1gO0wDyg2Opy_PlUK=FyJ9}HC(3b0d4>V zp`xgzLDezb!NTt;z@VRr3dxFzka1dL|_8>A0*^gRHx2L_zET* z0y}}nKeenI4p64jv2Cp!(j2XKIakYVKS!(99zJ(h(dNE7_20vn9pk}97X3Q zwpRTOe5_|DG^!{^>(EXB5yVb7-^NU6!^*Z^UPDrg>O&<=DCxE55ltIfDvW`mYH*d& z`6S!qhpU8+U2Qg-^1y2HD@s9%UG|YCE`+#o=l0mpewHQnut{j|XgOsmJcaTEXbJ}h zdIcT?eoZrBCoV?XhU}vvA5ZJUm_GqnJ=V3u!YmhDTH7Nr;q%UL&%6nkkmAh;G9f$- z%Baj?%Cyp9CzH6{(L~;^V{zl1wg7XdP3+ z0Z!2eC9h)TgK$W4Yy0$}Ip}r2}(>NSPh14ag!@T4Yta_*?9Ksnn={Dg{ zjG#bh>llaM2{j#M-pGwrj|sdXCN#Jhl84qY4RE>$bF^CTjB~tt0Vh<0nK{QMF=Xc%B1eGjPK22p!km$(H>J2kES{z3-O@_NscJBty?3)>E7~umY=?EmGMZ`*V z1OzT-(NQ{+tP_LZ%kfqBRnKWCShqz0gqHn6;u1_eBY9@+$dANju`;aLV?Utn$^4)H z{@;!2@ok5r*a`IFRhSTGB*AwmtZaH98^r+=4zZXBx<4Ux{)y(GAeRqd5ny2s_WfiyBC#SZXdUc!(@+R zvb7l=%C*FYL%Jji#!r2l3W~Ju1E^t7BtEO$wO;aq4Q(IH8tMD_#SdzolavZ5msnN) z=-6EJ_^s$&_MG@Q%6dS$n+@%zmdjhs-|YUD`|gL$hB0~8KzSUfttD0d_4anm*?tKV z;94Ulz)<)Gr-J$uLe*$?GZKUeqO$5$Xg>%`6Fp5ZpEj> z8d?b*7I2BS9U@ENSPKv6nW1)m*`Zi{4Rfz~Adv8F=hFGf|A+M=B_$vwC@h^1 zkzi=?ymEsb+-WLn7$fAM@Srs{;$?jH5a(EX$%n=B=BC!CND_6Q11!4C**G*Y6ce$w z`>J}Xod5|Fu#(hH7^-nh@Es2WCiuM3+4eBge9$g|ymVInjQVG1nV`~Z0JPYLQD1v~ z^-TyAkTOD<&=;71IW!N_7O98A@hC^Dt0kNgslX6lOq#eV_^X5`Ju_q!u!_|pO*1;T zNa=jQgaTdgm(^{vdK432@AHZ}m|@2gLHiz^!@MiI-UXGTh1W>lC6j^(rGz+zNm^IP zUnWSvcpj|5+;pLtU>p*x@jSrdpzwgbA2Gpo70z}x#2|Pa6!!jCN2haPiS|%T1w1Sa zdd|JfBl*Cqb~II|V_X?7V?r(shtq+4jJIbd0xe(y`_e|$A)y@F3F=HhkK`UDl&7jX zW|yqypb(?rVU0ejISLMF<3J<9~b0Fcml0Pw5l!>kW=$b=Mf=5&$5 zRHou;m89zhb-Sm;1c-J_V$pqH8l9~jCnOL1yiAs3_=LR>3dYErZ-HkxW@q?`YqS2Yt-by^I}1P6r- zzYEqGL%}Cuvojcg<}q=Rz(a3Y$=b6Ota70n5OxU`>|~j9v^GO19I(zZ;dmk-9F7g_ z1TcX%jtR|CRR>mrw-cZWxzj?yIyhbZ_P#AW5%4zZU%+*H|Dm5l&43*m)+}3hz5xLu zpMq$)Ydqe*jWWT*fGmZIH?b=J{$>BAk{qqqt942cScy`Y3X8Nz(#0Q(KbJHLGF>c7 z5ex;qn?mCQ_tL`1FOlKAZ4naYQDV5wAUMfrKl4PeIyo5%0Gg5f&)CHo*M=^6VK(?) z|59bdt!U{&A%a#)7zzB-JQ5JCU; zo`i<-Qdz@P5K~GfiS+W08mMbTJi+0qdw#66AV8s@j=4D|9FGiz1K9~Tx6Fj|`P&E+ zic|_73EFlj=%-X!mTfpU^A&Hm6rW&`JK+j=xXP+cmSo(d$S79fD(7d|4c(GArCjHF z37*ML7+lE77aLYp3nU>i6oy*U#opo>w-Yd4g4IpBU1BY!SJg6Jz&h$zt^;m}Mnp{b>Zf$M+48&y{d za;gv$cwmzYhR5KBVzDTag)s{n@3j15xm+M>am)4qoh>b2K=g=O53^7(v<{eXasQ=w z6EbHdz>nbPHMTAWyO4eL9ipU?`sFN#foH*5!cN$1*utU)5wIt&gwzD;DuqWvA~uBY zb%3f7mV$CYnebFSOa#3OIF)H|Z0A>?Z8*9~ZiFS%gZJZ92ovHihwi(z znzIu~2NK065ENh~__l*iVp61B0m6)8Eazi9_>8MJ?Yp1f+-L2FpMPzS?MGq)$(}z^ zvejQ+zkb_h2cLU2&)WAt(}0I}?a5zP`R2~^N*#<4+4(^ehf08e7Ygg0vxV@dyB}i$ zKIjfSC>aZ-Hx2krdnXT&V}cX^`|r65$0oh3IM^b*(!?a)pTLxIjC6+AD{E>buylD%>qV^9(gxH2u5DY_Pxw35NF z{DWE)TJ?EcV`AA>uu+0w3R#%$Y|ESsnDB^!4$jOLA{ZI9WHV>xQkjleQW2O4upB%d z&`Hl5$QrLqwj!N9&Z0Ic5lF@%A){qD7o9DApF?UC3EsmPdlR4qLqV0tZiR5P!2dTp zVdhG)!%WHRzUORIJ`ILQ8LZfQl$}7Y2n=4+OgPcCVkg|*&~hfJk5Tf5Al>)Km5jHp6}nie)YqP*RNl_ZO?SLLp2L7z_fJ z>L|~Wb?!s-L$20r3tul*g`r(|75`io1OlhUlHieZvGYE6?L~u4j*nCJs zB47z+1DO~KPq7#tMuLs6G~n&t3U$XPe*qPs=wC6rln9ZJQFRd$4o=@O5>CKE#{@Bv z{Wu}odMZ?`rZfx%+Z;7VD`i#WEQ?!v-+q!dLB3mum0cwlI`1e+g-;& z3aDVxKzTkjY9^?m0A#x614cpu3`ijr#ZOE#0dB@a#UGjXfy|mk1>$I$*4?| zh?xKum?X0xf@1d*HRRT9|=BZ#tdPU06vtR>=U*cTQd7#EsUBra1)Nr^F zJK^M*z`jL|`-O-Cu5a;F8HR!hbF_Y@<^fgw?2z5DJ@7f(mT)3|t~Q$%)VSkf`x6^`;RMM4$7pP_R)? zX-qiECDe96;{kR;zyuvShy_$AIcoBBs$eG=&Cz;qo9gBFl+JzQ`yy?=m7T-M26-rt6V*=_G^jt;)YAx?+FwsF(L5G^fMRyj>uZNjHaO9_>aGes*nx?4f z`0mK2Lf7x``+sViQcIyEU*f7*3lk)%QX$p>o&IB~CIOLxBbGSS(SQi56i|@XB^H?e zq0(i?1T)h^f%nd_NIUYLv4McxNFaf=zSRICxIGPFw7kc=>TfLOLJ~q@zyv$Vgr{kS26ob|kQlYIKm{97LHj0^3PD?uh5c;K zpYS*BgxjyTH(z5rL7=efpdjKnB~C(eFe%#!hojHYnsWlRpI`j&w%s@s&@tJm_F0g7 z6?xX^Q?*s;udAd2g9y(E1(_Z;96G5E1#c;+4p{t1%y}jN3PK!cm!>;8cH99EJMD}7 zbd6|b{ty1gI;HjkoN`EIUM$&Auw+6k3Qgj}1Ys8xTAnI7MNyAxDP(<_wQD+{g*vZ} z2x4j&E;9|-Oi&~wDQtx3>m6ar)^j4o#-xG<&kElwN%C(N4FnGd+&SO*8vY;C@ixq`{LI88JRFoh3O~Q_)_VD@PNfQEU72t~j8Xv#PBK3aiw0Sr@cR#|Q?Nf|t@#Mdm}1 zS#(@VkH2o#1ckLBsUIN}50naR z83{&GrA6L=f=6qt-O+tCy20B7LCr7dRcM^k5C~SKMEFzM4LSt`4D7HUQWud@VX@9c z0)=d1B$Q`@4wRnznjfmf`ba4iWW*!{r|$4i;i=%*5jz&NXGMg4x3l|uO!!ihaLU zEN!cUT-TPJ>rW*(cuG^E0TBcVnq2|!^?{a%H60QlO~ev1p&eyHU2r-T53(XHU^~c| z%P@{{M40Wp|{jmwHOLZt|?hxNh)T73Cv6p zzmIl02oLbi59A2DBr6mN5?hpD07OIts;e`;gMbWve7IMFhK5s6NwL!>0*r(LRYNZ^ z>vS;s=~{7s*Pn~ojt5`Cx)BdTgBd0~)i#)SKp2EDbyUbR!IEQ}Egbxsx3xO> z*ye-3Zr^CeBy6Cr{5UR1q=T9X{n?b*3C07Et8^g#YDklmGmDHCJD5QKw8{acc2Nay0kl`&eShw5} zSxU>nFbWt9_idrYv(1X7Ts{fRvfPfQBGFPOJBG!-DrYoM~D0!z+^9_GB4i_Y5#)f1Rd;R0sD z4U3_0J2D1}aexF3v4E!w#Mp;5j)#O(>JLxu8B|kFRWE<;Jw#s11O~QFwH(ba! zSw>g0GQO?}t0haVeTfTt1QT$Fue7)`PT5eaKOuB3u#`v<6WSgVR6G4q0tMPM(l=SL zC}!!uhzYh>bH{_tGQ(XEWj8sHEioM&Y<0FHROt#cOeZEtVH86F-=Pr^*03Ntv)pp6 zSLJ;fpR5gABoze{OqnB7v$P@nJdZ{?AiQruKTc7YKxiclRRs2ezhan+SS*mGCxTca zIOPQ-OoTATR9GajouF6-HdZjlT+$rZ2N5oRT@P^rS$XYh1SC@gqWq;8bHBv zO6G#QAhcV+a4^*VYP;H?VE)mRzJ zq?}D&3loqD^5F&p?GXq9CXA$VtzB^nD~-I=pU@8b9TWpQey&2HsrT5s<`k_yM=Km| z9JjitKr4_HC|HYfPf_Jztx+Z-@J-`Xn9S*l)kr`7mYWS* z_yICo%7`eJHV$S3{0Pc}H7V|TD6G+u)e3}ANXLSFGN}q7iGPMo1}D^@010X)=qTxs z2iQa&1;RpXCqzt8Bq$S zlu)k08zhZ=h(MrB7+2#Kw{fw}{A#D{1Yie0R4s*y4^WQQ-~avl?~-RJHK=3@7zoJ0 zO79v4Z!l>|fmz_f@E)02)}kyMl>rqx@vgYq2nzH{J54eHAukLB9aU^(LVR>J@S90e z&9BJrN3+K2B2yb`1pv{+G4!D=Z_EC%BTLL_)7Tn|hHbu&hdhoPb1 zdLq?M7{(=!f5lsTa3D;TcD7j&Q^Bg{eT_!sO$7@oOi3R-(t=P?j@BR*qFb?jfC>BR z3+Ds+^4(W56vR@vQ6J-&G~qD}1yhvO(Qfuln2gca{7oLTAYwZ~%li!_TG`K~QbS{) zOsb1G1beUp4*nLvT6+wMfk776F{7jC>;C+q>DtH<7N2W#|&rJ9?tzZIr{MjH_ zaox0Lm6Vv!EBDEUmJ2uLnO5!Ip&@MA@gau+zT`)Vf! zJnu^B0VWUa$7I00LfWG?6Y4~uP>?ymoqR9|HuQ@hHWuPBQK<5)Js@{ebB$R>#qc(!VN{*2-7saTi&7-s(CyPSCNYx8m zjAAQ{5dnq*h%nysAT@r332OASqBesA0_XRWs^g z^fet!&~ls}9xH^hX98)831EyC4ju}}{{|HP7j3@OHcO0jyRQTaWDvBU-oO9UX4bN= zpH3`NvJ-A^laiI+eR%W!K1VWFJE&eLTU5TH5OYBd22_W?XycUXY%H-dqk|$D4Bkut z4P;|S5yAKfgoFfVV;tYSFP}qPcl`HpCqE)G>WWk-Aea&Q(o4D2k8R$L zwH>U;7eEAO2~5e^5V(ZybPMT%P=&qC=r&`WP_{D?Lt&-~LErQhOTiF0`N4!cebIs) zwG)^LU+eP?V}Wuq&a@qN*G@akd6X8UD;mGR1D_zR_T~L2j!)E&AzN=H$a{J7`6W$8 z|HHdBpY2W9P`o1zw`LHa4tn`(By|esa-b*SgtO|SMg|JFeWZiq5FF(D)lT?^#zU)2 z=&7oE@zX^?P@onW{b6g#p8(qs`N~(AakyCByro<+uYPNjoRpZ-ura=P_kIT`B<2OxW5S^EaFUjTIQ^&tqA3hv;q&>OvlBL) zjy02bg#4=d_#>E*WZ8z!ZhZz@=6q~v*NODZ(wm1&wX$?}Bfu8r&jU*I*KX-b(eVJ8Sc zstUJ5crTeoY}81O)}6JbLDdpd!IWA1?&YhugoXDl{nq*Ct1O`t%TKRA8J;!i4r0l>diHQJ9VL*hq=OOPS0a7NELnf$rzZY?_e~4fO zT(ESD+WB+R=ZIG!fC80g70@g1WHgR(3JW)YfNuO7m{185&R^>jm_SfCP1p&idJQW< zR5u*pTolC{O1%1k>S%NNDCboV*;AaoeQHr z_wCF$fjjV3AN`+k59hHpJ!T+54gZG8cDR5ERV(8`aN{zf*xuD#X*g&E4@803to@I1 zcc5P9YS4LBK&dWd1k~&~4pp{(2a9kz_`I++PI?{J?YNVJ>N$h1L+52v9Owg1PBhj{ zI$t^Ba1(0#7q;yP)uvsml7x?N1)o%6+i$StjhV} z$OFN`oS}pVTOgv@Y>FKz8d`j9QTV03kW3Kpgfvh=^Ms#QQXU%Sgv81xM2P>;0y5`G z?<`S>Z$JgyuU?ih_zo5Lsttpq$#=l>qV8afi-sZShF}7OF`?KN$8GUJ@d+Ej1d^ue zK+3~!hK79(S+A!6fH7f01aufE55_lMRp4=kqdxF7(-_xY-S%ZzA#fXgP9 zEifdA6H%Bko>5bbBT}{u2^7R^FhNv@Fc3(<23w!g9pop_1}aEIG8<0QR4f~iN>=n* zf6p6Wa2XRWCK!<+Cgm8)22HTa!w<_63{jYCuY)cR@dYL{vZQ68m1kJ}AeG%)+ky(X z$Sny~Wlyr;Sh-3S5*x9wN$lYg6ZSUKp@9h^)|ybWCtxWt0Yu@w!~}XYF~JHjfy|CS z4JO2G3{pkjhP;a-rx`|W)U4o|948}|Wu7S2=_sX6>KU&r)#9atTtq;O9gTez;2xXlh-DD~Iff1nClQ?&PG4{y@X4jsfGbCjR!VJv2^JJC zDgfFSVKCw2GbY?N7nRM6^eL1hj14(oVptD9e4jDF1w0gm>&|jjI6d6xeg1Q<#vNcn zoKGEBWt$JuG5P?8Lb*MK*WB?!saWh%K@?Xs2s3XRB>budTUcg8O13m30@DPI3H(i@ zWi91^Ov3cqe9;9F);dN)5aO%MCo>~jHO;89Y3w$Ix~HN&SgF=dXxbU&F%QeIrKab> zhBh}b!N?_!kp7K!2(%wsmu3xlU2y~iZPmSbyFGpYk6^mUY#jNb> zGmLZ}r>87*@9*0E39OfU3ktRdqum%6!evajAsY))soVS(b4Z{VszQkHgx9%Xf}6z4 zlj}~iUS)RALDmoJq2rqcS zx~$-OigpKP`^zk!J+nmnGy_k_cN^beLKm1YA$4Gv?~c>QN8L9QOF@tbVBo5bI<`Bf zO|igUq^}ncPv|{7J*#Cgf6{%Ad~GWG=x2M_Cm-%w&tM9N22;?E#hN>kbp~=g$j*(J zAi+xp6Hue|mrr#Fg1G$s_Ia$ozP|V2jC6a&GG4guF3K1a$hbFWOz`WN;Ldi@i7+8S zY?jk;o&^*n1zUTwI8s1Rp~@uV&&EY8yfF{9Cg2&eKT`AuUc@?DMTAfg0Vu(UfLAnV zPyCxP*J>s<{2IP$ZFGR&dP<#e%oTn6OV{)*S-T9Vo0}k)?ze z@KcJrQ7~bb;Ki;|d}UxlkufGLbH#F?v!1=X)Tx1TI*!Vx(y+z%dKsiV9`+VI!}Onj$xFQ@B};ii+s+S z)S?*iG|r9+B3SuHz!1v6KS{m^2CF~#HBh)t6|C>Fi3ub%vLKH}e8S1ZHmAv7&>oV~ z#+bvNaI`T?pI{;1W4;Gb9DWO4-_zAx(^8x&ZFTh76ws` zmX}C4p08aHfuw0l5FcbEVkwBUHNh+r*er|C1cHRMwHpZR5DaL}P_MBlu;iO$!7KHP zZfi4|iiAyj0t3U3DlXWEV8UMp7iv zzlRUv-eF(leObYEj~Ck0BFRcotyTd{h{#Xaf&h#n)rNx5oVdE3}U=!dY?aB6B_RV%l2lT{`aJh$0=l*f5HWg#ZzTr~TFIi0ZsAGbq z6W1ZZVgj^sE*P-Z5v!YMGjZW#Y`gz`{pohQPy|M&*=7ZQNnnD9!wF4E65^l zNg)a1cF^^f)+?P6Wz~i?9}%~M&at+zaB1krm)fzkt!yfWJ~OdH)1c4{TKb<)sORNeti5Iv9z;sN zL&7mj#RpLZ7!(d)*cbr;NboUXj2y?fUtwM~HR|+)a_PIEu*fm);Q@CjPHCKhN@Rlp;tb-xr{bL! zI_u@M!#&35tr~LQgTIIs%#nGP9SH-eBNOlqxWK zjl_XYLJF~Oc)~@sL9+yQJwUJ~n!qI;phBc^NSGk_AhTY zmo77{H`mGiASlim&r&4c*w6Su!b zszHrW&D7}If9iE)ND|=*cw8h4k^^{cP7|I8QLAp_p2zz@SP+Xv)f?Z0jB(suIA8hx*NkLMHZH|TEq>1!}Wh7R5 z>iRJ@U>kiiQw)byi||?JG|jMHZ@I_$nDXhK^6`hz13S$RB`D-1XlUUBRZM6q4~N~Y zVtIm|mVmf|q!@08p2ts!;|_<|(NgXYA+;U5#!8MMYH4FcGzW3~iCm%yfviGW%s`QN zr6giZ*rbF7)n&sGOTNG6QnEg?9m6%61hb_g}h;F`Af6aOws=`Na9r(8=Z3tDk$^Ss-R6wDM36cO2UB?6w;Rz<5U|&k3 zM(e3uYP8Vg(e192XPZw)l zEYn>it>vN$322+l$zq+#a{d7xfiHY&*?o{T619abKFb)#e)5*{;iXvr!ICpOSvTQ+p6C;81hVO{+q1_TSm&?^PW8-FS2-ll!yFk?sHpIAQRpkGxOm?bB<>UONnu?G%z%%!i66=3ljDWjR{E^D7musMI%2N zcwim^3dYhHRm9MQrXArr9Q?V^6kzfHxjX+Kw~b?gJ1}798L(jlX@C@N-5Sjgb_yJV zpw4rj{o?v$ov4#oibqm3Dan-f|NkG}eA(sfa#tizCkJ^QNhEjqO?Sur+|2Cktp3Ty z*$?=QNHwrCFmYLwg`fZvek$cb@9GL2ChR*oRR9W1VYYpn+X>797iL*MSSyNGPAQ9cbFgp zj0ZfdW)4 z?N}FDc}NH*YBnpjB4XAI$jwOZ#mwr0pgrVgq>x4g11$jw62FYZAT=Suof%fjxM?d{ zs|g11!LSnHvdx4TPq73Tz$Isv=~kFlD?(5zL>Q|YxD9x$B@vnKhj?_b zvtfc55NtoR5{CtHv{s%_n?A+_lV>T{xz`GePiTM$GFDm?1)255mVyii_9tAk@qku) zzSeKf(R#92OmSi}0byzGSPO3QdQlTtvNV$k`9Hf7nA5dJ<)IO!xU$(6P*KTCOqD_{-!Ai6sjbJ6FLzXVF#zZc0 zxB`|E_ASgJEYUb=6V6C>1E~pV%9~DKB%uMyy4vY#~AGS5#C+IsNj_t6H?9v z5IE&RpphaIS|k|SAt&q~j`tTR6*0kwLcJa(AttzNCoGu31g6J-K|7ZLcQQUHR!X3R zsUYER;<-DnCoH&^f!V-ZFh&Av1ngpzo=9c_?*d69>TckENvP%6W4~7dtS5BaT;!!)<8hI$?I6P>K?|F8=gf;qM2#6OgR{c**b@nMKk&~t zHmyQD=f^^zK&k@N#XSZ@QImBX)D@TsAu8ZJQ0qKH!I&jXsDeRD!UPa&2B;1bZlW7) zJ2Wr8z1ZPY>!c`XD1hld!yK)rQ$W@-gN!jyj+RAD5GmkYz&j~y{~-ep11^XQ@*68q zVDGx>Frm?U!l=LmhY9m#Kr|NS(y6B<{8#pr%1-!gR~uitJ&{NONvhPwD+vrV*i=#f|t62=H%=;{j6 zEeTj?=gnL4c(fCMfD{;%v_}$&X&mcRNajp4Nx0UrA3|h+QA|gmKuj>DP8BsbPXx4;~V##CQ`vP}+kJ*Edy3kp@+nVf&OEt#_2J!*cDFLFDL=nox*0 z0fdQQz)&P8JSQa^4S7KuB{IQR6snQ4-}8vm`kgQ#hy`q%Xm*>JKR2dTmZ67*4n4|* z*dYRc^b{{P66Ty<2`|S>G9BQ)}Bfz1zgVaoa!h}0S<%S<3Vsp`I`zLj(Z=&w9XS0~8ad!O@Db$Hk8A5DdNHYGDG4HMEH0mV^o- zmq3N^iy;ArL^2Jhjg)|6WG$_`74EYEN#+!-#K%0wX4q(qv7o%507s$ON6El~!rK+{5nm zYDIN1MkABtz2fQuM1t;ka5*pus0j#((pD2}F1XMW^`V4vmk9@Ef6f$&|$YKuBH~M*@`K44lz{89;-%$$KdQjPaZx9>vUqk|YEQ%sL%SB05okm5C8yjfoN_=uMx4d|Ny^q$*4_5H6?i(W*l4 z%d94$d=FvaJDxUp>lhbq0|k<->uH1!Pc}>9hbt@#92Y4*!7vjmDjL~T)(VStKLfn%0@MWL3-AciG3dVUFrEsYrl5@~ zgu=qgj}g!lD*6mELA6ZSnkI5@2SflI02V;5(V=$j`rc|+Dw#I&3FT;=M#bjgqi?+& zt#vm?D>6aX#p>ll$eQ}#(Z@PH!2{`T`3^>b1I-;pToWjWmJmcj2}k=(I3N>9ZZ+OP z>+B|pNl=&z1O_9ahO+X+!U8U>L@J9;V1jWc=+2D=<@9op5W_Z606rYOBuv2j({Lzz zK$MyG$Ou{s$rSd+<|jySX*V=+joPuy;$85P2f-O3CNP9PxWa9|0|VLS-BB_+dU+TG z!U7Hs@s2-cJ`ARK*Rb@WmyE2)+~ZmSLx&0aRSq$MJ&w$T4YiVchy;MbHBPs*t>jW(H^qji6A-_6nbHRou3FLk_lf3aNW=aED^6a0RMgmi=&gnfM1eq{_MnfPtI5>&cL)*5GA1x3#(fSTX(qO4+0NjhoEo^MwB;M3M#^m+rT!%WmVin0tGM7591P8rp$x_nIJh@U#k6^V2;*hws}l- zOpGTlR!YVBc?73=?~a1Vi=6r)+aH1S0BHE+T{K;iR3tv2kcL$EG2xcB144mcxS+yO zEa>6IM!cpH=)+3!1wui2e3CnJtiCgRsH>9>B_d!e&oHN>aV$tDB)by~6XjZ3<3NF`fxBu?nHUEEY$#1qTbkgq*$@E~J)oj^h^> zT1|jrtl5G0A;031Gks|!U>yf3V9{bigtw&lgcZ*a1uIO=xQNAjs3(#&Ib1og8J8Xu zuq;#%r-J-=W`ox@6TEx|Xu%k7f&oDX2$%eU*EAXoR}97#GO481A4NiNe+WX0$!wf9 zl6vNY9l=6F)Px7V?SKn!jC0w~gdGlsy!YzWP7^_xP*)6vIwVzTpzy&kVFS6>qNXoL z>sO#ieB4X!)p)I5eDmO|M;GVO6q0Emf|1pP()sGZ049P_7|e=xvE?94a9s*$Qw%E# zxyuA2;_MqTB6#8yp+Z1bp>emVZ<$MH1iQ;Hb364?=roS;9H)k45C{@Da)A~1G?{BA z#KeRY3OE!pirQ?9LqTGAc zv`A2UW&#L60d?*G7bLxk&f8qCkqL)|X2Mg-qzd^&136l^j~=||x*V53L%sT#-%-SJ z@P|;d|sJtlJzi{J;5;HCuV{fm#kxkXx&SNm2E_fnhHpSo+8;x z8bqcQ1hF2#7S)MeY0bt2)e$O|6!%O_fa(}&5Y19p&gR&TqZvgr+kS;CbMgu$kv%*z z_S>RRfu4OmR6|%GXj?>a*kdPtg=j)P1SZ0zqa-jDf@XuT09T%f%Hj-Xv?H=5zDf@D z#E)WPx6FX1Aiyy!5rfe3G|nP-20+`NVC7GM*BB-=4N#%kHTQN(WS_T8C{R(T`xB4M zFh}e9eZAQ{dZL&KZy-nOOmeiAsgt9X>oTa@e*f$$SI^GFgks?bHM2v3Ee8?iCNn_u zKwc=7Fu{XDQ4DAbobSP9g2a5SO_Q7p-<-t0&$;TK2Bda#WH>m*5PM_ zfh-pD0o4TTk4TvbDP>NMQyei(OjvMCBaV&E3=%xY`rAm4(-H^haiv}B2-3DhxDdfT z!|nuES$O+DodMIlfYoH0gzn#$+N6Wq0TOdrEFMn+b7e2maZIqyht}jY_u;9dT=PNV z7)`H(u_IXen6L*b!0TRfuz-_k{axOC%@;`tfFSQzSj@L*#@ z!Rk*k(B%0buxxe-6DqAC7$^+t3911opxl$EU;GK&zi>0egjDBfO^ffJHAyjn&FE;3 z)-C2}U31;Xtor$T%9?rxALDv4O-grw1HK3eN#!$v2_RS~EU9{!pm0WQw5NI%1w#j6 zLZ28HWEcxU8^R(%t8?Sp-5;g{r)~$~!4~53i%nF5Rufj>dcYmb1bCt&b}`a-nk?FJ z2a)yvms6@h+r&6#yc)=yGoy>KaC9z}a1hff);VDf6Z&Ec;O^{QU><<#fT^E4C%DBx zS4TLAYP=j_gWO-j6yiQ6NXga|T5&v$MiIof*bdSY2^(DMV?st*%83ceX97x9(>xCS zk}DKY(UnTzaHXP~1Zx&bt*R{ZaJl+f(h?Re z9Z1SvCKL%kgP_poU(9dA1WBsSWLWqkrNrUr>k0Ds^*zAW)pX|rC1;E+ByfxZG+$UG z)a|&8nF$MG0?jpzlh6wj5D7+0pvh#ld;lf@9Vj!YGlr9H7;MnAgV(m!&fivNgygEg z`LYd?u5^(2j6y82Za8YuacVjeG-vwDu1%K)nZQt(rttt0bg|cbKq#Q9V5lHWz&8uA zfUA0$Ktwn&6MTOHdlaw*^hs5xSF@|QN^T~z8xUH~F!=T35>qP7uV9qZGMnqsPajvf zyqeLFsMXcS`PIkxu0+W4#L^P-oLGR1H6o!1I!GA@_{N3E4V3zrP$vmdqppr8XT>E+ zR&&3@ND;qda593P?#UH>c-tj`{-CrfXvXFPM;J05wDrJ8td{VM;RTTi>57=JOo<6A zV1nog7&3%*I&A?7CX*?Op6vn5<>}k0zM+iO3Q~b3i47ZYqs5n`fz>T+DBoE|Zx03o z=LCHQ@!e#=rx11gjC?;23=R`w9LDBB0X>bD3L&;b8>1)sLLU<_${CSx5GIH}frY({ zBcw=fP-w}5ji=3-wu-a-3rUtcNHDn^3*3iDsOm5jSQrd~gtBNgt}@7kf|!7p=+Dv0 zB4eQ7BR!C~E#=yjCIS@T%~}FaEKSG6F(y&N zI_)#S`#~{64$PsjVkU6jAiU&a0z&_kMWR`GDr6{m9--k-5Di+RfrFKN zBuF%4;R(%LA@NILCR7B4+J-{Nn0d|ekDq?}S8_EUBeOuApD}D6fzxrmAerm+vR7Q~ zce&&{Elq?Z;(^Em*D)bNn01kdLh!KdIqhSkbvCDOG9h&VKtOm2*d;OXM>trHXSeClH-t;knsGtH&kbw;@6OI6)5)=p- zq9iX(t3fEF=fpt^1wcYl6m2j9ym$yDti#?AuIfVJrhqSv8)ezggbK0YKw;y{_T8`l z4X<~cA=Q<)4wBHwonBYu~8s_x|Kbwu5OlMOiRm&wRuG+vxS#k}&QFO7W zV0Xz?DQ?$AQR6bYDK;#%Uh^%%L%pGp559ISx24V7N?j3+VPpI$;mU#g*I!UN->MSZ zc39*W!P41VS`qvWhaQDxv+rSoYE*MjK|Mh~iOKb#KOvi~g$W`NX$lqF4=CwbRE5mi zXtjjGnKif2g`(F%T9q~s$kwT<4ikJTXa}Xf1m9>M8cy;bE(cjQjXENYKI%XIw{aI~ z&iPl7tvzm!7hwWI0p1}WBe$jzwgVo%MkZV%28>+e`#M?_mE=>+&^*GXJYU|`shE+# zb^=H!On4G03b=-4fk-Hi&-8>Zwinvz?x=>TK%Wp3HgLfrFDn}g`-3lgBcJz&-;pR0 zyH0lK8vgLA-JmAkuxg3hT+`r!Pi;hY4TjB=X^IEP&Hb;S#tU6;^clkk4&4552f@4Z zYT%zlqkp5gAW{6{Dq0DGd%l?tw3Q++)OZm+q3ciBXSeb(W;k=J%6aNY^I|n20!kFn zZX{<~MiI1_(fLjaLYC2zIvflV5C;SXDCM+b%c1Yvw>eoW5Jz_)Dm2P}WJsX~D74B3 z+|z3Hd;NB0&o#8%n;JQqUfz3D>vk#*Y7hp44N~glQG^-+hu+uilz|!+cKudqBb0}bO0paqNm^CdVmSloN~0zk_eH& zAKb*W&H#m|q;@6^8$2ZW`xh@qLLuvxNemdWl#sxEkF1UuX9AP~3Bv}rB$5hv9z)Mw zt-Bm5fkGbC6J#YdXO`~c0b93wq6uOkTcj^x zIF$)3>U1s;RnH3r!T1S!Lc>t#@h99|55+899Z*kLC(-p}ax;M*YSa^;vl`yl*HKb| zOLUe16ta)=3?1HGyGcc1fL+ z3dgS|`_4=SpTc4%oD8LAtIiGuslPQ7kP1C8mY9qNQAkQQ9mY#QQ}Ca1 z@w2Q23RSQX2T^c-)uGeOLR=WweK2bu31T{Uw!^_?!fxYUG%)+_xuxkdioUD~Atc~+ zfDBUT72)RTP*4FVaJ@Q-3G7g~*97&1sw$>814yVTN2^-H$Ox08bviw8?p1Ix?l~W8 zv)H_Wfe_z)^UX80RzHKDpwxbuiXzbCNR(|9-lV~?pd>I4a6wjsNLdH;gr0e%d4(HH zg#=%X@2(feL8M1jI=iwU9FPs6LJQg7+uJ%; zgtFdnj@DIKT|a#E4drOv?S6Q8A9JsMh}L_~$66)o;;W}mzxo;nOh0*i|Ldnu9zRzf zm+>mI0u4l{;nOO&bOU`96Jdf%0uliig^5VOG9iJP;C3{WrcA(d3hkEY3S~I9*_>9& z)#Sklr~>ryun;OsQ8vdL6Y7`@pd)Bbh`nHKI{pN6qA?x(3+wKOgSf+CaW*Q5!;#4# zfVd-MMjhB`kwMSGZT?T+O;He73lvVW9yosjyirg1q)(QXyDW^(WZX(Ii zin&)0|Gb#aBp+*i@%8wn&PSq7c|_kUj?#*6Pd1u^n(4mzo;A zy#x^q$hzW1mrEh2Kb$zgH+!hSNf#7L&$buEE;PpGm zKR9`$Q8i%U!^Oa0@u_eU7e-mkCn2I4K?V7}9SKUHwLtGnSr76x=;J~`teo31f#lF& zKE_skksPg4K*Assu9yjwqxIAD(=X8er~jnftG_5KAL|Aki*J)zRLr;JW1Ph_3}R)* z>=74GY=?ps#GLY!1TzpILg92Za9@KU0i|;F1bbR(u4O0)2B?h7Atp$NgTB1^^?Qw- z7gUrLEzOpFkiVPH;q6h+$0(C}*cl-5xfC&Hvx{NTPxc)F5)0sE3aip397kw2@^bA?&u!Ogu~7B13MFjnIJh@ z8_k5qyz(R;1VcBL-a|ySVCf_duOkk8aP;igiyKX@$3nOe) z^yvws%ms07hyPog9J1>pK}kWF;Gltv+k@}qAl(6$%CkWF@HO1&_T{N?k`AU^8-G~j zS9ixK1>UyyG-3eW-k;DY^$DneSh&25noplTP0<=3W`g8sU2P#pYj*erdK*7c?$xip zzJRjH(OO)69wC7vGYY0CFxSR8H?h=s1Z-(u*EFL8u}BVtzy6NWehfyTyRU8YAiJ z3GR?6sTC15L4H5Keg&-AtB!tUS80Aqot4l$kKvgF& zL7yUVjJ4fAM<{6D^=Of_&et{)U>)T(K>-K=EjwvxkRcIh6DqOIvo#^Q0&nprDVQ`x zcuGriGmx_~p|+_|?p0VUr-Co8KjHi`?Dodp1KxcNDNz%|#n@p&zdHG2R+%6dWqFT?_hCn#@2F@*Q|Q;H`pz0I#E( zLPAhDO;0HMnc$3-?ltxTW0ypQ_`C+uy2R)8=g+4$`~Bz7pA}x?-|*Ux1w~!!?+9B` z9nN)zf32+&Cb($^-cn z+%=qct{87nQ6I9XBfO}~Se*k&vfP$++b5n%#DL3{~X zaMR1tIt+yp8?aFn)b2W3ROSL{4zI-_d4}0pni*X01Q@`s28KgH zRKN(PA~H$cLQsDqshNttT+TKc)>op(_!qK~054^{N z!e~7K-}fXlg)qUfoGY6Np&YHfJ`fbxZU zuW+EmB-+8?>iaN~Vo^{ZM4BhK0g@&O5P_qYnFs`hP?k8hPH=xQ)?OH9f=C||4r=-G zZ-4&N4=>jpucIq!{Rfo40}1xE`tc8c_@i2*^6;aE198L9L9a#j>hJov&vY(s{t>VD zqgtb~?n21(?;SSk(Id6NGH-cAV4-HbS5NLg`0BZ;q~$wI5aYpRLZu#m4cxIF&L4j8 zo>`|@@+_fD%;d&#pgV7V_%L>OTU}ZL6uI5W>lTb6qy~*3@ z^<+AkUQcNZ)O!gs$`?s8fe7huXYViG7n1_@1Zc+7i?`q2e~C;;iWFQ66YOIsqDd6Z z=DN9SO@XzKu%aMDJCi@59LEGz0ui47>Cb<J~~z5}rQ5AR<8MKj^^Uh4^a z^>2a$F`)je_Uq4fxHO!_%l)C|W3#i1v zm7n~Vf`@Da8^h|r)blpOIP zx><40S&3OKGnvegx53zJy8(Co|Nn5Gs_sskLlUwxMaJprt``HYhw3U*xnT+FHb^B| zez3Gwd8*1Z*X3G}OvgDDqF-yHFOJs$9WNzJQi#G z6whNa(E{d%CFe*zR-oUr$C!oC_O9MOxL2iKz&lSEHMH~V|WApzMK|z6ntTOt^ahD9CK_6_DUkWA4vhyLHsz+VY7km^I#e z*7tG0*={#m4MY%bzst;Bd`&zO|D?i>6nV0IrE_2585>Mc@}te9=PyK{l3Ccx6nqx5 zg#(I=EhU8#oyS+U%yk=(YUHBt@QHg;Mo?woq`az+F=6#AFm;%ad;MO8uxS0fTJphb z_nemYMn2=!nP7sfX{*ao;Wc|VjoItVLV0UeH)PFf0k_k?7$|sbH~|W$bBufQAa~DF z_6EVrj(fD`%Jpb{ifP*Kb$dgV#U)Qg3tH<%f3)Qut=n(JaY8jQncaOQ>a^JE_YC?t zP5}K!FkuZu$fVQjeE;JsneML=sT^(}zP;2=_7y-#)71W!A4_(c4=F$1hY2{97cpV_ z0a3z7s?v`BIENFTpYKTa{}^4+y}<;R3AmOx`xDsco^b-RXJ_Yscu)Wzq%^TC-CMqA z+5Pr$3w;;c4IZCh4Z`>2FA_!52ruxER6#S0>TMof*^&QJV@?aaS$&3pav<+uL5al# zMT3aXV=$mu|7y^i&3n;`MiEW=y`eyX^M9kc(IRcE^#>Mxj2qdxLuZ;t{Rk59bk^rn z5#YM&Uk!WHML$xr{+Pikj)sCcMxV1$0uw}5fWTpb;;H_WifY~=;wyCkVYiB>@kGO@ zphKO1!?dA~o0W@R*&)h&Lv?#^^-&ZQW@ zcTyTeOU(B|lal>;Pbb#{Xv)MXupSb*enBe?Am64%rgOSYGV|D@)PShtf{mxs5F{vs}z}1C5 zad04X_KXQTI+siU!-cTPe=sR@Pvy=Mv6SB7c<=bmoov3{uJ;BL{@yO_G62QhK$Fuj z2p9{R<8#gil0YH7fg8xzG<`8>;^3&ktD7O+V1xy$OpdscA!B1=5 zu-56q+LxfIDZ}|h1byLwz6McOWB~?w;V^-pd5DZi1r!qM2T>>qho|S&D40>ZHir9p zw*s`aYxQneUl3oT!qaaJCgitJ+SAZ7AahvG);>}( z#YV=2m+quGkxEQKzU^A-=)dCx{T3$VFJgkGe|uFL9pR`FV-}ZajT#)p|sf{nuYbT zrdG?3ytgQdTL5}mOk&9(^BX`}W-%d?PiDmu;~*S&YuyQ4w-*FGnucx4m3CUL>fyMg zc9XDBpGbr-@Av9;y!(U7obq<(^>Cy_e>VvF(BE=bww!!0?$tWj$IB&k3mjV9dVRAn z98*O$Y!MkuNJ(L}HHU_tf0ijlLD0RsYBb5McJXgCXJ790DEH-=F3eFjS<-+n?=etL zF(z=1F{WO)poBqW8U~|&tu+ZF(Y%~D8${2TaM$lOLY!$tG!Gjyh<7RHg!&Zf=q_@r z`sa5Kq&?XJ@cTi*V*(x=CzP6h_(AW&1n+-O6-wnsP z`s#YLu4z6Nx>X5LyN#(Wd>hh?37gDfLY7fGnEW-LTFv8_C_ZeHz>sjTb;wST-7{=( zbW+c#V2|kwn4l||kiLlt5-A|dIKCo%fC*<8J06`&5O9bIJBtYrNTi6s{*N`glejo% zy14h4KqNvu6@~~ z>t>TEr%pJdxQYq7gajsfP&vs%U;>85!(Hw?6VFYU8>?f=d37&5pcr!_p7U{nm*CHO zoB>xK=2TYV1W{BU&{f!in~R4-zvwg1Lu7j*+YPTMvvEQyny;$W44LcNh&}?Btj=n~ zaoA{63;s^dFkiq_uw*B^r90r?gdPMF9`+A{2`Out1syqwM&Eq?V?|MUw0DSUAlAAe zEadGVtWPNyZePIpbxZ)g(kABg-eJ#i!d^(r;h$lG@6lQ`TeL3c@Z85`FQ~N#bKj%2 zN97d`%k<`E^Ic2bVHMv2%9-j{6yI&ytP5qKdj0yBbbp0_ zcmGtPgg$3mlF1#F|7?v^HSzghY4DfU=Ji#1{1=iL;^EX9ur2?Yao^wFpv%9!pqjIKTdN6 zYlTj%?A*#G784Rc7hh^YV_Crju4~=EQx&g*g2tEIdV~+_a1su&ZYZY_bi}&MU>ca0 z&=AJ&pgE!+TVMjL#Rd}w)DN?~(U((OVkN626z;%NaKV9k#u{JvGz}qjE309ml4AeQ zFyR$YFoPHRLHvU_!IH*Lu_ma@($knw?a`VQ3K8ieF(%v;G_gHeL))XZlt2D-lc^$) zCCB&$1u?al3OyAmUoYQ~8?bQ#eT9b_Ot=Fk)aynIZe`L&vj+vZF{C)Ia3A(3%8%mD z0ts2>`Gx#;;vk=(5Az9jT?FOf){jdMV?+uE;+Nv5*E@*=N*rIp1Wk$f`c5$6oC(v{ z7fja%Nl>j64$G%4CajjO;VAdrw%Iyn0XW0P9a7Kslr!6@)~l~>D0$S8aq|Mrm%m+#NnoB1D|S1^Id`ZbSa zPatw(pbSp~*24$qv7u3MRNX;geD`-AGdovsE*f*hr>p7OaLn08MNiYF+gQmlTyb;NjyYiSIl-a#QE|?;e zus)8xCZ?apgwwYv2q>JcMcO#w7!%&6S9-Lr1rzeYaK$}ZBio}j8x9HAO`mRA zAkhm>=v++z3b7FTBkyhFkvW|&;%xgf(i91Cd85qp~)A{q>8mAfDxzAfr2!#t2ea&M`)m5!vq<>)D7Yk zCNL?+1R08ms$fED4#)ILkJcxeV@NObXjL;DzOY=jCflR+wi2{ctNG)bV1m`R%5HcR zw3dor%s{kg*4z&8?yjo^hRs9j#%}?Fb#@me*yIBbj@ZEW7A7R=iCSby3e3^6jUZbmMau#@)f-qB@M){kVcMP1j-UGKfMovls*P6N=ORfmIxwV)Ou z3UH$ad3V8bqZT%#7KsXStJb>PPBnc64)o;x~daDV~C0(gSa)jRrsgPAf2V7g}l7vZ`yyaP|7NBmn;Pho3SN2rU z#%QKl9T#k0YLNrLB^6;GCpRUaRFG6K0q26cY@@!^Gyh{9c{m+5swulJ&d99ZQ#{YH)qdbniwNRN{ekFkp^FP;EFUU+~fe78*$OppA*)u zaLtUcvxl|SExZpBrYykv-18Vkb1#dZs|e?OMWv|j4R8Wp;TX1au~wMXIqys-I6%>? zYp64#HKhaC7(oQOd$3SI^PDx$D0An`qA*YU!I;M2$?%`D;-!41Q4g#o24W2o15!}H zstcOQs?tA;3CB(0NQS5T&Q>u&*}8+;%1G$lm~h;qHGQo|Ynf$=Xzg5hZE(7(B_%?$ z1I`sXeNHnlBr&56Zd!7hU!u1VL5h!j^yx*AKOBR?AtpFrkR^l(C+iNN|9qSsHx|0lToLVpkWEiUVF@j>|l~KZ?epCrO zb9cqLtiK=b%^<@hFNjj%*W!i#dr*ci<%Po9@OMNjdj`I;Q&uHPXVz0`%-EGl(5I}z zqsi&>rz}EFcYf>vPE7k%&CDV)>lx~fs3_cGshn!z6wL+s6Wb7NxBSb!Ou9mb(^>P+ zxbVN;-)u>1H#nO2fN)%wFraW^@1V@y0Y^*fG$!P4^k}_#sYh#uf$)ILCFEroWe7Oa?(efQV9XD3Ry|=#zCUI8G_yc9xxZg;85ABtYa|)hj~V{6 z`CBl-LK09qrzCbG!R#VQb4oF)Qki7Apu6Ta`lU##r>E~vdx@5etop7fOGw9xQUQ8s zsWwq4;y4F`EWkz{YxiZOf?Xtip3`|=l37cQM81w=sI8J-my}}(MeHaF8~)CP82X63 zzN&1=1&%8D}xC>Zd{u`moCcLc=&Ctbi-F;2n!*Ip>VB80V)bd)RLu zHv7%@0>^#O^IoXj9e6eJSO3SbkYFq!N3ZyjDqC>;GAn8P*G9p?0RptefwO{n1G zgfx4t@{pgV3fBSuiOCJ-Uss5`ob&%D@9K73w~g?5hg<~6Re=JF0w-AH0BwO@o%X6o zkfNItTh`i^mKwkRJ7hGR$#6XU@oFuvPx6tXhN4LQk)MV?qDZh1E;tkmIP-xx9l8w& zCqyXwMoz#b?p4&I6jen>cBdJNC2RMO~?EtD*YUKL>?eJl0=q#sj@?o zRWM8Na|eas|5VCgi8nhbpkSg7d_oJbr(uFP+IrPF96_?eU_zS!{o@T4zY21R2DtPv zDwJlA)wPE5RMc%fffyl>PdGt72Fl>D4%)N7_S;I-L%Dp*g4k2d;cx*b6ux(0PUw{r zaKD9k`OnL%yPN=F17J2Yga@p7K8{)gQb)EhYMBnB{#3Ojrtl+sI@3)`tF&{XzK}a@X|P(w7~TFN^a;8y_3c!Q zWV2TGtTO^RY!TFY^n677tZcx{U^`W5<(n%gr4l4FyLpD&oM5tU!bRe_v!6%8)gNB~ zA%-y0=$0kgqhlglgYaKS5=xvfsh)sq+ZWk$nKHz1SPO4TyEY!-4BawL@RXo< zpIi;6gc4XMl*&RBq(Nb@?TKJ>N%N;O{_W0IZRys0?8o$b>8| zXC$z)zk=$V-JX8Do-htWJX_Q6z-&P1(X7k~%V?w2jG%L*kkB`(4!ttzYsyRYElgnWnGz{P?Y#!3g$4r>fnnuxB!tf6_&%D=?QdNYa!7IMq|fZy#XqZ zA5qy45-9(I>X%Fkc}|#c;eLnNpiMbJ8DV`&*f(gQryoPo2hL4aNc97js#OQ+TU?+g z6gdHIp(NP6F^GCFq@?g1m_r;X7ywL&K->_(X}6geA$Zwq4Fl*#|K9=Hc4t4I2UzKe zsYe)>EoAG5jkKV=WA^X&=FSr!#|aa+F^=etA>e9E>JFlRYro`SH8dz;-#}E@j=LGeF3lRxy8h6;FfG70+9U4&M6|Zkuct(&#(($C=+Zc%zrc_VRDg$F(N~i~S zPoyElQrjW+B`u*xI9=hgUI&gqYnDAHYfY*=(J{>Je^?-&@LA%nQ3_(xmL2!Y@@;S?q`Axx|%3`S2t zq-AAgN~r$AzDGt#55MobFMa+ShghP?vfQpZ_|8km2{kpL13MpP`m+qgwUkapekck# z&pB3*z!>$M6_)DPVF-={OzHP>UMmRq*2=glPl1$NeYh&S&SHN)+P?@c%!GTGH%Z8m z!mx?kry%1KYpeLa5HHUq>>R@)3=)OH+m)8@()ztzZ$hp${ z#rkZr63R`CYGTy^WFN?o0xYEjM*@lfUKY`HXx^WiuqG*JMGjcB&zHj33xz?8fJj3d zvn{En;79?WtS4|MNvbEXy#*y8%A*uJq=daEiZ5Mhm{Jt?t^j*Z=-JU2hCni$&}BKn zQ38N@K5u6OIDg`W&Ty|LJnM_dkitS%ka$A?+tE{;z(f$D<~z9RObXAmMQ-b?z2gOF7pP70#x?1 zO$Sz6dUI8N}2!dIdxm{Pjnkx{u-KY5K`FCEf)L4JP*3$91XG$W@l zK0O8mhUEkz1)7hM=5-%Y@bf1e)`e&yvqR=WH39kJiyOI!u0hx01glw;>JGIg#tu#l zr343nwcb}g1i~|6Ja9b*lvE%`3M5ahN(3NSNpJ_Oj#y491g6q(i_^+bZd`Ed;nlrU8+1gYK`4z-yMv4GL#`VEhbIdr(dw$|pU6)dVw+5$!D~fewNzrUSAJ zlvGXd*mpnoZk`0+(U9SUUKc|nO-|VDGJ8RyCa~-gZuxO9AjK0O1gnjDQm`K&2d#;g z7$Ku1P(imHY2{9dDIVXazA_c0k~ZCFABJ@Pt8{F%YQdpyFW?d{`3<{XrtNSg3j+2m(Blv z*=(YR(!W+bSCKiD~e@26hkSECg>pQ zi3y@Xln*Asd=vPNktG*EAatzQOi+}++>@DPr7&`AL*d>+8)}cq*QhJ zwdr1n^0l?`Si7&4v;70$LGTGuP6!{UIU(Ru&=Wp>`^~q1!Igj|3Zs>QhMK#MmKJp9 zAD`mse1pJsibu21eJ4qRAHMqzeg2j6`1l(&*YhGfa)TR_oBLLW0iOyU2tz|L5Bj!; zh8@V|T>>Eq3H!qC%m4pBb`Ye_0kpq+X&n3!2jkmrS zY)+p}p^&Mj29X5woJx6aTF00m4}#X`da-m=WXj&2R4AK#6ca`*E#)JIxXkmsv^~}C zTted5Fgp{8o%=v{?TI(||AiF^6aK=59`zGI_Uz#su;O12m{9)EsW2hEocsQK#_V~) z5i5O;`cXPsP{ZGR0?n=VWZ4>Xdpbv&{+8)ol*M!6v4?X9-2MAyzSx)X{UArr=w_I3|#Fc#I0!zqSjzkyqI>u5mp> z`b-xni?yEk39U?!ZQ}88WsV~(Y13(YRI+GJvnz0*?NA{ZB{BX5gWtylnWv?RwE0xm z_h?x(jcK5|$5{TKU_p#tf2N<{Pcrp?ssIwe6Dt%GKJ!NtKbL~(aXxp&V*kPfTfG&` zh+9V83rn%PL@#O@ehsGrXm3(wCMM)y(xTpG!%Y@3otF7jTGV9TJWl`@n@`-ckL-$G zG9*x`IedF*Bi{>FG+~oLw#Vy7>cf4w#4UsL zY0|XjNhz!NXdM%>Ik#0Cv4N-{;*vYN%>%<$A2#3bh6Q;i%l(9(zQG^xx0z1c*W!oR zVS5|6nDa%WdO<<<6KbicbkKUEu1hnNG)w?ffw6@(fVLCawiaZjNV@_~CN@EqgE)*z z+R{5kGg+}m%Yw#WBm^HMx}>>+1TI2gLYBzu4bhMoCbX*1PdTKyNb{ocK1YQt$`;or zw7~|2ZJ`0wtJu~%CsYimus^UAm3b#(f@_I4S3%M%wxShLZ<;GjvUIM28}(xNPSFh{ zFNz7(F(K=PO0hcIm@3?a*<@RV*RnxBwM6pL+(Ad(npIUR3u&vagRF}Wo8?RQRxNFd5~KcupcsX z`FN@L7A#3pwwIWn08G$#lL!VUY$GOo4j6#?227x7nHhQcnS7M)tta5-A>;xR=&$u< z0a=4%j6p7_&5RuLTEjF+(~cOqR#fQmgf3PDC}d0si2@TYw{wae+#$L|z0u#ye!?8@4X>1cPAdiN+bxkj>&&tu5CCSaAuiAs2*Rn#oq0c;1@GF#+nBSt<>wj%syo z*KXpHWlXr096R)E@|IP$CbkJ348Cz%lWl3sT_Wco$X$*$hir@HrMXT_Koa#@nHa1700#kV2fp_0&eZa zy@#~6X_$Z>X>Ta)m4!VA$LTd-rJ+A1HcSY;1SVVz6R0Z31gE$#G++>+XlBsZRH6xP z9k`-gyURh@1cPMUQD}XM+)wa&^n0NFsjSV<@nk)Lt72UE#<=&_6U0K1-m#y9AZ{(e{A}y)h1~EqWm?LZXyMu&yYO;eNhv?DBq0A>S8J-2H^FfBdI`LdJw& zY>m+=VM29G@a5*wPe9_O#DmdKnA(G%K<8VU=!BgXX-#PBBGngh{Z`ti&orZ40uw+p zn1l(~Ny3Ed5hj4Z1}0D_mJWWx5IdacaDtg^;+o;LMobXtQoC$x5D_x;!nT5@S9DQN z%bhE{=dNOY0xGHn6>__Nf&-M+TWg)RT)vlvh?mh%0D%om=v8jihPzajD7B0Uym`r{Y> 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 literal 0 HcmV?d00001 diff --git a/docs/cli-reference.rst b/docs/cli-reference.rst index c108138..e38d08d 100644 --- a/docs/cli-reference.rst +++ b/docs/cli-reference.rst @@ -61,6 +61,7 @@ This page lists the ``--help`` for every ``sqlite-utils`` CLI sub-command. "create-spatial-index": "cli_spatialite_indexes", "install": "cli_install", "uninstall": "cli_uninstall", + "tui": "cli_tui", } commands.sort(key = lambda command: go_first.index(command) if command in go_first else 999) cog.out("\n") @@ -1025,6 +1026,23 @@ disable-fts -h, --help Show this message and exit. +.. _cli_ref_tui: + +tui +=== + +See :ref:`cli_tui`. + +:: + + Usage: sqlite-utils tui [OPTIONS] + + Open Textual TUI. + + Options: + -h, --help Show this message and exit. + + .. _cli_ref_optimize: optimize diff --git a/docs/cli.rst b/docs/cli.rst index d567cba..3b9ac31 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -2163,3 +2163,25 @@ You can uninstall packages that were installed using ``sqlite-utils install`` wi $ sqlite-utils uninstall beautifulsoup4 Use ``-y`` to skip the request for confirmation. + +.. _cli_tui: + +Experimental TUI +================ + +A TUI is a "text user interface" (or "terminal user interface") - a keyboard and mouse driven graphical interface running in your terminal. + +``sqlite-utils`` has experimental support for a TUI for building command-line invocations, built on top of the `Trogon `__ TUI library. + +To enable this feature you will need to install the ``trogon`` dependency. You can do that like so:: + + sqite-utils install trogon + +Once installed, running the ``sqlite-utils tui`` command will launch the TUI interface:: + + sqlite-utils tui + +You can then construct a command by selecting options from the menus, and execute it using ``Ctrl+R``. + +.. image:: _static/img/tui.png + :alt: A TUI interface for sqlite-utils - the left column shows a list of commands, while the right panel has a form for constructing arguments to the add-column command. diff --git a/setup.py b/setup.py index 3964b3a..5898ffa 100644 --- a/setup.py +++ b/setup.py @@ -47,6 +47,7 @@ setup( "data-science-types", ], "flake8": ["flake8"], + "tui": ["trogon"], }, entry_points=""" [console_scripts] diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index 5a88bd5..ab9c524 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -33,6 +33,11 @@ from .utils import ( TypeTracker, ) +try: + import trogon # type: ignore +except ImportError: + trogon = None + CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) @@ -112,6 +117,10 @@ def cli(): pass +if trogon is not None: + cli = trogon.tui()(cli) + + @cli.command() @click.argument( "path", diff --git a/tests/test_docs.py b/tests/test_docs.py index 7f2e3ed..c305c7a 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -5,7 +5,7 @@ import pytest import re docs_path = Path(__file__).parent.parent / "docs" -commands_re = re.compile(r"(?:\$ | )sqlite-utils (\S+) ") +commands_re = re.compile(r"(?:\$ | )sqlite-utils (\S+)") recipes_re = re.compile(r"r\.(\w+)\(") From e240133b11588d31dc22c632f7a7ca636c72978d Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 21 May 2023 11:53:33 -0700 Subject: [PATCH 066/348] Release 3.32 Refs #544, #545, #547, #548 --- docs/changelog.rst | 12 ++++++++++++ setup.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 347601a..ee8713f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,18 @@ Changelog =========== +.. _v3_32: + +3.32 (2023-05-21) +----------------- + +- New experimental ``sqlite-utils tui`` interface for interactively building command-line invocations, powered by `Trogon `__. This requires an optional dependency, installed using ``sqlite-utils install trogon``. There is a screenshot :ref:`in the documentation `. (:issue:`545`) +- ``sqlite-utils analyze-tables`` command (:ref:`documentation `) now has a ``--common-limit 20`` option for changing the number of common/least-common values shown for each column. (:issue:`544`) +- ``sqlite-utils analyze-tables --no-most`` and ``--no-least`` options for disabling calculation of most-common and least-common values. +- If a column contains only ``null`` values, ``analyze-tables`` will no longer attempt to calculate the most common and least common values for that column. (:issue:`547`) +- Calling ``sqlite-utils analyze-tables`` with non-existent columns in the ``-c/--column`` option now results in an error message. (:issue:`548`) +- The ``table.analyze_column()`` method (:ref:`documented here `) now accepts ``most_common=False`` and ``least_common=False`` options for disabling calculation of those values. + .. _v3_31: 3.31 (2023-05-08) diff --git a/setup.py b/setup.py index 5898ffa..0d05c5c 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ from setuptools import setup, find_packages import io import os -VERSION = "3.31" +VERSION = "3.32" def get_long_description(): From d8fe1b0d899faaaa3d4714a39328f4c24932278f Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Sun, 21 May 2023 13:57:22 -0700 Subject: [PATCH 067/348] Reformatted CLI examples in docs Closes #551 --- docs/_templates/base.html | 5 + docs/cli.rst | 1201 ++++++++++++++++++++++++++----------- 2 files changed, 863 insertions(+), 343 deletions(-) diff --git a/docs/_templates/base.html b/docs/_templates/base.html index 43c2003..a253a46 100644 --- a/docs/_templates/base.html +++ b/docs/_templates/base.html @@ -7,6 +7,11 @@ {% block scripts %} {{ super() }} +