pax_global_header00006660000000000000000000000064142334556570014530gustar00rootroot0000000000000052 comment=53c6bb4d55bbd6cda6232f973391616b41534c9b singledispatch-json-0.4.0/000077500000000000000000000000001423345565700155015ustar00rootroot00000000000000singledispatch-json-0.4.0/.bumpversion.cfg000066400000000000000000000006731423345565700206170ustar00rootroot00000000000000[bumpversion] current_version = 0.4.0 commit = True tag = True [bumpversion:file:README.rst] [bumpversion:file:doc-source/index.rst] [bumpversion:file:sdjson/__init__.py] search = : str = "{current_version}" replace = : str = "{new_version}" [bumpversion:file:repo_helper.yml] [bumpversion:file:pyproject.toml] search = version = "{current_version}" replace = version = "{new_version}" [bumpversion:file:.github/workflows/conda_ci.yml] singledispatch-json-0.4.0/.dependabot/000077500000000000000000000000001423345565700176645ustar00rootroot00000000000000singledispatch-json-0.4.0/.dependabot/config.yml000066400000000000000000000003101423345565700216460ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. --- version: 1 update_configs: - package_manager: python directory: / update_schedule: weekly default_reviewers: - domdfcoding singledispatch-json-0.4.0/.github/000077500000000000000000000000001423345565700170415ustar00rootroot00000000000000singledispatch-json-0.4.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001423345565700212245ustar00rootroot00000000000000singledispatch-json-0.4.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000022271423345565700237210ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve labels: bug assignees: domdfcoding --- ## Description ## Steps to Reproduce 1. 2. 3. ## Actual result: ## Expected result: ## Reproduces how often: ## Version * Operating System: * Python: * sdjson: ## Installation source ## Other Additional Information: singledispatch-json-0.4.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000012121423345565700247450ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project labels: "enhancement" assignees: domdfcoding --- ## Description ## Version * Operating System: * Python: * sdjson: ## Other Additional Information: singledispatch-json-0.4.0/.github/auto_assign.yml000066400000000000000000000003471423345565700221040ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. --- addReviewers: true addAssignees: true reviewers: - domdfcoding numberOfReviewers: 0 # more settings at https://github.com/marketplace/actions/auto-assign-action singledispatch-json-0.4.0/.github/dependabot.yml000066400000000000000000000002771423345565700216770ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. --- version: 2 updates: - package-ecosystem: pip directory: / schedule: interval: weekly reviewers: - domdfcoding singledispatch-json-0.4.0/.github/milestones.py000077500000000000000000000012401423345565700215750ustar00rootroot00000000000000#!/usr/bin/env python # stdlib import os import sys # 3rd party from github3 import GitHub from github3.repos import Repository from packaging.version import InvalidVersion, Version latest_tag = os.environ["GITHUB_REF_NAME"] try: current_version = Version(latest_tag) except InvalidVersion: sys.exit() gh: GitHub = GitHub(token=os.environ["GITHUB_TOKEN"]) repo: Repository = gh.repository(*os.environ["GITHUB_REPOSITORY"].split('/', 1)) for milestone in repo.milestones(state="open"): try: milestone_version = Version(milestone.title) except InvalidVersion: continue if milestone_version == current_version: sys.exit(not milestone.update(state="closed")) singledispatch-json-0.4.0/.github/stale.yml000066400000000000000000000040211423345565700206710ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. # Configuration for probot-stale - https://github.com/probot/stale --- # Number of days of inactivity before an Issue or Pull Request becomes stale daysUntilStale: 180 # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. daysUntilClose: false # Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled) onlyLabels: [] # Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable exemptLabels: - pinned - security - "[Status] Maybe Later" # Set to true to ignore issues in a project (defaults to false) exemptProjects: false # Set to true to ignore issues in a milestone (defaults to false) exemptMilestones: false # Set to true to ignore issues with an assignee (defaults to false) exemptAssignees: false # Label to use when marking as stale staleLabel: stale # Comment to post when marking as stale. Set to `false` to disable markComment: false # This issue has been automatically marked as stale because it has not had # recent activity. It will be closed if no further activity occurs. Thank you # for your contributions. # Comment to post when removing the stale label. # unmarkComment: > # Your comment here. # Comment to post when closing a stale Issue or Pull Request. # closeComment: > # Your comment here. # Limit the number of actions per hour, from 1-30. Default is 30 limitPerRun: 30 # Limit to only `issues` or `pulls` # only: issues # Optionally, specify configuration settings that are specific to just 'issues' or 'pulls': # pulls: # daysUntilStale: 30 # markComment: > # This pull request has been automatically marked as stale because it has not had # recent activity. It will be closed if no further activity occurs. Thank you # for your contributions. # issues: # exemptLabels: # - confirmed singledispatch-json-0.4.0/.github/workflows/000077500000000000000000000000001423345565700210765ustar00rootroot00000000000000singledispatch-json-0.4.0/.github/workflows/conda_ci.yml000066400000000000000000000034771423345565700233730ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. --- name: Conda Tests on: push: branches: ["master"] permissions: contents: read jobs: tests: name: "Conda" runs-on: ubuntu-18.04 defaults: run: shell: bash -l {0} steps: - name: Checkout ๐Ÿ›Ž๏ธ uses: "actions/checkout@v2" - name: Setup Python ๐Ÿ uses: "actions/setup-python@v2" with: python-version: "3.8" - name: Setup Conda uses: conda-incubator/setup-miniconda@v2 with: activate-environment: env conda-build-version: 3.21.0 - name: Install dependencies ๐Ÿ”ง run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade "whey-conda" "whey" # $CONDA is an environment variable pointing to the root of the miniconda directory $CONDA/bin/conda update -n base conda $CONDA/bin/conda config --add channels conda-forge $CONDA/bin/conda config --add channels domdfcoding - name: "Build and index channel" run: | python -m whey --builder whey_conda --out-dir conda-bld/noarch $CONDA/bin/conda index ./conda-bld || exit 1 - name: "Search for package" run: | $CONDA/bin/conda search -c file://$(pwd)/conda-bld sdjson $CONDA/bin/conda search -c file://$(pwd)/conda-bld --override-channels sdjson - name: "Install package" run: | $CONDA/bin/conda install -c file://$(pwd)/conda-bld sdjson=0.4.0=py_1 -y || exit 1 - name: "Run Tests" run: | rm -rf sdjson $CONDA/bin/conda install pytest coincidence || exit 1 pip install -r tests/requirements.txt pytest tests/ singledispatch-json-0.4.0/.github/workflows/docs_test_action.yml000066400000000000000000000015451423345565700251520ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. --- name: "Docs Check" on: push: branches-ignore: - 'repo-helper-update' - 'pre-commit-ci-update-config' - 'imgbot' pull_request: permissions: contents: read jobs: docs: runs-on: ubuntu-latest steps: - name: Checkout ๐Ÿ›Ž๏ธ uses: "actions/checkout@v2" - name: Check for changed files uses: dorny/paths-filter@v2 id: changes with: list-files: "json" filters: | code: - '!tests/**' - name: Install and Build ๐Ÿ”ง uses: sphinx-toolbox/sphinx-action@sphinx-3.3.1 if: steps.changes.outputs.code == 'true' with: pre-build-command: python -m pip install tox docs-folder: "doc-source/" build-command: "tox -e docs -- " singledispatch-json-0.4.0/.github/workflows/flake8.yml000066400000000000000000000023311423345565700227720ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. --- name: Flake8 on: push: branches-ignore: - 'repo-helper-update' - 'pre-commit-ci-update-config' - 'imgbot' pull_request: permissions: contents: read jobs: Run: name: "Flake8" runs-on: "ubuntu-18.04" steps: - name: Checkout ๐Ÿ›Ž๏ธ uses: "actions/checkout@v2" - name: Check for changed files uses: dorny/paths-filter@v2 id: changes with: list-files: "json" filters: | code: - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python ๐Ÿ if: steps.changes.outputs.code == 'true' uses: "actions/setup-python@v2" with: python-version: "3.6" - name: Install dependencies ๐Ÿ”ง if: steps.changes.outputs.code == 'true' run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel python -m pip install tox - name: "Run Flake8" if: steps.changes.outputs.code == 'true' run: "python -m tox -e lint -s false -- --format github" singledispatch-json-0.4.0/.github/workflows/mypy.yml000066400000000000000000000024321423345565700226200ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. --- name: mypy on: push: branches-ignore: - 'repo-helper-update' - 'pre-commit-ci-update-config' - 'imgbot' pull_request: permissions: contents: read jobs: Run: name: "mypy / ${{ matrix.os }}" runs-on: ${{ matrix.os }} strategy: matrix: os: ['ubuntu-20.04', 'windows-2019'] fail-fast: false steps: - name: Checkout ๐Ÿ›Ž๏ธ uses: "actions/checkout@v2" - name: Check for changed files uses: dorny/paths-filter@v2 id: changes with: list-files: "json" filters: | code: - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python ๐Ÿ if: steps.changes.outputs.code == 'true' uses: "actions/setup-python@v2" with: python-version: "3.6" - name: Install dependencies ๐Ÿ”ง run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade tox virtualenv - name: "Run mypy" if: steps.changes.outputs.code == 'true' run: "python -m tox -e mypy -s false" singledispatch-json-0.4.0/.github/workflows/octocheese.yml000066400000000000000000000006111423345565700237400ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. --- name: "GitHub Releases" on: schedule: - cron: 0 12 * * * jobs: Run: runs-on: ubuntu-latest steps: - uses: domdfcoding/octocheese@master with: pypi_name: "sdjson" env: GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} if: startsWith(github.ref, 'refs/tags/') != true singledispatch-json-0.4.0/.github/workflows/python_ci.yml000066400000000000000000000053411423345565700236200ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. --- name: Windows on: push: branches-ignore: - 'repo-helper-update' - 'pre-commit-ci-update-config' - 'imgbot' pull_request: permissions: actions: write issues: write contents: read jobs: tests: name: "windows-2019 / Python ${{ matrix.config.python-version }}" runs-on: "windows-2019" continue-on-error: ${{ matrix.config.experimental }} env: USING_COVERAGE: '3.6,3.7,3.8,3.9,3.10,pypy-3.6,pypy-3.7,pypy-3.8,pypy-3.9' strategy: fail-fast: False matrix: config: - {python-version: "3.6", testenvs: "py36,build", experimental: False} - {python-version: "3.7", testenvs: "py37,build", experimental: False} - {python-version: "3.8", testenvs: "py38,build", experimental: False} - {python-version: "3.9", testenvs: "py39,build", experimental: False} - {python-version: "3.10", testenvs: "py310,build", experimental: False} - {python-version: "pypy-3.6", testenvs: "pypy36,build", experimental: False} - {python-version: "pypy-3.7", testenvs: "pypy37,build", experimental: True} - {python-version: "pypy-3.8", testenvs: "pypy38,build", experimental: True} - {python-version: "pypy-3.9", testenvs: "pypy39", experimental: True} steps: - name: Checkout ๐Ÿ›Ž๏ธ uses: "actions/checkout@v2" - name: Check for changed files if: startsWith(github.ref, 'refs/tags/') != true uses: dorny/paths-filter@v2 id: changes with: list-files: "json" filters: | code: - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python ๐Ÿ id: setup-python if: ${{ steps.changes.outputs.code == 'true' || steps.changes.outcome == 'skipped' }} uses: "actions/setup-python@v2" with: python-version: "${{ matrix.config.python-version }}" - name: Install dependencies ๐Ÿ”ง if: steps.setup-python.outcome == 'success' run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade tox virtualenv - name: "Run Tests for Python ${{ matrix.config.python-version }}" if: steps.setup-python.outcome == 'success' run: python -m tox -e "${{ matrix.config.testenvs }}" -s false - name: "Upload Coverage ๐Ÿš€" uses: actions/upload-artifact@v2 if: ${{ always() && steps.setup-python.outcome == 'success' }} with: name: "coverage-${{ matrix.config.python-version }}" path: .coverage singledispatch-json-0.4.0/.github/workflows/python_ci_linux.yml000066400000000000000000000165741423345565700250510ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. --- name: Linux on: push: branches-ignore: - 'repo-helper-update' - 'pre-commit-ci-update-config' - 'imgbot' tags: - '*' pull_request: permissions: actions: write issues: write contents: read jobs: tests: name: "ubuntu-20.04 / Python ${{ matrix.config.python-version }}" runs-on: "ubuntu-20.04" continue-on-error: ${{ matrix.config.experimental }} env: USING_COVERAGE: '3.6,3.7,3.8,3.9,3.10,pypy-3.6,pypy-3.7,pypy-3.8,pypy-3.9' strategy: fail-fast: False matrix: config: - {python-version: "3.6", testenvs: "py36,build", experimental: False} - {python-version: "3.7", testenvs: "py37,build", experimental: False} - {python-version: "3.8", testenvs: "py38,build", experimental: False} - {python-version: "3.9", testenvs: "py39,build", experimental: False} - {python-version: "3.10", testenvs: "py310,build", experimental: False} - {python-version: "pypy-3.6", testenvs: "pypy36,build", experimental: False} - {python-version: "pypy-3.7", testenvs: "pypy37,build", experimental: True} - {python-version: "pypy-3.8", testenvs: "pypy38,build", experimental: True} - {python-version: "pypy-3.9", testenvs: "pypy39", experimental: True} steps: - name: Checkout ๐Ÿ›Ž๏ธ uses: "actions/checkout@v2" - name: Check for changed files if: startsWith(github.ref, 'refs/tags/') != true uses: dorny/paths-filter@v2 id: changes with: list-files: "json" filters: | code: - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python ๐Ÿ id: setup-python if: ${{ steps.changes.outputs.code == 'true' || steps.changes.outcome == 'skipped' }} uses: "actions/setup-python@v2" with: python-version: "${{ matrix.config.python-version }}" - name: Install dependencies ๐Ÿ”ง if: steps.setup-python.outcome == 'success' run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade tox virtualenv python -m pip install --upgrade coverage_pyver_pragma - name: "Run Tests for Python ${{ matrix.config.python-version }}" if: steps.setup-python.outcome == 'success' run: python -m tox -e "${{ matrix.config.testenvs }}" -s false - name: "Upload Coverage ๐Ÿš€" uses: actions/upload-artifact@v2 if: ${{ always() && steps.setup-python.outcome == 'success' }} with: name: "coverage-${{ matrix.config.python-version }}" path: .coverage Coverage: needs: tests runs-on: "ubuntu-20.04" steps: - name: Checkout ๐Ÿ›Ž๏ธ uses: "actions/checkout@v2" - name: Setup Python ๐Ÿ uses: "actions/setup-python@v2" with: python-version: 3.8 - name: Install dependencies ๐Ÿ”ง run: | python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade "coveralls>=3.0.0" coverage_pyver_pragma - name: "Download Coverage ๐Ÿช‚" uses: actions/download-artifact@v2 with: path: coverage - name: Display structure of downloaded files id: show run: ls -R working-directory: coverage continue-on-error: true - name: Combine Coverage ๐Ÿ‘ท if: ${{ steps.show.outcome != 'failure' }} run: | shopt -s globstar python -m coverage combine coverage/**/.coverage - name: "Upload Combined Coverage Artefact ๐Ÿš€" if: ${{ steps.show.outcome != 'failure' }} uses: actions/upload-artifact@v2 with: name: "combined-coverage" path: .coverage - name: "Upload Combined Coverage to Coveralls" if: ${{ steps.show.outcome != 'failure' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | coveralls --service=github Deploy: needs: tests runs-on: "ubuntu-20.04" steps: - name: Checkout ๐Ÿ›Ž๏ธ uses: "actions/checkout@v2" if: startsWith(github.ref, 'refs/tags/') - name: Setup Python ๐Ÿ uses: "actions/setup-python@v2" if: startsWith(github.ref, 'refs/tags/') with: python-version: 3.8 - name: Install dependencies ๐Ÿ”ง if: startsWith(github.ref, 'refs/tags/') run: | python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade tox - name: Build distributions ๐Ÿ“ฆ if: startsWith(github.ref, 'refs/tags/') run: | tox -e build - name: Upload distribution to PyPI ๐Ÿš€ if: startsWith(github.ref, 'refs/tags/') uses: pypa/gh-action-pypi-publish@v1.4.2 with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} skip_existing: true - name: Close milestone ๐Ÿšช if: startsWith(github.ref, 'refs/tags/') run: | python -m pip install --upgrade github3.py packaging python .github/milestones.py env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} Conda: needs: deploy runs-on: "ubuntu-18.04" if: startsWith(github.ref, 'refs/tags/') || (startsWith(github.event.head_commit.message, 'Bump version') != true) steps: - name: Checkout ๐Ÿ›Ž๏ธ uses: "actions/checkout@v2" - name: Setup Python ๐Ÿ uses: "actions/setup-python@v2" with: python-version: 3.8 - name: Setup Conda uses: conda-incubator/setup-miniconda@v2 with: activate-environment: env conda-build-version: 3.21.0 - name: Install dependencies ๐Ÿ”ง run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade "mkrecipe" "whey" # $CONDA is an environment variable pointing to the root of the miniconda directory $CONDA/bin/conda config --set always_yes yes --set changeps1 no $CONDA/bin/conda update -n base conda $CONDA/bin/conda info -a $CONDA/bin/conda config --add channels conda-forge $CONDA/bin/conda config --add channels domdfcoding $CONDA/bin/conda config --remove channels defaults - name: Build Conda Package ๐Ÿ“ฆ run: | python -m mkrecipe --type wheel || exit 1 $CONDA/bin/conda build conda -c conda-forge -c domdfcoding --output-folder conda/dist - name: Deploy Conda Package ๐Ÿš€ if: startsWith(github.ref, 'refs/tags/') run: | $CONDA/bin/conda config --set always_yes yes --set changeps1 no $CONDA/bin/conda install anaconda-client $CONDA/bin/conda info -a for f in conda/dist/noarch/sdjson-*.tar.bz2; do [ -e "$f" ] || continue echo "$f" conda install "$f" || exit 1 echo "Deploying to Anaconda.org..." $CONDA/bin/anaconda -t "$ANACONDA_TOKEN" upload "$f" || exit 1 echo "Successfully deployed to Anaconda.org." done env: ANACONDA_TOKEN: ${{ secrets.ANACONDA_TOKEN }} singledispatch-json-0.4.0/.github/workflows/python_ci_macos.yml000066400000000000000000000051761423345565700250100ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. --- name: macOS on: push: branches-ignore: - 'repo-helper-update' - 'pre-commit-ci-update-config' - 'imgbot' pull_request: permissions: actions: write issues: write contents: read jobs: tests: name: "macos-latest / Python ${{ matrix.config.python-version }}" runs-on: "macos-latest" continue-on-error: ${{ matrix.config.experimental }} env: USING_COVERAGE: '3.6,3.7,3.8,3.9,3.10,pypy-3.7,pypy-3.8,pypy-3.9' strategy: fail-fast: False matrix: config: - {python-version: "3.6", testenvs: "py36,build", experimental: False} - {python-version: "3.7", testenvs: "py37,build", experimental: False} - {python-version: "3.8", testenvs: "py38,build", experimental: False} - {python-version: "3.9", testenvs: "py39,build", experimental: False} - {python-version: "3.10", testenvs: "py310,build", experimental: False} - {python-version: "pypy-3.7", testenvs: "pypy37,build", experimental: True} - {python-version: "pypy-3.8", testenvs: "pypy38,build", experimental: True} - {python-version: "pypy-3.9", testenvs: "pypy39", experimental: True} steps: - name: Checkout ๐Ÿ›Ž๏ธ uses: "actions/checkout@v2" - name: Check for changed files if: startsWith(github.ref, 'refs/tags/') != true uses: dorny/paths-filter@v2 id: changes with: list-files: "json" filters: | code: - '!(doc-source/**|CONTRIBUTING.rst|.imgbotconfig|.pre-commit-config.yaml|.pylintrc|.readthedocs.yml)' - name: Setup Python ๐Ÿ id: setup-python if: ${{ steps.changes.outputs.code == 'true' || steps.changes.outcome == 'skipped' }} uses: "actions/setup-python@v2" with: python-version: "${{ matrix.config.python-version }}" - name: Install dependencies ๐Ÿ”ง if: steps.setup-python.outcome == 'success' run: | python -VV python -m site python -m pip install --upgrade pip setuptools wheel python -m pip install --upgrade tox virtualenv - name: "Run Tests for Python ${{ matrix.config.python-version }}" if: steps.setup-python.outcome == 'success' run: python -m tox -e "${{ matrix.config.testenvs }}" -s false - name: "Upload Coverage ๐Ÿš€" uses: actions/upload-artifact@v2 if: ${{ always() && steps.setup-python.outcome == 'success' }} with: name: "coverage-${{ matrix.config.python-version }}" path: .coverage singledispatch-json-0.4.0/.gitignore000066400000000000000000000020161423345565700174700ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. __pycache__/ *.py[cod] *$py.class *.so .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg *.egg* *.manifest *.spec pip-log.txt pip-delete-this-directory.txt htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ cover/ *.mo *.pot *.log local_settings.py db.sqlite3 instance/ .webassets-cache .scrapy docs/_build/ doc/build target/ .ipynb_checkpoints .python-version celerybeat-schedule celerybeat.pid *.sage.py .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ .spyderproject .spyproject .ropeproject /site .mypy_cache/ .dmypy.json dmypy.json *.iml *.ipr cmake-build-*/ .idea/**/mongoSettings.xml *.iws out/ atlassian-ide-plugin.xml com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties .idea build **/__pycache__ **/conda __pypackages__/ profile_default/ ipython_config.py Pipfile.lock .pyre/ singledispatch-json-0.4.0/.imgbotconfig000066400000000000000000000001151423345565700201460ustar00rootroot00000000000000{ "schedule": "weekly", "ignoredFiles": [ "**/*.svg" ] } singledispatch-json-0.4.0/.pre-commit-config.yaml000066400000000000000000000041701423345565700217640ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. --- exclude: ^$ repos: - repo: https://github.com/repo-helper/pyproject-parser rev: v0.4.3 hooks: - id: reformat-pyproject - repo: https://github.com/pre-commit/pre-commit-hooks rev: v3.4.0 hooks: - id: check-added-large-files - id: check-ast - id: fix-byte-order-marker - id: check-byte-order-marker - id: check-case-conflict - id: check-executables-have-shebangs - id: check-json - id: check-toml - id: check-yaml - id: check-merge-conflict - id: check-symlinks - id: check-vcs-permalinks - id: detect-private-key - id: trailing-whitespace - id: mixed-line-ending - id: end-of-file-fixer - repo: https://github.com/domdfcoding/pre-commit-hooks rev: v0.3.0 hooks: - id: requirements-txt-sorter args: - --allow-git - id: check-docstring-first exclude: ^(doc-source/conf|__pkginfo__|setup|tests/.*)\.py$ - id: bind-requirements - repo: https://github.com/domdfcoding/flake8-dunder-all rev: v0.1.8 hooks: - id: ensure-dunder-all files: ^sdjson/.*\.py$ - repo: https://github.com/domdfcoding/flake2lint rev: v0.4.1 hooks: - id: flake2lint - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.9.0 hooks: - id: python-no-eval - id: rst-backticks - id: rst-directive-colons - id: rst-inline-touching-normal - repo: https://github.com/asottile/pyupgrade rev: v2.12.0 hooks: - id: pyupgrade args: - --py36-plus - --keep-runtime-typing - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.1.13 hooks: - id: remove-crlf - id: forbid-crlf - repo: https://github.com/python-formate/snippet-fmt rev: v0.1.4 hooks: - id: snippet-fmt - repo: https://github.com/python-formate/formate rev: v0.4.10 hooks: - id: formate exclude: ^(doc-source/conf|__pkginfo__|setup)\.(_)?py$ - repo: https://github.com/domdfcoding/dep_checker rev: v0.6.2 hooks: - id: dep_checker args: - sdjson # Custom hooks can be added below this comment singledispatch-json-0.4.0/.pylintrc000066400000000000000000000346201423345565700173530ustar00rootroot00000000000000[MASTER] # Specify a configuration file. #rcfile= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Add files or directories to the blacklist. They should be base names, not # paths. ignore=CVS # Add files or directories matching the regex patterns to the blacklist. The # regex matches against base names, not paths. ignore-patterns= # Pickle collected data for later comparisons. persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= # Use multiple processes to speed up Pylint. jobs=1 # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code extension-pkg-whitelist= # Allow optimization of some AST trees. This will activate a peephole AST # optimizer, which will apply various small optimizations. For instance, it can # be used to obtain the result of joining multiple strings with the addition # operator. Joining a lot of strings can lead to a maximum recursion error in # Pylint and this flag can prevent that. It has one side effect, the resulting # AST will be different than the one from reality. This option is deprecated # and it will be removed in Pylint 2.0. optimize-ast=no [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED confidence= # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time (only on the command line, not in the configuration file where # it should appear only once). See also the "--disable" option for examples. #enable= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once).You can also use "--disable=all" to # disable everything first and then reenable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" disable=all enable=assert-on-tuple,astroid-error,bad-except-order,bad-inline-option,bad-option-value,bad-reversed-sequence,bare-except,binary-op-exception,boolean-datetime,catching-non-exception,cell-var-from-loop,confusing-with-statement,consider-merging-isinstance,consider-using-enumerate,consider-using-ternary,continue-in-finally,deprecated-pragma,django-not-available,duplicate-except,duplicate-key,eval-used,exec-used,expression-not-assigned,fatal,file-ignored,fixme,global-at-module-level,global-statement,global-variable-not-assigned,global-variable-undefined,http-response-with-content-type-json,http-response-with-json-dumps,invalid-all-object,invalid-characters-in-docstring,len-as-condition,literal-comparison,locally-disabled,locally-enabled,lost-exception,lowercase-l-suffix,misplaced-bare-raise,missing-kwoa,mixed-line-endings,model-has-unicode,model-missing-unicode,model-no-explicit-unicode,model-unicode-not-callable,multiple-imports,new-db-field-with-default,non-ascii-bytes-literals,nonexistent-operator,not-in-loop,notimplemented-raised,overlapping-except,parse-error,pointless-statement,pointless-string-statement,raising-bad-type,raising-non-exception,raw-checker-failed,redefine-in-handler,redefined-argument-from-local,redefined-builtin,redundant-content-type-for-json-response,reimported,relative-import,return-outside-function,simplifiable-if-statement,singleton-comparison,syntax-error,trailing-comma-tuple,trailing-newlines,unbalanced-tuple-unpacking,undefined-all-variable,undefined-loop-variable,unexpected-line-ending-format,unidiomatic-typecheck,unnecessary-lambda,unnecessary-pass,unnecessary-semicolon,unneeded-not,unpacking-non-sequence,unreachable,unrecognized-inline-option,used-before-assignment,useless-else-on-loop,using-constant-test,wildcard-import,yield-outside-function,useless-return [REPORTS] # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html. You can also give a reporter class, eg # mypackage.mymodule.MyReporterClass. output-format=text # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". This option is deprecated # and it will be removed in Pylint 2.0. files-output=no # Tells whether to display a full report or only the messages reports=no # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details #msg-template= [BASIC] # Good variable names which should always be accepted, separated by a comma good-names=i,j,k,ex,Run,_ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= # Include a hint for the correct naming format with invalid-name include-naming-hint=no # List of decorators that produce properties, such as abc.abstractproperty. Add # to this list to register other decorators that produce valid properties. property-classes=abc.abstractproperty # Regular expression matching correct function names function-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for function names function-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct variable names variable-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for variable names variable-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct constant names const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Naming hint for constant names const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Regular expression matching correct attribute names attr-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for attribute names attr-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct argument names argument-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for argument names argument-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ # Naming hint for class attribute names class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Naming hint for inline iteration names inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ # Regular expression matching correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Naming hint for class names class-name-hint=[A-Z_][a-zA-Z0-9]+$ # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Naming hint for module names module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression matching correct method names method-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for method names method-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=-1 [ELIF] # Maximum number of nested blocks for function / method body max-nested-blocks=5 [FORMAT] # Maximum number of characters on a single line. max-line-length=159 # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. # `trailing-comma` allows a space between comma and closing bracket: (a, ). # `empty-line` allows space-only lines. no-space-check=trailing-comma,dict-separator # Maximum number of lines in a module max-module-lines=1000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format= [LOGGING] # Logging modules to check that the string format arguments are in logging # function parameter format logging-modules=logging [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME,XXX,TODO [SIMILARITIES] # Minimum lines number of a similarity. min-similarity-lines=4 # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=no [SPELLING] # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package. spelling-dict= # List of comma separated words that should not be checked. spelling-ignore-words= # A path to a file that contains private dictionary; one word per line. spelling-private-dict-file= # Tells whether to store unknown words to indicated private dictionary in # --spelling-private-dict-file option instead of raising a message. spelling-store-unknown-words=no [TYPECHECK] # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It # supports qualified module names, as well as Unix pattern matching. ignored-modules= # List of class names for which member attributes should not be checked (useful # for classes with dynamically set attributes). This supports the use of # qualified names. ignored-classes=optparse.Values,thread._local,_thread._local # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members= # List of decorators that produce context managers, such as # contextlib.contextmanager. Add to this list to register other decorators that # produce valid context managers. contextmanager-decorators=contextlib.contextmanager [VARIABLES] # Tells whether we should check for unused import in __init__ files. init-import=no # A regular expression matching the name of dummy variables (i.e. expectedly # not used). dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_,_cb # List of qualified module names which can have objects that can redefine # builtins. redefining-builtins-modules=six.moves,future.builtins [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict,_fields,_replace,_source,_make [DESIGN] # Maximum number of arguments for function / method max-args=5 # Argument names that match this expression will be ignored. Default to name # with leading underscore ignored-argument-names=_.* # Maximum number of locals for function / method body max-locals=15 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of branch for function / method body max-branches=12 # Maximum number of statements in function / method body max-statements=60 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Minimum number of public methods for a class (see R0903). min-public-methods=2 # Maximum number of public methods for a class (see R0904). max-public-methods=20 # Maximum number of boolean expressions in a if statement max-bool-expr=5 [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,TERMIOS,Bastion,rexec # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= # Force import order to recognize a module as part of the standard # compatibility libraries. known-standard-library= # Force import order to recognize a module as part of a third party library. known-third-party=enchant # Analyse import fallback blocks. This can be used to support both Python 2 and # 3 compatible code, which means that the block might have code that exists # only in one or another interpreter, leading to false positives when analysed. analyse-fallback-blocks=no [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "Exception" overgeneral-exceptions=Exception singledispatch-json-0.4.0/.readthedocs.yml000066400000000000000000000005351423345565700205720ustar00rootroot00000000000000# This file is managed by 'repo_helper'. Don't edit it directly. # Read the Docs configuration file --- version: 2 sphinx: builder: html configuration: doc-source/conf.py formats: - pdf - htmlzip python: version: 3.8 install: - requirements: requirements.txt - requirements: doc-source/requirements.txt - method: pip path: . singledispatch-json-0.4.0/.style.yapf000066400000000000000000000272001423345565700176010ustar00rootroot00000000000000[style] # Align closing bracket with visual indentation. align_closing_bracket_with_visual_indent=True # Allow dictionary keys to exist on multiple lines. For example: # # x = { # ('this is the first element of a tuple', # 'this is the second element of a tuple'): # value, # } allow_multiline_dictionary_keys=True # Allow lambdas to be formatted on more than one line. allow_multiline_lambdas=False # Allow splitting before a default / named assignment in an argument list. allow_split_before_default_or_named_assigns=True # Allow splits before the dictionary value. allow_split_before_dict_value=True # Let spacing indicate operator precedence. For example: # # a = 1 * 2 + 3 / 4 # b = 1 / 2 - 3 * 4 # c = (1 + 2) * (3 - 4) # d = (1 - 2) / (3 + 4) # e = 1 * 2 - 3 # f = 1 + 2 + 3 + 4 # # will be formatted as follows to indicate precedence: # # a = 1*2 + 3/4 # b = 1/2 - 3*4 # c = (1+2) * (3-4) # d = (1-2) / (3+4) # e = 1*2 - 3 # f = 1 + 2 + 3 + 4 # arithmetic_precedence_indication=False # Number of blank lines surrounding top-level function and class # definitions. blank_lines_around_top_level_definition=2 # Insert a blank line before a class-level docstring. blank_line_before_class_docstring=False # Insert a blank line before a module docstring. blank_line_before_module_docstring=False # Insert a blank line before a 'def' or 'class' immediately nested # within another 'def' or 'class'. For example: # # class Foo: # # <------ this blank line # def method(): # ... blank_line_before_nested_class_or_def=True # Do not split consecutive brackets. Only relevant when # dedent_closing_brackets is set. For example: # # call_func_that_takes_a_dict( # { # 'key1': 'value1', # 'key2': 'value2', # } # ) # # would reformat to: # # call_func_that_takes_a_dict({ # 'key1': 'value1', # 'key2': 'value2', # }) coalesce_brackets=True # The column limit. column_limit=115 # The style for continuation alignment. Possible values are: # # - SPACE: Use spaces for continuation alignment. This is default behavior. # - FIXED: Use fixed number (CONTINUATION_INDENT_WIDTH) of columns # (ie: CONTINUATION_INDENT_WIDTH/INDENT_WIDTH tabs or # CONTINUATION_INDENT_WIDTH spaces) for continuation alignment. # - VALIGN-RIGHT: Vertically align continuation lines to multiple of # INDENT_WIDTH columns. Slightly right (one tab or a few spaces) if # cannot vertically align continuation lines with indent characters. continuation_align_style=VALIGN-RIGHT # Indent width used for line continuations. continuation_indent_width=8 # Put closing brackets on a separate line, dedented, if the bracketed # expression can't fit in a single line. Applies to all kinds of brackets, # including function definitions and calls. For example: # # config = { # 'key1': 'value1', # 'key2': 'value2', # } # <--- this bracket is dedented and on a separate line # # time_series = self.remote_client.query_entity_counters( # entity='dev3246.region1', # key='dns.query_latency_tcp', # transform=Transformation.AVERAGE(window=timedelta(seconds=60)), # start_ts=now()-timedelta(days=3), # end_ts=now(), # ) # <--- this bracket is dedented and on a separate line dedent_closing_brackets=False # Disable the heuristic which places each list element on a separate line # if the list is comma-terminated. disable_ending_comma_heuristic=False # Place each dictionary entry onto its own line. each_dict_entry_on_separate_line=False # Require multiline dictionary even if it would normally fit on one line. # For example: # # config = { # 'key1': 'value1' # } force_multiline_dict=False # The regex for an i18n comment. The presence of this comment stops # reformatting of that line, because the comments are required to be # next to the string they translate. ;i18n_comment= # The i18n function call names. The presence of this function stops # reformattting on that line, because the string it has cannot be moved # away from the i18n comment. ;i18n_function_call= # Indent blank lines. indent_blank_lines=False # Put closing brackets on a separate line, indented, if the bracketed # expression can't fit in a single line. Applies to all kinds of brackets, # including function definitions and calls. For example: # # config = { # 'key1': 'value1', # 'key2': 'value2', # } # <--- this bracket is indented and on a separate line # # time_series = self.remote_client.query_entity_counters( # entity='dev3246.region1', # key='dns.query_latency_tcp', # transform=Transformation.AVERAGE(window=timedelta(seconds=60)), # start_ts=now()-timedelta(days=3), # end_ts=now(), # ) # <--- this bracket is indented and on a separate line indent_closing_brackets=True # Indent the dictionary value if it cannot fit on the same line as the # dictionary key. For example: # # config = { # 'key1': # 'value1', # 'key2': value1 + # value2, # } indent_dictionary_value=True # The number of columns to use for indentation. indent_width=4 # Join short lines into one line. E.g., single line 'if' statements. join_multiple_lines=False # Do not include spaces around selected binary operators. For example: # # 1 + 2 * 3 - 4 / 5 # # will be formatted as follows when configured with "*,/": # # 1 + 2*3 - 4/5 ;no_spaces_around_selected_binary_operators= # Use spaces around default or named assigns. spaces_around_default_or_named_assign=False # Adds a space after the opening '{' and before the ending '}' dict delimiters. # # {1: 2} # # will be formatted as: # # { 1: 2 } spaces_around_dict_delimiters=False # Adds a space after the opening '[' and before the ending ']' list delimiters. # # [1, 2] # # will be formatted as: # # [ 1, 2 ] spaces_around_list_delimiters=False # Use spaces around the power operator. spaces_around_power_operator=False # Use spaces around the subscript / slice operator. For example: # # my_list[1 : 10 : 2] spaces_around_subscript_colon=False # Adds a space after the opening '(' and before the ending ')' tuple delimiters. # # (1, 2, 3) # # will be formatted as: # # ( 1, 2, 3 ) spaces_around_tuple_delimiters=False # The number of spaces required before a trailing comment. # This can be a single value (representing the number of spaces # before each trailing comment) or list of values (representing # alignment column values; trailing comments within a block will # be aligned to the first column value that is greater than the maximum # line length within the block). For example: # # With spaces_before_comment=5: # # 1 + 1 # Adding values # # will be formatted as: # # 1 + 1 # Adding values <-- 5 spaces between the end of the statement and comment # # With spaces_before_comment=15, 20: # # 1 + 1 # Adding values # two + two # More adding # # longer_statement # This is a longer statement # short # This is a shorter statement # # a_very_long_statement_that_extends_beyond_the_final_column # Comment # short # This is a shorter statement # # will be formatted as: # # 1 + 1 # Adding values <-- end of line comments in block aligned to col 15 # two + two # More adding # # longer_statement # This is a longer statement <-- end of line comments in block aligned to col 20 # short # This is a shorter statement # # a_very_long_statement_that_extends_beyond_the_final_column # Comment <-- the end of line comments are aligned based on the line length # short # This is a shorter statement # spaces_before_comment=2 # Insert a space between the ending comma and closing bracket of a list, # etc. space_between_ending_comma_and_closing_bracket=True # Use spaces inside brackets, braces, and parentheses. For example: # # method_call( 1 ) # my_dict[ 3 ][ 1 ][ get_index( *args, **kwargs ) ] # my_set = { 1, 2, 3 } space_inside_brackets=False # Split before arguments split_all_comma_separated_values=False # Split before arguments, but do not split all subexpressions recursively # (unless needed). split_all_top_level_comma_separated_values=True # Split before arguments if the argument list is terminated by a # comma. split_arguments_when_comma_terminated=False # Set to True to prefer splitting before '+', '-', '*', '/', '//', or '@' # rather than after. split_before_arithmetic_operator=True # Set to True to prefer splitting before '&', '|' or '^' rather than # after. split_before_bitwise_operator=True # Split before the closing bracket if a list or dict literal doesn't fit on # a single line. split_before_closing_bracket=True # Split before a dictionary or set generator (comp_for). For example, note # the split before the 'for': # # foo = { # variable: 'Hello world, have a nice day!' # for variable in bar if variable != 42 # } split_before_dict_set_generator=True # Split before the '.' if we need to split a longer expression: # # foo = ('This is a really long string: {}, {}, {}, {}'.format(a, b, c, d)) # # would reformat to something like: # # foo = ('This is a really long string: {}, {}, {}, {}' # .format(a, b, c, d)) split_before_dot=False # Split after the opening paren which surrounds an expression if it doesn't # fit on a single line. split_before_expression_after_opening_paren=True # If an argument / parameter list is going to be split, then split before # the first argument. split_before_first_argument=False # Set to True to prefer splitting before 'and' or 'or' rather than # after. split_before_logical_operator=True # Split named assignments onto individual lines. split_before_named_assigns=True # Set to True to split list comprehensions and generators that have # non-trivial expressions and multiple clauses before each of these # clauses. For example: # # result = [ # a_long_var + 100 for a_long_var in xrange(1000) # if a_long_var % 10] # # would reformat to something like: # # result = [ # a_long_var + 100 # for a_long_var in xrange(1000) # if a_long_var % 10] split_complex_comprehension=True # The penalty for splitting right after the opening bracket. split_penalty_after_opening_bracket=100 # The penalty for splitting the line after a unary operator. split_penalty_after_unary_operator=10000 # The penalty of splitting the line around the '+', '-', '*', '/', '//', # ``%``, and '@' operators. split_penalty_arithmetic_operator=300 # The penalty for splitting right before an if expression. split_penalty_before_if_expr=0 # The penalty of splitting the line around the '&', '|', and '^' # operators. split_penalty_bitwise_operator=300 # The penalty for splitting a list comprehension or generator # expression. split_penalty_comprehension=80 # The penalty for characters over the column limit. split_penalty_excess_character=7000 # The penalty incurred by adding a line split to the unwrapped line. The # more line splits added the higher the penalty. split_penalty_for_added_line_split=30 # The penalty of splitting a list of "import as" names. For example: # # from a_very_long_or_indented_module_name_yada_yad import (long_argument_1, # long_argument_2, # long_argument_3) # # would reformat to something like: # # from a_very_long_or_indented_module_name_yada_yad import ( # long_argument_1, long_argument_2, long_argument_3) split_penalty_import_names=0 # The penalty of splitting the line around the 'and' and 'or' # operators. split_penalty_logical_operator=300 # Use the Tab character for indentation. use_tabs=True singledispatch-json-0.4.0/.yapfignore000066400000000000000000000000401423345565700176370ustar00rootroot00000000000000tests/stdlib_tests/test_fail.py singledispatch-json-0.4.0/CONTRIBUTING.rst000066400000000000000000000024771423345565700201540ustar00rootroot00000000000000============== Contributing ============== .. This file based on https://github.com/PyGithub/PyGithub/blob/master/CONTRIBUTING.md ``sdjson`` uses `tox `_ to automate testing and packaging, and `pre-commit `_ to maintain code quality. Install ``pre-commit`` with ``pip`` and install the git hook: .. code-block:: bash $ python -m pip install pre-commit $ pre-commit install Coding style -------------- `formate `_ is used for code formatting. It can be run manually via ``pre-commit``: .. code-block:: bash $ pre-commit run formate -a Or, to run the complete autoformatting suite: .. code-block:: bash $ pre-commit run -a Automated tests ------------------- Tests are run with ``tox`` and ``pytest``. To run tests for a specific Python version, such as Python 3.6: .. code-block:: bash $ tox -e py36 To run tests for all Python versions, simply run: .. code-block:: bash $ tox Type Annotations ------------------- Type annotations are checked using ``mypy``. Run ``mypy`` using ``tox``: .. code-block:: bash $ tox -e mypy Build documentation locally ------------------------------ The documentation is powered by Sphinx. A local copy of the documentation can be built with ``tox``: .. code-block:: bash $ tox -e docs singledispatch-json-0.4.0/LICENSE000066400000000000000000000020551423345565700165100ustar00rootroot00000000000000Copyright (c) 2020-2021 Dominic Davis-Foster Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. singledispatch-json-0.4.0/README.rst000066400000000000000000000154161423345565700171770ustar00rootroot00000000000000======= sdjson ======= .. start short_desc **Custom JSON Encoder for Python utilising functools.singledispatch to support custom encoders for both Python's built-in classes and user-created classes, without as much legwork.** .. end short_desc .. start shields .. list-table:: :stub-columns: 1 :widths: 10 90 * - Docs - |docs| |docs_check| * - Tests - |actions_linux| |actions_windows| |actions_macos| |coveralls| * - PyPI - |pypi-version| |supported-versions| |supported-implementations| |wheel| * - Anaconda - |conda-version| |conda-platform| * - Activity - |commits-latest| |commits-since| |maintained| |pypi-downloads| * - QA - |codefactor| |actions_flake8| |actions_mypy| * - Other - |license| |language| |requires| .. |docs| image:: https://img.shields.io/readthedocs/singledispatch-json/latest?logo=read-the-docs :target: https://singledispatch-json.readthedocs.io/en/latest :alt: Documentation Build Status .. |docs_check| image:: https://github.com/domdfcoding/singledispatch-json/workflows/Docs%20Check/badge.svg :target: https://github.com/domdfcoding/singledispatch-json/actions?query=workflow%3A%22Docs+Check%22 :alt: Docs Check Status .. |actions_linux| image:: https://github.com/domdfcoding/singledispatch-json/workflows/Linux/badge.svg :target: https://github.com/domdfcoding/singledispatch-json/actions?query=workflow%3A%22Linux%22 :alt: Linux Test Status .. |actions_windows| image:: https://github.com/domdfcoding/singledispatch-json/workflows/Windows/badge.svg :target: https://github.com/domdfcoding/singledispatch-json/actions?query=workflow%3A%22Windows%22 :alt: Windows Test Status .. |actions_macos| image:: https://github.com/domdfcoding/singledispatch-json/workflows/macOS/badge.svg :target: https://github.com/domdfcoding/singledispatch-json/actions?query=workflow%3A%22macOS%22 :alt: macOS Test Status .. |actions_flake8| image:: https://github.com/domdfcoding/singledispatch-json/workflows/Flake8/badge.svg :target: https://github.com/domdfcoding/singledispatch-json/actions?query=workflow%3A%22Flake8%22 :alt: Flake8 Status .. |actions_mypy| image:: https://github.com/domdfcoding/singledispatch-json/workflows/mypy/badge.svg :target: https://github.com/domdfcoding/singledispatch-json/actions?query=workflow%3A%22mypy%22 :alt: mypy status .. |requires| image:: https://dependency-dash.herokuapp.com/github/domdfcoding/singledispatch-json/badge.svg :target: https://dependency-dash.herokuapp.com/github/domdfcoding/singledispatch-json/ :alt: Requirements Status .. |coveralls| image:: https://img.shields.io/coveralls/github/domdfcoding/singledispatch-json/master?logo=coveralls :target: https://coveralls.io/github/domdfcoding/singledispatch-json?branch=master :alt: Coverage .. |codefactor| image:: https://img.shields.io/codefactor/grade/github/domdfcoding/singledispatch-json?logo=codefactor :target: https://www.codefactor.io/repository/github/domdfcoding/singledispatch-json :alt: CodeFactor Grade .. |pypi-version| image:: https://img.shields.io/pypi/v/sdjson :target: https://pypi.org/project/sdjson/ :alt: PyPI - Package Version .. |supported-versions| image:: https://img.shields.io/pypi/pyversions/sdjson?logo=python&logoColor=white :target: https://pypi.org/project/sdjson/ :alt: PyPI - Supported Python Versions .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/sdjson :target: https://pypi.org/project/sdjson/ :alt: PyPI - Supported Implementations .. |wheel| image:: https://img.shields.io/pypi/wheel/sdjson :target: https://pypi.org/project/sdjson/ :alt: PyPI - Wheel .. |conda-version| image:: https://img.shields.io/conda/v/domdfcoding/sdjson?logo=anaconda :target: https://anaconda.org/domdfcoding/sdjson :alt: Conda - Package Version .. |conda-platform| image:: https://img.shields.io/conda/pn/domdfcoding/sdjson?label=conda%7Cplatform :target: https://anaconda.org/domdfcoding/sdjson :alt: Conda - Platform .. |license| image:: https://img.shields.io/github/license/domdfcoding/singledispatch-json :target: https://github.com/domdfcoding/singledispatch-json/blob/master/LICENSE :alt: License .. |language| image:: https://img.shields.io/github/languages/top/domdfcoding/singledispatch-json :alt: GitHub top language .. |commits-since| image:: https://img.shields.io/github/commits-since/domdfcoding/singledispatch-json/v0.4.0 :target: https://github.com/domdfcoding/singledispatch-json/pulse :alt: GitHub commits since tagged version .. |commits-latest| image:: https://img.shields.io/github/last-commit/domdfcoding/singledispatch-json :target: https://github.com/domdfcoding/singledispatch-json/commit/master :alt: GitHub last commit .. |maintained| image:: https://img.shields.io/maintenance/yes/2022 :alt: Maintenance .. |pypi-downloads| image:: https://img.shields.io/pypi/dm/sdjson :target: https://pypi.org/project/sdjson/ :alt: PyPI - Downloads .. end shields | Usage ######### Creating and registering a custom encoder is as easy as: >>> import sdjson >>> >>> @sdjson.dump.register(MyClass) >>> def encode_myclass(obj): ... return dict(obj) >>> In this case, ``MyClass`` can be made JSON-serializable simply by calling ``dict()`` on it. If your class requires more complicated logic to make it JSON-serializable, do that here. Then, to dump the object to a string: >>> class_instance = MyClass() >>> print(sdjson.dumps(class_instance)) '{"menu": ["egg and bacon", "egg sausage and bacon", "egg and spam", "egg bacon and spam"], "today\'s special": "Lobster Thermidor au Crevette with a Mornay sauce served in a Provencale manner with shallots and aubergines garnished with truffle pate, brandy and with a fried egg on top and spam."}' >>> Or to dump to a file: >>> with open("spam.json", "w") as fp: ... sdjson.dumps(class_instance, fp) ... >>> ``sdjson`` also provides access to ``load``, ``loads``, ``JSONDecoder``, ``JSONDecodeError``, and ``JSONEncoder`` from the ``json`` module, allowing you to use ``sdjson`` as a drop-in replacement for ``json``. If you wish to dump an object without using the custom encoders, you can pass a different ``JSONEncoder`` subclass, or indeed ``JSONEncoder`` itself to get the stock functionality. >>> sdjson.dumps(class_instance, cls=sdjson.JSONEncoder) >>> | When you've finished, if you want to unregister the encoder you can call: >>> sdjson.encoders.unregister(MyClass) >>> to remove the encoder for ``MyClass``. If you want to replace the encoder with a different one it is not necessary to call this function: the ``@sdjson.encoders.register`` decorator will replace any existing decorator for the given class. Note that this module cannot be used to create custom encoders for any object ``json`` already knows about; that is: ``dict``, ``list``, ``tuple``, ``str``, ``int``, ``float``, ``bool``, and ``None``. TODO ###### 1. Add support for custom decoders. singledispatch-json-0.4.0/build_docs.sh000066400000000000000000000001631423345565700201440ustar00rootroot00000000000000#!/usr/bin/env bash rm -rf build rm -rf docs make html cp -r build/html docs/ touch docs/.nojekyll touch .nojekyll singledispatch-json-0.4.0/doc-source/000077500000000000000000000000001423345565700175445ustar00rootroot00000000000000singledispatch-json-0.4.0/doc-source/404.rst000066400000000000000000000003271423345565700206070ustar00rootroot00000000000000:orphan: =============== 404 =============== We looked everywhere but we couldn't find that page! .. image:: not-found.png :align: center Try using the links in the sidebar to find what you are looking for. singledispatch-json-0.4.0/doc-source/Source.rst000066400000000000000000000026001423345565700215340ustar00rootroot00000000000000========================= Downloading source code ========================= The ``sdjson`` source code is available on GitHub, and can be accessed from the following URL: https://github.com/domdfcoding/singledispatch-json If you have ``git`` installed, you can clone the repository with the following command: .. prompt:: bash git clone https://github.com/domdfcoding/singledispatch-json .. parsed-literal:: Cloning into 'singledispatch-json'... remote: Enumerating objects: 47, done. remote: Counting objects: 100% (47/47), done. remote: Compressing objects: 100% (41/41), done. remote: Total 173 (delta 16), reused 17 (delta 6), pack-reused 126 Receiving objects: 100% (173/173), 126.56 KiB | 678.00 KiB/s, done. Resolving deltas: 100% (66/66), done. | Alternatively, the code can be downloaded in a 'zip' file by clicking: | :guilabel:`Clone or download` --> :guilabel:`Download Zip` .. figure:: git_download.png :alt: Downloading a 'zip' file of the source code. Downloading a 'zip' file of the source code Building from source ----------------------- The recommended way to build ``sdjson`` is to use `tox `_: .. prompt:: bash tox -e build The source and wheel distributions will be in the directory ``dist``. If you wish, you may also use `pep517.build `_ or another :pep:`517`-compatible build tool. singledispatch-json-0.4.0/doc-source/_static/000077500000000000000000000000001423345565700211725ustar00rootroot00000000000000singledispatch-json-0.4.0/doc-source/_static/style.css000066400000000000000000000000001423345565700230320ustar00rootroot00000000000000singledispatch-json-0.4.0/doc-source/_templates/000077500000000000000000000000001423345565700217015ustar00rootroot00000000000000singledispatch-json-0.4.0/doc-source/_templates/layout.html000066400000000000000000000003441423345565700241050ustar00rootroot00000000000000 {% extends "!layout.html" %} {% block extrahead %} {% endblock %} singledispatch-json-0.4.0/doc-source/conf.py000066400000000000000000000037651423345565700210560ustar00rootroot00000000000000#!/usr/bin/env python3 # This file is managed by 'repo_helper'. Don't edit it directly. # stdlib import os import re import sys # 3rd party from sphinx_pyproject import SphinxConfig sys.path.append('.') config = SphinxConfig(globalns=globals()) project = config["project"] author = config["author"] documentation_summary = config.description github_url = "https://github.com/{github_username}/{github_repository}".format_map(config) rst_prolog = f""".. |pkgname| replace:: sdjson .. |pkgname2| replace:: ``sdjson`` .. |browse_github| replace:: `Browse the GitHub Repository <{github_url}>`__ """ slug = re.sub(r'\W+', '-', project.lower()) release = version = config.version todo_include_todos = bool(os.environ.get("SHOW_TODOS", 0)) intersphinx_mapping = { "python": ("https://docs.python.org/3/", None), "sphinx": ("https://www.sphinx-doc.org/en/stable/", None), } html_theme_options = {"logo_only": False} html_context = { "display_github": True, "github_user": "domdfcoding", "github_repo": "singledispatch-json", "github_version": "master", "conf_py_path": "/doc-source/", } htmlhelp_basename = slug latex_documents = [("index", f'{slug}.tex', project, author, "manual")] man_pages = [("index", slug, project, [author], 1)] texinfo_documents = [("index", slug, project, author, slug, project, "Miscellaneous")] toctree_plus_types = set(config["toctree_plus_types"]) autodoc_default_options = { "members": None, # Include all members (methods). "special-members": None, "autosummary": None, "show-inheritance": None, "exclude-members": ','.join(config["autodoc_exclude_members"]), } latex_elements = { "printindex": "\\begin{flushleft}\n\\printindex\n\\end{flushleft}", "tableofcontents": "\\pdfbookmark[0]{\\contentsname}{toc}\\sphinxtableofcontents", } def setup(app): # 3rd party from sphinx_toolbox.latex import better_header_layout app.connect("config-inited", lambda app, config: better_header_layout(config)) nitpicky = True autosummary_widths_builders = ["latex"] singledispatch-json-0.4.0/doc-source/contributing.rst000066400000000000000000000024231423345565700230060ustar00rootroot00000000000000============== Contributing ============== .. This file based on https://github.com/PyGithub/PyGithub/blob/master/CONTRIBUTING.md ``sdjson`` uses `tox `_ to automate testing and packaging, and `pre-commit `_ to maintain code quality. Install ``pre-commit`` with ``pip`` and install the git hook: .. prompt:: bash python -m pip install pre-commit pre-commit install Coding style -------------- `formate `_ is used for code formatting. It can be run manually via ``pre-commit``: .. prompt:: bash pre-commit run formate -a Or, to run the complete autoformatting suite: .. prompt:: bash pre-commit run -a Automated tests ------------------- Tests are run with ``tox`` and ``pytest``. To run tests for a specific Python version, such as Python 3.6: .. prompt:: bash tox -e py36 To run tests for all Python versions, simply run: .. prompt:: bash tox Type Annotations ------------------- Type annotations are checked using ``mypy``. Run ``mypy`` using ``tox``: .. prompt:: bash tox -e mypy Build documentation locally ------------------------------ The documentation is powered by Sphinx. A local copy of the documentation can be built with ``tox``: .. prompt:: bash tox -e docs singledispatch-json-0.4.0/doc-source/docs.rst000066400000000000000000000002521423345565700212250ustar00rootroot00000000000000************************** API Reference ************************** .. autosummary-widths:: 45/100 .. automodule:: sdjson :members: :exclude-members: JSONDecodeError singledispatch-json-0.4.0/doc-source/docutils.conf000066400000000000000000000000501423345565700222340ustar00rootroot00000000000000[restructuredtext parser] tab_width : 4 singledispatch-json-0.4.0/doc-source/git_download.png000066400000000000000000000765131423345565700227400ustar00rootroot00000000000000‰PNG  IHDRชnฦvญsBITแOเtEXtSoftwareShutterc‚ะ IDATxฺ์y\SG๛๖ฏ€žˆ%ฑ%กbโc•M!ผฒU‘โพ[ฅUฐjตZk]ซญ๗}mmkตึชญ‚kwXฌ€X!ดด&bIฐ&Tอฑš๗ qฝฟO?>แdฮœ9๗ฬ™๋ž{ๆL8zฝAAผJุ ‚ ‚ไŸ ‚ ’‚ ‚ H ‚ ‚ ๙'‚ ‚ไŸ ‚ ’‚ ‚ H ‚ ‚ ๙'‚ ‚ไŸ ‚ ’‚ ‚ H ‚ ‚จ๕ชMq๛๏‚•Zงำ‘ฑ^1๔‡ฌ@ผRpฎฃฃ ๑›Žฆ๎ฤๆํ๑ป๙ทศtฤsBS'๑{aฦพ]mJNีฟ๘w-๗บN๗ภััั5[[[ฒ์ซCๆ•+ญ[“^œ๚๚ปu+ฒCmx๔่QQัฟ๙ทo7lุฐนห[ีฆ๕อคำWm3๛zœzT่K9ไ?u7>ซ(Ž?ฝพ่ฟG ]˜Oศฦ๑ซk>๚ฟwN๗ภลล™„Ÿ ˆ—[[F๘๖๖ฏy-๗๖฿Uว~8๕ห้ซ‰ถอrlก็@ฯัs jo"๙๔c๊ฤำ๒ ่Q6๊q์๋ลg$p๊—ป ญกจิ฿|ำฦึ–๑ซูœจ_ภ€จ-6ถถN฿๛๏ชๅ๋ษถN ๊Neฃ|Oi๔o๔<9ฦŽภF_ฏ)76~Oอๅ_งำฝfY™ ˆWŠื์_{pใA ้฿๛6ผplŠ๛]G_2๔็”ํ• ขN~ฝ๑_`˜อ็p8z๛z y~ี็ืซ:s[[Sะ๐Ÿ ๚zฅฐตฑๅTฉถ›‡ซฯppl8z่๕ลc}N๙1AิฝI3ำ†ล'6ฮc่๋9 ‡}ฬึBฉG!I!Hฉ๑Wัp๔6เฐๅะsสH>‡๔Ÿx: TŽ๕zpภั?ฎพืฃ๎„ ฺง๚"j$0jฑภฉ€ ๊|๔ohiC€รyฌืXิ‚๋‘ ‚ jาr8z=`[เ”_์G๊Oิก‡Zneฉ^ฯัQ้9ฌ;%๙'‚จa„ ๔]?Ž้ไOง–k‹zCH€ร Œ)ž๘*7-ชถ7 žส๐฿03ศ้+dโฉด>Žษฃฏ7zz}qƒฌSO๊OP}‘+Uภ1้I๛‰gโ p8F คMZะ‚้'‚ j>๚'ˆดqา‹ี>ีQ๓๑?Aผ P๐Ÿ ฤŸ ฦOSBPํS}“Sๆ ษโย1์๙kุ}šSvW€šศญPeุ๒หม iฒ[Z–ŠœEn~ƒ>ˆ๎๊ฮฃzฒ ๘’/ฟ>+ปU๛žkฮฯฮXูmxœpdืœํฑปNฟภ‚้ธ๔Šฎ็ขะู_ผ๙:€vsNฏ๋mRืฒ•"ใ”ฺฦ$|‹oQfO๋•ฑC{ฎสพ?=ำ๏ฅฉ KศXั{ุ๖Jซพœำ๋z3ง'wžt–ฺฬ;ทพฏฎŒl}I^T›ดี" ะม‰oหชŠn( ฅq™qG -N๐Z๗๐]ซฤ %M>๛ศฃ—ด“ณ๏7pœ/P ›rA๚Œ ๑ดL\์Sีู๙ใg๎ฮ)ูp˜Uๆ]Qๆ]I;ฑgำ€ู฿อ์$ฌใปา˜2็ภ„ฎLZ๚b>ช fส4˜ี้ศขบ ^์ฺอ Ÿ5ฤ)›7๒7๒ t้๕กlู‡’Ud"โ™ห ฃ‰:ูW%ฺฯ4๑n็็.ฒg•—“ฮๆจQ$3sข๓ถŸ฿w~jw๘ขD•Wไ,ˆ|ฟuBk! ็นy๛ต"ฃ/{ku๗ญ}ž๚ZWJ {ึuQw๕UrŠ๓;“฿๗gส}+๒f๔เ ผต:0‚'tk๚'T’บnu‚m๓ษ%ฺ(?]!อ.*โฺKฺ7kแ{O๗Y฿3ไ๊_คLฤ 9๚W\ตอ Mย—|3ปซ‹1*งM[9l|\หfn’4pv0Wน-บฯ๒Lภพ็ฦรัชฏ}ผ(โ‡mŸบ€Ny๖๛ฏc“3sไZฎศลองS๋$*๏้ไgbฟ฿~(3๏–Jห๐"žŒ้,ช*X]že†{วf๚“ เ…ฯ\์นvหดLน–็โ๖ƒฉ“K๎จส<•ปข๛,ฬ˜ŽKŒ‘ษK1แPLวี็V„rํ‰Iแ3ฯฐ€฿ŒS? 0‰ˆhฯ,œ๐}ržฺIYqŽ฿G฿|ชอษผtY 8ทcŸฤ=>[๒ถ}<๓€๕Ÿ๗ ๏ภฺุคด<ญะูวฏ็๘TaV&๔รถณ™2๙-–ฤูออ'"๒ฃ&gXะ6T—6ญุpRŽ’น๛๗h@—ซปบะฉา}ำไฅJ žศูฏ็Gc๘ŸIu0.…šm(ZeฺฅดK,@ฦŠGร#ืฌ$/d๛ทmะข3“O.;r๗กแ8ฯi์Ž๐ฎถŒฏ๗๖ฒๅ‰ๆCะ{GE7๓๔hไฤ}”Ÿ[xM*฿๛อŸYZใทotoฟ๘C€=;็\–ฤoH?'OWฆ(ท ๕‡”๏J.€ฑo7o@ˆcsW†Uฝ&ฝq๐›ซษŠช‚U_ทqTm_8์๑จqBฟ‰S\^;|์ฃU…f#’ฃบ;5?สฯฮ?๑ฬฺห5c\G˜๊%‹ฏ์gท๒ฏ‡E;–ํฯ์พฬ€C๏91]™’x~~๚ฮ…ๅ—X@™งะป๏Qํฑ…cvŸ(‚^=ดษK†Mู›g์W‹๒2/ไe^8sๆใขน =!j๑ๅโš`ีทrิWฅ%_Zฒ}yWก๖ภ่ฐ˜KฦฏฮL ๐uŽฺฑgผจบ<หu#jyŽ,`t ฃ2œV$ปฐwFด{–wจบœMBๆuฐ9i2}Wo@™)3h›“&ำ‡z๒K2ถ–ฑณ^›wEvซไ๖ฎหิีำ๕ะ—๐Xf7+‡?z๋kฟl๋ั๋อฆ๔z@งฬห‘ิ{bFaดLั•3yW’.ๅ}ทyฒ?oช >™V:ฉTt=๓ย๕ฬ '๙yeOชm๙กqcๆ%ฎˆœดƒซฦ%;pอ–น.๋Bu|ๆเ/ฮฉ‹OR็]Iฮป’|๖Bi9k2ืืไฌjชปญY๔hXiไš•คฎ]๔ทร%\(5q™ฉh๓ทฎ”|แฤNฎ๖Hผ[Q8็„ฯ3™2hๆ+nๆ+ํlูำ'ภต๐t1szDyฺRฺ{Š{ญ๊แ€_gน<๑ง;:๗rต5ๆโ๊่เ๊ึlcิฑธlณ@๕ื-มพ{•C\€ๆ๏พั;?t,ฤฮ๐G‹ภ-~๊ฌปํ”plๆ8Rุ{zุร*ˆ/I๗N=F๖๔บ ห็์อc&งญ฿yไเ/ซ#[๓ํ•oงoศูฏึ^ึh๒๖s–m\ฟlๆ€– ๕นŸN(๐œ[: Œ5วk๒–ปs^ตyV{]ฦถ8%fษœ#œ >๛นm'”@๕ๅt้ไ'€[ฒL%]ๆน1ำ[2™€R–ฃ€&ๅฆBxฝ7คคฏ๏/ธOู—~้๛AีJB๏๑ูขCฆkู{ส‚ ณyq 6็˜ˆฏ4N* †ฬ[ป๎E‡6aจฯ.YpPmI”ป/IV`D>žทhมฬญน๊Bmฅๅซฃบศ~ๅ95มVธc๓W๓†ฟ- >ท|ํ…gัGฐสณ{wํc๚฿ฑLu  ซ\ป’ผ@ํ฿ึ3ฐเ~๊ฏ๙ๅ†žฦ'Ž=4l๗”ญตฏ…ง ๒๗|~rสจsOฑ›MœำโฒWiๆi›—ฒlrโฦรw “ฐมQ.ภึ‹๖ฝ\mขคEว>=ๆใซW5฿qิ*฿ๆๆJlอu™เ!.ศบq<๎๊‰3ฒฺxp‰!vXลŸŸ[0[vUg็ภท๒r๒าp๒pl €q๔t5ž๋ไ้๐ฑCs1ไงไe‘Mjpหถํ&๛I๘ŠR7$ฦD‹™-๛Sภ.๘Ccž–์sทฺVซU๏[เึคบำ‹ฟต๏ดh๊C‹gV`ฺ‰yฯŸ ษิ˜แ—]›ƒผใ‡คŸL๖Qณ<ŽQฯBืทํ๙#ะ(ีzˆ:}ฑอฯ{B‡9ฟŒ฿”]ซCก;3ณš<+-[ฌj‚;@ง&ทาฦ์Uสผ[zˆtษี•ำญฃฟใE]สy๐žH–™รƒZ]˜™๓``›์Ky hำึญšir}ลR…?T[ž &k7dาW6บ+“น}ฤœ๏็†๒Dดh{อKfK \ๆํน]g‹ ษUk'๛p๘ตsรเ฿ๆ€M}Bั+RT]€ไฎK,ฆ๕คVˆุ่DืgกBKซ ํuฅAัฮ~nBธyฟeญษZภ™ัLMว5>Evh๕ยCeพj้แ-จผโ*}4jfไZ”คNฺ`็ 4 ป๏_SX79<ค™=ํr๔ib!~ํ7สLˆ{จ๘ฯฝ&ุprฦชย‡N5์ฺห7rn3โˆ0;lB๚ฒญ๙Pค,ม้ง)puyGriดVื-:unฬx๙J†กCœบ‚๏ขNR')lทmlแ`ๅ “Rุ^=x:5gฎ๖tlฮ ๎:ฺ9x86gdRงfp_šPf๖กR›ิฤิvŒ6?้ ๘sอชwคoํยเ;ท-บูฒŒ๗๏UE๘&}๋7้[Ÿชืb2ั๒”~ฅsยWrŠ ฝภๆo/™๔3 ›ฏศ”๐ฝ3sๅ;€N)ป|lwNfๆ•ฬK๒ชžgฎ>ฯJฮดoํW7ไŠjภฐ๒‚<[w๒fŽ_`Yูeผe9ท‡v‘าพู{+'Gฉdๆ`ผ;๚<‰นษš฿ใณลพmฏvล๗ย๐ํ–$Ÿeq+GฆB๙Ypูร\‰๓€ฅs้9ฐ๕ท‹ฎ9Wไ€จบถกหนb๘ำปWงb{pƒ{uฺซ~r๗dA]4 5qจI๋ถกC{~<ำๅลy-ถ๒Gใ้๙ลnึพ]ๆเ้a นฒCา’s๏+5ืp๐tJe˜อJ)4†ุขฟ€#ภต5„๚› +๕2&็#โ๖ีโบคGnฉด›w0ำฅ&Ÿ๒0๑ฯค‚ฝญบฃฌ๘ถ‡˜แ:H<‘ๅแเ&^M ๔๋%vh!ถฝ#qฐ +H’šนr›ิฤิEg็œ> ิ;Hบปท8zJœ\ญฝู ๊npžš๖ื๓<`[ื•€EOc/4ั?ฮ๘~•:๏ …๖–€Ny|๕Œ•{K#‚ cธfฅqม๊๓ฌnอ๓ไ๙wrร…+ธuE&ฯน$ทvmิ๖สป$S 2”๏NOคหฏอ=>Kx“ฺ็๒๖@! ีVฌNVkธ ะิ`๖B!ฐ`ตZ-ภซฆmh3BQฉว Aƒ?ฑฐ๊๋ยoยข(ๅœธไ[,Pt๋สษํWNn์zL[4ฃทหำ_ซฦtZฟ:ิบ๋V–บvFฎII^˜๖ฯjถ€ฤฆยYฌฝAถU๗M๕นHuŸฺุ๓ห๖€•u†\[รฐŒqlิข‚ู‹™ฺ]๗Q‘ชrท†ฯุs€-(2q๎฿VŽึ]๎฿”B์{OFอ€GืไRกo/q#‰ง}กฏ=Vz#ซ\ณOาิ๕ล.ใV๖๒ต+นwVg๒DXtณ{uคีศMึ~ูป๙9#9P_8“?๏2_ฆญ|๓j@ะkลถ eื๚”dภฺ`“๗ถํŸ๊nฎT๒ธIs๖ๆฐ`šดู้?ขŸ7NŒธ:ณ8+ ช ๊๓ิWŠ,-›๕y :บแJ๒’^ศ+š์““|PวxซSพบˆณ^oฆๅาิเซ[Mฎo\\ญFซื๓L55สฑ}}“๊†Vญz7ใsฃำจ Wฟ์ี๕ะ3< ด*ญ^/(},Zรc๊W฿6์ํ ซRฉK3Q฿R—3uื…ฝ฿๘oFห/;{๐ฤ๑ —sิ,€ขœรณง๖ŒwฏA__ซ•ต}™GตาGฃFFฎAI๊ถืลา?๖Zส]„8vaŽH)(๓e`เ๗+]€ย_Oดช \ด H8<;{เNฉ๏kธ๋GE:หย šG†ง0หก‹ -‰R<™๋ะฐdํ(อสฎฑะ๚หฉ๒“ณเๅ‰f!-\ PšUtM|!อCZบุธq*NM3”กั€oฺ๗๒ด…ฎH๚๋ีƒGาt„nํ7ฮืช›ตฬจ;ํGโŸsDฯึ ฺฝxUš้y{ึCญ.Tk๎ฮ•ž/๒vณต,งิ‰ื*332332๓ด€๊านำiสW3฿{ว฿Yภๆ]–WYฆj๓ฌๅ้ฦฟ €ขดC'๒[k4qwณ “]Phโ็„b’uq5)†ณศJ”8gR)ฺดƒ—ีล สŒ์Š.ธP\:ีน— ฏŒบนU|PZป1s๐P้4y'fฟu็V฿6ธ๎n{g:ซ,7ฆฟ๐dƒาีึ…Nv่๋ต฿|ฝvož ็sพŽ;h๔wšฎ_ษ|มท}y:F~>uuไฯซ:pาvl ษ7zŽoัยัฮม‘น“]q้_a–aYพg‹wฐEจ+PP˜%G~VaเะพE€#€"C‚aA„Nํsโ7ร[BMzFuโ้+[%ฦm#Tp๒ šฝ๔อโข;52๓๐สล[3‹ภดžป๙-žqY{iO์žOu๎งU็Šฬ"XญZ,ศณชกˆYณX–gหv~๖ฉ ฦอฏ%ภธyปแะeตบ€ภฏƒ{ฅฎธฉI+Ÿ๔ึ฿ฃEใซปถƒC็ชม^Y=เBh;7ดสฬsgฏำj@ฏ–es.<1g๏“!ํ์ ฯn๙๖Bธ ่็Sq[^‡mํ/œ+ย๕ญใ?มิแฺKWnฬd0~ยEะซชmฮ=ธลญฬaูKซวก๓Žณ๖Rื{oUwหOธ.\Š2n๙U ุ_RฑŸผใ&`ด%หํ^อžตฦย{ฏัจฉ‘k\’บkO…l]œ๛ส่F ืaภึ’S ฉ๔๎a#I—ฑ-(Œ;efๆ+)NQาฬ"วโฬd#‰ Œ๒ตภฆศฮX8ภ*Žว฿ํgว๚M\ด1พbวw>o้ห@W๐]฿รi๋ๆบp๗D\แ€/ฎำธ:ฺ#ฟมs0ลฉF—๛3!ฟ(บ…=฿ฮ`ณ๓f\C ‰ฃ€‚ไ์šGhช/ƒ†eL ๛€0Vชตk๗a`0฿ฺ›ญ.`๚แ™ศอœึSื.ะ~2๋เu๊?ฮ๚ใl้wL“Ž“พ™โวญR@>๙ผGๆ‡๓ิ็9ฟจไธเํฉs๚นhฃญๅ EP_๘nสฐ๋-๚บะjต† žศ8ัxiq๗ท๗Œ‹]Mž5นอถไษ๕n†9tเ๖๘{๓Z6มๅ[`๏ืฉ๕“3{'5)†฿ิQyŸlอ,Bั๕฿^อด๒฿๙|A๙ํ์๏{นงไ@๋q_Dš๑B?้•7๋เuV}a๋็ถšไ๛ๅ^ย๊†๓ภูŸณ๚R›wโปฯO.iฯฑOาีี…฿‡_ผ๓”ทŠฎ์๘|ฬ“3๏แQํ^ฐํ๋อ„Ÿ†‘Ÿฯ๖_}l9k๑ษ๐Y1ฐkัฅE‹.ฆ๊|ใปQ)RsV๚7ยฒ_อ๊ืˆqGฎG–ž"_๖๙Ÿบฅญผp\า1ยี.xnืเน%ว๏ง.J<(ฏป๋ภ_q‰ปtศ0ฎ.ฃVช„-างษญบCฉBชkฬ€”‚จ T@"€ข”WkQCี—Aฅ8žภJB8ŠG}+อvอ๓ฐฌEภ3“š&D็l฿ั๓ะึุƒฟeๆฉี,ำคI‘s›w†G,๛ู :ลl฿๖ฺตqrฎ+ีเ5yหป]แั=ฝ…ะ๕\ผถhัสธไ5ทIK๏NัSF ŽO˜wHไLRต ~ฮ‰ผพ๒เ•ผ"ึ5yZf„r,ษำฏญ7ฮ_`฿ฺญ ๔™ทX€iฮk‰…๕•)7ช๑=>ฺูท๗x๓มg6oŒฝ”ฃฬปฅfํฮMœ[๗>บ‡ฟ[˜‚žsพp่’์:-ฝEO฿Qฤ5ƒvœณ}ว;ฑทŸฝ’“wKหmโ์์ึ.rิ่Nฮ\Xฺ6๗!kท7ูฐ2๎์•๎~GG๏™ป5GˆซJu!่ด่วอmใ6๏9'ปฅVซYF :ท๎9j`'gOตพชํVถทnฦ7r๕ร:huEQ๒็>๘ตลจ’FŽถฌข(_qWz83nGAๅ‚z?๙๓๖2nEว”Ÿ{๗Zยีญ฿๘ห*งJucy฿ฉS๚‡84AStCช8๔Cๆ ้บฝ.ถp๏จ๙ใtql.Davกo2‹ทโi U‰ghูๅด๙ฉR`ณฒ ๑ฝBiึฃb[เQVbรZUPตe(:1๙คœภ!บข?ฅ7โง฿้>ญŸะ(4ฤ.-พe7๛ฌแ่+_โr!ๅrซV^ ^Iฎ{๋Vญ๊,๛œฝ‡ลŠฺพ๛cw2wญ๙๗ซ๔ด>Y{ถ lSE—h?ฆm#ุ6pภแไ/๑ฬะzฝqาวz<{แฎ|หฅ*ฮฐ!ฃAฤซษ?AAผrิอ?๑R๐ิ~c—š™๑ีl๑œส?u)$(u–yห)~›B=-9ไฤs)๔š$XDe๖ึs๔ะ=๔ งตฤ3l ื๋ซ^าoอAi๘GPํS}‘=ษฤห ‚ ศG ^นฦY?z๔ˆฬHฤ+……žกพ๎ƒz=yฤณะ๚2ฟล ่กX}Sฌ*๘oวm๐๏ฟ๗๘|— ˆW‡ฝgวmPuš&ึj จวั@ฏ‡=LฮšVu9ผื—๋๋๕8†%€ฺGM;ี\7vTบฺk mmmษึAผ"Cทo;9ฝYuฒฎม]~:ผรททแr๔ฦ>ื่ |ฏLO#`๐8ภใ‡๚Gื๏wํัฟๆ๒/vzSฃัๆๆสœœ6ด#'€ ˆ—[๘๏ปŸŸŸZCป5i\uโ˜>“Rฒ/ž™]ฯลŽำจžžแp๔ฦแWi‡ฬกั?Q—z_>ฐ๚วw๛O~ฟUs๗˜>“ชฮ ๚๗๙฿พ]p_๗€ฌmeีใO/vŽo ซีๆ์[ฑ๏Mก†LG<5เT้V๒๘;๕๙ฒฯฤ๊๓!•"™'‚ ž-w๎๐ฦoิ‘[P‘zdt’|‚ โูj;>ืุ0&K\ืญ๊“y ‚ ˆชตŸรฑPXXเ7^ฏฬเXฟšคŠSh๔dTœdž ‚จ™๖;8๕พฐฐะ0๋T&4Uhผแณ h๔_sแทฤtd^‚ ข2ํทฑฑ-ั~……w 1€ื_oTัฐ(ค_yšr_‘[-•YŒ,IAิX๛-๑,ื{๓#~“ƒ$ต~KŽAD9ํทตตupp0๛maaกZ]ฝPQเ-9Rrไ฿"ํ/wค๊?ษ ‚ ชิzCiิ๊BตZmึจvX_ํŸ†#$ึieŸษ ‚ ,ัz๕๊ ‚jSชีj•ชŒ`ญ๐W๖ูx„๔ษBํ7+ีzd^‚ ยD๛๋ … ำซTj•JUโT/๙ำฌ๐ำา?Kตฟjฝฏฺ-‡CฺOA˜j}kดฟฤ(จ่‹Kี2_7@Uต—|ฎ(;“Rf&2.Aฤ+ฎ๕๋ …ยœซRฉ.0เp8(๛fe*z$ี Mต฿์มี7๎ฎ`.ทชฏEAผิฏ_ฟ~๚5>ฟ{๘๐a9ึ—Sw?ภฌ๖—?HRd•๖—๛เมƒ๛๗[2 @ม3boช๚๚ฒ†#‚ ˆำ)Sฃั?ฬ ๙/๔ฆฮAAผ0ขfxูe6d—ชพ-k/’‚ โ’น2ฒU๖งhi๔_ฉOP๒ฒอ๚A/วXทไี’jย“)r‚ ˆN8ๆ๖ช#๙/oฆR็p๔ำะŸ ‚xI›’#$ๆ-UQ๓ษ ‚ ^$-Pvฃ_ำxฝ๘gฦ?*cโ }‚ ˆt@‹ oฐ“ืส? ‚ ˆQณH+ก๘‰J7 ‚ ˆฤ(•ณโน›W"U/™~ไ— ‚xั†ฒ•้ซ๗(@AผะCŠmศ:0๛? ‚ โ๙ส–“9ืฤvA๑BkษฅะT?A๑ฒ๊ษ?AAผr[MA/“~‘“@AผršE๒o๔า?A๑้ษ? ๗ ‚ ˆWNัH ‚ โ•ƒไŸ ‚ ^9๊‘ ‚ ˆ็“ฉ7๕}ทyRS;™Rผ๊nƒฮฮืีัี๓ewๆ็\>›wฏ ๊{85Œ๊ๅ?[k““|Cธๅ฿Ž๏ตŒ๏XAsฏ(k๏:Eธฟษ?AA<[qฝ๛iฮŽุ้Šืฑ!นซป๘๛‘าป"šล๗ทk`]2๑ ำ้ภๅrษQ!๊ ๛{ฤi\‰|ปa๑ัปEใVp\9/ ๙ขY‘ŒไŸฐคGVœ฿ท๋p|rzถฒ@ฃ_ิิEไ๊โา}Pฯ@q๙Ž:ๅหถรจ€ Y‘ธฉ๛ห‘K—uŠฆฮŽ฿6XXM๚'bŸ”๙m‡๏V˜nซ_ูนBŠ์ๅ=o>ท[๒บ|œŸ0๒(kแ\Gํ˜ฉfU๚ใ“= ;6"hz‚ู“žX์,vq้ะศ๎ฒ_jฒ๖ํฎSฉ2…–5$ Ebฑจฉ‡พฝร=๙๔Dz๚ฟืa;บŸ‰จhdฟ๊]ตwฎ[๔?ฎๅษH‰jธ?โR•ษ!U^–*/+5๙๐ฮŸV|ฐtลฤŽdงสีืgฺ™ธกMษ$ฌV!ฯTศ3SNุผoฺk‡zป ต‡ฮMV•MฌRสTJ™4๕ฬแŸฟ๗์;k๕์ฎอ)$@ผx๗น‚y็•ๆ๗OรzณŸะฏ์<ฝ[~-ุ ปŸญ~@ภํ่+Œ้ล๓จ_ษ@์cณวธ:|๐“ญUษH‰ชFืvŒ:7MUy U๊O##•฿ล. '€ฐUโ๒q‹ฯ เศฺ6iqฒชชไฺฌ}ำ‡๊˜ใ+;[ศปq๓าI\อฮ๑๒p๐VณฆOBU@<v=์รฦMํ:ถ{ญม็~ฟ3r%‹Yอข QBีซ”ิถ‰}t+Nอ๗ฟนwAŒ7฿6็ธปpน๎mูฆpW8ฆ5ทt ฟaƒ>ํX›ŒไŸจ\ฅ_3ี~ž{H˜ฤ]ฬhณRฯ>’ฆ0ฤx•วงฯ ๖ะ็u„>]บฉTภ๕taž็’๚~ใฎ(ำ –ญdม)-€๑Ÿบแำ “แ5#tiชณ.}หD,Y1ิ0dื้ด…์ิฯ›๖fฮ‡b฿ฆ““z๒‘พใ—ฌ’ษก๗G๔๔qsu*Eล}›ึ•NP]uT็Y ๗นฤ {๖.P–นš‡ฑlhืฮ]รC_kุsขนWด4™ๅบ5น4ๅu'ภ?ฉ5฿๏ึ฿ู่Ž ๐฿Žํ๙๎r?›๎ผศี๘บ{^๒_ม[ิ#๒อ.นwjืxQฺ๕)9š)k5_DดโEดz-ยอฮนaM’Ÿ”ํิnพ๘N$ฏ*ŠุลปrKค?เำMkG๘พz๖:aิ/#"—งh@›ธisVŸižฏคฤ]gญ์๚B””๏๊ใ[N น€QŽฎๅฟฌM_žศ3ะวซ๔๏เ๐๎ผรถๆDุฬ)z†(ฒฒี%ƒ–ฉ๋ใi๔)šบz๘†tํ8rะ4-(Oษ˜%๑ฑdฤ[ฏ฿ธi๘ำ๏๙873ฮฝจ S/ง฿ปw๗พรGNœhdT@ŠuH@w๗ล|^'[ฏOP๊l‘=nไ๛fŒk้V7ฮํŒ>Vด์๗ข์ <*fWฟมงSšwLgCช๖xฮƒ็8_ุผๅฦฌ฿›c\mญKh์ยpชฐฎๆŽ฿า่UEบms[2Œ[jขธฎC—~|,bI& yงๅำ<]ช (Lุต~วฑ”Œผ›-ภใ‹}B#?ิก์ B]ยฒ฿f่€ืs๖บแยคอซ7ล&สnjภ;{๔™0yoล@ฐ&cฯฦ_๖ฦงe)ดZp…bg฿๖}†TU\Bต'บŒTรrณะ๏.ฎ 7ฤd๒ž๑๙eฦใบำcƒ&2$˜wvKว K๙สฏฐหX๊ตŸ;ข9ฬ”9vีบุำ™ื4เ‹}:๙ๅไๆ๚๔sฒ1 -PiVSb*‘wg๙;owD‡Ui‡ตP W๊เรญN๛็/]}๏};ป฿้ํฮๅ†๘cu.๑ย๎}‡U๊ยUkื4rXว๖m้q'๊„†ผE๏พvq็?f฿mิฎฃซ]วVผˆVvฮ†ภพ๚์?M๛ห>ญขซ๐6เQ‰>๚ฟ ๓r๏“?ศnห๎N^)ฟ8ึeSk[+“มฃmใMๆ๛฿๛]’W”k)iŠโฯฎ}G„››tmฺ๗ำiนวฎ:vqu9j26Oธ0QmrHซ’ง’งฺน)dาš5๚”\Dง•งddF™MSy[c˜€Jž™ ฯL8zz^์บHW“์S–๘d›T[r€Uษ3Oษ3Oํ๘eศส็‡Uถ _ๆŽิL`e็ณn dฆหKŠ)KฯEธ!ถ‘Vฉ๖ ๓ฏํ|‡โุฤ1‡•ฅทvJ>||าšุ๙/๋Z .ฯDพนB!ฐ L;$ี๙Jส๊;7x้ษ„yฦฯ| ตฟูฤ13&U๏ุพmว๖mืmz>้ท๏ูp„yย8˜ๆ! ๊ฦำ6›]๓ฝ<]ป๗/ฆ๎<ฏ†]ƒ c›ญrทloIฏjWažฟ^}w39฿ํ?ํึนVbล‡ผ`ใ์j๏์jaผ1นหvช'ด~ำืยdฯซeIŸ9šฌิ๕ถฏฤ ๅ Ÿ`กะล~2naชถ’oี ซวๅ๏ุ6ธขก>ผdน™3ดษ –๋ฒกซQ%s;z›ิ(ฬ O$r„๚ฆRห`๓โฆŒไoู9Mb^;šถ๗wEf.(ณค HฤtูY&ฒ’žก‚ง@AvFฑWไผหใ:xึผ@ูกoทฅึMˆBง~ฬzWt6แTxGŽUX๘ํ s|, ฐG'ต<๚ขด+UJbiป ๙:„๙0‰i†า–่nY‡๖๏ๆ$๑iZ์ r๙| Mฑnใƒ๖/7ณไเี์œ๓IฟจิH:ฟmp ฦŽŠjุะ๎ุษ3[cw{yธ9 ๔ุา@P฿ ศหี=cสMัg฿|p_งzpMW?~]`ืงฃ]ŸŽ็ษ Gฎ+๘z็ั_ =ฬ[๕pฑ~_žI&๒rู†๕ฬฬปืgœ๊ไhŽ฿ใ๕ฉเู6ชไ?ผพ–&{NmK{?sด*“˜ฌ—KmณำYพผD๛…ํฆฎ฿w้ช๔฿Ž8ฉ]๑จ\›ฒj๙Iูณy’w็ธ็ศ™“;~œZ"นlสฑt1๛CซพO1”—q|๔๒ฉวO%ž4gHšทy๑ฎ›•ฮตsˆศ๘1+%ำP„kฉฆฎJ–Tฆ]zJ^ฑU‚ƒ\อ็ื4ฐOไเพผ’๘‚มƒ"ร<ฬPXฦตด๗์;srวwฃผKฮP$หzูZ”Nฃศ8ดxโ๒ิโvล๓้เŽƒgอ๋,2ฎ™dี)๛พš1fX่’6]}— ถป˜ซัYp#'N฿๘Kagื fฦ$ำƒ๓—ฎ9—๘ี์?ฎfฑu๛๎ ำพฬ+^ O๗–†ฅ๔ฬVเฺจOcMป๕^ฒฮ4/หyPป†Qญ๊ภ_๊ ไม;‹)–6gW;๗†ภรว4ด์ฮน›๖๗นJsHฯ^š72๙?sWต‹๒g ัŒX๘[™ภฃ๔๘หnขQ+๛Ž๕-OFฃข๙/(U?SฉhีษIล๙9^ฟnŒ!ฮw๘p/xท๋jh“bใ5แสซคฐ๏าMs‚ G›Fอ›™า}iญa˜ฌPb@sfoข1{๑เYณ…ฃdฤาQวBWหฐปbs‡N3/ุ๎แ‚-๛ิุŒิ,t ‚&+[ ^?U๖ฅธ๏ˆฦชv๐อ>฿๘u+พ=p>[]โuj•ฒฅ,ๅ๔-#j7|๖ฌ !โ*Lr๔ฤi๛๖,‰๙ง^–ปป\ฒ{๗๎ฯ_บ๚๋ๅ๓ ษข๔๙์E็“~ุท‹ฑ[4Q๚uม-นโC ‚š2ฏืt;มo7้๊1QรEั ^ยšyAฌแE4ญ๎bฮฟีถ๏Dฟ๎ ๕ข฿sฺฑ๔ึืKฏ]”๐‚œlไํ—๊i*ุำห๋yพฝฤ+ๅ7ฆ~ป*‰kw~=<๏๚อ{ีznใF฿o๔บ5ษH ๓ยไศ+ขฒบZๆ&Kษ.ŽหŒ^V6›๗ธzA ฐ้ฉ™่\ฎ$A‚M<พซD„ำ2ใˆาP0iZzq๖|ญlฯฏ๒าขซx<ใZ๕ผ๔T\อฎเ๚†๙๐๖ัPeฆ็"ศU–"e0’Aร™ฏbNณPศฒ4๐าeฆ+‹KๆS{u๖ํj2# ๖tๅมtะYnr&เำM“หF'฿E้ช‚็ ฦใƒ5“M ศm6q]ุDFžž˜–ž’z1[–•ง*๖Xe๒†1}/N๚y‡fอžwใฆJ]hX๎Wrp๋๖]†‚฿v:ุณˆม8—๘›!ฅsณฆ~ฯ็าeค^–šžKUำภUฟˆท˜๊๋œฒm>lœˆฮฯบพ๑vฃโD๕LŸ๐ึ๋‡ 6^ดE๖๘AรzฮN•๏ วด.~CX๘๚‘น๕ฟl•iืฎ]ภE4]T๙ถ?จ฿เำ)-"ฎ.;ญฝ˜ฎพAร๚ฮNฏM่)ฌ“ตษH ณ๒/ไ/ศ‚๒šจอJ4ช$–เ่โR>'GgW! ำไZ•J”3ๅถฏธผFฅ*ฮžอฺท`ฦ>๓ฅPไ*๓ นกพฬ™€bถf 2ณดะ<ภ?๋Žำ™`e)ู่ฉK5ฦไ๏.ตWฆอ05ฺ6@่dๆu8F\žณoทฑ3?๏๊eฎx\พKPw— ๎ƒฦ€ๆฆ4๙๐ึ’WY้ท_๊ปn€นฆ˜v9@@›า๕LWณsTjใKcGE#K๖€{๗๎•ค๔๒pป๔i$„ต4ไ๖้/๎SušFvco6ฆชLชKPึยMญ…ตO๖zปfตซไปึ"๕zษซ ฿3ภ‰†Aถ2ฅxA\ytฉ›็0*ขว yQ ˆนUชQ˜AวZด/ฝF[y๎เp$d`ณeHฝบ‰โ~6ฤ๖๓โFฮํ;bxwwWฑ€ซำชฒ“;6m>]<ฒ…จd‹^็fM/_ฦฟ๗ฬ๐๖ํQ์%4,ูธรYด์Ÿ H‰โž9๐ณี3๒†.1์ธญ<9ncr\ลt<๏ฉ+ฦzU9ํฯ๏้ดi1†W•ษ ‡w_X!—ภษŸ๖ฌแ„บธไFOE›ผp@ุzGจnสี%‰ฐด ’ชW&๘„๐โŽ–œมxถ7†"<™}iliA;[ &ิษwOฏ•H:O๘0˜ฆ *ส๗ใYCโวลๆUXeสฮ);อฆ๕ŸQe[•ๆ๒/Ÿ4๚ฝAฟ5ึ_eค]–xซ&3A<{(๘ผtอ^Q฿ํZั“Wi ฦ%bษๆาŸiฉ*–ฐ๖ปฉ• ฐ!3พ[7ธๆป ๑?๛na„ธx†A%—e•j?ใฺw๕ฎ•ซ“^ฎo˜ทษส{— โ}z๘&สภ ๓ท`MฝK—n๎ลนiณN๏Ž;*ำPƒ2_yม๓cฟPๅเ[่ฝvใาJC/– T…Wณs*~๙ม8รฟ๗ฝด“ ฿!eDฑy๗๙ฺ8นoืแ๘ดtนฒ@ฅใ EM]Dฎb฿ฐAร\,ฑ๓}ฦl9ฺ%~ืๆ_ฅdไTiY†';๛tญ๘“?5)gฟeว๛ฤฐ๋PbFฎJญใ …"WI็ศaฝร=-šSเt๖e’ „พ%{นx๒~ส5.๚ท๔•ฟๆฎ^ง]พโHฦ5•šรซๆI/1ŽมำถL9v๘HาEนRกPจดเ E"ฑPไึ;ฒ{@ำ*อ๗Zร†‚฿>Ÿ๔ž‡ซ๋—ใ่‰ำ๗๎ ฌ:‹ ˆ:‚ร>๏ผmฝ^o๚กไ฿R่๕?6e๘ฟวs็ท–อฉฏ2*๕๔˜…๗๏?ะง๛ภพ=ห พœ>ัTๆฏf็ฬ_บภไOฦะ่Ÿ ž I[ทrทฑฑแp86#666เp8‡รกั?AVF„‚่๗}ใฯ{๖yํต†ยK๗๐๙r๚Dรำ ซู9+ืฎะ!๘mา~‚xN ๙'ยj:ถo›wใๆฑ“gถn฿}5๛จศ†๕ๅ๛ปw์ไรฯ4๛ŸธdO@‚ H ‚x!‰~oฃP๐s์๎ดหาดหR6’€6Gก ูฤืoผw~ฺei๊e้ฝ{๗ ใ~า~‚ ๙'โe ๛;›5ฝ๏p–์ƒP1Pเ0ฐoŽํ’น‚ไŸ ˆ—/ท˜ny7nžKผpฦอผฟ€ง{KGกภ +ษ?A/!ฮอš:›ˆ ˆ็ฺ๖‡ ‚ H ‚ ‚ ๙'‚ ‚ไŸ ‚ ’‚ ‚ ^(๊]Vฺพ‚ท]nฯ”์๙_๒ {s ื๋ก/๚Ÿฃฝkใึ’š A๑\#n๒f5๒๏ู๘UดKฑ๚รDกื›@cใŸ >ฦ?๔ฆ$A๑โCม‚ ‚xูP฿#๙'‚ ˆWŒ‚"’‚ ‚xละ="๙'‚ ‚ไŸ ‚ H ‚ ‚ ๙'‚ ‚ไŸ ‚ ’‚ ‚ H ‚ ‚ ๙'ฌ@บฌ“—คe๔ฎs_jŽŒm้%i;'๕)F“}๒‡๙c‡๔iะสท}งC฿ฟ,Vชzฑ,zuUŸ–^H๋๚:ŠอC$-ƒ>;2ถสZูP“ฑg๑ฤ๗๛๗iะ*จ[ฤ‘c๏Oื›ตฌ“—คe—๙้ๆOึœาพฅWภ๛;T ~ูาKา2hโ!ณGสถ^’6_คพฌ}รำjษต'{AIห.หฎึi๏งู?ยKาฒยญ‚:G๔9}k๊Mษษ๑[yIZMI"…!๙'ชง aูภ๐มใV๏>•‘งแŠš{ˆ •ฅœูญำ่_Jปoจbฃ%-ฝ$ท*ศh/&uXƒ ๓{‡›๑๓™tW,๑๒@™q๊็˜A๚|™ ฯ=\e๒!ณชฆK;œจใำณณฐ๔ ๖ฬย%Iš—ฮVDM`žฅy7็แfvฺ%ฃ""ื\ี‘H kl _ณMช„ŒZฝ\ส๏็๗ˆy๖|b๒ๆY] H\>b๊›Uบ๐ซฒษŒuE][๘Iๅฏ93uw–Vิe๖Ž ็w๎ฐnำ–_ŽŸ฿ฟ0BฬๆลM]pHภฃG˜3 ?% ฅืะอeg–/ฮ้า๋๕นZXต': ฅWŸๅYฅE94: ฅ—คี๘cฅSๆYk"Š—YY\ƒีฺฐl;๓\;rฬ0=pโถธ_ึ๔3Œก]บts7—KP™‰‚๐Ÿ†๐ุฌoฤZ3_m-zฆสญฟ3ดาุอ๘5cM-Snm‚.{ฯโ‰{tnิญ๗่ฯึ'(t๑[yตŸ˜`พ้ิฎฌzไญn-–_‚-ศŽ32ขC@+฿ฮCฦ~น#ใฉญุ(P(Y ฉซˆK*A๒rำL4|A\ŠV่›ฏอุปd /N›๔Š=S๚๖žบํpถN( ๒` Rvว ๏๛ึ์สeYv1`ผƒQ—๕ํธA_S รปป2ส”} ไ—kVสข ™h่‘2x]ธฎŸ ืบปฎฅ……Aผ‹ฉลู๊2ค,6#)ซ๘~nฆฆๅžaํ-อฟZVฮCป๐ภf|5(|่๔U๛ฯgฉ*kuอป๗๖ฌO?’ฌจ๙/ฮบฯผษ ›ถ|๎ฑ‚งL™q?ฏฅธฎ]—ฮ๎-„์๐พŒJz.€…ฎ|ุ;3!1น์ฺ–<-๖จผˆ™ณ*€/ฑt”;ุดุฃe†ย’Qำ*ื~@sfsผ.็%ื+๊ำHgพ*ฯtœแ5หุcเ๚๔o/ดึbCY|!ดŸ2T๛ญปฺ๋โฺ.ฤฌ4้ขnfคๅย=rTจฐdgแ‰‚ร]ญษต*š‡๏9tแ๘ไ=฿-™4ฐ‹3Oซ”žฝbๆธฎบ๘ม4,๎าปL?ุ๋)นูศ .รgr…๚๐โฏ.๊žฺ3eIิGJ=กง‡ะj Gม๑ุD-tชซ)I Gwm—™ฐzไ๎พQฦ๚mึGฒ$S,s_เZbr.˜@ณ‘’|%Ÿฮ{๗ฬะปcV๕9๐น๗)ึ๚"ึบ%ืฒตX~ žภ2uย ้Ex Nž‘…AŽOชฅ ๎:ฅi้4Š๔} &.93†็z๒ห ๒H_F„แ‹~ัใซฏ>sxใ‚ร†็* ๘ำศ@!€Vซ <ณb๎sใfถ’ž=ะƒู›š™ ี ฉ์ัQฌอ2|ญV๐…ผ๒น๓…<€ีju@ษธ‚Wๅำซำj5@SqตฑD^U๙X`(K/ฤๆIsฃ๖ฮ]ฒ<แ›ร~๎Zƒป~xv๗๎</‡D•ม2>มพ|Ÿœ’&ฅ๋บŠSาเ๕๓ฑJxxึ•O•Ÿv“q๎b๒ึW่าว+คOdท/{9 ธ้bิ—Aฦ๑่€€eาTC_~๊จฌŠศIvA“ง๕8=้๐Ž›๛๎๓žฉส"fUฤงดZภ–๓๓„<@]๙ฏeKฎmkฑ^€๏๊oฤovž]!„๚ไพLpt๗ูIหฝm“{z๓›๖H+}งiทแBhฟšnvEบ.{๓โ]ตšฉv๕๑ไอ8vพlฎ%&็อญx^=|\Eโ้ฒป‹gฏานm๙-ฬฤCYx!ž{‡@>€ฆƒgM๓aุิฏb~UYืl™ท•uฒ๓R๋ฃฤ\. ฒONLห…s‡!€ๆB(SโสfIื:Ž‹บ๛z0ะ&ว&škiJ…ŠL_ w์—"ุษ๘cาสึWฌฝ๎ณฆ0lฦ๗1ๅ–ฆVŸฉสถี>S5O 71น์+ู็SีOไ5}ไญh-–_B›ถทlี๋œR๑๐ฌ}๏Wญ#ฤeVงกืI_ ธร๛:ฒŸ|Yๆวu 2bงL:ƒ…K๏แํ ข|๏ฦŸVฬ]wาไำศีภัi๔‰์๎ ี๑…s•พธฌษXล๔…ซweUแ๓ƒ็-์-†:aๆฐ๗Wพfšึ้‘ฃVdWŒeVptUผึ&€๊๘Š%งKอiค›ฆo”๑ ณfตฎchd{ไปcLึ‘<๒ๆ ตฮ%ุำา\,0”ีr>{ฌ'ฃMXฝภ๘๛4–5—วไ'K6ดัคปlฏาJ ?(ฬ‡aำึo”A่๏k˜ƒ๐่ฤCึŽ๏/j฿nื E๐ร‡uB{๊‹Qำwd˜ฎ๖า)’–O]žภยต[Ÿ2ำ๊Žม(,6ณโn?Uธฬ‘ณวz2ฺ”ฃษUOIY`jŸฉ8๔]#3ศณ5[SrฤZถe‘•ตPำGŠึb๙%ิ‡/8ค0&าๅ›>๗€ ‚žรB๙O ๗ซV@รZc=ๆfฎZฎJƒW ?๚๏;yอ<ลธงวDžY.5๒ U”ซต#ŠXฒ๖S_C๛๗žฺwศ}วว…หกฮอ–e)ตEฬeœำ๕๚x้L้ธ…GงGคn๒๕qv„:+5-Wห“LZ=MRีSฤ™ห Œโ@สฦI]7๒ฤฮb†Uศๅ - ž๗่ k:yw่ัส<† ไ]6Q็2๘ณfด‘4cit๖ค-๛&Eคธ๛zˆธ*yzFž–u™mํป:ยณg%Dyx๕ฐN๛ƒ<PdžฯPฒยะีณ;[ดภPึ_ศ๓yƒ๗๚๙ฬย%IV๓-ปk฿~ฝ]๗$]2,"14PEFาE…จg7๗ฝGญฒ08†z"MชlTYฎwˆ„9œจีย;<ฐrqต,๊๕?dึบชฑซ’๗ฮถwฑ@์*3Pฉ”7•jŒธฌี“หญื†w๓ILVi-‹—ฦ“_ตฟฯฦผชSUo[ ž)๋˜=T๔๔SK†E์๐๑uแiไ้*Qไป[vสชX4PณZจ๑#oykฑ๔ํBTว'๕H q๊”Yูy*–็9l้ฬ๎่{wืิ•ทwCHI@ BซDญ%ธ ึ ลvจKตตฏZ‹U_w๋ดvjตต:ขํŒ]ีy;VjญuฉE[๊Ze,.ฒ`kะ$$@H๒‘ไQAž๏' แๆ“s—็œs๏ 6ท=ก€ˆŠฒsีำ.ฺะคoŽ[ฐงษ Dใ฿Yข˜จhว _ๅ๑FšบiBื;c€i๘Oฟร?ํ\RLธ_Q˜“[จˆ#Gฟดv๗Og>xฑแ.8แศต๗ผ3qฐฏ&/์้ นr~Ÿง_‰?ฐ๏ำอ6ฤž๕แk'สG—~๖๔e)?xโชm‰{็ผฦญ?uz{+ฑƒy๒ผ_ำrคjQ๘๘yœณ|ฐ(โ…9“วฒ โoM}R$ฯ=}๘dZs_๔"Œ\}0๑หฟฤF‰ิy.fษ๘!1qุ—ธ๕…ึ฿จ๋๛์ฆc๛?}et_šš|6K&ˆšบ๚ภ๗›&ดฺ๊b6ี๊๑#\’แ๏ำศY~jพไ= Kว‡ ไi'žธ(=ปq฿—ซž๚tฬ ฑ 55LDพรž &"รฉ๚N๐ CวnD 5อrlฺฒ3ทtl๛ชฉC‹๊ขฌฉš๏ฟ๓ฤฉ„)กMฏ 3)ŠGฤzไ฿ด ะ%ซ_ฒu+‹บeฑOตi`๋ม๑ฯ๒T็žป!ธ๏หื๓x-_วืถตะๆ]žึb{‚ˆแฃŸ;gำ๗฿ใ#ฯษHอืxJb3๑ภ_#…๖9๚ู ๗%’`หUณWeนi๏^๙…Œซ ค1•*}ุzฝ‰้฿†Ÿu:รO†t:]Eyyxฐ?ถฐ$/ผzKํท๙ฌ๛“I“ถ โS๖L๗Duม}w่bจฐ๎‡acฤแpˆa"†aะ๛h?ก_ˆ)๛ำ฿) |zฝลwฉ/LพE>แd?t ˆ๛ ŸใCาค5+dษีjy้ีธเฏ๓y!ำ}g6ภCƒK์‹๕ืลห^pbอ”†๋7yโฑ๋5ง๑๐ศ6ง฿‰๑y้ฉ9ทŠd$๒ํใแ+Dลโเ'G>#ŽD=@ว„sˆ@โ€๘ฤ? ๑ˆ@โภ โ_‡P^!/ฝWกRฉQvˆJ@w8า’šew!Oิร0จปะ๋๕ืoๆQๆฅ์ŽYB>๏้้แีำ๑฿๛55J๏ž\"=ƒ๘ฐS๖๋๕ =๙DXว,กVซญชช..)ฉชฎ้๋฿๑฿ต”ซ๐่๎ๆศu`†ˆcˆ4ฺ๕๊;r9„ฎฎnฬ+น[๚€ว™Jฅv๒๒`‡รabู`ฎ?้t:๊เM"Žƒƒท—Wษปˆ.วcภp8d?€โ_฿Y๎n๋ๆฺM๕‡๊/๑฿0 C†์Gุๅจชำ้FO๚฿'rเ80๔ ˆฑ™šC…ดฟ๗_Dํ ๙O๔เ ‰๘๏0ธกŠ๐ท ฿๚‡์€.ฝŠคาŒฬั |}|˜=ึ}๖งUkืU)Dไ*ผทnmฬ่QบV0๘r๔ๅฑjํ:kทMŸ|ด้“ฎVญ]งท฿ฬ๕8๗6คญ*T2qWQW๘ฌฅ‡Fฒผำ4/dI`่}ฅu]<*๋”ลบ(ฺ9Y8r}ึƒ(N๓ห’_ทrn์ศแaก’ภPIXิุลวeณ<ํ๏็ี•ๅไๆfdfšI?ญR(fฬˆ‹=*f๔จ3โชŠคŽšO““›น>#กCR_๒ูฯ๊เๅฏถkš๑Hภใฃบศ๚๊d฿ฟ๕๊š Q๘่ษc๛๙Djงˆ๗pห๔ถรNณพ์ุo…#IG฿]ปฎ้๋1ฃFf3jไ‡~ผzอ฿Mฐ~ฺI'ดฃœˆhฟผMฑ“3ฆ3ท}ำ<$…‡7”zLX;ฅo๛ฆi‘์ฏ/ฬฆNY?q}uŒ1Šณ/((|๙og๘u”2ฺูป๒๚z(แOt๘H’้๙ห/ฟ$tฃGE๛๘๘fๅใใsุ‘ณ?ฅ(*๒*ล7฿|kzใฤถฦƒG?ข‡ใ y;L๓บ’๙Y…ฏ~mฟ]ำ:„ณ.์ื๙/’๙Jยฐพ:ล๚zX๙_๏น ใ~k™ีˆ๖}ฬgฦ๔้†็ น‡ฃวใ๚็ศภPIเ‹_ๅํ~ลl˜๔až]๋๕gทฝฆฝ; 4r๑q™q•„EMš๓๗ถ~›ทฝพl๎๏y;bC%–[ค1๑F”$p๒Ž›ญ~X7ชŽ— ˆด๙hศNฝฮG“ถy๎viศุหŸ—g๎ุ๖7ๆŠ๖'อ๔ฏo”ฦผถึ7๕ะ฿^=—9#ง๖!ๆ7ฉฒ™†x|"E๒†น9Šˆq‹&ˆdY‡ฟ;๘้ซEด;i~ฐiืฯ๚๛ป’ฉ3V/๑จeW“์|๋•<๕แ/ˆฺ๘๑ไ็๖_)2๕มึOSด๏ตน๏ฅ๓Cž_Zค@–~dอฌ5" ๑ษ|บiซใkˆิY‰›ๆX›?ซ๚aฑ.ุ๗ว.lž›ฃˆze๑j~Q๊แ๏Ž}๚jํOš฿ช๙ศSสœRฯศgงฟ๎G๙็'%ผ๖Jึ;ป๗6”G~nๅ+sหฤ1qห— 9gv}๗ัยŒฌ๖o'""CEe}ฑ™fฦ๏ˆ|๑ง›฿^เ#ทz๋เ‹ฑ๗ร3๖Nฑ^๙;_{๕ฃห‚มS—พ5Xคษ9™๐ฺA“7ฉ<6—ล˜ถ"~Qษwg๐‡วฝ>ฦT'‰ศฎuศ๖ณณุ‰ว'M^โsีA ฿ู๔–Hฝ{ร†oฟ*2฿6Xm๓,ึ—ญ]pNม“L‹ํŠ๕eฏใFั}<3ทhมVMyย,K*U๚.๘จP๊ ๒my๖^uฝ๊บฒชZ™BS*W฿ญT•TชJ*”ล๗ช๏”UIeŠยRyมสลๅูW๓๔v•}ๅšJฅฎญญีjต:ฮฦิฉ๑CBย$sพบYJๅ‰Eƒร^๘๒†ๅ„ฟฌ๗UaK3ณ1qYq หRฅญ0xuŠส4QฺŠแกK’^ะ~๗ืEKาTVfธบHxภˆีฟด๘ฏ|<% dโ๚,U[ฆษŠ ๒fre 7โBCยฏNฑ6Ÿ”7„ฬ๙ๆn{๊งลu‘?$$นฏอgQ๚อฬ๐ษ๒”๊น๒ยาแ#ฬ๋™…฿?SH๘%'>*{˜๐€มK0UGึฦ่๐!K๊G_™ผtDxภˆ๘_ŒหJ^$ ณ๑’^ฏฏ<ฟ’๐€qŸ^ั๋๕ช‹$แšฦ~]จRW ^ฐ%=ถดHxภˆ๘Kz;—‡ีฒŒตด1:$O_ฝŸuุš๒ดผถห4-Luaลˆ๐€qŸh๋6฿์พรbฏ<ณผศ —ฟตุžฟ›?(@bช–OkŽ-ศŠoแ—:Nซีึึึ’๑฿*ฅบUiqณBย[๛ฎ€๐iqณZ๛.ำใ—Œฺ7\^ศปW^^QYY)—+Šชชช๊๊jฅRฉTฉT*•Zญฦเ็#3gบธแ“QDE๙๗้พ1เ)“MหโGN๎AŠหฉ ƒฅR“Fz๙fรฐ๏‹๏o4?าJฯc๐๚ž}๚จ–Wz๒Ÿ‰น‚˜ล %6Ls3ํื"D=?ฬิ9้;mFฏฌ ‹z›>ƒdๆ๕l[ึแSyไ๑๔ฬg`8?๕„ํ{–FีืSึ๑ณEไ๑๔ฬ˜†ฮ›0fzŒษ~>mึ๛ไ‡‡‘P$ไฯ7ธ/๑=„BRหj๖๋โยeyŒืฐ%xŽ™8าฒ lฏ๒ฐYึƒฌC{•ว@3ๅำย๘}"|ˆdeEmๆmํ_-๏๏ยแSฦ๛hาOl˜แูƒiั8ณฒฺ~Zshแ `ฺฏ๗๛`›”tt…MIIG(Aw>~ม>f; Ÿฯ'R฿ฏณfžAๆ7๙Š}ˆคERำ ƒฆ?฿‡—๓ี”gฆฮYนi_๒ๅยvEฌฮฺพ๕Œขฯ๔%fว฿ึL#+’y๘๚šU0(J ึEใznTฯถGoๆK‰C-Oล‘QOcๅ…๙ฒฆำˆC|ˆdyE ลๆ †w๐‰H`|ฮk๕บ๙๔5šๅ๛๛ZŒ์ฺญ<,–๕@๋ะNๅฉ฿6,ษ'หmฌ5ผํหฦฮœ=-ˆr์ปj|ก๐ฤ4ฯ๘iรฬc›ล๖cŸใ†ฌˆ6่‰๔๚ึ=ŒoิY๏Xต๘ฎ6>t”เ'ฤpท) Eอq>ŸODjตšŒ'๙}@ฒ#aืฉs‡ฟ:๘+โy„Œ[wฆ„ถแvโา“&ŒtaHงQ+4D|O‹" „ผNฐ.lีณMšˆx‚–&ื(ิšฆำ๐y"Zญ0[ฏ๋KญVฟQ๕๙D ป—‡อฒdฺซ<ฌถฑVl๓,๖/›a฿็ใถๆุ๎๔ๅ๏G๒)_b./.i<ˆ`๓#ูๅธกV๗;s&>7aโsอศงPTm–ฐโญ7;i’pฏ–tลตh‘๑Ž C๓หุฐ#"=้tฦ็:CใLGŠJ ๏Jฅถ๎5ุ,~ยะqol๗ษ‹ฒา>zhฯพรfศxง&yถrQฉŸ}žFAo-‰ถu>฿PH‹=Tฎy4๊นe<ŸGคQจ[=Zฃ โ ๙ญ›ny]ิ‡EๅซKๅ๗ฃiBซŠ’><,๛ืtqง‰<ˆคy2"ำ๒ฌ<ึ]h๛›แ`๖’ฌฐˆE=็4_ฯอLลม"บ{>‘ูแณ4ํศ™| ‰™แIDBฑฟa55๔ีyูR"ฑ+๋ศๆบ๐๕!’^5ฟ4[~+OFdvฮ^ๅaฑฌZ‡v*ปฯฮr›gณฑฺ฿…#gฦ๚žHฺwA&L;Y$๖ึXQป>@;Ž"‘วƒ3า3ท}ฑ”๎ฆ๘7d๎ตk๔7Mผhั‚u๔ั]ข๎-L€s&>‘ขๅ›i’N›๎UงฝPFขA#MG์qใงพ›ฆn:^ๆ๛ฦ้-}Eฑํi๘ƒใฆ๛'%์{๑t O––ด3gd๐ฏgฬ:…iGฮ็ŽŽ๊ฌ|"’ž;|ภุ-๓:}„oซ๊ว฿aใƒ?(y๓†ใผ้มMฮษ-sEม”ืไ’~_‹z>(ณฌg6u2gีิS‹๗ฟ;eV๚๔qมBuนฤค3๙‚๏-m8@Kล?v๎แwg9g๖˜`&็ุฎฝgิ>/ญฺชอ‹อบ7=๘dยแทงะœ้<๛ศพlัศ`:#3+พสรjYlุฉู”‡vhใณณุๆYํ_l๖w#ษฏ ฺฒ๊็4๒™eyั[v:n๐วฑ๋m8หz|ุ๘FฉTzงธุ๐ฺc>>>D๔—ฟ,3dฟอๅถrL๑๖ ทzSŽ๚ฝร฿n™วxŠƒ"$‚ถL36พ๛lรฮ|‰‚ž~gำF๓๏ดแGฎ฿ทฝ๏งŸ๏ฝ๙จLกแ |ลOŽo๕๒ย[sPŸlวUฟค…~ุLCฤ^ž๐ฉzๆc‰’่2|b|ย”ข•งฮ˜ 7ฌ9a9’ž๙tƒa^ฬฦษ#.กfS?ถ๘ฯhcแบอวVฮ๛–พมฃพทัw๛๓iyณซฉ4j"๑ดOWัŽ๗ถฐ3_ม๗ ฮŠ๘Vwpไ฿๗†lฒ๋์หญ6iรIDAT–u฿ix฿เกom[บะ"H„#฿฿} x๓‡‰IV}E<?ษฤ๘๗O—ดชวn]P๐๒m›hๆƒว7ฏ9.K&ฎJ˜#_w๖L‘ฦๅaนฌY‡ถหร~;ด•6ทy–๋‹ล^ฯsฬ”‘๋2ฮˆง4น่e™ํr โ฿๏iาG๊ฬŸทhแ"z๋ญe๓,ชฎฎ๊์1มิจบโืšทyEhp Kr๙๗๋A}{;8p8ร0 รt”:J[ิ์๏<฿9ž4ณSล›m“ŸHwไฬŠP่ ฺดอทfWgฏžไ๛‰ฝ/ˆ:l-˜ฺ—.‡…ตn๏Ÿ?QๆฅKอH•Jฅw๎1๖{ฬะ๛'ขk๓,ชฎฎ h๎สfื ฒqๅส๏C"ุฑฮ_L}",ศ,ฦta‡C ร1 ƒ?pPิ€ฟ๘โ๓6W ฺ๚ๆ—;D›?ujหบ๏4<ฑไูU/Ÿ‚๔l๓ํŽ์=kึLข ๑kใ7Ž๋ร์>ฃพํ78€๔๓š ๊ ~โืญ๑ฬ;ฯEC3ฟ๛Ÿ฿฿ํ„ๅEอ\Lฌ?่:์ฑอณุ฿…/lฝ๒Bืชูถ<ถ/ธvO‡๘hเ*p%ข ทํˆ€‡ฌ c , ข*E๋nํ9rฤย :ฯุ?โะ0๘ษว>๐ะ ฤ?<๊ h฿๙ะๅ ๗rฯ_฿Iส‰? ๙ฦฉ'ฆบ.ญV‹๘๏’๑Ozดบฌ๊๊gพำ^(ฮ?dN|žRฉrํๆ‚€={V๚ฮัณาjต%%%=]‹งG๗โา2gg'ใŸb;eOญV[Sฃ,..๎ๆโ๋1/ฤืา]XUญผUp็ฑž"''ž‡ร0 ช ๑O:Žˆฎ{‡-ง3฿ษป็ƒฯ~ฤ‡ะหืซผB~WvOฅึ 6์Hเ*$ข๐ะ@TโฟƒŽtwขเมภษfฤ? ๑ˆ@โ€๘ฤ? ๑ˆ@โˆ@โ€๘ฤ? ๑ˆ@โ€๘€fpซชชปเวึ๋๕ๆOL้๕ฤ^ฏำ้ ฏำ้t55Jl4ะ้ใ฿ำร๑฿(๕๕?7าaฃ€ฮƒˆ@โ€๘ฤ?t \TAkQT‚Jฐ#''พJฅF= ;ดว}ฝP ะ‘พuซๅ 0๘ะๅ €๘ฤ? ๑ˆ@โ€๘ฤ? ๑ˆ@ ๑ˆ@โะฑpQCฎิ๗“ส’ฦฏ2nฮ\๏^่ˆ๎+Fบ๖q์ˆฯ9™๗ฤ!5๑%>eฏบ9=ธ%ซ7ฎฯ[UHDฬŸๆ๕;iญท“Uะmk•ฺฦ|œ>~ฯฉˆR๖^sNKDไ็๙ปข`"๚M๊๑ฯสส&oเ;;๔๑ๆK๚นฏyฮ-ุ. ๗vฆฏTึๆ^ซJุ_ถ^zD† ้ิJmn~อSา'Vฑญ๕่€0nŽ\"าซไuท•zcไ”Tพผ•—i่•KฮŽม^<••_h‹Kดฆnฝ“#๛•B*e]ฑ\gQWฟน๓^์;=๚ ช๑ํร[๑บ๘moใชJeโ~้ซ5‘บPถไg๗ไกุฏY ๒ฮŒท๒rqzมภํU†๘๏”็47›3โฏyำฉจงๅๆญ,ปฆ#"u~Ebqำ๚@€8น9ฯš๏WQ–ฟ,_OคOIฎศฺ0 *VlN–งจฒ‹kUŽŽมฝx’~ยืGบป~_ทํใ๋KฎI^g>ห'""ๅ›+om)ณ|ฑV1๙ยค:"rX๐fฟQV๒KาตDไ๙ุมAฺm๛๏nนข.ฎๅx{;ลŽ้๙P'w[%ทU6S”ชœ“mIWๅ”ีVิ2๎nŽมฎ žํ1ญWฃร—.'KR~ช Nๅศsช uZ\ททสp™฿ซ๛8W๗ึฮมลy้ิ๎ สr‰ˆ4ู2"ฤ? ภ๘ณฦt[นฝJMD…ี)•ข`7"ขœŸ ฦ๏ญบ]W?•R“zE“zฅ*!Yู2฿…"ntŸฎฉ‰(็šฒโYพ;UชฒหŒ๏ศนฆ2พXPm˜sทX1Qeฑ*5_KDn.๗^>Y™$7ผC{ปฐ:แ๋S๙gฦuk!5Y”ˆˆd•“7J๋gND๚’2uI™:%ฃโ๋Y}ŽๅีฟฎMIผ=๎฿jใ{]mjF้˜๊จ6ิeญjๅ%)Jรา้Wฝ‡ดํฺ=กฃ7QฎกกSงร…Vะั`‹xธ๛w‹จ๏Tงฉ ๎พhสWg^ดD8ฑฯ8†-ฏZ๒ฏปฟิ๗s้MDD๊eถแ5Y๕ณU+s แ๕mCCC-ฺ2+ฏT&ษ//งhžiŒ๖ฯฒฤสๆ๛์สFค=rจุ˜ฮผ่An3นH„ŒแW?’ฅ'ฃŠ฿Š_6eฟณcT?ง g†สjRหZ[‘ฺ”….งเ)ฮwi[{ํช2ีญ๚รฌทGZ@๏๎7G/.Q้ŠๅZ":๒Cyฎ!_ฝŽ-๓‰u#"*ํฮ˜VไQIE|บ่๘P.๕rv.฿ฅ$’+)ฆhoบuMีeส์Jโฆหฮ7^ๆาคO๏8s๑ใ;"xDT‘{gุ'นDTงLษื-Œฐ{ZถeซU&^ั๑๚๋Ÿˆ9DD5ใ–฿๙ฑŽHฎฮชคhiพ>*7Œีป๙{{]4ฤ…จVตqหํUืtญชลโt้ห็Œm ฏA>;FZ5~ -.ฎŠ฿[yุqŽลศ? ๗๗…#cบ:ฝฒVOต5GŒษว™๘œWl}ฏ๛‰ž๏2์๕๚”,ฅŠˆcล†ด&ต@Kคอ.ฌ%"ท~.""MJŽจ6ซภ07^tฟฦ‰ศ}a|ั=จ๛$รS}Eพ™td_6๋ณพ_์๗b฿bใมJ%ฏ+1ž2ะซ O*ซ๗็kaึTัรฅŽNoOํิช:”5œ๒'ฏ๎฿ฬด&ตUหV]ๅ.ผส]x•๛็k~ค ฦ!๊”(ึ( ๗๗Cญ^Uk`จLซ4๖='…9˜M็แฬฯจVฉหิทHLั>]Q้Sฏi(R—R 'ขˆˆ’ฒš์2]๖ตZซR 7ฏ ]b{5^ฒปะัl<€ใๅlซจญ(oHHW\Ps*๙njž*ป@•Uขm-=ลส[†'ยn“ฤfฏ๗D หrๅ,+Pตqkร)๗ๆ๗Œถรื๕0ฝ%^฿Ovvย๖ ˆธ/*k๋๛ฤoกีึUwqwหฎง“3ื‰HMDJใ4A‚ Rๅ•(oษ([NDŽ—(?†ส๔ท Uลล5†๋๚นZน˜ฎต1ูšฒฉŠ+^ขdWก๙>ร'ฝy @ฅิg่์`yƒงทˆUkS๖ญช?ๅ=ี๗ํ^ญmธ๏Ÿˆศ‘ำ):ข๛ยH'wlœ€๘€๛ค"ฟบz=งจ^DJฎ;ัm"ชำVินX„ฅ๑4>—cL&๏nัฅนeDี)ื(‹ˆธ่^Abeซี5)๙ช""&*ฬลYGึeซUฎื]%DD|กำคAnฑaฮัbํส•๛•fฎC}ฃAkyนกฎDษชDลYล/Ÿำž{ ๒ู†S–๗tx8๗๐ะ|\m์๛u‹v#๒เ๗6ถํ•Gฎh-บนYJร”|o~นm$Q:แœZMDฝบI)ุฯษˆไส-้ฦwฤ๖sฐCaู—-ฏkรฉxกม๗ฟ™ึcึฮ}jk‡บท“๑{ไ5ง ฬS]qŠอ•ฒ๒ธฦ+ษร›8.ิฤ?ttชJีื; Vๆฎ™่1๎มDไ่2-ศpAŸ.้‡ปง๊;ลนw฿ฬางŒ0—ฯ‰Šp6|นOj~-y๕r๎CDฝœ#ˆˆิฉ†™{u‹ถK๏–uูT๒๚มbœLM„ฃ๗RอPิmข—ฑดmฟ,หp Dญj๓๒\›…ฑ8ๅฯ_ฑุ+—้Aื€ม€ฮืื฿ฒๅๆ.ใืหื–7\`ฯ๗}lฦ_‡Iƒฎ”๎ฃฟถๆOaฮ๎JUสu•ํ๎k"v๗~‚ชNญฯZ‰แ๒~7'‰ฅิw ฝ‚๖)?ฒ9ypฝ ง ไ“ืkขล\*ฎ>•ฏm2Cฌ็บ}ฐฝบ’จ๒Z้ฐ••Czq+ ”ูrฝํฒ\)‹/4MVwไ‹#Vฺ+Nk^๗e๑ฝฟˆธ๔%eš’ฆ่ๅ๖อb‘)กฤ=ฟŸU;๋ชDคิ˜กi˜ิร๕หW{Z|™›Kดฅgส‹๎eไ็G๛1[ส๊/ˆ“๐ํ๕ุ–M์ฑ"LพไŠŽˆ*Kj’Jˆˆ้&์“'OQQ]ฎLG"๒Ž๔ูqๅึ‹ฉ%"ต\“rECDไฬ rิุบ๒฿์ขย:mn‰ึฺ4œโZlu€๘€„แ;s๚๔rŽŽ่พbคk‹‹๐9มC{eŠNVXฌษ1|ฏพ7Oๆ๚HAใ๏ี'ฤ ‡ แ'tb<๛อ1\GDฤuž$ถในB–eใ-œื้‡ป[ฒ•9•Lฑs์Sžk†:ฆ์ีTไ้‰(๗?Š[Oธ๕!"โNšํŸัฏteฒ<ตXGnผ(แย็?Hท๋‰˜`gl*–วฝ^฿?ถ้Sž˜m@DzฝNง3d๘Oงำ•฿+‹ฑ@Gvb๊aA‡acฤแpˆa"†ap้@—ƒ๘่rธฅe]๐cณ'ฝ^O†มา้t• 1ถ่์๑๏้ัฟ’ฒอ็H‡:;  ม ร0จx๔ ๑ฯN—ผ=ีBc0บ\f!ะๅ๒ ๑ะๅ ›…ณ๐จๆโ฿ ๛ภฃ—eิHฃz1]ดเ‘์มjวf•™žฃ.ศฌ&ง‹W ›ิ7>วญะน่๕อๅzอVYž@ื:๏€๑y}W๑o‡1€ฮ•Yˆ–jJ฿จัDไเเ€Š€ŽSŸVฆำ#ูดš^o๘™หๅjตZTtXZญ–หๅ2Mบตๆ้†๘o๕oจ,ฝพi;ภษูIกจBu@‡ฅP(๘|พ•ิฏ?๑,๓ฝe๑ซซซ5 ๊ : ตZฃRฉธ\๓lำ[ือ6KฆืBAiฉฌขขRงรญ€ะQ่tบ๒๒Š{๗ส\บuณ™eฎœ๔๚ๆoๅ7ึl๓“ก@ฅR๋t:Œt9IŸ%^ๅ|eŸฎ๕มy<žƒวััฑ›ซ+c๕"6ฝžฬ^แb[iฺ h2ฐrK€ษษIฯืพ ‘^ฏง๚ท›ฯDo ธœ๙\ฎฃ#฿ฅ[7ืG'›=7œล7=g†,;๘{M.D[ 0o˜r[฿te่๕ฆ ี1ึfย0Œพษ๚C;เพะว\ฆณŽฦ€1ผs฿$๛-บซ†„j๒EvˆV ‚&}ฃ€กวฯ49`šD>พ;เแ1D†แ/,{Jะฐภ’ซY?]ส“฿ฝซt่แ้๕xŸ'GŒ๕q6oืXไˆy๐“ูุ~รMfรM3๑oฝะ่2@๓๎ป๕ฏoXน~ะlz๔,ษณg๗ก[*"'wวตŠ๒ปyฟอหน6vหฃsl๖}ชวw์ฉธŽˆ\$ฯหJฮป‘฿ฯUK ้ฦx๒ีึCCลG๓ฏ]ฟšWื7จaiUWฒ ๊งฐbg๋6–ล–e์๚fj"ฎ“‡GwญัšUนG๖~‘q็งิย1“๛6๗พงTขๅvš๔๒„งs""eA๚žoN็–0ฤ0Žฑ๗o{.มผv#?๛mH`}l+ndiI๚”˜ฯแดฝุw>~๐f W>mึณป;‘2?;๗žฝ^ม0 ‡ฉข_qoVนึนwLค๑""eแน฿']ฯู่RŸนz˜'ˆY/ด๑๘ฟ้ว†s'C๏Ÿํ@ใ€แ.หf—ีป,n0๏๚ฃPOญr~ฟoศ˜'{๐๊_G๕๋™๑Ky•ฆŽaฌwU7ฮfษ๒Š5eจง๑5—ว๏ดฒพผTN ็Yฬ฿๙‰๐Ž7๒rฝSืฏทaqŠœ_จcz„ ไ1ํ(๖ํ๓็๏hษkŒ‰ฦ'r฿IE๏ํฟฆlธnฏ"๋uใ๑โLื๕ป๔Š~y‚๔ใo3๓ำน3ะ; ˜๚๖Hำ?ๆk<mูSeฌ](€๘ท‘฿V5ฟฑ6“^Am˜s“Œ[$iิณฎธqr""า5๗ี๔7 ๊ˆ๑ีำbพx@ัฅณฒ†+ูฬฟ[hh@R^๎๕หu}‰จ"๋’TKnO๔k๎๖Vล–ๆๅU๓˜d€e!ป…†8\๛Mห ฉใZ)1~Q\,ว*ศL/๛ฃฐŠ๑4อ‘ฆ‰Nฆ อพภ๚”ˆZ-ด #MYdyณ€ล`UmUQa‘TZxซ DZzทจธชŽˆโฑญจฉชV1ไฺ]$l{‘o.•Yns.ฤ๋๙YEuŽT~=ฃศ3t๐ใ-vฺlฮถบฒŠ!ฎงปฐังเ‰zvgYe{ีี -ร๔๕เ5^œ‡งCีๅฒ†6 †ฬjF,ฒ็ญ๗ิ[n4ผอฺ`>cmๆ^Aƒภ˜กฅ™ปพ๚๑ท -qป๕|ทท8jpดใ•}{.#ฆน๗Lฝ&ฟ7ฦš้์ๆ/ ๏อฝž๗e้TqŸ’_/K‰๑แื|๚ณšญ–ฉ%††ืดŒ๙…yf็๎-ƒƒฯ็ร0ว๒‹ฟโcึใ'ซืXห~๔›m4z<๛›ป๘ŸแpšปАฑี ่’*๓้+•ށฃโfŽ้#4็—ๆ1Œ๑๚=ซƒฎ†ฉช,ฏโˆ…ฟQTศt๕๏mล‚ž tฬฯนz๕ๆsยฝห0~Q=9œ๖ป‡ภa๎Uศ+8œmyEรP๕‰n!—นSQVกๅ4บัก\Zษ ปป’ตฎฟE๊“ล—4ื0‡๘ทฐš6Zึ.๛หNtษ;Zโ|ฺ_h–|5ฒ{๒๚cฉ๕7๚๔ๅ^-/3M6่iOณ๙IณำdDฤ๏ฉc?ทพฤœky—าธ7dฤG ่มดทุ=๚:ฅ+ศพ,2สฌ5ืฟชf๔ cผ;ัล/ฐ'“s'/ํบ*$ฬ์+jo]ศฉึ3๎}ล=ฬพ`ึโ๋d,nIc๑ฤ9เoณ๒ฆ฿‘DMnฌค&Yกแ›‰8๕O๐ภ<๐ฐ๒ptrแี฿.ญญฑV‘wfวก\Q]JƒF p#*9๙ๅw้Rใ{๋คปvฅ%ใธx+็/ ๏อฅŠด“™wษ!`@?ท๖1`t”QษัoŽzฯ๘^ๅฉป]Q้๕Œแ{โ‰๒Œ&vึW]:”xๆถา๘ขฒเง]฿gศ๕\aร}ฉqฆX+ e5ฟŒ?ข‡ฺHำ i๎๗5šาjMขzZPr~๛ฦฃEZฎซฯใ^=ธuŠา’ๅu=%aย์*ฎ“gะิ3Phํสยค„ฏ“ฅuDไิ]$คชปๅ*r๊ฆJษฌบdูดว[9yึึGr๊ˆธ}gฎž1ะูลฎ-ฝฐkืkUD\WOa]eIนสQ;”{&๙š_฿fG—ขธผห™•uDฮ๎^Uๅฅ•ส:"๗ Y _ุรzตนŽซแษญฉใพอM†มรVOๅ…MlแE0็=rฦ[.?น[TpปUไ๋๓ฤ “‡ ๊ฎฬ{์ฤู฿ ษ•ืๅ.ฝ&-Y่แb๊•ย[ฅ๗ศตwฤ๐qฯF๙–คPฉ๏jล๖ๅๆ\ำ:E†บุ8vณ-ฏ็ˆyKzO>ž}๋๖ฒrWoษุQ“G๛^฿~ŠธNฮŽฆ&”L[ดฌ฿…ณ™ทo฿‘-็z๘๔ ?๑ฬ่~ฮ6๒ๆ่55๓ทะ๛g;`s`}€ฮL™๙ๅว;sน็.›ไx?t็๛๗ทฤ๖๖ŠgฺืYmแ•[า>ฝ|Zh+ ๗฿RตZžๆz/บ๛˜๗‹yZF๐ฤฐพ<;ศ๏|มถŸ*ผ&พฑ๘™ž ฏึฬผ\Aฮƒฝํออƒฑvืค๙+ธ๔ฏีีj~…ีื]‹ฟ๋๛‰๓7๊จGฤ ~v๋๙?้ลญ+9พ๗‡ฬๅ5ตT+/ฯฯ๘แฃฏ3๏‘(jHŸึ.‡Mสด>๘ฮ; 4ื๕g฿.€Žก๖ํวoW)TDNใF?nวรvฏ˜้3J๗$fd๎,ำ,xร_œ|žฝ–c3hฌN€๘oo# ๅฺวฅืYเTWง๘i๒„!B๛ฮผ๛ iฏ…Žพ๛อโขาช:ปฏปศฏo?a[FฺะŸlแ-ธ๔ฏ-Piะ‘ปฌ†KZj๕ šฺูCS:T0ฑ๘ฟ_5Žf<๔˜Gwึ5`wธ๑๑ˆ@โ€๘ฤ?<<๐%Ÿ}^™IENDฎB`‚singledispatch-json-0.4.0/doc-source/index.rst000066400000000000000000000067671423345565700214250ustar00rootroot00000000000000======= sdjson ======= .. start short_desc .. documentation-summary:: :meta: .. end short_desc Based on https://treyhunner.com/2013/09/singledispatch-json-serializer/ and Python's :mod:`json` module. .. start shields .. only:: html .. list-table:: :stub-columns: 1 :widths: 10 90 * - Docs - |docs| |docs_check| * - Tests - |actions_linux| |actions_windows| |actions_macos| |coveralls| * - PyPI - |pypi-version| |supported-versions| |supported-implementations| |wheel| * - Anaconda - |conda-version| |conda-platform| * - Activity - |commits-latest| |commits-since| |maintained| |pypi-downloads| * - QA - |codefactor| |actions_flake8| |actions_mypy| * - Other - |license| |language| |requires| .. |docs| rtfd-shield:: :project: singledispatch-json :alt: Documentation Build Status .. |docs_check| actions-shield:: :workflow: Docs Check :alt: Docs Check Status .. |actions_linux| actions-shield:: :workflow: Linux :alt: Linux Test Status .. |actions_windows| actions-shield:: :workflow: Windows :alt: Windows Test Status .. |actions_macos| actions-shield:: :workflow: macOS :alt: macOS Test Status .. |actions_flake8| actions-shield:: :workflow: Flake8 :alt: Flake8 Status .. |actions_mypy| actions-shield:: :workflow: mypy :alt: mypy status .. |requires| image:: https://dependency-dash.herokuapp.com/github/domdfcoding/singledispatch-json/badge.svg :target: https://dependency-dash.herokuapp.com/github/domdfcoding/singledispatch-json/ :alt: Requirements Status .. |coveralls| coveralls-shield:: :alt: Coverage .. |codefactor| codefactor-shield:: :alt: CodeFactor Grade .. |pypi-version| pypi-shield:: :project: sdjson :version: :alt: PyPI - Package Version .. |supported-versions| pypi-shield:: :project: sdjson :py-versions: :alt: PyPI - Supported Python Versions .. |supported-implementations| pypi-shield:: :project: sdjson :implementations: :alt: PyPI - Supported Implementations .. |wheel| pypi-shield:: :project: sdjson :wheel: :alt: PyPI - Wheel .. |conda-version| image:: https://img.shields.io/conda/v/domdfcoding/sdjson?logo=anaconda :target: https://anaconda.org/domdfcoding/sdjson :alt: Conda - Package Version .. |conda-platform| image:: https://img.shields.io/conda/pn/domdfcoding/sdjson?label=conda%7Cplatform :target: https://anaconda.org/domdfcoding/sdjson :alt: Conda - Platform .. |license| github-shield:: :license: :alt: License .. |language| github-shield:: :top-language: :alt: GitHub top language .. |commits-since| github-shield:: :commits-since: v0.4.0 :alt: GitHub commits since tagged version .. |commits-latest| github-shield:: :last-commit: :alt: GitHub last commit .. |maintained| maintained-shield:: 2022 :alt: Maintenance .. |pypi-downloads| pypi-shield:: :project: sdjson :downloads: month :alt: PyPI - Downloads .. end shields Installation ------------- .. start installation .. installation:: sdjson :pypi: :github: :anaconda: :conda-channels: conda-forge, domdfcoding .. end installation Contents ------------ .. html-section:: .. toctree:: :hidden: Home .. toctree:: :maxdepth: 3 API Reference contributing Source license .. sidebar-links:: :caption: Links :github: :pypi: sdjson .. start links .. only:: html View the :ref:`Function Index ` or browse the `Source Code <_modules/index.html>`__. :github:repo:`Browse the GitHub Repository ` .. end links singledispatch-json-0.4.0/doc-source/license.rst000066400000000000000000000002061423345565700217160ustar00rootroot00000000000000========= License ========= ``sdjson`` is licensed under the :choosealicense:`MIT` .. license-info:: MIT .. license:: :py: sdjson singledispatch-json-0.4.0/doc-source/not-found.png000066400000000000000000001352371423345565700221760ustar00rootroot00000000000000‰PNG  IHDR\rจf'ธzTXtRaw profile type exifxฺญœi’7’…ใs์pซู`Ž?฿C)Jขš”อˆMป*3w‹รƒ๎ฯ_๗_ืS4—Kณฺk๕—{๎q๐๓Ÿ๚๛3๘๖_๘๚๓O฿w฿๙š๘š>?hใ๋]ƒ๏—?๐ํ3ย๓๗}$ฺื…พ}๒ื“>9๒—ใ"๙~|?ไฏ ๕๓๙Kํึ~\๊ŒŸฏ๋๋…o)_ฟS{—~๗ใ7rc—vแU)ฦ“B๒๏ฯYA๚.“้u|ค”ŠใKH฿๎• ๙ำํฑม?nะO7฿u๗i๓ใ๘๚~๚ห^ึoQซ?A(?฿ทล?|p๚พข๘—Œ˜v;_ฟ๏v๏๙ศ•ญ_๕=exแdหำ{[ๅWใwแ๏ํ๊2?"8/?๙ตB‘จ\rุa„ฮ๛บยb‰9žุ๘ใ",๚žฅ{\DŒ(๊Wธฑฅžv2"ทโq„.ง๘}-แ}nŸท‚๑ษ;๐าธXเ-๘หง›_๎ฅ- พ๏๋Šส\–กศ้O^E@ยŠ[yํื๗ข๕?6ม๒ถูธมแ็็ณ„?r+ฝ8'^W๘๚ฉŠเฺบ[ฤg๐•„5๘c }44XyL9N"J‰›EฦœRฎEJ†ฯๆ=-ผืฦkิทม&QRMุ๔4Vฮ…iูศกQRษฅ”ZZ1Wz5ี\KญตUhฉๅVZmญY๋mXฒlลช53๋6z์ ,ฝ๖ึญ๗>Ftƒ\k๐๚มwfœiๆYfmฺ์s,าgๅUV]mู๊k์ธำ&vm๎{œเHq๒)งžv์๔3.นvำอทzต๏๘ตฏจํืฟˆZ๘ŠZ|‘าฺ๋๗จ๑]ืฺทKมIQฬˆXฬˆ7E€„ŽŠ™ทsTไ3฿ฃ๐,ฒศขุธ1B˜Oˆๅ†๏ฑ๛#rฟ7W์ทโ9งะDฮบฟวํ'Qโน๕"๖ฉBํฉOT??6\ด!R๘uฟำ\กŒ[ 5ณŠp๒่m๖ป๗ก๚าvฎ•[]ภเํ`c-ฅต[็๕sฐพ๋น%YeS@ฐE"t_ำn1ห—Y๊Xํ:ฟ@ิ[ไŽส -๊a฿39rAษ †ณ;์Ni|ศ˜ํฎ8)ุvู n›;๎ช๚+ลแบณZXe๎ณOn m•œรN๋ฌืf๋ถ๐ฎ<ต +šZxe็o “๏ฅฮm7ฐขูอ‹ ชmQZšlŠ‚นใlฉฌbหvˆ‡Pี•Mใ์rธอpษvGŸธ;ุ7ฟบ฿xaแถ<ทหงฤ0oจ—ิUงAkฅSl7wœb„iu?ูๆฺq๐uฐ์ไ8ฦ ฉIƒ]็;ฬฦ^๋ิศ$ทNฟ7-ส4๔uนวฝุีล~ฒำlM'๘๋ำ"gึ;j™‰xŽcกฒจz \Rลqฺ๔7sกส™mคk‘MccCฺท—ู๛โต‰eฯrWš๗$พMา๕yญฅๅท5๚ศlv<~๔iqJ?^า๙šษ้Chฺ G"ms3;ฒม'ป;+}œ%žก|โœ ?ห—:UQัqsอYฺขi’—ž่`฿ˆwำA/&uต)KDฐ>@้ซบฝห`8žห ,-ฉ4ุ"‡Xšี—฿๘I„ ฟฅk฿Z ๒wQๅQ"ฤapาไฆR=ขi&( D u๚Mผแท —ก%+][ `”CvZnร(ดฑ฿&_p9๘1tผศCชฆ‘ D45๎[ฒอKo0„JžอJุA:K€ArHkl‘]]ฤKฐŸ,ฤฮ`ต0Jให5‘๎๚ ภ‚‹#ut!50ๆIeญ3ยจoโ็2jjฎ@yA‰eMฝฒฬ<๕จ]”„•=“8C#'ศ,ฝิ” อฒ39Kjโ‘S9~7€~ฃpฒณ…B๓8Mสf๔•zˆ441*'3‹ธˆ$ฉ:D*\ˆาค[ุm9ธ๙ๅ8eAP}*Dงต๒Dกg—PXภ@ครw•qตEE6–ภฝฅขD้งƒ!%‰>ŽŠหดaใPยFzHd--คC่Zd^ฤ|ฯRจ้็!มš1}ุ^$@u@c5๒€G ;ฑUlีRเทภ๋ฅŠฃ4รฃเSไ‰ฬ`w(}ไฯญ"+ซ7ข&zฅdGRr&uo&ใฃœตLq๒‚IุAฦL หCเ๚ษwLM ๔ฏ5$JซSดmฅ~kุ2R "Zน\‚Ky•~ฟ-้6นช Qํx'ต-ิaO…€ ฅฌ๛ฌป>dฮฦใl=Šw‰L $,ๅก9สฺwบ}ูฎŽ$บD๘ยห<๚˜ ึเูcP ึ๔„้hุถ๘c"ัo”Ž †์+8f์Pb่6PกษGQ™๛ Tะ.D่ˆ„|๎(๓p;˜โจW*œFๆ-ด๋`ฟvCศ^0ถ$”ยs(บรrฎ๑=H(uX ๑ mr่Ÿ{U๎ค+บฤ์`ฝุz+,{?X|! FFŠvฬ้Eฅฬุ๋๒<ุ้,*ศฆb:Lขาษ>จƒจ–|: |0:—:‡“ู๖แ]€ชaปsS!ูไ%‚ำZู`6ะ˜ cรํ•‹W!Kภ=ฐyj%็ํ ƒHpฒ]ขUŸ Œห=แ๐@ญ์ืh๘าAํืกŸณร…-ำ~UŒEˆG๚๏U9ๅ5q… ‘CM๔.7‰ก9ศ]d๐ˆna%€2uYัเ฿ม๑c‚b@B€2ญฦํ+ˆไา,ร~ฉ Uฆ‰)C7!—rg‹Ns ล:"ŠP@พXw4Rsศ<๗ ฯ‹ขIุ€รีOย‚ๅ์•›)^8’>APGJ€x\์9ฒˆฺ$ยฌู๑Fร฿ภพ็(…าฤถ`—Xz๑8”ๅสP(๋dˆVJRิcB3๔จปK=)9ม/่จ J ช!mE๒…ภž q`ลU†$1-W”?ฏƒ:ย`i‹Ž ๆ"่œ‰คnMฉBภฌHjJฑ‚y ัฝpวAธ‹ฌุmp‚๏Qโ ฃ ๎:ทOnLnฒฟ–ž@ัhl•ด๐ปžษฆB‘] €t๐๋๐L@f#Ÿิ๚พปP#มุ†@ร7สน๒Rธๅค๐Dœ ฅณน็cX๓jqย“ค็‡ผว Z๋ผั‹?/ๅ๊€u`ุี{šแSฐCถ™g็(l |ขŒกs5Q^M่“ชC ž*;e t>ข0แ?ชzF=๖–€อuŸล; Iม5ฑ—^6‹ |ึํฉz๋vฬ๏hrŽdล+”แฯ‰]'Q3rIฬ3 ๛‹qล๓@|Jข‡X+—฿๚โ๛Pe&uฮ.7XI‹dจ—าKมฌ@2]กร`A[vศณB^aM‰?๐ถIDฬw wิ`แีีYžXฃŽแUVRsd4šŸa+กŠ,ษ๊ฎY2ศj‹6'มRณห๐าฑํ๙ั๑s(ฦ^Q˜ฤ})ฐV’๏‹ PฟKKBSแอ˜ iŽg>๙‰`ึแ>พ^ Lบฤฮ€ฮFฎฐส6ขŒLH๖ฯ๑๖,ญ!ป๋)ไ’`R‘VT˜Jฤค#cGฌภ—1H,ข.ย’Ž d4 ˆ0M’r˜1‡Lˆ`ธะ๘ม‡&0jช ฅ„ ผี(‚‘b~w๊c&œO๚ !SฏกPะ๛SŽ'ุSศ‘สžUe!J/ฟึšC€จp’ถC#ธt์ง[ฌ 7„ˆ๊` Xe›๑น.Pํ‘หกลp#+ 1N+นj(U&ผDฤ}?ุฅ’ฒP๘1ษ#@ub_ย)&นฟ|/“ิโA&Iู”‡@ ฒ”wณ…ผ‘ฏ”P@H้VีฝEŒพ ฦž๒—ฒแฮ &@,๐Sฤ่j2๛Bเ”๔ ๊ ศโxˆ ๋ิว;จณ‰ˆ€ย.wงŽ—ฟ๊ฑQ๗p dšถฬšแ>2kxผ03ŠิFm ฿ปงธ!ซญ>f‚5ŸF๒ ‰€‹T๑ V“ฦhs฿๋R‚xซ\†บcวฅ].คVPฟ ี‹pๆฎ}oฐ?ฆงhˆถ Pน ภ` ๗"\hาlU>…|C+ฬฮa>^[D(‘Nl-=๘9y†๚1~?6B๕Rทษั—ฮผ๊ ๐nCe\๒Ed@e‡}จเo–)๋๚6์๒š$๗ๆ N]ˆ#—ฺŠR0cฐJ5v4D›xกƒ;”=&ฑ ๅ$nไ+ปถก-J2)ƒo“Nn„IOjsQศฉชV*๘OŠ๎พ รูพFาeไเ้ขฃม?l›ภ „ ํโ˜@“มA˜%2oฝเ†7๑ี ค_ุOOj9œC‚bเ์็˜P@U}kgx?t5๋หื—นP๚ คdฦ€ฉ#บฬลW„lTา %)ฟlณe@4ฅŠ›€ฦ1{:mAR Wถฌ9ไเ๑ŠBˆj…#๘dัไๆ‹;ลปŽธƒุ'{Ÿผบ‰z๔:ฟ‹๙Vฬ$|บTP ๋]HqEค๊Ÿ 8<6ก ข฿ลoH8.ต“ี`ศชv๔•3๐a–Qั้Hqโิุ๕0u!ๅ‘Dฤ“;พ!ข ๛$Uxย๘+LTญล(a|ฦ($๖[่ฏฎ˜ัsSถบN„'6 utปG C๊ฟO‡Iก|*q.นG มฮ#Ÿ  Zร–e@ ุXiุE‚„ผรํจ„๓2ๅoภ8p.ศ๊`$ธษฑP’้$่}%ฉื8W'วตณE’๚rqโ•แYL7ฑ์ชห {ฝฒปษAิช‘r~>RujRไ™=|X‰”odง้ิ ^มำ'oณ๐๙]ํ–ฌญV๛iซ๋LI!‰ถ/๙L๕†ํ|ธ%ูกNช+xผ.คf(= n’C'ƒƒzยJฒส๊ชม0‚„|˜()ผl“ไ.0:V'Vค๐ฯ‡โ-@ดK‰™ฌรu@รEg[uฃค0Vภ AYฦˆR5ฏฟขjีŒ็b)yg&uฬb„฿ใ์F› คHˆ-ฤ๑ตฦx%ะ$จ7งŽKœ (H๒‚กŒMูา๑พ{RŒk‚ื5ณ vV 9ใAFRป`Yžtไ†$™•฿›ภ๒ฑjีกKŽไ๐ำ”H1'/7รrC๊F6œš?BA…0ฮฤr’ะXรฆฉ1ขจฉK)D~ฆเี๊๗@$x`า~b$u4 „,โโrศ-Td"zษุ€˜a/#ฃQi;o ง ใ^w‚๒\ธSด. บำ|๔8๋‡‰VPg่ํ7จ๛ฤBัธl}^ บฉCวุุVทฆCลแิฎซl!`KLยฑIt<†อp8ฅมnไะ!ฺ0E•พศRแจฐbผKืถฮuQƒขแ‹ฎ๎ฦึล{ๅGฎึzใธฐVgฒ้C™ใž)Vปน$o–D„ะ‘๕Qฆ˜=\-'JEB^๛ฃพmฤ๗%*แi™:ถร™_ะ๓เž๓ค˜EตvŒ3Bั้๗+ M}ฑยp& ‰?.ฑูจS—t0EGวƒcมึDเท๔ R-ธเJ h๑‰6($d“๐'l85Dˆ-จ>SE^–Q4{{HฑcSŽ„k–ฯ6hˆฌ๓ำดษ้,ๆ‘ฝTวX3!๘๒,,f%h8ฑฉถฏ้H]่ฌรฉ>œ ฆ๒ฐช‘ฆย๒@ WัJ†šIผ”p7ลอ%}ิ)ข ฒส„ชqเ'H งบZ€h ƒS ~กฏพบb-‚ิ#ŒนœSP“V*นb’ล๛๖ํป:#ส(ซแนp;~F„ิ† ิw–.Ožฺ„๋iฃjย‚บF•v˜ –Bฐภ^uŠ…%fไ๑E|5Ÿ๚ฦœŸฮฆ6ูuฑี"1˜^Pฆ์™้ฃf-šŸŸJ’๛I็ฦจ)งc›‹ีO้"t„ม#๕Œ+jจ0์…ๆ6๊๋+@๎ถ๊U›{ษ๓฿๐!Hย๔บ@“$2[}%nT~O'ณ ฬกฬน๗๙ฆut,; ปPซเ5—V—$ฦlืœฃด๏ยถw๔O /ขน็า”JuX˜ญWC|bŠBถ!9`C–ด}ฮ3ฑ2_ต1Xฤ‹โ๔ ŒEJˆpBภ•[ิูB๋N”Xu๗u2ำjDkฬ์โAIXu-‘๊OI’ฑ๘:V žะ)|น…’mMตˆะฌ‚eีะmฅ’Hšuุไ#…๔FvM)Ž#ำจ๎0๗›,ชS ฿ฺu^ง€h*ไพ‡:์‘>”ˆP ืา9ูœfk`hฆ Iงะ ู—ฅFจฑึม6V„Mชhกีืก,จฅ๙ฆ+ค,โ ๊๘“Wว“[‚PˆT-0]‡wศ{uะ—ิ5ฬห=;n่ท‡b๒GูฏI/๗ว—ธpฆiึRจ4ิมŠšXM๖~่ะช“๒|QV“m‚—อˆา4ูี๚ตr†ผ}ZY‡^่OระEv๘ ”ะ}ฅ#-`,นjูฝ^˜z+0้ฎ๏„ฉณไ!Lˆ›ฃ>ั๋# ฒษ4u๊‚า*อ‰ุฅF„ŠแxฒSfสk๊B5ฐ#([เ-bW#V๐œืง†ูฤ{6\-$šทฮะIแsLทิKIพ„9‰NฉQ) /โ•X่ฮหxำ'ฟSZY*ฅเš!Ntด‰ฃๆ‚`nา‰้N0ฬcw>%โ2pX„t DJ„“„ฺ(V…$ฐORp†vคHว%Z๗$b$ฯ‡bื‡p`ฉMัAๅ]ฎoj…Fน2’KT‹ฮ„ัผฃ่’ŽS‘ูฃ‹o8RใY0+ศg€้`~ฮ&ฉIษ1,Pฑบ`eŸฉวQi๐งX‘ไ›™t:‚ฉวS อjLˆ”ฯgฺๅužIyะุ‚z&ฺ*t+%B–ษขTnFใ†j#XŸ-kZPฏ๘‘ญ6ยV6:Iู๐K‹ณพD>z5uีŸ„]€<ฮw)gB>๚‰ทAzิ7#}H:ส’ิ`Pล H^บฃมI:ฎื-กz ํฎˆcjไj-Gฑถ=’ผ่ค็jฯ๋(D ค`*-cU–‘Y:ฅ$˜ ฯ˜„ ิๆถ!0bะฤuรDษsDฟP˜ {F–+้`ๆ~oเๅ๓•ญ“&ืPp ]€๕šBrยแa_`“ญC 1Hฺ๒kZธ8๛iv7!™4}†ˆสพREฐ-]6 WE๕giฝ cร7R‰%ุุDวNsฃหฏ:€$™Aี+ศHT y" HSคฏกภ=Pป&ถเpฬ> ojŒabคษศ ใN(J@ฺกม๊์ๅMผจฤกcj{#~ mฃ†ฌัEG฿@/†ULP2โ'ฎ]v๊‚โqjlจaLrษP๖ฏ…จฉ/'v;”Xะ< jq๒ ฟq#(Œ๏%ฯม‰X๒‘ใsค",V1—pT^0วzษN* šไศผ„ :“1ƒE„็•@คLuA.[ใŒืL!rC3=@]ˆ ไไภ‰€nˆ,ส•ฺ@พ๋่>๗#ฑ—ชEำƒฐ [„๑k่1ำXชฝ1ค โฐ‹}ฆ™4|๖Sฌไ˜ฝ,Nม7W;ะJ๎‡Uยะธฌ%•&?ีpdอlฅ๙ Z}วฌB"น ฆ k|ƒฏ9ื_ฏAฃฑXธ|ธ็e`=%š๒–nยถ่ไั8(ด‚bิƒฆ1ฃฆ^qท…–œžžhฺ:u2’†UฦQ&ˆ=:ขณ๘6tŒ฿Bั8(๓ ฐถ –Rนษ๘้จคhX^#U˜฿5ก ฑผฃ๓Uอโ0`•:šSงf€›ฟ}Gอ( F&&k– hQ ไๆD;9p;?utŒUืa/ลBNf^ะœ zaM•,่HฆŒLม6L๚ัA]œV‰“\†}’d2Nมะษ้)n,คดd,`ฯ\Uฯx’ุEส%ขVQEภๆั 2F>W™ณŒึขภอ8*ƒฃฑœs@Gืฑi2 Ÿ็ฺR๑Uƒ(ุหึศไaกd•vAQPaไx8ž i"8A™›ิฎDlRอ!ฯ๘5WCำkhbjx์"งจ้gจ‹žf๒Š$8:คgŒdDvื๔า›พ’แฬว(6I\ ‰ฐ†,ซYาฬฝ5eศิ 40฿4๒๚Tซฦฃล–'ูฝC๘ƒ+ˆ’sWิฤ‡HฅHๅฬD6ฒๅ๒๙แJ๋ฑŒŒฝDฬjด“ฑ–ณS„๊๏ซQ7ŸI@ ซPภ˜a๊ขCywๆคี›ส็บ:#ยๅ5%์๊ ๐ิฏบžH๘BSXuผ5_PAส›฿|5ถ๔ …\•W’แงOุฏน]หงฯŸp‘๒*[สu๑j๏š_XG2๘u>,ฌg0ะู(rOHI{ะตŠq ฟš”$8ทหต@4XQัร ฏF๖เ๚Hex{ เจฃs‰eไร๖ฒะœKด}@ลŒˆฏ๋I(a@Q๓ฑฒ‹๊ฒท๗๊แ4ุ=lฆF ถ๋อ3๛ถฏฮๆ†mืž๕š3•๖ํ)ห๋uฌ›Nหg_ฐศTณ=z5มัx–๕†14„คVHrbYฑูK33ฐลี—@ุ8ภ;:…7p…nƒ’Gฤ้มXj j+้Žป ‚ยsีี(ึI‘๔[ยุ7 Cท7าฅง0Xwi*‹Ÿ๊1ŒHก”ัิไm๊๎_ผ้อsŒE: )๐ลGกฌ‰„๖@U“ จ˜IOัฤ‡ๆฏmL`FN ํญ๋๎ะ*Sรj|ำ7ฝZํlb/นฃŸ™จ๒Au|ฑ[a๏ฉฤ๊ม'นˆนซฒ™**ผทa7‘ฯ๊๊„™|ˆ†'ณ›บสlƒ฿hนฅงYPzNO}lDƒLึ๎™~=^IวอH< ํ—Kฑ.›Uคงพžk–฿า1žวuส^‡b)ŸP!้lอ ๒v6k^{ ›ิ ฯ–&อ๐๔ฏLt†ใาฟyๅ๛ˆb‘ใ“ตŽˆw"ณฑํโ๘Š{๓๖zืQชŒTBPขสtHฮ'๒T๓™Mšี‰ฐฆc4๙ฌ™๙ป]ชuซ™ซฑๆธีˆ฿;๊‘ฐ <แ6แจi\๖R‡ำิ่ผ <๐Ÿi%๏[๊C๚ๆ MF๊ŒI“ZW“ง๙ <ฦ]Q‘ธ^…2ืe™&:qE็B ็๗ƒจ?{X—<”,W„มg๛ฃK’Flkšaลฎhฑ$ฒ`/ฆ;7‘ … T2 ว๚ใ‘^ฏ~J;’ํ0คtฬัsLgพqBiชsญ‰ถๆุP–Œœ'ึ„ทธY'่,๋ว๛$ํ‡ํPษภW็๐€๘ูบUฬ๎4ŒvหฌชinBญ^ๅิ ็ม๋‹9`มฤ$!ะ{ƒž4ฮ†ZS^ช bjณMไๅ'R9๏ƒRrlรTkA็5ฌใฤจT\špNฯE3๙๓0m%?LฏY pฑๅKใY3˜๏‰šŽฮ0ƒยZฏ?“5ญ>)ๅWคVษ–UJ๏ศฆŽบHฺa„น๊gu เ{้ ‚อ‹“งฤƒ\ฆn\าN้8๋Jx Vใึ XI]'yAภ0#๓P2Mศญ–B๖๙ุิ‰แ'Hค6uฃ z ผิ 0คgDฮณ"๏Y ,hFยk ะธyoอPคเภ^๕ฎฐฆ,BไU —bŠ6แืF๙๋jv_ๅฬฆลUฤฆy`>zJจQchยbํXtœ๓ฏม`*ŠT๛•Vแ‹ม$?๐๊Q6o*ฦTด2ˆQซhVโ๎+3Š8ƒืs„า;2c่๋ยซะž8ธาชFำฅIMฬ๒มBปื่ี้ณa๊~อ.,0v}จ}ม†l5๏3d+nฟฑy‘๘ฌไฅ>€0"๏ล˜ŽะXบ^๎๔๚w"่)@ำเ๐‘3ี7ภƒ๚cm(›Pฤz๐L-WŽ๐s]r[_ีดAe=—ฉœdจ๕ิ!—ˆ…ะฑยีƒ๏ุ็L๎ารW9˜ูAฦนD=+ช็๎ฆฃ e”Z๐ธkถพภกภ"๘ึA _์ฉ๏ญููyาแ๔้จฎ้กHAล…HEI่!ผU—zทUc oฺBGฏษกNy๊ฏ`&^ +๎เojิณ ˜SU^Nฯ ิืD๕เฤŒZP=ŸYืฎ็ 6ลทฝฆœ{:ใฒ?8ฯ๓ขช:CE;้ม–Œ์]ณj: สหโ๓=ภIฆ†…ซiุgHทNt๖€‰เ{›š[ผJ3S„-ฏCฃ้Bํฑ+ •แฤ฿>s]|a:๏•Œ‚_4ถสฮx`[๓3z๊hˆr)~›๒ƒ‡ฌผ๏มๆpp์ฮpฟ`ู|K—&‡ดY ะ฿zp‡ŽU!‰kำฑq›‡ฑ*า@#eื๑ฉr4&GIถgึแลั@ƒŸ‡฿ะ‚H”pโM›ฆBW‡HgE9#,๔ะฺ่K0•Jq F:F $f๕]ส  šZ,๖ผๆศ"ธฎ๋็‚h:žEฆๆ—ฆ,‚ึEร็ญŒ•ถ๖F๔๔้tึX๑k _^šš~ |๊<)฿5gV้A –ฆSQัšฦๅ5 ุEหฆ ˜คsZ‚71ิ{พ}ไฯd๔๊ฃ’Ž๐ ƒž฿ฆƒPิุ‡'—‡ว•sO†$ฑBน1ณุYI 0EWๅwฅ^I"NE$ถ๔๒z<ๅ ูธŒ;ศ๏"`p&35Xdz˜แˆ^(X[q่>- yNฝ"ฤ“ฆุ-W๎๘tจ ฏVฒ9xุ๊ฟ้y๎+, YๆHิึ๓0=’dš๖aXะะม2œๅuผ?พs“ŠVูจฮ๙ๅเ`ž๗ืึ9.“c}ธ}รXiขd”ษ6Vuก๔๔œฺŸง•จ๎ฑ๊ท‘ล“ฑษภeQ'ูบ฿ูฅPQอ‚&ช@=!`&ํก^ก๙šน_9Q'๒บางฺ5์6kไฃศ…จใ๚tๅ.a!fAH„ดาน2ผImฏช'‚5/LP?zIฯ?lRแึ lZ]0ิ@นš|h ^g-zข/W#tๅ๕hIf!่ฯlZFฯ๘m@้!V6Uผ ฏะ#ฑฎฤ๗B]€‡1ฏ”$•๘œxิค ?2]๖๘qัeŽe๒f&=Oฬ ล–;X)™๑$qLีtส็ฒซŒทk•šาบ'{a$ฏฏ,3fI,b "ศจกŒ lฤiีIฑฆ„ะ๕‹ไ’ษU†BŽTกAr`๐ป[ซ01๎%E@่ลq>†๐.ะฌ;ฮ๗ฑใ4O€เ3pฅที0IzฝญลŽ€พmเโบญษ{ภๅ0๐dHฆไJAš\กผŸั7ๅ€[ gอ๋ญตำ C]ฅn€ƒC`คHู๋>๏๎๎์ํ฿3ญ~ึอrฯไ}ฏbKGD—ป$บ pHYs  šœtIMEไ#ฆ๕? IDATxฺ์ฝwผีy6๚ฌูฝ๏}zั9Gฝ‰& H˜jภค๙&_ส็๋าntโ/๗๗/ฟ$ๆ`'พNฯ—›ๆc\0QD“PC]็่๔ถ{/k?ฆญ™YณฯFBุณ๐๖ัž=ณหฬผ๏๛ผฯg9หYฮr–ณœๅ,g9หYฮr–ณœๅ,g9หYฮr–ณœๅ,g9หYฮr–ณœๅ,g9หYฮr–ณœๅ,g9หYภEœSp๑ืฃ>๊ะษ๋ะ ก<"‚ส๕h(ข๒ศH˜0MH~ใwNฌณp9ญกกกWุ`€UสฃW๐๗j 8เ$€cSJ|๖ณŸญ8WยYŽธ๘ย๎p%€\ซ{ู๛ตjŽ8เE/‹ลS?ฐsมœๅ(€๗@่WธภํŠเG?_{ภษ„ภ็๓!‰ # ย๏๗ร็๓มํvƒI’F”Rิj5T*”J%”หe”หe‹E‹E4๗โgR?๐ฏ„o์ฝปไ\yGฬฎGyฤMนภo(฿uก๏แvปั๖๖vดตต!##รใ๑hB~ม’J)๊๕:jต …2™ ’ษ$Rฉฒู,’ษ$Šลโป๙i๘วมมมCฮเ(€Ÿ%kŸ๐k>`เBŽลbXฑbzzzะŽD"—หuIฟ?ฅ•J•J้tำำำ˜››รฬฬ ๆ็็มปะท|ภ)ฅ฿์g?pDฤQ?ญ‚฿ เ๗&ไx‚หๅraีชUXพ|9z{{ว/ห฿ฆบ™Lใใใ˜žžฦุุสๅ๒…ผอiC„ุฝ{ทZtภOเw๘ ิ๗/ึฏ[ทซWฏฦ’%Kเ๕z?Pฟทัh X,"Ÿฯcbb8…ธ ฃœ๒ํฝปๆˆŒฃ>จ‚๐‡~@hกฐeห,]บ>Ÿ๏งโ4 ?˜˜˜ภ่่(FFF@)]ฬแgOท๘๏๏3Gt๐X>๚จ‹1๖๋@Gณ}}>ฎผ๒Jlุฐ‰Dโ'๖อkตjต๊๕บFโฉJฉญเI’I’เrนด‡ํ†ํ†วใ๙‰EMPษรฑฑ1œ>}ูlv1‡พเ๗_rฤวQ—ปีฟภ฿ฺุlฟX,†ํทcํฺต๏ โซD\น\FตZEฅRAญvqัฒํ†ฯ็ƒฯ็ำBŠ๏F)4 ไr9คR)LMMแิฉS˜žž^่0เ?์œvฤศQ—#๐๋lฅ"‘Hเ๚๋ฏว๊ีซ/ˆฝgŒก\.ฃT*กT*กRฉผถฝฝ„ภ๋๕" "ภ๏๗ƒrAฟ)›อ"Ncff'NœภไไไB‡ฅก฿๏็฿ํ฿vG\ย'€ฐฤnŸp8Œ›nบ kึฌYดเ3ฦ42ญX,.ึo~฿–$IƒZโัb•ชRฉfggq๔่Qฬฮฮ.tุ๓~mpppุ)Gผ_‚๐ศ์พ-lพ๑ฦฑeหx<žEฝoน\F.—C>Ÿฟ์…พ™2‡รˆD"๐๛‹:†RŠT*…L&ƒฉฉ)ผ๖ศ็๓อษ๘bฑ๘ฏN๑‘ฃ.ต๐_เqk์๖Yฟ~=nบ้&„รแE๙|้t๚ข๛๒—zy<-Cq1œAญVร๒๙<ฮ;‡#GŽ,”Šuฟ188˜qฤหQu=๐รƒŸ๐W„ฑบx<Žnป  '๙5 d2d2™Kbํ๋ †Zj!ทท๋โ_I’‹ลFแv/ช ŸฯkŠเ๐แรkถ๛9|ร1G\”๕ศ#!เํ๖นๆškpํตื.ศ์ื๋uคำidณู๗Œฬ+WHeส˜M—ฮV0“ฉ “ซ`4Uลdถ†LฉŽ:–["ˆ่ŠzะŸ๐"๑ก#ๆC<๊C{D,ฟ๗ฝI9&„ "/จ(ฅ˜››C.—รไไ$z๋-T*ถI‚ฟ588๘ฯŽ˜9 เฝ†}พน ‡eE"uื]่๋๋[ดŸ๛“>ฅ 3ฉ"Fง๒83žรก๓9œš+ใbVต๙ฑน?‚ฝ๔u…ั‘B’ศOคbฑ‰ฤ‚ฎAกPภฬฬ Jฅ<ˆ๑๑๑fป=€฿tฒ๐žีพ  [๔๚ฺตkฑkืฎษ.ตš๎BBน†ณฃ:“ย฿I!S~๋fb~n^—ภ– ,_C(เyW๏#IZZZ‹ลDMชมมƒ›qฯ๘นมมAง‰ฃ~"แ(€ฏBnคiฑ`ทz+ฎธโŠฆ!ฏrนŒููYTซี RฅŽร)ผrl?>™Eƒ^žกo‰ผ:Šk7ดcอ@…ท#๔zฝhoooชHcH&“š๛ด~ไr9ปธcpp๐ผ#vŽx7ย฿ ว๗-ฮo0ฤ}๗‡ฆ7๋<2™ #ง€๓“YผzdOœGฅม ๚๎นข7v` ;zม<ขตตตฉ[ บ•Joฝ๕V3—`ภmƒƒƒGัsภ…๏AŽ๑[พoWW๎ฝ๗^DฃัฆVffๆ‚Bz๕รฑณ๓๘ฮ+ใ80^|๗'˜€๙ผžbศ๏)xฝžข฿็.y๎ŠํชI’ิpนฅ:ั~CฝN]”Rwฝ๐ิ๋u_ฉRTซต`ก\ Uชต c๏š]ัฤ=ื๖bรŠธ]‹Ovป่่่@ ฐงZญbrrตZ 'Nœภฑcว์vpป!pภb…™่ตUซVแŽ;๎ฐญึcŒ!•J!•J-๚๓juŠ7ŽMใ‰—&0’ฎ^ฐฐG‚t,โO†Ct8H‡Bฌ$I๏ l ”J…B)’ฯ—๙B)žษ•ZrลJB•ย@‹ฏ๏มU๋;แu/^$ $ [ซัh`rr•Jcccxใ7์8–Œโผโˆ ฃ•๐oบ7฿|ณm*/OR-f5 Nฬเซ/Œ]เ๛ฝ๎b["<ี’ˆLลใ‘9ทuIู๎zฝแNงsํฉTฎs6•๏.W๋มล๗โกKฐumวขs~?:;;mC†”RLOOฃX,b~~/ฟฒ๒ส)HเeG   ภ็Eฏm฿พ7pƒญ%*—ห˜ššZt๓ฬำฃi|yฯ0ŽN/NY๘ผ๎RW[tดฃ=>‰„.+f;—+ฦgfS}ำsูพrตXฬ1๋;๘ฅKฑชqŽ\.บบบl Bฦfffฯ็‘อfฑo฿>ปnDทพ้ˆขฃxแU,๚~7x#ถmf+น\ณณณ‹Š๋งs<๙ฃsx๖่ย.!`ํ‰๐Dwgหน––่ !ไฒฎ~cŒ‘d*19•\:›ส๗.ฦMธ}C|x)Q"ฮA{{;"๛njำำำศ็๓ศ็๓ุทoŸ]ขY;;โ่( เIุ;v`ถmถวช]r๔ฅรk‡ง๑ฯGฎา%ธ$ฉ?ำv&๐ฝ+6ะํ"ค-์๕ต…=พ€ฯํ x]ŸGrI„B@cดึ ดTฅ๕RตQห—jต้lตœฏ4๊?้9-—+ม๑๑นc3้emš ๖J๘อ]ุถฉ า"*ใ๑8Z[[Tลb{๗๎ตS็ !ืํฝ{ษŸa044ด ภ!ˆ๓฿pร ุพ}ปะ๒3ฦ0;;,ญcฮ|_๙๐dfก•j}‰ำKz;Ny<๎E๛๕~ไZีŠ๔$‘XุŒ=ก€ฯํWพ5ฃ๒a` `„10จ›ไืรช๕FฝPฎ—2๙jq.[)œ›-ๆาล๚ปโjตบgl|vี่dre}Ep๓ช(~แ#+,-‰ ฝฝ๖ฺLOOฃP( P(`๏ฝvผฬ„w๏sฤ๒gP ุyzฎa]sอ5ุฑc‡ํ 655ตจ&—วฮ&๑ลงN#Yj4ƒถดฏ+qf ฟ๓ธวใ^ุ฿โฎํดtต๑ฐ/ช~Mฮ แ=ฦด „Qฦ@P ``LQ…Rญ<“)gFf ™ssฅ‚v๐โwไิšฑฉ๔Jส˜}รฟ ฟ๛ัุฐขuม๗ ƒ่๊๊ฒฝF“““(•Jศ็๓xแ…์jžp๏เเ ำŽ-ืๅ๐%}๔ั €=Vš_ดinนๅa"สb…ฟAพ๛า๊ปร(ีํeฅ-šผ~เ•ฮฮ–Q—KjzvDผพ›ึต๖ผฉcๅ–e‰Žธ?๐น}D๊™61พโ2ซฒA@ m'``DอP6`๚>๒&eFภ|ฎš;5ž=9Uศ^$˜M๕œ<;นตRkุฒ7,เื๎Yƒpะ๓ฎ‘@ฝ^ว๘๘ธข}้ฅ—์ศฺ‡wฤ๓gy็Ÿฎhฺp๗†šิXsำ;UยะW6อไ๋ilZฟ๔ๅh“p! Wฤ๏ธฒkํบพx_ศ๏ ชึ(]QDต๊ะŸpV_ูLŒฏ1}m/Qu‘† ิ๗!x]พึ`|yg(.1VŸหU5ะ# ไzบZ†k•ช/Wฌc็SU9=-หใM jตชี*BกE H’„@ €\.‡P(„@ `ืw๐Ž]ปv}kฯž=ณŽˆ+ฅฒ๏kๆ๏แ๗๛๑ภุVคอฮฮ.ิž รYฏวŽa,[ณ%๙ึฏ๊}c` ๋ธหeŸฉwๅ@ดๅžซzึฏ์๖๘ผ./$ข‚{Y]๋ซ.€ขT้kฆdศฯS_ FW€้"ฯˆŒ@ฬ Ž€x’ปป%[ฺŒVkสbHCI’h[[l2ไ๗fS้|eฬro$K ผrd"H4!ีV่กu‚ฺฺผP( ฃZญŠ"7^นs็ฟ=๗sN๑Oฃxไ‘G"„h7๛‹w฿}ทm-*•B:<๗ๆไH ๘ ไชbนކ|ฉ+6-฿…็ํcIยธ๏šต–ฦ|>—WxY‰n•™ฎ4…  &ใ}u“jัอ9gA`ไ=„ฯ ๐h๏ม s^หฝค=”่ˆz๓นjฑR_ธ๖9 ไ:ฺccูLกญRณ&•๊ {ฮcใ’ฺโอk๋ผ^ฏึJฝฃฃ๓๓๓"4ืAi฿ณgฯำŽ˜*€nปํ๏์4o฿พ}ปmIฏฺ–ชูz็l$ช ฑูดaูซ^Gศ๐ป%B>ฒนฃวฦฮuแ 'ศ[t่V_•uำ3่ h  LJ€ “๊v๔ท Eท๐ojๅsิํAฟทผ3œp1VŸษV่qปk]‰‘Jนศ \‚exแ่<ึwะัbŸy\.—แ๕z…™€ึ]นณณฃฃฃจื-ๆUปvํ:ฐgฯžŽจ)ฅฎศผฝฟฟ;w๎ๆ™W*LMM5}฿ใรฒ๐ี้ฏ่o?ผrล’รvY|-ภื.ูุื๊”$ขฑ๎ŸฏIซๆ๔1W‰UXy4 +ฆ(u?zFxYถA2Ÿจb”|#V ค๖˜?า๐fาๅBตมšขBko‹OบภษLฑำJย{%ฑฑ7ˆ๖„=(‹ƒ–kJั๘—ห…x<Ž๓็…ญnตkืฟ๏ูณง่ˆ๋OPฦq? ำD@ €{๏ฝW˜Zฺh4011ัด{ฯ™ฑ ๘๑ย>{!tชืz{ฯูใš–ฮ[ถtmz~Hชนg<ังน๚ไ—tJ˜O9Bย4:P–zย#}]qp˜žฉšBˆฌฒnิ ผ`•xพ๖`ฌPช•ฒฅ…นX,<๒{rsษ|3ฝฐ๏XW „ั๓7U‘HฤสUวž‹E„B!0ฦD/ ฯž=O8โ๚S vํฺ๕7v˜ท๏นK—.3==ฌ๙$ฦf๒๘ใฏผƒฒ ฦ/Iคฑiํ’Wb2๛ฏ๎^น~ ฑT"dกึ„3O4?! DR์ฟ&ไœ"ะใ๘gืGU :๖ทบผภ3;5ก ž$๕ดใn‚ฦtถฒ`T(ศFBพิ\2ห˜qาeภห๏$q๕Š8ขaq๓UJ)ชีชPน๛|>m”Zkk+ฆงงE…Cwํฺ๕ึž={N:"๛VCCC7๘’๙๎\ณf ฎป๎:aฒฺnสnฅฒูcว„ู}.‰4ถฌ๋ฉฅ%6#:6โwน?q’ญม6jห4ป(,'๋ยร~=เยz<๙g๐CŒ(€ƒฐƒw8ะฯ,พจŸลN@HkิŽ๎๑dนฐP@0่/ฤ#น™นฬณจQ†ทOงpํฺV|โ2แZญI’„a]ี€ึึV ‹๒ฎตkืณg๘ *€กก!7€ง`J๕ …BธใŽ;„;ชีjำA•ๅj๙๕c83_ยอ๋๚^ni‰ cษฏ๏ม๋๚6วBˆlี ักฟื(z่\f๏็#{๋Nธ๗eฤจฬฟั 0HทeW`g๑ ภหฝIi„ƒž@Gฤ๋K–๒t”bฟ฿WŒ…ษ™๙ฌE ไซ็Fำุพพ n›&#ๅrกPศารA„\,ต!ง33@๖์ู๓ผ#ถ๏’.แg}ภ&๓ฦํทฃฅฅŒ1หcffฦถฌ—1เk{Nใ๐dQ@`m\ณไU;ห฿๗๙๏฿พdsศ๏จpทฟผ5U=w.ื˜า ƒ"€ชLLœœ*ึ๊ท7์@ฬ0žหฮ`zcกน7ฃ ๑‹๚ฆ–จ?rำ๚๖%^๗ยฃ‚‰่์ฦีK๖B,„ฬ‘ฉ฿sฦถzณkFตฮN+Wฎดk๑๖๛CCCซฑ€)…๘{ุผฝฏฏ๋ึญC๛Tชฉ฿ฟ๏เพs()|mอฒฎmmqaŠู’ธ/pฯถM~Ÿหgb5›Faิฅ‘(ฅฒฺ_ำ๋ฒ๔Yจ๛ๆBKšH41}E๓[1Sพi_า\oฤBžะŽuญKผฎ…•@[[|rอฒฎขืž>œฤ‹์3ช+•ŠmG[[›†ถnNt๗A=rึๅ@‹™พ};|>Ÿม๊ซะฟYฒฯ่t}qw้ž–==bถฟ=โ๕yuฯŸวๅตสผQ6tSS„žฉ1{fPบA7ฑู#Mk1X3๛ดˆใ‚ <”ขAo๐†umฝฎE4่้i^ฺำ*lโ๑7?ล่”}Uo*•ถe๗๛Qฺฺุj7ฮํพกกกั€(eŠฯo™ทฏ_ฟB˜8??o หีแฉำ ‚ืโกษๅห{„-ง#>—๛žซ{ึ๛ฝ.฿ใาฅž่า6Š…<ม๋Vทt/fš๘ฒๅวฺใ! าขŒแ๏Ÿ:…rฅn๋ ุ%tตดดh.ั๚๕๋ํz>ลืฟuGz? เเ7k๚-[ถภํv[Bกะด‘็w^ฦ‰9k2[ะ๏ษฏ[ำ:5ษว%rฯี=kย๗B=๒˜™gเ6ฉ`”1€(๕๚Dฎ้็_Wtำ…[{n<Ž€iฮ1์]#0ป๗ฒˆล,ำ-Q_dKlม&„ญ[zะ๏ฑfœœฏเฉGlU{˜—ํึj@ึฎ]+:†แแแ๑ฝฬภะะP?€_1o฿ดi“-๑—L&m฿๏์X_{mF@๚บaM฿~ปฮ=ท_ั1ะ๕ล ษ•์rฯy#ฅiญ|—[z^๘ ๛1ฦ#๓๕สnŠ‹ ๗า๐>Q•S(F๔็Œฆธฬ ิ๘็gฯ _[ัื~ิฎ;๏ีหcmKป" inฯฃYQฅp_7qฒ,kอy,‡2“๐ห]{U$L;ฦˆ.”ฒ๐ฃฅ'ถž่@ํX…ุg:ฒะ_ณšลป0"งZ.บ+O$Jฏ่o?"zํ?{•š๘ญjตš0วรๅri(ภใ๑ุล‡‡‡w:"|™*€G}ดภฏšทฏ[ทฑXฬb๙)ฅMวvฝx`'ะ? ฬ๕๕uœำ๑๚ฎ\ูถฬ„ล/บนิ €j๒uกงฬฌไF๚C๎ํกw๎ะ•ƒ๖:'ไL`ฑม˜Qx•ื S1ˆ`„*nU2™‚ “‹…™ž)ภิ`๘…@*ตฦตๅ๊[าq: Xr.NฯW๐โ[M AQŠw,ำPภภภ€๐—AG„/Sภ๛4Lอ=CกVฏ^ I’, —หู๖๑Oeห๘—ฌ7‘DHcอสทDล=„ฒsK็J—D\ถ~ฑม z฿ฎk|ไN}Œ๊ม ฆ้ฆl(๏ €™ฌฑ๒œZjฎQŒ๎€šศGtdภ จ0ี๑€ฎl˜พฟ๎^€>„๓l8ฯลjใ‚2๎!lอช%oI„X.โฟผ0ŽdV\„ุh4„(ภํvkท5kึˆ฿544ดลใหL<๚่ฃ^ผ}๕๊ียUุญง_:/สนtI๋๑`( <๐๚ี‰ฮxศVns=gFHฬ8ฒAaฦ5แb–c((“S4ั--ื๓“dฆฝ%3๘๋ฤ`๑™Yiƒ0SนWจบ]S(jฦhhBสs -(J((ศจ?ๅg฿E็แ`ะŸ_ถคอฌ6žg?8NƒRj1 ฝ200`7่ท1พฬc์c 1>วƒ•+Wยใ๑™;๋?>“วทZ๛v}ž|_งฐ8ค-์๑ฎ๏‹/a: ืpธ‰ฦfœ)ื๗า๚์้H@ํฝGศ_ฃ =5 ?ŒŠ@w2`d๓™ขฃ8ซฏ*ขณLxrPS ๗FFบฅ'CW,L73Gภ2ฅwืzผฏฏ๓ค(*๐ิ๓›ษข‘๐zฝZC๕ฌ‡”D3g]F.ภoZˆบ+„พBึปฏŒŠ‰ฟฅ‡ํ†n^ฟถต฿%I‡๖ฌ ะ„ฐi&”ฉ@_Jฦ ๆžiŠAทุTูWั„้ึ฿ไำเ}y9จyœuีHBU!` # ”;N #}ำP—yTฎ6jต}WM%‰ะ•Kป‰^{ๆๅQใ2™Œ๐เ+Db>ๅˆ๒eข†††Vธษื—.5dฉซ\.2cำy|XZDอฺ•๗ฎ์ F:[B ฃ'ฬŒT๖Ÿi>พ@ P.จo†๕ฆ{U๙๊~ฏ`˜ภำ™|B gLๅJ4๋ฎ)*>8ฉ„„ชฎƒ!lจNเ””๊0อ'ทPฐbฅ^๙Iฺฺโ“‰จ•๓Nฺ6CฐVซ ›ผƒA-b …„ษc~๕‘Gqค๙2AŸ‚)ตณณำ29†‡v๋GoŠูใeGํŽูบ<ัห๛๖ฐฅบภ๓ฅ‘%@ŸqKSิข(S฿F•Bฦฌค ีชEgg`Q˜!ผ†ˆNVmป#™H๕cUย’1ข๑ษ—๊%@E/๏n-ทนFฯฟiศๅr  พvํ—-บ‰r•#ฮ๏ณ๘็>Gผศ๚๛~หลm4vSc1Ÿ)ใฉCV฿ฟ5œŽว#ยfžkปCัxุV'่จI2ฦคfLa% K ๎ฯ›S็๔0P. ภ๔๙=๒vU9hึ<]A(เ_ฑะJ(OŽ฿SอE`<๕  2ำ…>7@\9H๔๗เ๙9ŒจธGชหก+—dฎRP>‡๊ดว…ญX,2฿Yzธ=s8‰นด8ำณX,ข^ฏ[๎“`P&ตทท…ม็๗YH’ด ภr~›ํFooฏkึืีรSยฎIปmƒศ ฑnžฬRXมสเ ลบSซเFq๑บ์๓a0]ะ4Ÿ€2ฦ[Sแr ำ‚๛ฬผ/3ธบย!@Cv๔ุ‰๐_m=y๒Lxv.้)—ซš›ฏr๊o$๔กœย "+:™ฉaSพ %0 ธVŒฏถ๏๑ฯ็-ู>ŸO‹H’dื9๊“Ÿ็%Gค/lน฿ใ๗{ะส ๗ ๛รซฟhUช <๙บ5ๅ7๒ฅ‰จฐมGซ?˜๛ยJzพ,–J๖‹ฺTWFสD~IVD"ฤPจฏจ{DŽ๓ษ-๛!L;€Jภว IDATฃ็:oˆ้sฑ6uๆ'”เ“†x(ฎ’|ช๖ DQ0<๕ฝญ?>pฦPpว๕rŸ๙_8G๕๖T{ObLeึม F(+”๋ๅjึกw เLยBEŒŠว#sัฐ?™อ— • ฿|cปถ๗ม็ฑfƒ DฃQ‹ƒZพ@oo/Nœฐ่–nJ้u๖9b> €‡~๎7o๏ํํ…ื๋ตXzฝŽZMi:>œDถl ๖๕ดžฒ๛}ฑลˆ<ฆ๘ษšyeœDจญq9หM(๘D?ลฺ3ไูsB 4BP๓ห)•฿G๖วyซฮ'šP€‡f็’ณ๐ภ๊E(ฎ„ๆR=๔Hxฮ‚จฤก)X*Wอx ใƒ^(7ะืzฺย๘—8q.eKVซUหยฯˆวใยR>ๆˆ๔๛ค‚มเzooo7ภ…ฌ?์=d5๒^ทซาn3ป/เ•\ํq J|ŸY”x%@ีpŸ)Stี๏gฺ ฯ ข‘ ะ_ืณ„๔ธ€๒~”1ช๘:ื"Œr$•9ิว8•yy฿ื^[8พk๓ฦตi่|…ม]1ฆsІ๒ฉหำ้Rฮ(์ƒะ u ฺใใทหRฃทํQ5จวใ1๔Œดsท#า๏p—ศ๚››@ชŠภฎO&_มำึš€ฎ๖ุyปธฦ%ั„$I“;fศ๒ต(๚2ฦc”)‰hTd9ง&ซฬฑ๛สฺ๋ƒชณ๑„ชD ฅL!ฺˆj9‚Pอ6ฆ๋i Š0๑๕z฿+–ฤ—m๋๛K]ํE%j๕{้฿Ÿส ้ม”ฐzึ'R•ผI๐M<แถ/ฌ$IขํQK]๐‹งณH็*ถ @ เ๏ฅฮฮNักซ†††V;b(€,Bี%„Fฃ O oซฎฮ„mqyok0กฦ๚ต๘{%`Iำๅศ=fศHeฺฟ)Wท$ /QถษัYc0ช*=tHxaัc๎๒%†ฯ4ก’ฑ‰ŸืŠ~d…Bุแ#'b#ณY w๓‘[ถอI>ย%๚cศR๑ˆ1 x2Wษ4ฃbุo€ไtuถ ‹ถฟsN\๚]ญVัh4,๗ ฯ' ป2แ]ŽX_b044ไ`iำิึึ&l๚!jฅฎ๏X#|แ€7…ฅ‚ั€ {CŒห๖S‰6ณะ)/.Oอึcฆฬ<93F—j‰@Jุhก?ช‡ๅคฦ ฝˆ`ฟ@5.0u L+4ะ3 uพAu+ภุณ{^ถ4๊ˆ}t๓ๆ ๓zฆŸสcฃ{bjย๙๒๗O–Rb่oUฤ๐ผน‡ƒูpภkน~๛๗~(—ห"xBˆ]Rฃฐ ฆฎ?๑x@@ุ๋฿ฮ๚Ju์;cอkoŒู}๐ŠŽ`”๓k5kP~ชaaีi œ ๑„ bษๅจ!5๘๏ ”ฅ,Œฝ6ภX.ขน”Q=7@&๓Uฟš2F,!Dy?rŒŒ^8xฦ2Œ๏็n฿6๐{๋*j`|ษฐ๑ปจฎ๔ฤ ๙s๋ Z™+eอBOฌ(€iHๅ”@Gkิย฿ผt6‡|I|/จ."o8ิIBฺ{vtˆฝ๑‘Gqย—Xhะูู)ดŒ1[02™๗๚kMฺ}pg"แcต|ส˜ๆ0พ&_ฏะW_โ;cบฝีo|7งYp9จ e*5Kศ`…)a–\E`ugL‡ `ฯ๘หOม๕ื~ho eœ!ผBมe6ŒอคสIj„สo%"`vTmญ1K c #Y[7@t๏๐( ฅฅEth !dƒ#ฺ—Vl7ohmmี’7ฬ?์*N ฺG๙<๎’]ท ๒†)็ุ๓!>•3…ี”„ขeY๚{(œbะร~<ศ’_ฅ (S(E่9มืIEฆ๛KJtศฯ ‚6ŸLyžx๎ญ˜๙พ}]ฆณณฝำบ~โแ:ก†‚!ฆ8า3ำ๙9!์'fโOs!DกB[%Ž3>ฏB๏Ÿส=xAิƒvšฏฮYOM๐ฉสหnฟP[ฑ”„ฺ๘ะบEgผ€1ร>Lฮ †ะœ่ ๛^koPjษภนํึ๋&ๅ๏I˜1jภ(t’2ƒbP ก„–-ิฒณนjQH1#๑G ค&กT`h\หืNw+ๆžํํํv.ฉณ.…๎`ธ ~ฟ_๓Eัสk81kอ ˆวBณvŸ๗5ยื›“kศก ;5xœ สอNM)ฟŒ'โธ่€œแ[%)W<ค' žSะstิO)+KาWฟ๓ฒๅNฟjuoq๕๊ๅi5ฤH๔vย" 5šขฃฐsำ…i•ฐ!8ร,‘ \๛a,ถ\หSsed U[`พฬ  ตUุผ๘JGด/ ฐUฤซร๓D€ข55'ฎ ŒEร๓v yJฮ,ใ;z0c๓ฝŒBดŸb!uxฎเxN๘uหฬิP!ี}~9Œ+าอ'Qƒย!:a0ชtึ•ICข&พบ`{ฆXฑ\ซqใ„\Rฬs„=GนR`-3‘p๙ ๅJฝtfฆ˜ึัa6>พIธญึ฿Jb2“อ]ศต!BˆXถ!ถแ๓Ÿผว๏Kฃ,„K<ท่ liwx\ฎj0ไฯ†—.?—8ฯธt?%Lวeิษ ๘ิ ‡uช@1นLตะ„ƒ๗kD ๙+์ž๚:Qณฤฯ <aeZlŸ)IG„R•{ะ" ตZO<ฝื’๕ฒด#Vฝbห†๗ง†F$šฟOyTข)EเฯMๅว™c๊ฑด ๑gฑJฎY).v0เ/x.K๖ฯไผธ(Lฤ˜Q€MJฐ—R๊ฬผD `ฝE0รa!WI@ัฑ‘ฐ/์ƒ^ทื ๕Pšo๓~>ๅ๊๙จM4_™Pฦˆม‚ลgศฮวV4Šๆ&hdัณนค`ี๚Rช"๙?ฝฬ}ไc฿y็ต“กpจสO!ัZ๑ฟŽบ’huย๙r-|<7งDS)”yจL1 2ภ@$ฅ์Y=?T1T)ึ?ำุ่TO’ }–kzzพ‚zƒมํ"B`N&3?‡ร˜™™Y๐พtึ{Œ†††Bฺ ฮ—ื ว#leง2๙*๊ิŠ ‚AŸญ๒น\Z)ฏž๐ส8GVซ๑|œ8u&๔่๛/Ky'ฺจ7๋LuฒPCr๊ฎม฿g๊CํDใBˆะ๐Tฯ๑'๚C+Pd'EC”' ฟ๕ฬ—X‘ฤnบi๛จึHๅdUจFจšnฬธp#แHษฃ็ำรฬฆฬฦ๚[ภb|+/ฤ)Tl9#3’47ตแ๑พ๘ OUซo†๛v๐ฟX‡|>ฏmห ฏ[rQตื‡ษฒซHjGmขีผๆแุ<ั›ง‚k๛ฺ:?~๏อณบrsา๋‘ิ4YฝUแK5๋ฎฝ=”mชะฦ่๙‡DG†#ฆQ`Œk~๊๔p|฿กแˆ๙w?ธ๋Cำ-‰xQ‹c่8Lใ๘'ฺฦ€ูtifdฎ”1ช ษd้•ฃe Q$n;ณ"#:๐๛}Bผ_,ึะ๕*s$€_6ษ@}Žx_|`ฉฦเป˜/œ-(ˆย=`~ŸืvLฐE\Z›/~`J{ku่…|kjqมrน"}็ว4ม:>:็๙_๓_=+{žk่[g>tีๆyฏื#ฯู#zOn‰#dิO4ˆอดŠ;ฆM#\rn๒ˆI่ย‘/”\งNŸMœ‹NNฯNž‰B`;oพv˜#1UF๖nึŠศชZL๙6ตญฝ~&5ฌ๛๋๊HAYุ•ฬAN Eภ™\&xฎบถำ}>OYฑ ฤxT…+บgฬ €oา์tึ{ฏ:ญVgซ์@ฉ,ศ๘rนชD"ถนๅ’DT#HdดกŽส’ป(ฝ๏eณฆี๛€Av์น]TDvr,s6_ฎW•S$]B)๕9งิฏซ 7/ไขฑ่ก็Muป\•Zฝแ7โs#!Gข>๚h`๗๎%Gฬ/žฐคaูภฑฆซZต2ฝขxฑ‰ผ ŒBํีง๖ธ7ސ…—ื=์iA9-ฟNง<๚ทO.Y?ฐทใก๛oธ๊สอs’Dังp0ย๕ู3˜]ฦ’)ฅฤFfL•ฆZฝŽื฿xป๋k฿๚Q฿ฑ๓sลžงw^V)0า ำญฝn๖ฉ:3 \Z$ร|ถ2d,7 ฝฉeุO,dŸ €๘^"ุฏฝfQ„C<^ซjV•jฝ)ภ[}3เ๋๗็˜#ๆOXโ/*(าฺvš<'ะ^ทTo๖มz๐™hฐ t$ ‘โ†้9ภ๙k?ท๏ฅ7ฺŸ๘ม–rตnkqฬz็_zลŠบ~มฦ6n\›”a ็›xBาฃฯ`˜Q ฅ ฏฝqฐ๓+O>ท๔๘่iสท,Kญ\ฑ4ษฬพโPCำ3N$นญีiๅี“sง8_฿,๘ฤไ฿Yw์ทฐBภGhฒkว๘‹๛๖w~๕้}ํ9K ฎƒg&๙๗•7l^–ฤ9ฟzๅาŒYZ% ‡๓ใ†vvโไูฤฟ>๖Uožœฝ›ั;n:ญฐ)G 00Cป(c”g“๏ไหjม็†์ใ…์saNึิ nIฒH{นึxื.€ํถ3:QGฤ/ฎฺ)vmไ๋ !ัำt6]ญAL/๔ั}ชฤ๏šศยฉ3b ขัp๕ฎ;wŽ~๘ฆk'๖พธฟ๛ซ฿yฑC”oฏฎ}‡ฮ…๗๚‡๕wฐ1๕ฑ๎้ํ้ส๓๑~ฎฟฌxแOgฒฏ=๑ีO< ใžไซV๗fืฏ_=ญษ7ฮ—ส …J<ŠH@ŒL็‡ฯฮ3<๔็cผ€@bฐD(๘ฎโฬฦ7 >5 =Œึ{€D7ขหๅี™ฟธ ภo%็$ƒŸถE ฺ…,0œฎRฃ นV›ณว4 `š'ห!m6ำฦbณP(D๏ธ–‘7n๑WปฟญบJีบญ"xf฿‘ฤณ/?๔‘kf๏บใๆs-‰xYฉZ@™๑๗๒+o๔ํ<ณr.Wjzž=.ป๗ร›''f’ม—Xช๛๏บ้$Qš{ขื1หdฃ>ทŒ?{j๙ใ\ถ2๓๊ฉไy๖›‰?Qiฐ๖ฆมขBf฿ฮาร<™›Oh๑๕›'‹น‡l€฿๑‹ซ\ห๔นh‰va LŸ(T๊5P0F˜šนว(Q‡’L“ˆ!b`ะๅ&บ„ฐP(Xป๛Ž[Gvpอฤsฯฟธไซ฿}ฅณRณ๒”2๒•g๗w<๙›mฟ๒ภŽ๑nฝq8ิิ;5™ฮ๘๕ห฿\ว[š†€ืMแ๎๋Fvr—หE?๕ปjg]{~๋ฦม#{ขr Z œ๕ีVฑศฝpt๖3 :U(IแO(k๎๛‹bขP ญะ๓ฏ1y–š'‹Qขึsx๏฿8 ภJฦร–œY<|“D]jFfหS[0ล฿6Œ๗S"b|Šญภg๊˜ Fิpก>8' W>vgnบฺ้ฑgฟใ'~๐F‡u*ื๊า฿?รพง~๐Zื๑๑]gทoฟj์ศัใํ๙OฎŸอฺ[}—$ฑ‡๎ธf๔ž;o9Gหุ7ฟuลJอขPธ{วI"I nผนa !Qษn~9Pฎ5ส?:2}จขO๙Qฃฃ’Œˆ๎/๐ืยๆs[$ PฏŠๅฺบฅ…€9ปt†ฦ้ x‘@c1k!4เ๓XฏSฃA›ฑ‰ฌ ฌ\ง5ฟGrA0ฅคOฎŒ—ƒŒ(|f$ซดขฆืึฺ/าC๗ฟyว๖ัo็๙e฿{๕˜ญ5Oๆ=๖฿\3๐.™ษ๘šฐo]1KŸผ๛๐’%Y๕„ๅ EฯใOฟl{ปผ+^ผๆ๊+†‰\จr๊h2ฆอ๚f ะƒzƒึ^::s0SฌW`๋Eภล๓-p_ซJ๕Bl…=œRjน๏ผiA J^%4Ÿภ…ธaฟ uXz์ฌฟ๚rฅ^๖นฝA-๛–ณRฺฮŒจำ2™f' ดlƒUcD๓ๅU%ฑคท;๗™Oโูu.ตo`ลซGGl™ๅfย฿U๛/฿}d๛ถญฃ‰bม_ุฟ,]ฌXฎลwxยํv5ิ๖ฦ„˜Ÿ( ะ‹š V}ๅ๘SูjAfตŠ#HำPY๖[ Aกื•ฌแ๕ชเฺ†n๗,**`ำiชๆˆ๘ลUฅE^ˆๆภkีFร `ูbญ yูg„ฆL™cษt๒LรซL่ซf๒จฏๅ๒฿‹ญ\ฑ,๙Gปcเม#=นgๅ๑ัล'๐ณcำไ/~๒ฃoวb‘ฒjฆี๙}ฅRษ๘ำ/ฎ๑"วOด์ใศอs™ข'๖ื๑pฉญ%QhmๅZ[r-ญ-๙ถถ–ผวํฆAฝAkฏŸ=x~พTPa?QฤธขoฟฑวŸ“ะธmFซHIิ๊ึk๐นอ\€pฒ/ฒ(ฺ]sใ†f ะ๕zรงค๙2‘๐@บP--i ลugT`51GŸ์ญ็๏ชcฑ”นฟZ"UQ {•I[r,Ÿยฎบir๓ฆ๕S/พ๔Z฿—Ÿ|~ลLฆ่Y€แงห๚{า>Ÿฏชd(\•โ_[6›-yE‘‘๏์=ด`E›$ึ+ฌ๎oฯ๋ฎแtํŠ$Zฆc๑ถ้hKk†;k’าYZ€ไkชฤ(ภศ{d€ZZุyฐhาi:๏ˆ๘ลUYซเึm†ฉE,ไh}Jฅๆ๗๛๙‚ ฮฝุTฆR gร1ญ.^X-N…ขฤตGิษฝDcะตโF๔ข"™`P sน\๘๐Žkฯ]}ี–ั๏ํyaๅcOฟทOรอ ฯฅคS~บ๊hiบrะ<(ฅ€Dิึ_J‘ขยแ`ๅม๛๏8rำ ืœฦS{ึ}g๏กปuz2Ÿ_๊ถ๋7ฝฒโ็๎Žห—๗ฯฟธ๏๕•ษE‹USJน\พ?—ห๗OOsำx ˜ฯ็OFฃ‘้p$:Žฦfข‰–ูhKl0)›Rpuย LแP)U„ล๛ก€ทฉ ะ,`งcsŽˆ_\`i๓ฌŽV/ฑl@ุ‰K2HฑTวbถ]ๅ–ูj!ะๆ๖๊7ฑค๋=๖"M้VฆT๐2mฌ—$*‚ฯ0J˜–๒K4ทhooอๆ๛ฤ[?ผฝๅ+_๎ๆื%์NุK‡‡^>๒ปธuู๋ืžzJVHฅ\n-—[ggf ฝ๓<O>‹M‡ฃั™H,>‰ทฬEญsกHดAL–Ÿ็ XฑT ‹๘ŽXุ”7โ๏#ณฐ™4])•JGฤ/ฎ˜ถS*L3Wq‰|:‹`YซgๆŒmมJลJฤฦื`*]ส๗ดโ๘f 2 ˆ6๋Oƒ๒ŒIŒ#๚ธb!ฦนF+ว๘ Bnจฆโaย#lๅŠฅsูO?ฟ๕ƒ}_ฏlžษ์,ฺฯฝต|ก“;ะหo]ฟtฒฃ-QศๆKžt:›žœOF+ี๚Ei{]ซียsssแนน91้vปKฑXl.ฮEc๑๙p<1ท$รฑxAํฤ๔œ.fง ŠฅŠeบQย[ ฟศh˜๏๕ž3ญ๑‡~˜9"~clย ๓ …‚ฆDคŸMส&6๔„, _ไof6+ l:S)V๋ดๆuป\Z[Mุ๕ถฏqฯ]ทบv•'Ÿ๘ึ๗ฏ|z๏กฅ‹1Gฟr฿Žป๗#oHกZYณ’ภdฒ4๒รร3งช สโmํ$ึžp zEก”บRณำูิ\O>“้ฮ็ฒ=ฅbก;—ฯwีk๕๘{}5 o*•๊JฅR]ภฐ~oธคz,KGcฑT4‘H‡c‰t$–ศd KBUGะ๎ๆ˜Iภ|^H๖Ÿuฤ๛"+€ปwW†††&๖Yrน ฟ฿Jฉฅ<ุฎ\ธปzTk น\ ๘พ’€ฮ—ฒญืสZฑŒ1BTภOตLTขMศ#r—N%%˜1๐ƒึ`Di;ฆ ธ\๋ซ#นU:1ฝ๙&aญญ‰๕ทžz๓ศูฮ‰d~มjฅโrนHPzs†rต‘;t6u์่x.ญP+*ษf้้'IZ;ปงZ;ปgโ•Cฑe็gz2ฉdO!—้ฮ็r]น\ฎซ\ชดq~฿ฒAฉTช-•JตaXW ึ๘ใ=ฆkฒU‹YูlV„N;โ}๑98อ+’ฉ ภฌมฎyCWซ๘&ศd๒m~ฟoMช“๙j9Wj”ย~—สt ษๅ+ญดษV‰?&้1yศฯี์Zย1ฮT” ืTuˆ<์Co &็)คณพฏื3W?ฝ๏๐ฒลžฬวพ๗ฺฆmื\q|ลЁฦP-œ}แุ์ฐZ.ซฟ’้,l๎aฺ †ยล`(|ถซ๙0wŒTฏื<™๙ูŽljพ;Ÿอtๅ2™ฎ|.Yศ)ฃ๏iAหc ztต…lัข˜‘%c ้tฺc์Gผ/8 `‡’ตดด ^ฏ[๚ตู5oˆG|่‹๛0š62บ้LฑญณณuTDส2.K้ฤ|1ฝบ'ากศ1(ˆฎˆ–๔c่ZฃฮS•ัg๖)9CD}ฎ๘๒qช‘# *GH(7tํ}๑ีีฤ๓[็s%฿๒*ไฑz๖๚O๚W๊ๅใณง็๒ีช‚hป/ว)ˆI๐นช>ป„น7'้ธžzkgฯTkgŠ$ฃิ•IอตeSษฮ|6™หค;๒ูl{.—kk4จ๗,’I๔ฦผHD|B_„อ๐ฟP(”ืFˆ#—Fณƒd๕z]Hไธna์๖บUQ|ํucd1™)tึธ’ึ่CนัGๆŠนฅกKrAฎfLฮ~ีส0Fจ2C๖rw~IาC~2!จ( Bdd F”> ŒIL๓ั!`#็วใ_๊Sืพ~|ฌฝูI[ู’๙ิ'o๑ซO๎น๖ุ๙YCŸยW๏-3ž•ถ–AT9U2”Aง^ิภTดCด2bำg›๊ศe2-…\ถ-ฮดึjต&nฑ €๋Vลํ‹Uแง”ๆ=ฯžRˆW IDATGผ/ฐhฺT*e€kข&Ž"ฐฆ?˜@ฉR  ลH0ศ2ฯk0F'็KฉพŽPซๆร+q{ชBwN€™R$*— K’š†ภแ—‰=IATž๘ฃฯTธ*ฟNห•Š๛ูgธ๙ฑg^Y฿ ิึy๕บ\_ผ็บท๎พ๓ึ7ผ>oอ๏๗•wลฟ>haฐNžx`ๅฦญo*U5„ำ2ล)ใ(‰K`2(]ะ‰!qฆ$nฬyผฮีz*ฏ…cฑ\8+0`X้ , นlคMตไ2้ึl*ูžฯfZRฉtขVซ๙%ฏ_๎2ฮญตb "E3๋ovิ{~PJ฿คŽx_pภโ“'“ ”B’$ิ๋uKืVฏื+ ๔ˆ ํๆ็ณกP@้ลgมฺzfฆ๎j D=สะฅg8#rMNไัH>ฆฑ๗„0Fฉœิฃไ#ฉีw”*ช@ฮˆฌxืเะG—ใ฿ฝfd&nvขถo่ิC๗จฏฏ;ลZฉ5 ‘ึž็‰x*•พ†฿7—ห]55zฎปซo๙ธ^k์๊รU๕้ €†Ž3๓˜ฑ ธฒีข SGˆฆ8B‘h>‰;z&ภ)†ำงFฎ:76๛!๓9YjsอyฎHUขศาฬฬŒY๘ม{อํKค็†††Fภbj4ศ็๓ˆFฃจีjภใ๑@’$ œ‹…ฝธบ?Œืฯร:3๓นฮใสํ`ฉ฿ฏ•:ฅ/=›๐บp,ไ บ]r};  พ้ตtaฮ+๙ฆร<ฑว$9j@K&S'ž๎ี{๖ฟำดhง=,๒ƒ;_ฺqใ5๏4(+ฯfส3็ฆ๒ใวฦs)F]ถzcฉฏ\cฦหg9zGW฿ฒRไU‰B(๓M%bH_†J‹X ฝAJ๕ษศ/Šฐˆด-฿]D‹†๐รญฑtพฺg†W๕… ‹YlWซU$“Iณ๐ƒ1๖’#ฺ—ภ~˜fฑฅR)DฃQTซUแ์6ฏื+ฬเบn]‹Ed ๅ–bฑ }9ำญยฬัcM– ฃษbN’$ึ๗๛;โP,่ ๚ฝ.า!@†๎\ฆ1ฤ๒มดฬBe˜Ÿ)eŒQFฑo฿+k๓?พ"[ชุ&ตcหฑป๎๕—?ึ้ไฬฑ‰|’Re0clฺูG9tเTฉT6Lด›ฝตัh“KƒH” […๔#MI>5—…ž๐_‡ *้ฉ๔ ฐ“~๐ูฬาO๐”‹ๅhถPฑ ‘น~ฝxDƒyถคP‘&/”R`Ÿ#ฺ—Vผเใ†›wnจื๋Z>๏ห๙|>กXปฌภyห๖้™ไภฒฅGฌึˆXšแซmฒ(el,Y*Œ%K9ะxภใn‹๚๑ ' z~ฏ#IZ8ฉ=Q†sหึ_๛ญ_๋ตWต™๐{=žฬฆญ[๎–>๘Hkgฯด>O“€ศYธฬ8Gƒ๛vLGฝฝ๋ล( <|เ:ณ็|zำO%b_œhๅอzฤX€iธ_ก่b.›Hn1o๓น%l\!๖ ๘แฒผต7๛"แORJR+e}฿ผabbBป€ๅrY+V>ŸO˜ํ๒ปq•m–ํฉlฉ3›-ฤ/๖Ii4าั7^น้๙gพ5hฎ—7฿›ฝฝฝ?ฺy๏ƒcอ–ฝJŠ !ฬ0อGmกGx™fซ6_๙‘ค‚ลšฅ๕-ar6„ อ9Q“‘gM๔ฆ‰Wa‹SฐอW6ShM็J–ฉ฿๗mmCPะL’$xฝ^ร=bg@ฮž=k”างฯi๚>)€๏šoฏนน9่+•J I’ล5ะโ-]ยํฃcM๒'^™ไlห๓฿ฏ฿9~๔ศ”ฺงป†Bม๑k?|ห_lป๕ฮ/๛C‘ข.aD`มˆ:มร€9 I$๘มj<{ม||.—ฟ6—N…ก$*ศeฐŽTbฤ๘๙ฤ๘m่ีฺh๑‹‹/ฑ›ป๊Bฎญ฿๏#fnn™LF„พแˆ๔๛คว`J bŒAmEUญV…eภกPH๒้n แ–ีVิ=ฬ๗็๒ลุโnPฒ ว cyBั|ฝ^๓7แ;*ซึญ{rื}Ÿำžฅ+ฮ‚่ญD-V’10P9*g‘>fฺ$2่ํุ#๘Mž“‡jย๙๊$w`9;rดnแ9Kฮกƒุ3‹!v”ฎ}t๋Ÿ+ดฬฆ๒ซฬ{xu =‚๊?• 1‹?22"„๘"ฐhเ๓็๕žธ\.โ๔๑ถ [์‘แ‘้อ&BM(ฬข[–เ1oฒๅภŸฏบq๋‡พ-๚เถ๖๖ร7฿๕ั?|อ {\7ี3*฿ƒˆsfb๎Hฒrใึ.—หาmzr๒=!H„(_™ะ๚›X์?ฑ๘œP฿Wษœ"‹ต๘Zกตฒ†Gfถ‰.ภํW๗ุ๚j๕0‡ŠkตN:%‚_๛็>WqD๚UOˆˆ@5ํWmbพศขD!Xน$Ž๋–Y&c6U่Oงฒ&ฒJk i๗;[H‘h๖ฏZwฌณซ๓wsฆทnฯ7u฿?ฦ[ฺ“า &รฮt&๖rBงtฬŠภๅrณถ๖6‹%+•J๋วฮž\ฮy๛Dฮะ]…YะgรŠ:t{+๖ด?แ•*Yภ‡`I&ณ=s้‚ล๚_ท,‚•b ' Z๎ ภฺ๏ott•Jล‚‡#ฮ๏ณ<เM๓๖๑q9rG)ึxฝ^ˆภ7๖ ทŸ>7!f่ัษก]‘ฏสš >1‰ห–ํ7~ํv—–-ป๋พŸ๛‹ๅk72[MขOแถ:฿fจnuว•Aน†ฒ^ €ดtีZ!”>yneฝ2FฅรLn1ฤขด*ฆ‘nฤท4ัจr๖๔นฉ›D฿พฤืา๋๕jฑ,—ห–t๑cวŽY„ŸRzฌX,๎wฤ๙G๐Ÿๆ gฯžีX|>oั๔„ฤbโ(ฒnYc}-WฌดŽOฬญ2฿‰ฤเ‡k’0]ุฤ7ฎั… แX"ณ๋พโC;v~ห๏Tฌœ!6n1ฑฒqเ]™EbP ’ฎˆดd๙๊‘`0xT€ฆn+๒AศณM„.+pœแU%ั0Q^gคนฯต’šˆv›ู˜/U-A›Wวฐ|‰๘› zO˜sDfff077g)๐wืํ4ฝc์+ ดmกPะrjตš ๐๙|ถ\ภวnZ*}๖์U•J5 ผa™NฌฉBฮšปฐฤl๎‚ ปฯŒBeศEไzY„,œŒ+็UJœ *J€Iคพพgฬ_’R:๑๖;˜$ื.ย๎ก:Fึ฿Eฌฟg1็ร|jี๗Waสๅjุ่หMF๘๐€ญ๏oBPฉT,ค๑‘#GD•YJฉ/๐‡๘‡ณž2o?sF๏ฯ ๊แ๑ธุ?์j โกํึฬฑzƒzOž฿.ภฌaxร&|qJAแAฯ(@EDLw ญrี‹.—d9Qใ็ฯ฿ห(“”ว’ษ‹?ˆHG„"<|ณEb  ๔ำsโิุ-๕ตd๒<ดญรถๅ[$nW๙"uฅR) ‹*ํ _๘Bฮๅหว€0o˜œœิบทVซUabื๋ต%oถak๒ศlบ0011ทŠ1๋ฯY<ญJ6ณ|Mฌ<ฑz`ฐ๘Œษm“Pญณซ๋{ๆ/QฎT๚O9ฐMnยA,.ดชAใop CฌŸ~ž˜ะตโศVSs||f|ฆh)๚้Žxpต}ถฤ๏๛ซjตj 9rฤ"”า:€ฟrฤ๘2S็‡ด ใQ€ฺษล|๑‰„0<๐เืo๗ึ<5<ณฝX,G eนhU)†๑ม6ฃ?;hฎ๔๓’`h์ม$#Ii; ญฺxล3Œ`?w๊ฤfEยีศEWAฌnใูAƒ‚ BC„ฤ`กPŠŸ™)บ6ฟq๛R„‘@F…|P.—ณX“'OŠ*ยพ0์ˆ๑eฆ~๏๗~๘’ๅฦ=wN#vjตšๆr'X;W`๓ช6พ:uซAฉ็่๑๓ทะu-`ลAš+š กฝpณz๋ƒE!HๆmŒฉญปwพตญี’žฯ6 Ÿ<ถQูื๊Zฟซ$ิž’ฆ…ซ …u=>zOƒ2KึไGึ'ฐie›๐ZFฃQaฟT*YฌDยOc8"|yบ „Lฃร(ฅ ถs6฿ัhิาEH]Ÿุต\่ ไŠีถใงFฏ'bาJ(๔L”Œรฌฝ@0˜ 3ฑj^ูO2Zjํฝ,(@ํณทrฆoŠฮร้ฃ‡?กขฦป‚ฯ‡Mา‘)* นอ\fBHวOž฿•/U-3ปยn|b็r;„(t๕c~hffgฮœ%|‹_ขำ๙๗rUปw๏.๘kห{๚ด†๊๕:า้ด%KA{{ป0E8๔โ3๗ฌ~ๆิ\nอ๙ั้๕6–ะ"๔M ฟ–ญวl”€n]™ Is่ฯ$b ”๖ค๒sฆ &?–,[5Ešk&›rฬ๑ตฒฌ๎kฆœD(pฉ”†฿.r ฮŸŸฺ:=Ÿ฿"บฟu๗ แศoBZZZ„ะ?Ÿฯš~0ฦ๐๊ซฏŠ„ฟม{ุ฿หX(๋o`!N)ลษ“'๕9“ั Ÿ9,h็ ฌ]šภงฎึ=32{์lบo๑^ำ› !ดX]&1ซฅ70(Bไˆฃ“"ญฐ๙๋ข฿zโะ?oEผข!โ่ƒ‘่3ๅ[ดภ๛33ษๅgฯฯํ}ฏ_พพหถO$†๊๕บล๗ฦไไค(๏฿ฟ๔ฅ/pฤ๗2WƒƒƒK{๖์Yํb3ฦ07'๎เ”H$„๕แpว๕ุฑ""„๛วN๏Jง๓vBOฬคฑ„ม ภ@้฿„HR…P!๚์|u]!…”˜Bไ็„IซึŒลข‡อฟ5›อn8w๐ใ๛^ู๐คฃdF%Dœ฿ะ,Š$•สuฟsz๒~&ธn\มื‹c^ฏฑXฬ‚๘Tw/๙ญVซx๙ๅ—Eย_๐8ข๛ม@`Œ€Yณฏwไศ฿›Gวq฿w‚Ÿ_]]} €‚IQข‹’lE–EE–mญํŒ|g;™y;;žทปูMฟ๏}ำ|ซT*( ŽjaooฏcT@เ >1 †%ง Oผqๅ๙|ฉ Kx๒™พO[ฌ๔ฤดy๕ท:ษHภพ๒;h‹‰Aฤฬ x3Yเฦ๖๎N฿๏นSฏฬnบจY,7e าvษฬIs"น|1๚™ษ;9ร>๓1|๓๏ลqบปปวล—JฅฆขŸ฿ๆ7( N-ฟพุcMw ปIเsŸ๛\ภ์็gggอRaXXXp,E==ฮCv‚> ‘yš›Šจš๎yํ๔•Šๅจอผ„บ๏n'7%๖XWYBH# 9WีฟมIศlลs„Esภˆ ๎ผแ\4m(ทŽD"ฏ฿t๛มฟu๘lฆ 9gaอโ_(”ปNœž„ช้M)›A‰ว?ฒ Aฟณ๓6‹9ฦ5Mkšํ—Jฅp๘q' ฅ๔+ุฎ๐ืโ:t่7> ม0ฬd2ว-Ž่ฌื๋Ž™a’$Aื๕ฆส0F{ถ๙๑‹“ MฉพบNล๙T~4๔Nzdฉdซtkj)lM88_Žhๆดช l2 (YlELดโ๓๙ฆฎ\ผ๐ž@ p้ฆnๆป๎I อ;™6๐s6อภnpฯ{4‚ไrลพใง.kUำNŽฝ?่†ถ†]ํH$โXํ7??฿๖ำ4 ใใใn>7๓7':ฐdp๔่QํะกC—|ฬzพ^ฏƒใ8s…WUว9Vzฝ^TซUG-ก+,cGท„็ฮdšžำu*&๒;ƒ~yึ็๕,•2บ์‚‡šฺฆ|| ธi!‹วo6๛w*โ‘H>œ>pื=?ŒtลSŽ~„FRฑŸk>œ4ไ๔€,,ไOœ™๚”ฆ้Žํ›๔ƒ;\ใฒ,ปFt๒๙ผ™สไตื^รูณg*~๚o|ใฯ;„๗฿YBศ]vXฯ/,,`๋ึญ&่ซี*ผ^oำdB|>JฅRS‰(ฐุAh{Tย ็H€R!™ส๏๒ˆ|6๔-ฌ๔ฤ”uึF“ร‘ณฝ˜ยแh>G+9$๋NB์ซ9gYล9 ษ›F% Ž$เDำ3๓๛N'f?ช๋ิั๛ผ๏ุ๋แทฏฏฯ1แง^ฏ79Sฉž|๒I'๐<ซ_*์&%€cวŽแะกCฟ๐o`kGžอfฑ}๛vs•จT*ƒMฮ?ึCฉ‡v้ฐ-$เฅ๓YงฅKeŠฃบช" L/ัxู‰YโF@sHฎูŸ@อH„๑jณูO`?{ฮม‡ม5พZ†Zๅ'3๗\˜L=ไvŸo๏ย79‡cyžG__Ÿฃฏ๋:ฎ^ฝฺ@ไŠขเ๑วGน\vช๘ซ่บNoฝ๕ึณฏพ๚jพMH†)>t่เึ๓ฌ0ˆ™ฬMช#k!ๆFƒ}AŒty๐ผƒ&€ไ •|ถล‚—xžW[ฏ๚ฮตEฤฑภฐแJีœ:8ฬภƒตZ3T@oหใoฒแูŠOดีฑhช๏ GH[DPฏ+พ'/~d.]ผ‰๓>๗๐๎๏“ใ8lูฒล5„;77ืิ่๓๙็Ÿวๅห—ภ]ืeJ้;u]๗ˆ|๓อฟ<~xงืf#xเ^๐}vS งงว์ฬFŠ9u ฒ,7ูLถ๖๘ฑปื‹ฮf ;(๘•šMฮ็v|าœwiฟภRnkaฌ-r@Z8ิ(๘Ik=iตnwHs๚1gัFšฬ……ภ‰7.nฉข8–๐ ม็?<Š[nˆป๊B}}}ๆ$(๛ถฐฐะ๔ปMLLเ…^pฟu/่บ~'ฅ๔w๗๏฿๒ตื^›่@x“ภัฃGuร๘Œ๏'“I š๖ญVฯ๓ŽNAQแ๑x Š ทห‡†Cx๕\ต™TM—็ๆs๛4E!ัHpšฆม๎ „๖ฤfืฆด ?k1หzํเ'„ะF3ภอˆึ G&๘-ืว้:ๅำ๏>w)๙aMฃŽล๛1™วŸ|7npษ๒cเwk๕žหๅฬ*Pซ๙๗ำŸชช.~๋>ค๋๚'oผ๑Fๆ›o—':MC \=t่เ=  TU‹E๔๗๗› uน\†$IŽลAK‘@$่มม]1\žส!YTM‚bu`>•๐IIY๖๏าC ez1ฑ;๗‰ˆc~1Ÿ3ิ{าJeทšิูฐฆsV๐S€หd๒Nœพฉ…lyฟ››coฏ๚๑=่VRฉ„d2ูt~aa;v์ภพ}๛ฐ{๗n #ƒใ8dณYhšๆไฅ”่บ~Ÿฆiร{๖์9|๊ิ)ญ็ๅ น^๘‹_"ฯqSšZHํปปvํjธนถlูโฺ2ฌRฉ49•| u ฿2'^Oท๚"๔พžะฏw o}N’„Šะญ๛ล9œฒุ๐l<—1ณว๒zงเด๕n˜พN๒A#!5>vสๆsำ ธz]๑&.ฬw5Uธ-’ยท/†?ฐฒGpต๙™ฺ๏๖๛ฐœ~๖{ŠขA" เšฆ™ต็ฯŸว‹/พˆd2ูJ;xJื๕฿๙แุ‰l€ฟหฟภโ0‘ฆAqฤึญ[›Knƒkตfgg›FH›–:ž;>ƒฟy๒ T=๒'๐\e๛ถฎgโว9Bดf%@o๚&ภ›ฃผa#ƒ%ใ‘ญ}้›3ฝอ EU"5ิ+“Woฟ2“9คบฤ๖€็>๛ภ y๓Vื>็<ฯทt๘UซUฬฮฮBืus๘‡[ฉw๓oถ˜VฉTpโฤ <๘ใจื๋n$pZื๕‡~ใ_๎ภzƒ›S ่ะกื|NFณณณ่๋๋3O)EฉT‚ฯ็kส`ŽAฟ฿JฅโH„ทq๗๎(ฎL็1_r่”Š™\ytv.ฝฃด๚R†ภ’Cญ?ำg‰{ณN{๑‘k]“?€4ฅ7๕puR S“ษงฮN~2•)ะ)~›=q้c{ฐoG—+๘%Iยึญ[]อHYืu‚€@ เ๘น!ฆ–‰Dฐm6LLL Vซ9‘@ฎ๋{๚นsณho ภข <  )หK–e{๏ฝ vๅRš€ฎ๋˜››sœ?`˜Š†ง~5ฟ{v๚“o<’ูj ?Aเ๋+RZ<ฦ›฿L%&อS AˆYžH›ร‘vข(ชgj*y๛L2WMQปZฟ!๘๔=[p฿ํ๐ˆ๎๋ƒฯ็s-ิฒฏŒœึ#"›อš™‚,๛3•Jแ๔้ำ( จT*nQƒฅ๔ใ‡i\`r๏ฝ๗>ห๓Mvo†d2‰mถ™+ฅลbข่kŽีุ+ฬ,j>v„qืXs๓E\อป”ี4›อWvLฯฆoฉ”+$ไ=ฉโๆ๘ณ:ˆณƒpŒ 4Œz3€4UZJ}]€„$Ÿ/๕^ธt๕ณณคsๅ}š๎ฎ๎ภm>วGvใ–qวŠ>&ัh๑x1ฝ—9n™Oฆ]๐็r9\นr˜ŸŸGกP0Wz+้ฤb1ไ๓yhšf>oำ$]ื?2::บH$~ี๘&ะ - เ_์tรฝ๓๏l}™Lฦฌacธภญฺล‰'Nงฑฐฐเ4&ŒษอqŸ>|X๏@}ƒ€A^ะ฿t“vwใฮ;๏lZ๕cฑขัhK•า:Ÿะ•,ช*ๅ73๘‡็gQVฺพ_๔฿s9๑Ÿ‹Eƒแp`nฑฏQทD ฌ?†๊FSภํฑ e@)H.[ุฒฮfrๅ…rm„ถYๆํ9|โฎ>sหV๘dฑ๕k}>ฤใq๐ผปา˜อfฑฐฐ`t p5สๅ2Nœ8s็ฮ™^ถ€x<Žพพ>pื@LEมฉSง077‡T*e&Ž9ศ9Ž๛ร‡W:p฿เ`ภ†&ะT[ฺำำƒƒ6‘@0tญ8ณช˜้tฺ5Tศ$_ชใ™Wง๑—ๆPื–W(๐|1๐\ ผ“ก o*๑ฯŠ‚Pw๑89้a@ข(ช”หทไ๓ๅ|ฑฒ=_ฎ ซช\ฮuJ<มว๖โ[ท9๖ํk๐ pbฑ˜๋่6๋Jnm้ลzปฏผ๒ŠูยJึcž็166ฏื๋Hบฎใ์ูณ˜ššยีซW+E y ภรใใใ๓ศop0Hเท €ทย7๎ IDAT]M@–e๔๖๖ถ๔2+Š‚T*ตค6ลr/ŸJโ_œEฒคฎ๔_กขศ็1้‘„ดว#ๆ$QศK’X’Dพย |ใน:ั™ฒ jบจ+ช\WUน^WŠขซ55\ญซ]ๅชWT-ฒา฿ญว/เ‘;ทเ๖ฝq}K‡โ|>บปป]ฬ4ฌนนน‹(ŠฎfB>Ÿวซฏพjฺ๑๖ี฿พง”bttแpุ‘€ลv๓ฬwเๆ๗0เก๑๑๑sุop0Hเ!?ะtงF"uื]M‘ž็ั๋šbตS:ูฅฎh8}!ง~3‡.nฮ Tw๑ž[zqรHฌฅg฿ิd]]]-+ภb‚ฯ\“ลฉ’“ฝ๘๑ใ&๘[฿ฎ ขททืี/0;;‹3gฮ ™Lถ"๘ฎ}3๕ยŽ=zศ๘๛uVซUฬฬฬ ฏฏฏ!M)EกPฅิ,FqT% กPวตZ1 Rแะืํว๛โธwoย"าน2•y:๓เฃwnมงฦกwlC_ทฟฅgŸูํฑX ๑x5ฑ‡}ฯ™L๓๓๓M™ข(:ๆจชŠD"|>฿ะจ=!™Lบฎ›MEํ ‡อ๐ S๗(>ŸM$‰S๘o` ภข |ภ็ค ˆขˆƒ:๖ ๔x<ˆวใKfiš†L&ƒ|>๏X^์ `&UฤนหYผ|6ƒW&‹ ืy85!ภm~ผc,†ฑํlํ €v฿ป8Œ%ถt๒‹]œ’ษคภเ๕zฟ๓้้i\บtษ$ƒฅV|ท็"‘FFFL"bตVํ๎ฤ‰HฅRฆCาลฅ๚งาฃ>ฺ!€M@๘กมเM7๏044ไบขนญ๖Š%ŸะeขนXQ0“,bb*SW๒xๅJฉeบ๑Zˆภ:เวํ!Œ๖‡ฐ5@ภ+.“4‰D–ฬะฃ”šŽTง๏งRฉเาฅKxว;ัคื๋uผ๚๊ซPว™อ@—|๋๏๗c็ฮเyั/PญV๑๚๋ฏc~~ Iบศๅ8๎ณ‡V;ฐ๑Iเn,ŽwฌEฦ๛W0I’ะำำใš=hื๒๙฿่๐O:…cวŽaffฆU;n่บฅ๔?่บˆัž|ฯ็C?A€ชช ้ธ,๓ๅXดใปX‹0กื๋5อ9ึ}˜3งฅข(ะ4อM3๊1œƒฯ&‰้lp๙๙ฯฮดฟะล.Cฤฆฆฆ0==m†’–Rน™บ[.—‘ฯ็‘อfQ.—อ›ˆญ^+uฮตใlT•J…Bก๐์:ฺu:‹Eผ๖ฺkx๚้ง155ๅบสวJ้็ม7ฟ๙อ“ทz๋ปu]ฒพfjj 0myง||ึ”ๅd,๕=9Eฺ5€ล^‚ฬษk}Ž}G,LXฉT@q‹ฐ&<•H$ฮvœ€›หIx€เฮฅ^+Š"FFF088่>\Ž0›_ำ‘g๗xo~ๆด: ™sKUU3ชฐZ™››รูณgq๑โล%U{}Q…๘{J้๚๋ฟkณใ๎แ>B)๏๖๗๏฿ฟ๚ะ‡ ช*.^ผAœ›์ธT*arrฒษ้น\็`ปY„๑x คfีŽฒู,Nœ8|>T*ๅFข€?ฌC›H}๔Qโ๓๙> เ/`k>๊&lhOพ™ฅX,brr็ฯŸ7หk—ม]ื๘ใฏ๋วํŸ๗้Ošp\ื๕}๖๗=๘เƒ8x๐ >Œฑฑ1ำ™i'Zญf]ŠฺMn๕\(ย่่จiึู{ ฐ0a.—3‡‘ธศื8Ž๛?>ฌu`si€O๘<€m}I„ ททˆวใ›Š ุTžห—/czzz9๓๗^ค”~ฑว๛Eซฯฝ฿๛ฝ๗PJ๊บN์Ÿ๓๐ร›v๏mf]ฺ‰@UU\พ|ูฌl‡(ฅหZร„Nี„Œjต^uคำi$“I(Škื่๘ไ๘๘xนC›Lพ๔ฅ/๑„G|ˆ[I,C__บปป*ฅฝ–ข๋:rนๆ็็155…นนนey๒)ฅOQJฟT.—Ÿึทพีึ฿ิง>๕ J้ฟs๚ผพพ>ฤb1ศฒŒnธม,Dฒฎ๋˜œœDน\nน๚[ฯฑt์•˜‚ `ll ฒ,›OkoUUq๚๔i$“ษฅZฝ เใใใษlBy์ฑวHตZ}7€ภ๛–๋eM3ปบบ‹ล ฎถ[ Q๙|™LฉT sssŽูzK€_ฅ”ˆR๚•ฏ~๕ซ//๗>๑‰Oศ”าง)ฅw8ญžž๔๕๕A์ฝแpุั „`ffน\nI3ภž9ธ’jBุนs'@ƒ&ภิ~J)ฮ;‡้้iคR)ื!ด.x฿๘๘๘™ln๓`;€ เ๗l]้็ฐQVกP@ภ Cฑ VM6„ฅืj5”หeณL9“ษ˜7ฅีyธ ๐ฯQJฟฅ๋๚๊Wฟบช๐ึG?๚ัnJ้3”า=N+c๛๖ํ;w๎4รoNฮม๙๙yฬฯฯ/™5่ิ[`นูƒบฎcddัhิ‘เ๒ๅหธpแ‚9ฑศEา><>>L‡6น|ๅ+_แu]?เ>„ละฺ}้ฦt\I’ฬAƒ›Yำ (ŠYยบTHo™เฏx\ื๕๏RJ๖ๅ/YYซ๋‘G้ัuฐฎ๋ท9]G ภศศ$Iยเเ 9Iศfณ˜5C€ญภ!ฐFL–c  ฏฏฏษ`฿xใ  s‰ƒิ|f||{x๋h2€๗๘€ฤ7า๕ต *ฅ๔ฅ๔‡”าโฟ˜^ฏ๋๙เ?่๐฿t] ำฤวcฺ[ถlมะะPSX”ณ0!3ตฺัฌฮมๅš===l 2ฐgณYผ๚๋ๆ๔#—๐Ÿซี๊ž~๚iฺ!€ที__ซี๎p?k๎€Kฆแ„ฎ๋ฯRJว)ฅGพ๐…/ไฎีu=๐ร<ฅ๔ฟ่บฦu™f@ @,รฮ;อœ}ป6ภย„ฌ&ฃ“€ญเ+)& …BqMV*•pโฤ  ฬออตสษ๘(ฅห‘#G๊x‹ส—ฟe/ฅ๔6,&เธิ ฌ3๘U]ื฿ ”’R๚,ฅ๔…G}tโz?=๔ะฎ๋๚Wเ0ชŒ‚;v "˜aB;pg† ๋๕z[šซœ\i˜ะ๋๕b็ฮมั/ภย„,ณE„เIŒ็;๐๖1ข๖;m0€ลดไี”๖eL˜ ”& ะŸข”พ๙ฯ~Cฮพ{๐ม? เฟนiJๆธ๗nธ>Ÿฯั ”šaยฅr–ำ[`ฉ0กวใq$&LฅRKEN`1B0ี!€ŽฦภˆSJปฐุ‚* @ฦbปs+1(*rฦ–0'๒'›rภๅƒ>x‹I3ฝNฯวใq Bลฆ0กฝฎbvvูlถญdกvา‡[‘ŒŽŽถ ž?ำำำศd2ศๅ\ญฌi,ฮ&u‡:๒ถ”฿ํ฿&„<เง็รแ0v์ุa๚XoAง ถ›5่”>ฎV ๋:†‡‡‹ลšH€๙X˜ฐP(ธฮMP๐ฑ๑๑๑'6า๏ยwnอM-dlmษฤฤDvtt๔{†ฏคษORซีหๅ‡‘อfมqœc1!~ฟข(6ด+_ชรณฝฌx9ี„้tฺ,ev’H$Ÿฯ‡BกI’PฉTœH@๐ัััั๙D"๑J‡: ]ฮฦmฐ/—ฮ%‰๊ศศศ๗ !ŽEA:6›w*Šโ:ี‰%Wๅrนฐต"ซƒฐๆ"ึวน\Šข ‰4 ๛@‘Hูlฒ,ปuโผott4ฐ{๗๎ŸŸ;wŽvเํj๎:€[O0ฏ๔s&&&tY– …Mc„X@ EQP*•FWm6ไ5—ห5อZu^J(•Jๆ๕ุ›”PJ!ห2บปป‘อfแ๑xPญV๚ ฅ๋๚ ฃฃฃ' ตC›€ร[C#h›$’ษ$I$O๏ุฑcŠ๒ >ค”baa’$็ydณYspฉ}uแpลbัฑjฯ‰์>…ๅ˜ีjตแz์ื-Iz{{‘หๅ I’™ั้ {ผ{tt๔Ÿ‰DนCไ ธึ ฿Œ๊[ค011q|ddไeBศฐุ“ฟAฒูฌนฒ.,, ›u๖yแpีjีujฑ0BYnฯAึ์”MZฒ>ฯ๓ˆวใ(—หfยๅบ|httt<‘Hค;ฐq@พภ็ฎเ9l •พ๗NLL\วqa1,ฺ …Bีjม`ะ4 X?๛tกp8lถJ[๒f0nฯA๛c]ื1??`0่z=๑x์เฤ๓ผuu๘๘่่่๓‰DbฒC๋tnมฟZเsx๋8ฑ’ื_ธpaพฟฟ‡‚ ‹ล9} ยz†รaคำiH’ิ0ณม <ๆ4,‹mk+MF)E*•‚วใq!ัีีž็Q*•เ๑x">,๖<—H$Nwเฺ„มึA#XษgแzยฅK— ?e๙&ฃ๖บ^ฏ#“ษ "—หา&dเ๔๛$ษuชq+M`น3 “ษ˜aBkoๆ‡ร๐๛ศfณ๐zฝ(—หN$ ๘ัััj"‘xพC๋B[O๐_Kภ๕ิ ฐึ๏™ššR8Ž๛วH$าK9`๑5MรยยBก*• ๊๕บนโUuY–แ๗๛#ญศภษ9ธ0กSฃWฟ฿H$‚L&Ÿฯ็! ŽŽฦวฦฦŽœ?žพ เz{ kื›_ทฐฐ@/^ผ๘ณแแแ*!ไ^ุ’˜ํ ช*ŠลขcXŽ | …BKฮ>t#•„ ํืc%Y–ัำำƒt: วำj\๙ํ”า[v๎๙ำD"Q;ภตธAน  %ฌทFฐ‘๒ฐืPJษฤฤฤKรรร็8Ž{ฏก7 .Šขiฐฦ*๖1`,Lธœv๊๖Veํšีjน\ฮ์i Qั‹|>Q[Eฦ<8::๚ำD"Q|ซภ๕๛[ษ?ฐดฌแkžฟpแยƒƒƒย๓๛เPM˜หๅฬ.ฟ้tกPจกkัVฉTPซีฺ๊ืธšัdึŒFง๖o<ฯฃททืฮสๆ:ศŒŽŽK$ษท ดบ!pnเ๕ปm$ภF$ด๛ล‹งทn๚ธ(ŠˆฺoฆRฉ„Jฅ‚P(d† Y๛q; #‘Hร€า๕Mfอhด^‹Bฬ0aฝ^‡$InƒSรF„เๅD"qq=ภxญ@๏๖˜,ใตซ}๏Zฝg9็\ฯฃศ‰1ŠœƒAโ๓๙ˆื๋%ฒ,Y–‰ DE"ูo„ู•VQ…ZzโQUUiญVฃตZVซU”หeฉัRฉD …ย’S‚mฒ\ง]ล๓ ฯqว=‘Hไ๛„œ^์๗๛166QฑcวฤใqืjBึ=yฉถใญบ/งšphhศฑš9'ฏ\น‚‰‰ ิ๋uฬออน๙+ช>2>>ำอBk\ฒJ“u €ๅ๘|>ฤใq‹ลธฎฎ. …ธ`0ศฮ๋๕๒>ŸH’ฤy<NEย๓!†ษ๖v €. S]ือMำ4]ำ4ชišฎชชฎชชฎ(Šฎ( #ญZญ๊ๅrY+•JZฑXิ2™Œ–Nงตt:ญฯฮฮา……ฝ GฺJWถฮวb1๎ถn๛ Žใg7•uŠวใ1มkืJฅฎ\นbพoฝG“uuuappะ iฒ–nม`ะ,xR—/_ฦwฟ๛]ทพi๏Ÿุh@ฎ! \OBh8ว๓<ถmฦ๕๗๗s[ถlโ๑ธF๙`0(A–e๋๕๒’$๑‚ ’$1ภ3แ ะ/"๐!„!„pŒุฏร ~๖ะะุ^gb;ึ,d ี๋uญ^ฏk!hฅRI-‹ZกPะา้ดšJฅดd2ฉฮฮฮ๊ฉTŠถˆฑฏ%4ป๛ญ แๆวฺฑcบบบ‡Mำภi"Qฝ^วฅK—ฬt•Œ&[ŽYเ๗๛1:: วƒ-[ถ ปปฌ'`ฤP.—q๖์Y|๓›฿tำ^!„๕ณŸLู@ึ6…ฦภ๓<†‡‡นพฟฟ_่้้"‘ˆ‡EŸฯ'ศฒฬหฒ,H’$0เ ‚ภ‹ข(@gK=่yFL0Ž๚‰ำuภo ฆ0๔30๖šAšAšAšชชZฝ^W!TซUตRฉจๅrY+‹j>ŸW3™Œ:??ฏอฬฬ(333๚ผขM๖zฝ๏พ๛’$้[p้7ศปzฝ^pร MGญD i._พŒZญึv๛qง๎รํวqธํถฐo฿พฆjB‹)‡rนŒห—/ใฑวssว๑๑๑ฟ\_fอ`ฝ€ฝตื็bฑๆ‡††„ญ[ทŠ]]]B(ƒม `_”$IEQ`๛Es^8Ž่yž8Žใ !ผmo’Œ•฿ิ(ฅv"€ ๘์กฮ`๑žา)ฅTณ€‰~Jฉ‰Fฦ^UUU5ศ@ญVซjญVSหๅฒZ*•ิ|>ฏๆr95•J)Wฏ^UงฆฆิษษIฝZญาต^๑ฮึoึY–โ8ฏ!chhศ์7อึtดX,ถีuุ"lืะ˜O}>Ÿ+ PJQ.—‘H$๐ีฏ~ี)›ฑจ๋๚ุ“O>yu…ฺภ๕zkMฃั(ทkื.~xxXฒe‹‹ล„p8,ั๋๕І]/ ‚ ˆ‹ฒธไ๓ผภ๓ผh^`ภ็8N „ฦช/X‰€4ร๑g7ฌภงฦอ [4Ÿญ˜›Aชกจ๊" ˜{F๕z]ญื๋JตZeD  %›อช ๊์์ฌ299ฉ\นrEฏT*t-W|ทืuื]ƒม`๐๛„1งFฃfฟม]ปv™้บvmXl:šษdฺn?พT๗av<44„ฑฑฑฆ4bŸฯืrธlฉTย‹/พˆ๏|็;NOm||VฅYฌุ7" `ฑใฬุุฟs็Nฑฟฟ_๊๎๎#‘ˆ %Ÿฯ'สฒ,z<A’$Q’$QัP๗่Eƒ๔ขAข๑˜7ˆ€ภ๖<ำ ๐ณcำ`ืg‰Pซ+€฿ุ[มฏY7 ๘U]ืออะถ7H@ฉื๋šชชŠข(jญVSชีชb˜Ša(™LF™ŸŸWfff”‹/ช333šm๕ขkD oบ้ฆp__฿w!w9ผ^ฏ{๖์ฯ๓AooฏฃOภ&lว!hMๆD###p–ื๋m๐ุ5Bก€o๛8~ธ้Bฝ^x๊ฉง ถ๏ฃm"เืxี'+<^อgญล๋š’Qยแ0น๙ๆ›ฅ={๖x=}}}žh4๊‰D"ž@ เ๑๙|ฒ,ห’,หY–=’$ฑMEQม๓<๏ๅy^Aมห๓ผll>ž็eŽใผ<ฯ{ฝฯxฝใ8Ÿ๕ผ๑ุวqœyly={lูหวy,›dูDƒDB;fš‰`h&ฆฯยขฑ˜> รษ๓<ฯขœ(Š i๚|>"‚ต งีwี<ž››ซjš๖OฑXl;!dVU ˆลbfAVฝวVckNวใi๊7่”วa@ุ“††‡‡ั฿฿฿\,ฒโค ฐ9’x๖ูg›"Ÿ<ฯŸK$ฏ-ฑธญ˜ศฎ่kE๋N‘H„ป๑ฦฅ;vH[ถl๑๔๔๔ˆแpX ’ฯ็๓๘|>ษใ๑H’$y<GE(ŠA<‚ x ฐK‚ x8Ž3ฯq'๓<๏1๖^ 2วq^c“ p{-เg ๗{+!ฐ๗y !^ŽใdBˆฬqœ‡โ!„Hvะ€6ปย็LSฤnžXL“7รขฟ’ๅ0y ด Xอc ูlVฟ|๙๒แํท๓วiฟ™5Mรขัจู=(‰8‚ฺ๋๕šี„K gตพื๊่้้iน๒ฏMืuวิaV๚<==ููY๛ำB"‘๘A˜Y6ฌี๊พ _ -ฃๅ๋A cccยะะวลX,&ƒAษ๏๗‹ฦŠ/J’$‰ข(ฦพ$Šขฤ๓ผนqg๎9Ž๓๐๙คŒDI’ybbbvพฐ7ษ๊:z“ˆ๋็zฝ^"I'Š"Kะi๐ศb๔ึี’ท>ถชอgo@)qบ๙'''q้า%TซUœฟ฿Y–›ฆ๓<ปw7ต[ ุ๖^~๙e;๙ผ–H$ฏ Œฟ,ุจซ๖švกP€ฎ๋$ Fnร8ปฃฬjฏู r์ชฟU7ำq oฎเึt]ฏišVำ4W5Mซ่บ^a$`ูj–ใบชชUMำjชชึ๘๓uUU่อcEQ๊ฦ^QEฉื๋Šข(JญVSjตš™ TฉT”rนฌYj.—S3™ŒšJฅ”ููYujjJอfณ๚๕พWสๅฒ~๑โลฃCCCUŽใ…ๆtjฬฮฮ"ข^ฏฃRฉ44๙ด๎eYF hh:บu๋V๔๔๔`ญD’ค&˜žžn"J้‹†ฐl€ฐ’7ญ‘ฌeฦเš^‹ฎ๋ธt้’VญViน\FตZีkตD"ด^ฏำzฝN}>Ÿฎ(Š.I’๎๑xtMำ4EQ4Q5ATUUUž็U#Pๅ8NแyLดฑ%๐-R‚ํ!:8x์฿ฬถ๙,้ฟŒpฬใฑ™kS๕ร`€bฌ K6@ญT*JฉT2ณณูฌ2??ฏ\ฝzU™žžVŒ๐Ÿ๕ปฆื๋˜RŠcวŽ๕}๗7)IาcN‚“'Obืฎ]‘ณjB]ือr`–ฦปcว\ผxัtญ้M้qp้˜YมNญ€๋tl rqTมfggi6›ญ/,,h๚–-[ด๎๎n- jกPH๓๙|ชวใQฝ^ฏjจ’$),ุVภR€y ๐เญฉภึhƒe๏ไ,cื@VƒฑJ@VศR-6?ณ๛ออฐ๓ซสฯjŒีŸฉJ.—Sา้ด:??_ŸU็ๆๆtMำฎุ—”งžz๊ว๗sฯŒฯ็๛.Z={ฤฉSงฐ{๗nศฒl‚Ÿ$Iุฑc&''›’ŠV#Ny ฬp้•i)•J…พ๑ฦ๊ไไคพ}๛vuถmJOOิีีฅ„รaม็๓‰@@”eY๐x<ขวใแ%IbuAฌ(ˆทT.v0Rk-ฉถๆกศ์h]b๔ $ ๋: /.๖T3ผึ`vฌY~6ฟฦl}ฆCฌ5j*•R’ษคšL&ตzฝN7๚o๛์ณฯพ|ื]wฝ/ ~าไ^Ÿšš‚ชชมษ“'ฑ{๗n๘~lฯ๓<๖๎ I’–ีyธ•8%1brศxcต+ๅัธข๗y<าืืวmถMŒวใ|4eB‚ื๋}>o$ Fกภ๓ผ Š"/ฯqใ~1cvฑˆญ$ุฉ'q"kุ ๋Q[W Eภ\๘฿l ยภo”›ž}EQ4zฝฎ}ซ“ฯ์ JฅิT*ฅf2๕นฆ,็x->ฃญใฤโ๑๘๋ึo0c฿พ}ะ4 ;w๎4ป๗X‹ˆzzzฐu๋Vs`ษjWVถl•\.‡ฯ~๖ณ๖—Wณูlฅ—^ชฌไ฿LŽฟ ็„ คทท—๋๎๎zzz„h4*„B!ม๏๗๓F๖  ห2๏๑xAXW s3ัฯ2ŽlBlน๖๖† ฤ๎Œ4Qิ’ฉื@ฌ%˜ ฌ=๋ คฑญVซ™[น\6› 9๙ดL&ฃ+Šโฺk โUฦฮ;ๅแแแop๗~'PสฒŒ@ำ4 7U  ปปดำซี๊Šํ~ฟ฿๏ุ'เล_ฤ7พ๑ ๛้Ÿฟwฅ๗R&ตlซ9^ YซkYณใbฑˆbฑจMLLh‚ ิcฑือ๘p8ฬ๛|>ม๏๗๓>Ÿ—$‰๗x<ฌ oh 9!–,DๆlกGkTยŒ:ฐิ\ ๘Y“P]UUณ/ ข(ๆˆๅ๋ีj•^3@ฏฑ^6›ีฒูฌ^,™ณื๘๋ช)œ?พr๕๊ี?8x๐เฃ<ฯปฆeถZล/๙K~๛ํธt้jตL3ภšบห:น m)^ฏืชชโ‰'žp๒Ud5w;๕๒o—์ภ5=ๆ8މDธP(ฤฑNภ>Ÿ๗๙|ผื๋ๅ<OCฒK™5rL`y1 €ๅ0`ู}๐aฌ๘PUUื4ฒF ŠขะZญฆ ื+•Š^.—๕Rฉค‚ตBก@ …‚^ซีจรผœzkMซ:พ๛@„ .ต2๛๗๏G @$ม๐๐0Aภฮ;‡›@๋2nZ†[ ๑ห/ฟŒฏ๋.ชสะ3ฯ<“^O่/?ขเ๚:Aˆ1ธ’๘~ฮ˜ภy<N–eN’$b4%Fขูœ็yึœณG,(ฅฤp๖™mมู๔f[p์ๆพRฉPฃ0-—หจื๋NM>ํโณRBุdq฿}๗ถ$I฿ฦโ่๎&C<‡,หุนs'๖๎Dฬ4ซVซPeลเฯfณ๘ณ?๛3dณY๛S฿๗ซัช–jy๒๚ผฟๅsุa  „ใ8ˆขH€ฝชช‚R EQ(๋Nร6–ืพ skนฯำ๋LkN๗sฯอ>Ÿ๏{“๛อ’฿๗ฟ่๋๋sย4M3‡2’ๅ8‚ ˜&ƒ“ิ๋u|๛฿ฦK/ฝิd•จชบ็ุฑc“ซ๘Žฺpั๕๒฿ฟœ็V๒ธ฿nญeญ{าk๘Šˆร่7๘Bศnงผททปw๏ฦM7„[nนลตษ็JDำ4่G?ยO~๒' GŽyt5เ–ืdณƒ๗z๕ฤ ภฝR€ฑ๓ซlฝŒฯ[ฉ‰ฐ^Zมšj “““ูP(๔~ฟVBศv๛?P*•อfฑcว๔๔๔˜}ื?๑มเT>Ÿฝฉฉ)m5เ_Š6ฏg ๒ีœฎ8o;คฐœื/—ึฺlธfNศซWฏึ4M๛ฑXlฒฯOิj5œŸ9šฬI …xใ <๑ฤธpแ‚๗ซRJศ‘#?X เฏ๔น@HcลึำทRฐ_ซซ•ฤz›๋e&,๙{๓ž{EQ€Xซ/G–etำMุถmบบบ ห2€ลฤขd2‰s็ฮแฬ™3KEh ”า฿=rไศใ+ธึuนy6ำ ต|ํz~ตฆนฮ€_oRุ(~`q,ูˆื๋€๋๘}ฟฎiฺ'=zzญVๅ๚ึ“ึjE_ฉnน;ฒฮ@Zญฟพฯ[ฉเZ8 ฏฅ™€ห—/งu]n4-Bึ๐จPJฟฯ็™gž™[kเฏ๕ ฝึช๘†™ผBาX+ฃซซีึe`่๕๐#ผ๋]๏๊๕๙|D๙ *พฯ2€ฟSU๕ห–$Ÿ5z@ืฺ ท‘ํ๗๋e็“ ๘v^w-œ†ืœ๎พ๛๎฿๏W„‰๒.ธคW{ฯRJค(ส?>๕ิS™e†ึฤ๐ืsล_ซ฿Œฌ๋ท‚Fะ๐๘ึ[o•bฑุM„=„mXtŠ๊„,ฅ๔ ฅ๔TฑX<ย /ิื{ตฟž*ไF]ัฏ…oฝˆ`3™+ZƒZh ๋๙MKkศ๋ตš_oฐ“ ๒ปาuz]ว๓ัl +ธžท `~ญี๙ตNูLซzZƒ#๚๐v"€อ๘`็“ ๖โX)ุ฿R€ทหF๊ ผัปศ:ต8ฃ. ค-ภI.]ภ้:ฝFฟ้z๛6šFฐaW,›Yล฿LaพkEโืJXkaS~3ภZ™ฌ๒๓V๖๕ ๓‘ ๖ๆXoุ\คฐ’ืฎ” –๛oๆ฿่ํข Co bXkีญีป]เZ'ฝ-€๑v’h็“ ›nงเตะ๖่ศฦฑ๓;&ภ๚i›ผ๓ฌ๋๗๗Vr๚ญ‡fp-4‚Žt`S}ฯdฦk ,zvG:ะ๙6Vะ‘Žtค#้HG:า‘Žtค#้HG:า‘Žtค#้HG:า‘Žtค#้HG:า‘Žtค#้HG:า‘Žtค#i-?_=0.5.0 domdf-sphinx-theme>=0.3.0 extras-require>=0.2.0 html-section>=0.1.0 seed-intersphinx-mapping>=0.3.1 sphinx>=3.0.3 sphinx-copybutton>=0.2.12 sphinx-debuginfo>=0.1.0 sphinx-licenseinfo>=0.1.1 sphinx-notfound-page>=0.5 sphinx-prompt>=1.1.0 sphinx-pyproject>=0.1.0 sphinx-tabs>=1.1.13 sphinx-toolbox>=2.13.0 sphinxcontrib-httpdomain>=1.7.0 sphinxemoji>=0.1.6 toctree-plus>=0.5.0 singledispatch-json-0.4.0/formate.toml000066400000000000000000000017331423345565700200370ustar00rootroot00000000000000[hooks] dynamic_quotes = 10 collections-import-rewrite = 20 reformat-generics = 40 noqa-reformat = 60 ellipsis-reformat = 70 squish_stubs = 80 [config] indent = "\t" line_length = 115 [hooks.yapf] priority = 30 [hooks.isort] priority = 50 [hooks.yapf.kwargs] yapf_style = ".style.yapf" [hooks.isort.kwargs] indent = "\t\t" multi_line_output = 8 import_heading_stdlib = "stdlib" import_heading_thirdparty = "3rd party" import_heading_firstparty = "this package" import_heading_localfolder = "this package" balanced_wrapping = false lines_between_types = 0 use_parentheses = true remove_redundant_aliases = true default_section = "THIRDPARTY" known_third_party = [ "coincidence", "coverage", "coverage_pyver_pragma", "domdf_python_tools", "github", "importlib_metadata", "pytest", "pytest_cov", "pytest_randomly", "pytest_rerunfailures", "pytest_timeout", "pytz", "requests", "typing_extensions", ] known_first_party = "sdjson" singledispatch-json-0.4.0/justfile000066400000000000000000000007551423345565700172600ustar00rootroot00000000000000default: lint pdf-docs: latex-docs make -C doc-source/build/latex/ latex-docs: SPHINX_BUILDER=latex tox -e docs unused-imports: tox -e lint -- --select F401 incomplete-defs: #!/usr/bin/env bash tox -e mypy -- --disallow-incomplete-defs --disallow-untyped-defs | grep "Function is missing a .* annotation" || exit 0 vdiff: git diff $(repo-helper show version -q)..HEAD bare-ignore: greppy '# type:? *ignore(?!\[|\w)' -s lint: unused-imports incomplete-defs bare-ignore tox -n qa singledispatch-json-0.4.0/pyproject.toml000066400000000000000000000075521423345565700204260ustar00rootroot00000000000000[build-system] requires = [ "whey",] build-backend = "whey" [project] name = "sdjson" version = "0.4.0" description = "Custom JSON Encoder for Python utilising functools.singledispatch to support custom encoders for both Python's built-in classes and user-created classes, without as much legwork." readme = "README.rst" keywords = [ "json", "serialize", "singledispatch",] dynamic = [ "requires-python", "classifiers", "dependencies",] [[project.authors]] name = "Dominic Davis-Foster" email = "dominic@davis-foster.co.uk" [project.license] file = "LICENSE" [project.urls] Homepage = "https://github.com/domdfcoding/singledispatch-json" "Issue Tracker" = "https://github.com/domdfcoding/singledispatch-json/issues" "Source Code" = "https://github.com/domdfcoding/singledispatch-json" Documentation = "https://singledispatch-json.readthedocs.io/en/latest" [tool.whey] base-classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Utilities", "Typing :: Typed", ] python-versions = [ "3.6", "3.7", "3.8", "3.9", "3.10",] python-implementations = [ "CPython", "PyPy",] platforms = [ "Windows", "macOS", "Linux",] license-key = "MIT" [tool.mkrecipe] conda-channels = [ "conda-forge", "domdfcoding",] extras = "all" [tool.sphinx-pyproject] github_username = "domdfcoding" github_repository = "singledispatch-json" author = "Dominic Davis-Foster" project = "sdjson" copyright = "2020-2021 Dominic Davis-Foster" language = "en" package_root = "sdjson" extensions = [ "sphinx_toolbox", "sphinx_toolbox.more_autodoc", "sphinx_toolbox.more_autosummary", "sphinx_toolbox.documentation_summary", "sphinx_toolbox.tweaks.param_dash", "sphinx_toolbox.tweaks.latex_layout", "sphinx_toolbox.tweaks.latex_toc", "sphinx.ext.intersphinx", "sphinx.ext.mathjax", "sphinxcontrib.extras_require", "sphinx.ext.todo", "sphinxemoji.sphinxemoji", "notfound.extension", "sphinx_copybutton", "sphinxcontrib.default_values", "sphinxcontrib.toctree_plus", "sphinx_debuginfo", "sphinx_licenseinfo", "seed_intersphinx_mapping", "html_section", "sphinx_toolbox_experimental.html_section", "sphinx_toolbox_experimental.autosummary_widths", ] sphinxemoji_style = "twemoji" gitstamp_fmt = "%d %b %Y" templates_path = [ "_templates",] html_static_path = [ "_static",] source_suffix = ".rst" master_doc = "index" suppress_warnings = [ "image.nonlocal_uri",] pygments_style = "default" html_theme = "domdf_sphinx_theme" html_theme_path = [ "../..",] html_show_sourcelink = true toctree_plus_types = [ "class", "confval", "data", "directive", "enum", "exception", "flag", "function", "namedtuple", "protocol", "role", "typeddict", ] add_module_names = false hide_none_rtype = true all_typevars = true overloads_location = "bottom" html_codeblock_linenos_style = "table" autodoc_exclude_members = [ "__dict__", "__class__", "__dir__", "__weakref__", "__module__", "__annotations__", "__orig_bases__", "__parameters__", "__subclasshook__", "__init_subclass__", "__attrs_attrs__", "__init__", "__new__", "__getnewargs__", "__abstractmethods__", "__hash__", ] [tool.mypy] python_version = "3.6" namespace_packages = true check_untyped_defs = true warn_unused_ignores = true no_implicit_optional = true show_error_codes = true [tool.snippet-fmt] directives = [ "code-block",] [tool.dependency-dash."requirements.txt"] order = 10 [tool.dependency-dash."tests/requirements.txt"] order = 20 include = false [tool.dependency-dash."doc-source/requirements.txt"] order = 30 include = false [tool.snippet-fmt.languages.python] reformat = true [tool.snippet-fmt.languages.TOML] reformat = true [tool.snippet-fmt.languages.ini] [tool.snippet-fmt.languages.json] singledispatch-json-0.4.0/repo_helper.yml000066400000000000000000000020741423345565700205330ustar00rootroot00000000000000--- modname: sdjson repo_name: singledispatch-json copyright_years: "2020-2021" author: "Dominic Davis-Foster" email: "dominic@davis-foster.co.uk" version: "0.4.0" username: "domdfcoding" license: 'MIT' short_desc: "Custom JSON Encoder for Python utilising functools.singledispatch to support custom encoders for both Python's built-in classes and user-created classes, without as much legwork." use_whey: True python_deploy_version: 3.6 min_coverage: 100 standalone_contrib_guide: true conda_channels: - conda-forge # Versions to run tests for python_versions: - 3.6 - 3.7 - 3.8 - 3.9 - "3.10" - "pypy36" - "pypy37" - "pypy38" - "pypy39" classifiers: - 'Development Status :: 4 - Beta' - 'Intended Audience :: Developers' - 'Topic :: Software Development :: Libraries :: Python Modules' - "Topic :: Utilities" keywords: - json - singledispatch - serialize extra_sphinx_extensions: - sphinx_toolbox_experimental.html_section - sphinx_toolbox_experimental.autosummary_widths sphinx_conf_epilogue: - nitpicky = True - autosummary_widths_builders = ["latex"] singledispatch-json-0.4.0/requirements.txt000066400000000000000000000000651423345565700207660ustar00rootroot00000000000000domdf-python-tools>=2.5.2 typing-extensions>=3.7.4.3 singledispatch-json-0.4.0/sdjson/000077500000000000000000000000001423345565700170015ustar00rootroot00000000000000singledispatch-json-0.4.0/sdjson/__init__.py000066400000000000000000000277361423345565700211310ustar00rootroot00000000000000# !/usr/bin/env python # # __init__.py # r""" JSON encoder utilising functools.singledispatch to support custom encoders for both Python's built-in classes and user-created classes, without as much legwork. Creating and registering a custom encoder is as easy as: .. code-block:: python >>> import sdjson >>> >>> @sdjson.register_encoder(MyClass) >>> def encode_myclass(obj): ... return dict(obj) >>> In this case, ``MyClass`` can be made JSON-serializable simply by calling :class:`dict` on it. If your class requires more complicated logic to make it JSON-serializable, do that here. Then, to dump the object to a string: .. code-block:: python >>> class_instance = MyClass() >>> print(sdjson.dumps(class_instance)) '{"menu": ["egg and bacon", "egg sausage and bacon", "egg and spam", "egg bacon and spam"], "today\'s special": "Lobster Thermidor au Crevette with a Mornay sauce served in a Provencale manner with shallots and aubergines garnished with truffle pate, brandy and with a fried egg on top and spam."}' >>> Or to dump to a file: .. code-block:: python >>> with open("spam.json", "w") as fp: ... sdjson.dumps(class_instance, fp) ... >>> ``sdjson`` also provides access to :func:`~json.load`, :func:`~json.loads`, :class:`~json.JSONDecoder`, :class:`~json.JSONDecodeError`, and :class:`~json.JSONEncoder` from the :mod:`json` module, allowing you to use ``sdjson`` as a drop-in replacement for :mod:`json`. If you wish to dump an object without using the custom encoders, you can pass a different :class:`~json.JSONEncoder` subclass, or indeed :class:`~json.JSONEncoder` itself to get the stock functionality. .. code-block:: python >>> sdjson.dumps(class_instance, cls=sdjson.JSONEncoder) >>> ----------- .. latex:clearpage:: When you've finished, if you want to unregister the encoder you can run: .. code-block:: python >>> sdjson.unregister_encoder(MyClass) >>> to remove the encoder for ``MyClass``. If you want to replace the encoder with a different one it is not necessary to call this function: the :func:`@sdjson.register_encoder ` decorator will replace any existing decorator for the given class. .. TODO:: This module does not currently support custom decoders, but might in the future. """ # noqa: D400 # # Copyright ยฉ 2020-2021 Dominic Davis-Foster # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE # OR OTHER DEALINGS IN THE SOFTWARE. # # Based on https://treyhunner.com/2013/09/singledispatch-json-serializer/ # Copyright ยฉ 2013 Trey Hunner # He said "Feel free to use it however you like." So I have. # # Also based on the `json` module (version 2.0.9) by Bob Ippolito from Python 3.7 # Licensed under the Python Software Foundation License Version 2. # Copyright ยฉ 2001-2020 Python Software Foundation. All rights reserved. # Copyright ยฉ 2000 BeOpen.com . All rights reserved. # Copyright ยฉ 1995-2000 Corporation for National Research Initiatives . All rights reserved. # Copyright ยฉ 1991-1995 Stichting Mathematisch Centrum . All rights reserved. # # Type annotations from Typeshed # https://github.com/python/typeshed # Apache 2.0 Licensed # # stdlib import json import sys from functools import singledispatch from typing import IO, Any, Callable, Iterator, Optional, Tuple, Type, Union # 3rd party from domdf_python_tools.doctools import append_docstring_from, is_documented_by, make_sphinx_links if sys.version_info < (3, 8): # pragma: no cover (py38+) # 3rd party from typing_extensions import _ProtocolMeta # type: ignore else: # pragma: no cover ( Callable: """ Decorator to allow removal of custom encoders with ``)``, where is the custom type you wish to remove the encoder for. """ # noqa: D400 # From https://stackoverflow.com/a/25951784/3092681 # Copyright ยฉ 2014 Martijn Pieters # https://stackoverflow.com/users/100297/martijn-pieters # Licensed under CC BY-SA 4.0 # build a dictionary mapping names to closure cells closure = dict(zip(func.register.__code__.co_freevars, func.register.__closure__)) registry = closure["registry"].cell_contents dispatch_cache = closure["dispatch_cache"].cell_contents def unregister(cls): del registry[cls] dispatch_cache.clear() func.unregister = unregister return func def sphinxify_json_docstring() -> Callable: """ Turn references in the docstring to :class:`~json.JSONEncoder` into proper links. """ def wrapper(target): # To save having the `sphinxify_docstring` decorator too target.__doc__ = make_sphinx_links(target.__doc__) target.__doc__ = target.__doc__.replace("``JSONEncoder``", ":class:`~json.JSONEncoder`") target.__doc__ = target.__doc__.replace("``.default()``", ":meth:`~json.JSONEncoder.default`") return target return wrapper class _Encoders: def __init__(self): self._registry = allow_unregister(singledispatch(lambda x: None)) self._protocol_registry = {} self.registry = self._registry.registry def register(self, cls: Type, func: Optional[Callable] = None) -> Callable: """ Registers a new handler for the given type. Can be used as a decorator or a regular function: .. code-block:: python @register_encoder(bytes) def bytes_encoder(obj): return obj.decode("UTF-8") def int_encoder(obj): return int(obj) register_encoder(int, int_encoder) :param cls: :param func: """ if func is None: return lambda f: self.register(cls, f) if isinstance(cls, _ProtocolMeta): if getattr(cls, "_is_runtime_protocol", False): self._protocol_registry[cls] = func else: raise TypeError("Protocols must be @runtime_checkable") return func else: return self._registry.register(cls, func) def dispatch(self, cls: object) -> Optional[Callable]: """ Returns the best available implementation for the given object. :param cls: """ if object in self.registry: self.unregister(object) handler = self._registry.dispatch(type(cls)) if handler is not None: return handler else: for protocol, handler in self._protocol_registry.items(): if isinstance(cls, protocol): return handler return None def unregister(self, cls: Type): """ Unregister the handler for the given type. .. code-block:: python unregister_encoder(int) :param cls: :raise KeyError: if no handler is found. """ if cls in self.registry: self._registry.unregister(cls) elif cls in self._protocol_registry: del self._protocol_registry[cls] else: raise KeyError encoders = _Encoders() register_encoder = encoders.register # type: ignore unregister_encoder = encoders.unregister # type: ignore @sphinxify_json_docstring() @append_docstring_from(json.dump) def dump(obj: Any, fp: IO, **kwargs: Any): # TODO """ Serialize custom Python classes to JSON. Custom classes can be registered using the ``@encoders.register()`` decorator. """ iterable = dumps(obj, **kwargs) for chunk in iterable: fp.write(chunk) @sphinxify_json_docstring() @append_docstring_from(json.dumps) def dumps( obj: Any, *, skipkeys: bool = False, ensure_ascii: bool = True, check_circular: bool = True, allow_nan: bool = True, cls: Optional[Type[json.JSONEncoder]] = None, indent: Union[None, int, str] = None, separators: Optional[Tuple[str, str]] = None, default: Optional[Callable[[Any], Any]] = None, sort_keys: bool = False, **kwargs: Any, ): """ Serialize custom Python classes to JSON. Custom classes can be registered using the ``@encoders.register()`` decorator. """ if ( not skipkeys and ensure_ascii and check_circular and allow_nan and cls is None and indent is None and separators is None and default is None and not sort_keys and not kwargs ): return _default_encoder.encode(obj) if cls is None: # pragma: no cover (!CPython) # TODO cls = _CustomEncoder return cls( skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular, allow_nan=allow_nan, indent=indent, separators=separators, default=default, sort_keys=sort_keys, **kwargs ).encode(obj) # Provide access to remaining objects from json module. # We have to do it this way to sort out the docstrings for sphinx without # modifying the original docstrings. @sphinxify_json_docstring() @append_docstring_from(json.load) def load(*args, **kwargs): # pragma: no cover (!CPython) # TODO """ Alias of :func:`json.load`. """ return json.load(*args, **kwargs) @sphinxify_json_docstring() @append_docstring_from(json.loads) def loads(*args, **kwargs): # pragma: no cover (!CPython) # TODO """ Alias of :func:`json.loads`. """ return json.loads(*args, **kwargs) @sphinxify_json_docstring() @append_docstring_from(json.JSONEncoder) class JSONEncoder(json.JSONEncoder): """ Alias of :class:`json.JSONEncoder`. .. autosummary-widths:: 33/100 """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @sphinxify_json_docstring() @is_documented_by(json.JSONEncoder.default) def default(self, o: Any) -> Any: # noqa: D102 return super().default(o) @sphinxify_json_docstring() @is_documented_by(json.JSONEncoder.encode) def encode(self, o: Any) -> Any: # noqa: D102 return super().encode(o) @sphinxify_json_docstring() @is_documented_by(json.JSONEncoder.iterencode) def iterencode( # noqa: D102 self, o: Any, _one_shot: bool = False, ) -> Iterator[str]: # pragma: no cover (!CPython) return super().iterencode(o, _one_shot) @sphinxify_json_docstring() @append_docstring_from(json.JSONDecoder) class JSONDecoder(json.JSONDecoder): # pragma: no cover (!CPython) # TODO """ Alias of :class:`json.JSONDecoder`. .. autosummary-widths:: 35/100 """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @sphinxify_json_docstring() @is_documented_by(json.JSONDecoder.decode) def decode(self, *args, **kwargs): # noqa: D102 return super().decode(*args, **kwargs) @sphinxify_json_docstring() @is_documented_by(json.JSONDecoder.raw_decode) def raw_decode(self, *args, **kwargs): # noqa: D102 return super().raw_decode(*args, **kwargs) JSONDecodeError = json.JSONDecodeError # Custom encoder for sdjson class _CustomEncoder(JSONEncoder): def default(self, obj): handler = encoders.dispatch(obj) if handler is not None: return handler(obj) return super().default(obj) _default_encoder = _CustomEncoder( skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, indent=None, separators=None, default=None, ) singledispatch-json-0.4.0/sdjson/__init__.pyi000066400000000000000000000154411423345565700212700ustar00rootroot00000000000000# !/usr/bin/env python # # __init__.pyi # # Copyright ยฉ 2020-2021 Dominic Davis-Foster # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE # OR OTHER DEALINGS IN THE SOFTWARE. # # Based on https://treyhunner.com/2013/09/singledispatch-json-serializer/ # Copyright ยฉ 2013 Trey Hunner # He said "Feel free to use it however you like." So I have. # # Also based on the `json` module (version 2.0.9) by Bob Ippolito from Python 3.7 # Licensed under the Python Software Foundation License Version 2. # Copyright ยฉ 2001-2020 Python Software Foundation. All rights reserved. # Copyright ยฉ 2000 BeOpen.com. All rights reserved. # Copyright ยฉ 1995-2000 Corporation for National Research Initiatives. All rights reserved. # Copyright ยฉ 1991-1995 Stichting Mathematisch Centrum. All rights reserved. # # Type annotations from Typeshed # https://github.com/python/typeshed # Apache 2.0 Licensed # # stdlib import json from typing import ( IO, Any, Callable, Dict, Iterator, List, Mapping, Optional, Tuple, Type, TypeVar, Union, overload ) # 3rd party from typing_extensions import Protocol __author__: str __copyright__: str __license__: str __version__: str __email__: str _T_co = TypeVar("_T_co", covariant=True) _LoadsString = Union[str, bytes] _T = TypeVar("_T") class SingleDispatch(Protocol): """ :class:`~typing.Protocol` representing a function decorated with :func:`functools.singledispatch`. """ @overload def register(self, cls: Any) -> Callable[[Callable[..., _T]], Callable[..., _T]]: ... @overload def register(self, cls: Any, func: Callable[..., _T]) -> Callable[..., _T]: ... def dispatch(self, cls: Any) -> Callable[..., _T]: ... def unregister(self, cls: Type) -> Any: ... registry: Mapping[Any, Callable[..., _T]] def _clear_cache(self) -> None: ... def __call__(self, *args: Any, **kwargs: Any) -> _T: ... class SupportsRead(Protocol[_T_co]): def read(self, __length: int = ...) -> _T_co: ... def allow_unregister(func: SingleDispatch) -> SingleDispatch: ... def sphinxify_json_docstring() -> Callable: ... class _Encoders: _registry: SingleDispatch _protocol_registry: Mapping[Any, Callable[..., _T]] registry: Mapping[Any, Callable[..., _T]] @overload def register(self, cls: Any) -> Callable[[Callable[..., _T]], Callable[..., _T]]: ... @overload def register(self, cls: Any, func: Callable[..., _T]) -> Callable[..., _T]: ... def dispatch(self, cls: Any) -> Callable[..., _T]: ... def unregister(self, cls: Type) -> Any: ... encoders = _Encoders() register_encoder = encoders.register unregister_encoder = encoders.unregister def dump( obj: Any, fp: IO[str], *, skipkeys: bool = ..., ensure_ascii: bool = ..., check_circular: bool = ..., allow_nan: bool = ..., cls: Optional[Type[json.JSONEncoder]] = ..., indent: Union[None, int, str] = ..., separators: Optional[Tuple[str, str]] = ..., default: Optional[Callable[[Any], Any]] = ..., sort_keys: bool = ..., **kwargs: Any ) -> None: ... def dumps( obj: Any, *, skipkeys: bool = ..., ensure_ascii: bool = ..., check_circular: bool = ..., allow_nan: bool = ..., cls: Optional[Type[json.JSONEncoder]] = ..., indent: Union[None, int, str] = ..., separators: Optional[Tuple[str, str]] = ..., default: Optional[Callable[[Any], Any]] = ..., sort_keys: bool = ..., **kwargs: Any ) -> str: ... def loads( s: _LoadsString, *, cls: Optional[Type[json.JSONDecoder]] = ..., object_hook: Optional[Callable[[Dict[Any, Any]], Any]] = ..., parse_float: Optional[Callable[[str], Any]] = ..., parse_int: Optional[Callable[[str], Any]] = ..., parse_constant: Optional[Callable[[str], Any]] = ..., object_pairs_hook: Optional[Callable[[List[Tuple[Any, Any]]], Any]] = ..., **kwargs: Any ) -> Any: ... def load( fp: SupportsRead[_LoadsString], *, cls: Optional[Type[json.JSONDecoder]] = ..., object_hook: Optional[Callable[[Dict[Any, Any]], Any]] = ..., parse_float: Optional[Callable[[str], Any]] = ..., parse_int: Optional[Callable[[str], Any]] = ..., parse_constant: Optional[Callable[[str], Any]] = ..., object_pairs_hook: Optional[Callable[[List[Tuple[Any, Any]]], Any]] = ..., **kwargs: Any ) -> Any: ... class JSONEncoder(json.JSONEncoder): def __init__( self, *, skipkeys: bool = ..., ensure_ascii: bool = ..., check_circular: bool = ..., allow_nan: bool = ..., sort_keys: bool = ..., indent: Optional[int] = ..., separators: Optional[Tuple[str, str]] = ..., default: Optional[Callable[..., Any]] = ... ) -> None: ... def default(self, o: Any) -> Any: ... def encode(self, o: Any) -> str: ... def iterencode(self, o: Any, _one_shot: bool = ...) -> Iterator[str]: ... class JSONDecoder(json.JSONDecoder): object_hook: Callable[[Dict[str, Any]], Any] parse_float: Callable[[str], Any] parse_int: Callable[[str], Any] parse_constant: Callable[[str], Any] = ... strict: bool object_pairs_hook: Callable[[List[Tuple[str, Any]]], Any] parse_object: Callable[[str], Any] parse_array: Callable[[str], Any] parse_string: Callable[[str], Any] memo: Dict scan_once: Any def __init__( self, *, object_hook: Optional[Callable[[Dict[str, Any]], Any]] = ..., parse_float: Optional[Callable[[str], Any]] = ..., parse_int: Optional[Callable[[str], Any]] = ..., parse_constant: Optional[Callable[[str], Any]] = ..., strict: bool = ..., object_pairs_hook: Optional[Callable[[List[Tuple[str, Any]]], Any]] = ... ) -> None: ... def decode(self, s: str, _w: Callable[..., Any] = ...) -> Any: ... # _w is undocumented def raw_decode(self, s: str, idx: int = ...) -> Tuple[Any, int]: ... JSONDecodeError = json.JSONDecodeError class _CustomEncoder(JSONEncoder): ... _default_encoder = _CustomEncoder( skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, indent=None, separators=None, default=None, ) singledispatch-json-0.4.0/sdjson/py.typed000066400000000000000000000000001423345565700204660ustar00rootroot00000000000000singledispatch-json-0.4.0/stubs.txt000066400000000000000000000000741423345565700174030ustar00rootroot00000000000000git+https://github.com/domdfcoding/pytest-regressions-stubs singledispatch-json-0.4.0/tests/000077500000000000000000000000001423345565700166435ustar00rootroot00000000000000singledispatch-json-0.4.0/tests/__init__.py000066400000000000000000000000601423345565700207500ustar00rootroot00000000000000# TODO: compare dumps to load and dump to loads singledispatch-json-0.4.0/tests/conftest.py000066400000000000000000000000431423345565700210370ustar00rootroot00000000000000pytest_plugins = ("coincidence", ) singledispatch-json-0.4.0/tests/glossia/000077500000000000000000000000001423345565700203045ustar00rootroot00000000000000singledispatch-json-0.4.0/tests/glossia/__init__.py000066400000000000000000000001011423345565700224050ustar00rootroot00000000000000""" Package to test registering encoders in different places """ singledispatch-json-0.4.0/tests/glossia/talon.py000066400000000000000000000003251423345565700217730ustar00rootroot00000000000000# stdlib from fractions import Fraction # this package import sdjson # Create a custom encoder for Fraction that turns it into a string @sdjson.encoders.register(Fraction) def encode_str(obj): return str(obj) singledispatch-json-0.4.0/tests/glossia/thorn.py000066400000000000000000000003201423345565700220030ustar00rootroot00000000000000# stdlib from decimal import Decimal # this package import sdjson # Create a custom encoder for Decimal that turns it into a string @sdjson.encoders.register(Decimal) def encode_str(obj): return str(obj) singledispatch-json-0.4.0/tests/requirements.txt000066400000000000000000000003451423345565700221310ustar00rootroot00000000000000coincidence>=0.2.0 coverage>=5.1 coverage-pyver-pragma>=0.2.1 importlib-metadata>=3.6.0 iniconfig!=1.1.0,>=1.0.1 pytest>=6.0.0 pytest-cov>=2.8.1 pytest-randomly>=3.7.0 pytest-rerunfailures>=9.0 pytest-timeout>=1.4.2 pytz>=2019.1 singledispatch-json-0.4.0/tests/stdlib_tests/000077500000000000000000000000001423345565700213465ustar00rootroot00000000000000singledispatch-json-0.4.0/tests/stdlib_tests/__init__.py000066400000000000000000000011551423345565700234610ustar00rootroot00000000000000""" The tests in this module are based on those from Python https://github.com/python/cpython/tree/master/Lib/test/test_json Licensed under the Python Software Foundation License Version 2. Copyright ยฉ 2001-2020 Python Software Foundation. All rights reserved. Copyright ยฉ 2000 BeOpen.com . All rights reserved. Copyright ยฉ 1995-2000 Corporation for National Research Initiatives . All rights reserved. Copyright ยฉ 1991-1995 Stichting Mathematisch Centrum . All rights reserved. Converted from Unittest to PyTest using `unittest2pytest` 0.5.dev0 by Hartmut Goebel https://github.com/pytest-dev/unittest2pytest """ singledispatch-json-0.4.0/tests/stdlib_tests/test_decode.py000066400000000000000000000073001423345565700242020ustar00rootroot00000000000000# stdlib import decimal import platform import re from collections import OrderedDict from io import StringIO # 3rd party import pytest from coincidence.selectors import not_pypy from domdf_python_tools.compat import PYPY # this package import sdjson def test_decimal() -> None: rval = sdjson.loads("1.1", parse_float=decimal.Decimal) assert isinstance(rval, decimal.Decimal) assert rval == decimal.Decimal("1.1") def test_float() -> None: rval = sdjson.loads('1', parse_int=float) assert isinstance(rval, float) assert rval == 1.0 def test_empty_objects() -> None: assert sdjson.loads("{}") == {} assert sdjson.loads("[]") == [] assert sdjson.loads('""') == '' def test_object_pairs_hook() -> None: s = '{"xkd":1, "kcw":2, "art":3, "hxm":4, "qrt":5, "pad":6, "hoy":7}' p = [("xkd", 1), ("kcw", 2), ("art", 3), ("hxm", 4), ("qrt", 5), ("pad", 6), ("hoy", 7)] assert sdjson.loads(s, object_pairs_hook=lambda x: x) == p assert sdjson.load(StringIO(s), object_pairs_hook=lambda x: x) == p od = sdjson.loads(s, object_pairs_hook=OrderedDict) assert od == OrderedDict(p) assert type(od) == OrderedDict # the object_pairs_hook takes priority over the object_hook assert sdjson.loads(s, object_pairs_hook=OrderedDict, object_hook=lambda x: None) == OrderedDict(p) # check that empty object literals work (see #17368) assert sdjson.loads("{}", object_pairs_hook=OrderedDict) == OrderedDict() assert sdjson.loads('{"empty": {}}', object_pairs_hook=OrderedDict) == OrderedDict([("empty", OrderedDict())]) def test_decoder_optimizations() -> None: # Several optimizations were made that skip over calls to # the whitespace regex, so this test is designed to try and # exercise the uncommon cases. The array cases are already covered. rval = sdjson.loads('{ "key" : "value" , "k":"v" }') assert rval == {"key": "value", 'k': 'v'} def check_keys_reuse(source, loads): rval = loads(source) (a, b), (c, d) = sorted(rval[0]), sorted(rval[1]) assert a == c assert b == d @not_pypy(reason="Strange behaviour with PyPy") def test_keys_reuse() -> None: s = '[{"a_key": 1, "b_รฉ": 2}, {"a_key": 3, "b_รฉ": 4}]' check_keys_reuse(s, sdjson.loads) decoder = sdjson.JSONDecoder() check_keys_reuse(s, decoder.decode) assert not decoder.memo def test_extra_data() -> None: s = "[1, 2, 3]5" msg = "Extra data" with pytest.raises(sdjson.JSONDecodeError, match=msg): sdjson.loads(s) def test_invalid_escape() -> None: s = '["abc\\y"]' msg = "escape" with pytest.raises(sdjson.JSONDecodeError, match=msg): sdjson.loads(s) def test_invalid_input_type() -> None: msg = "the JSON object must be str" for value in [1, 3.14, [], {}, None]: with pytest.raises(TypeError, match=msg): sdjson.loads(value) # type: ignore def test_string_with_utf8_bom() -> None: # stdlib import sys # see #18958 bom_json = "[1,2,3]".encode("utf-8-sig").decode("utf-8") with pytest.raises(sdjson.JSONDecodeError) as e: sdjson.loads(bom_json) # TODO: if sys.version_info.major >= 3 and sys.version_info.minor == 7: assert "BOM" in str(e) with pytest.raises(sdjson.JSONDecodeError) as e: sdjson.load(StringIO(bom_json)) # TODO: if sys.version_info.major >= 3 and sys.version_info.minor == 7: assert "BOM" in str(e) # make sure that the BOM is not detected in the middle of a string bom_in_str = '"{}"'.format(''.encode("utf-8-sig").decode("utf-8")) assert sdjson.loads(bom_in_str) == '\ufeff' assert sdjson.load(StringIO(bom_in_str)) == '\ufeff' def test_negative_index() -> None: d = sdjson.JSONDecoder() if PYPY: match = re.escape("Expecting value: line 1 column -49999 (char -50000)") else: match = "idx cannot be negative" with pytest.raises(ValueError, match=match): d.raw_decode('a' * 42, -50000) singledispatch-json-0.4.0/tests/stdlib_tests/test_default.py000066400000000000000000000002001423345565700243730ustar00rootroot00000000000000# this package import sdjson def test_default() -> None: assert sdjson.dumps(type, default=repr) == sdjson.dumps(repr(type)) singledispatch-json-0.4.0/tests/stdlib_tests/test_dump.py000066400000000000000000000026631423345565700237330ustar00rootroot00000000000000# stdlib import platform from io import StringIO # 3rd party import pytest from coincidence.selectors import not_pypy # this package import sdjson def test_dump() -> None: sio = StringIO() sdjson.dump({}, sio) assert sio.getvalue() == "{}" def test_dumps() -> None: assert sdjson.dumps({}) == "{}" @not_pypy(reason="Failing on PyPy3.6-7.1.1") def test_dump_skipkeys() -> None: v = {b"invalid_key": False, "valid_key": True} with pytest.raises(TypeError): sdjson.dumps(v) s = sdjson.dumps(v, skipkeys=True) o = sdjson.loads(s) assert "valid_key" in o assert b"invalid_key" not in o @pytest.mark.parametrize( "data, expects", [ ({True: False, False: True}, '{"false": true, "true": false}'), ({2: 3.0, 4.0: 5, False: 1, 6: True}, '{"false": 1, "2": 3.0, "4.0": 5, "6": true}'), ] ) def test_encode_truefalse(data, expects): assert sdjson.dumps(data, sort_keys=True) == expects # Issue 16228: Crash on encoding resized list def test_encode_mutated() -> None: a = [object()] * 10 def crasher(obj): del a[-1] assert sdjson.dumps(a, default=crasher) == "[null, null, null, null, null]" # Issue 24094 def test_encode_evil_dict() -> None: class D(dict): def keys(self): return L class X: def __hash__(self): del L[0] return 1337 def __lt__(self, o): return 0 L = [X() for i in range(1122)] d = D() d[1337] = "true.dat" assert sdjson.dumps(d, sort_keys=True) == '{"1337": "true.dat"}' singledispatch-json-0.4.0/tests/stdlib_tests/test_encode_basestring_ascii.py000066400000000000000000000010271423345565700276050ustar00rootroot00000000000000# stdlib from collections import OrderedDict # this package import sdjson def test_ordered_dict() -> None: # See issue 6105 items = [("one", 1), ("two", 2), ("three", 3), ("four", 4), ("five", 5)] s = sdjson.dumps(OrderedDict(items)) assert s == '{"one": 1, "two": 2, "three": 3, "four": 4, "five": 5}' def test_sorted_dict() -> None: items = [("one", 1), ("two", 2), ("three", 3), ("four", 4), ("five", 5)] s = sdjson.dumps(dict(items), sort_keys=True) assert s == '{"five": 5, "four": 4, "one": 1, "three": 3, "two": 2}' singledispatch-json-0.4.0/tests/stdlib_tests/test_enum.py000066400000000000000000000060111423345565700237210ustar00rootroot00000000000000# stdlib from enum import Enum, IntEnum from math import isnan # this package import sdjson SMALL = 1 BIG = 1 << 32 HUGE = 1 << 64 REALLY_HUGE = 1 << 96 class BigNum(IntEnum): small = SMALL big = BIG huge = HUGE really_huge = REALLY_HUGE E = 2.718281 PI = 3.141593 TAU = 2 * PI class FloatNum(float, Enum): e = E pi = PI tau = TAU INF = float("inf") NEG_INF = float("-inf") NAN = float("nan") class WierdNum(float, Enum): inf = INF neg_inf = NEG_INF nan = NAN def test_floats() -> None: for enum in FloatNum: assert sdjson.dumps(enum) == repr(enum.value) assert float(sdjson.dumps(enum)) == enum assert sdjson.loads(sdjson.dumps(enum)) == enum def test_weird_floats() -> None: for enum, expected in zip(WierdNum, ("Infinity", "-Infinity", "NaN")): assert sdjson.dumps(enum) == expected if not isnan(enum): assert float(sdjson.dumps(enum)) == enum assert sdjson.loads(sdjson.dumps(enum)) == enum else: assert isnan(float(sdjson.dumps(enum))) assert isnan(sdjson.loads(sdjson.dumps(enum))) def test_ints() -> None: for enum in BigNum: assert sdjson.dumps(enum) == str(enum.value) assert int(sdjson.dumps(enum)) == enum assert sdjson.loads(sdjson.dumps(enum)) == enum def test_list() -> None: assert sdjson.dumps(list(BigNum)) == str([SMALL, BIG, HUGE, REALLY_HUGE]) assert sdjson.loads(sdjson.dumps(list(BigNum))) == list(BigNum) assert sdjson.dumps(list(FloatNum)) == str([E, PI, TAU]) assert sdjson.loads(sdjson.dumps(list(FloatNum))) == list(FloatNum) assert sdjson.dumps(list(WierdNum)) == "[Infinity, -Infinity, NaN]" assert sdjson.loads(sdjson.dumps(list(WierdNum)))[:2] == list(WierdNum)[:2] assert isnan(sdjson.loads(sdjson.dumps(list(WierdNum)))[2]) def test_dict_keys() -> None: s: int b: int h: int r: int e: float p: float t: float i: float j: float n: float s, b, h, r = BigNum # type: ignore e, p, t = FloatNum # type: ignore i, j, n = WierdNum # type: ignore d = { s: "tiny", b: "large", h: "larger", r: "largest", e: "Euler's number", p: "pi", t: "tau", i: "Infinity", j: "-Infinity", n: "NaN", } nd = sdjson.loads(sdjson.dumps(d)) assert nd[str(SMALL)] == "tiny" assert nd[str(BIG)] == "large" assert nd[str(HUGE)] == "larger" assert nd[str(REALLY_HUGE)] == "largest" assert nd[repr(E)] == "Euler's number" assert nd[repr(PI)] == "pi" assert nd[repr(TAU)] == "tau" assert nd["Infinity"] == "Infinity" assert nd["-Infinity"] == "-Infinity" assert nd["NaN"] == "NaN" def test_dict_values() -> None: d = dict( tiny=BigNum.small, large=BigNum.big, larger=BigNum.huge, largest=BigNum.really_huge, e=FloatNum.e, pi=FloatNum.pi, tau=FloatNum.tau, i=WierdNum.inf, j=WierdNum.neg_inf, n=WierdNum.nan, ) nd = sdjson.loads(sdjson.dumps(d)) assert nd["tiny"] == SMALL assert nd["large"] == BIG assert nd["larger"] == HUGE assert nd["largest"] == REALLY_HUGE assert nd['e'] == E assert nd["pi"] == PI assert nd["tau"] == TAU assert nd['i'] == INF assert nd['j'] == NEG_INF assert isnan(nd['n']) singledispatch-json-0.4.0/tests/stdlib_tests/test_fail.py000066400000000000000000000204621423345565700236760ustar00rootroot00000000000000# stdlib import platform import re import sys # 3rd party import pytest from domdf_python_tools.compat import PYPY # this package import sdjson # 2007-10-05 JSONDOCS = [ # http://json.org/JSON_checker/test/fail1.json '"A JSON payload should be an object or array, not a string."', # http://json.org/JSON_checker/test/fail2.json '["Unclosed array"', # http://json.org/JSON_checker/test/fail3.json '{unquoted_key: "keys must be quoted"}', # http://json.org/JSON_checker/test/fail4.json '["extra comma",]', # http://json.org/JSON_checker/test/fail5.json '["double extra comma",,]', # http://json.org/JSON_checker/test/fail6.json '[ , "<-- missing value"]', # http://json.org/JSON_checker/test/fail7.json '["Comma after the close"],', # http://json.org/JSON_checker/test/fail8.json '["Extra close"]]', # http://json.org/JSON_checker/test/fail9.json '{"Extra comma": true,}', # http://json.org/JSON_checker/test/fail10.json '{"Extra value after close": true} "misplaced quoted value"', # http://json.org/JSON_checker/test/fail11.json '{"Illegal expression": 1 + 2}', # http://json.org/JSON_checker/test/fail12.json '{"Illegal invocation": alert()}', # http://json.org/JSON_checker/test/fail13.json '{"Numbers cannot have leading zeroes": 013}', # http://json.org/JSON_checker/test/fail14.json '{"Numbers cannot be hex": 0x14}', # http://json.org/JSON_checker/test/fail15.json '["Illegal backslash escape: \\x15"]', # http://json.org/JSON_checker/test/fail16.json "[\\naked]", # http://json.org/JSON_checker/test/fail17.json '["Illegal backslash escape: \\017"]', # http://json.org/JSON_checker/test/fail18.json '[[[[[[[[[[[[[[[[[[[["Too deep"]]]]]]]]]]]]]]]]]]]]', # http://json.org/JSON_checker/test/fail19.json '{"Missing colon" null}', # http://json.org/JSON_checker/test/fail20.json '{"Double colon":: null}', # http://json.org/JSON_checker/test/fail21.json '{"Comma instead of colon", null}', # http://json.org/JSON_checker/test/fail22.json '["Colon instead of comma": false]', # http://json.org/JSON_checker/test/fail23.json '["Bad value", truth]', # http://json.org/JSON_checker/test/fail24.json "['single quote']", # http://json.org/JSON_checker/test/fail25.json '["\ttab\tcharacter\tin\tstring\t"]', # http://json.org/JSON_checker/test/fail26.json '["tab\\ character\\ in\\ string\\ "]', # http://json.org/JSON_checker/test/fail27.json '["line\nbreak"]', # http://json.org/JSON_checker/test/fail28.json '["line\\\nbreak"]', # http://json.org/JSON_checker/test/fail29.json "[0e]", # http://json.org/JSON_checker/test/fail30.json "[0e+]", # http://json.org/JSON_checker/test/fail31.json "[0e+-1]", # http://json.org/JSON_checker/test/fail32.json '{"Comma instead if closing brace": true,', # http://json.org/JSON_checker/test/fail33.json '["mismatch"}', # http://code.google.com/p/simplejson/issues/detail?id=3 '["A\x1fZ control characters in string"]', ] SKIPS = { 1: "why not have a string payload?", 18: "spec doesn't specify any nesting limitations", } def test_failures() -> None: for idx, doc in enumerate(JSONDOCS): idx = idx + 1 if idx in SKIPS: sdjson.loads(doc) continue try: sdjson.loads(doc) except sdjson.JSONDecodeError: pass else: pytest.fail(f"Expected failure for fail{idx}.json: {doc!r}") def test_non_string_keys_dict() -> None: data = {'a': 1, (1, 2): 2} if sys.version_info[:2] > (3, 6): match_string = "keys must be str, int, float, bool or None, not tuple" elif PYPY: match_string = r"key \(1, 2\) is not a string" else: match_string = "keys must be a string" with pytest.raises(TypeError, match=match_string): sdjson.dumps(data) def test_not_serializable() -> None: with pytest.raises(TypeError, match="Object of type [']*module[']* is not JSON serializable"): sdjson.dumps(sys) if PYPY: unexpected_right_brace = "Unexpected '}'" missing_colon = "No ':' found at" unexpected_colon = "Unexpected ':' when decoding array" property_name_string = "Key name must be string at char" empty_string = "Unexpected '\u0000'" unterminated_array = "Unterminated array starting at" else: unexpected_right_brace = "Expecting value" missing_colon = "Expecting ':' delimiter" unexpected_colon = "Expecting ',' delimiter" property_name_string = "Expecting property name enclosed in double quotes" empty_string = "Expecting value" unterminated_array = "Expecting ',' delimiter" def __test_invalid_input(data, msg, idx): with pytest.raises(sdjson.JSONDecodeError) as err: sdjson.loads(data) if PYPY: assert err.value.msg.startswith(msg) # Fix for varying messages between PyPy versions else: assert err.value.msg == msg assert err.value.pos == idx assert err.value.lineno == 1 assert err.value.colno == idx + 1 if PYPY: assert re.match(rf"{msg}.*: line 1 column {idx + 1:d} \(char {idx:d}\)", str(err.value)) else: assert re.match(rf"{msg}: line 1 column {idx + 1:d} \(char {idx:d}\)", str(err.value)) @pytest.mark.parametrize( "data, msg, idx", [ ('', empty_string, 0), ('[', empty_string, 1), ("[42", unterminated_array, 1 if PYPY else 3), ("[42,", empty_string, 4), ('["', "Unterminated string starting at", 1), ('["spam', "Unterminated string starting at", 1), ('["spam"', unterminated_array, 1 if PYPY else 7), ('["spam",', empty_string, 8), ('{', property_name_string, 1), ('{"', "Unterminated string starting at", 1), ('{"spam', "Unterminated string starting at", 1), ('{"spam"', missing_colon, 7), ('{"spam":', empty_string, 8), ( '{"spam":42', "Unterminated object starting at" if PYPY else "Expecting ',' delimiter", 1 if PYPY else 10 ), ('{"spam":42,', property_name_string, 11), ('"', "Unterminated string starting at", 0), ('"spam', "Unterminated string starting at", 0), ] ) def test_truncated_input(data, msg, idx): __test_invalid_input(data, msg, idx) @pytest.mark.parametrize( "data, msg, idx", [ ("[,", "Unexpected ','" if PYPY else "Expecting value", 1), ('{"spam":[}', unexpected_right_brace, 9), ("[42:", unexpected_colon, 3), ('[42 "spam"', "Unexpected '\"' when decoding array" if PYPY else "Expecting ',' delimiter", 4), ("[42,]", "Unexpected ']'" if PYPY else "Expecting value", 4), ('{"spam":[42}', "Unexpected '}' when decoding array" if PYPY else "Expecting ',' delimiter", 11), ('["]', "Unterminated string starting at", 1), ('["spam":', unexpected_colon, 7), ('["spam",]', "Unexpected ']'" if PYPY else "Expecting value", 8), ("{:", property_name_string, 1), ("{,", property_name_string, 1), ("{42", property_name_string, 1), ("[{]", property_name_string, 2), ('{"spam",', missing_colon, 7), ('{"spam"}', missing_colon, 7), ('[{"spam"]', missing_colon, 8), ('{"spam":}', unexpected_right_brace, 8), ('[{"spam":]', "Unexpected ']'" if PYPY else "Expecting value", 9), ( '{"spam":42 "ham"', "Unexpected '\"' when decoding object" if PYPY else "Expecting ',' delimiter", 11 ), ('[{"spam":42]', "Unexpected ']' when decoding object" if PYPY else "Expecting ',' delimiter", 11), ('{"spam":42,}', property_name_string, 11), ] ) def test_unexpected_data(data, msg, idx): __test_invalid_input(data, msg, idx) @pytest.mark.parametrize( "data, msg, idx", [ ("[]]", "Extra data", 2), ("{}}", "Extra data", 2), ("[],[]", "Extra data", 2), ("{},{}", "Extra data", 2), ('42,"spam"', "Extra data", 2), ('"spam",42', "Extra data", 6), ] ) def test_extra_data(data, msg, idx): __test_invalid_input(data, msg, idx) @pytest.mark.parametrize( "data, line, col, idx", [ ('!', 1, 1, 0), (" !", 1, 2, 1), ("\n!", 2, 1, 1), ("\n \n\n !", 4, 6, 10), ] ) def test_linecol(data, line, col, idx): with pytest.raises(sdjson.JSONDecodeError) as err: sdjson.loads(data) if PYPY: match = "Unexpected '!'" else: match = "Expecting value" if PYPY: assert err.value.msg.startswith(match) # Fix for varying messages between PyPy versions else: assert err.value.msg == match assert err.value.pos == idx assert err.value.lineno == line assert err.value.colno == col if PYPY: assert re.match(rf"{match}.*: line {line} column {col:d} \(char {idx:d}\)", str(err.value)) else: assert re.match(rf"{match}: line {line} column {col:d} \(char {idx:d}\)", str(err.value)) singledispatch-json-0.4.0/tests/stdlib_tests/test_float.py000066400000000000000000000017151423345565700240700ustar00rootroot00000000000000# stdlib import math # 3rd party import pytest # this package import sdjson def test_floats() -> None: for num in [1617161771.7650001, math.pi, math.pi**100, math.pi**-100, 3.1]: assert float(sdjson.dumps(num)) == num assert sdjson.loads(sdjson.dumps(num)) == num def test_ints() -> None: for num in [1, 1 << 32, 1 << 64]: assert sdjson.dumps(num) == str(num) assert int(sdjson.dumps(num)) == num def test_out_of_range() -> None: assert sdjson.loads("[23456789012E666]") == [float("inf")] assert sdjson.loads("[-23456789012E666]") == [float("-inf")] def test_allow_nan() -> None: for val in (float("inf"), float("-inf"), float("nan")): out = sdjson.dumps([val]) if val == val: # inf assert sdjson.loads(out) == [val] else: # nan res = sdjson.loads(out) assert len(res) == 1 assert res[0] != res[0] with pytest.raises(ValueError, match="Out of range float values are not JSON compliant"): sdjson.dumps([val], allow_nan=False) singledispatch-json-0.4.0/tests/stdlib_tests/test_indent.py000066400000000000000000000024621423345565700242440ustar00rootroot00000000000000# stdlib import textwrap from io import StringIO # this package import sdjson def test_indent() -> None: h = [ ["blorpie"], ["whoops"], [], "d-shtaeou", "d-nthiouh", "i-vhbjkhnth", {"nifty": 87}, {"field": "yes", "morefield": False}, ] expect = textwrap.dedent( """\ [ \t[ \t\t"blorpie" \t], \t[ \t\t"whoops" \t], \t[], \t"d-shtaeou", \t"d-nthiouh", \t"i-vhbjkhnth", \t{ \t\t"nifty": 87 \t}, \t{ \t\t"field": "yes", \t\t"morefield": false \t} ]""" ) d1 = sdjson.dumps(h) d2 = sdjson.dumps(h, indent=2, sort_keys=True, separators=(',', ": ")) d3 = sdjson.dumps(h, indent='\t', sort_keys=True, separators=(',', ": ")) d4 = sdjson.dumps(h, indent=2, sort_keys=True) d5 = sdjson.dumps(h, indent='\t', sort_keys=True) h1 = sdjson.loads(d1) h2 = sdjson.loads(d2) h3 = sdjson.loads(d3) assert h1 == h assert h2 == h assert h3 == h assert d2 == expect.expandtabs(2) assert d3 == expect assert d4 == d2 assert d5 == d3 def test_indent0() -> None: h = {3: 1} def check(indent, expected): d1 = sdjson.dumps(h, indent=indent) assert d1 == expected sio = StringIO() sdjson.dump(h, sio, indent=indent) assert sio.getvalue() == expected # indent=0 should emit newlines check(0, '{\n"3": 1\n}') # indent=None is more compact check(None, '{"3": 1}') singledispatch-json-0.4.0/tests/stdlib_tests/test_passes.py000066400000000000000000000050731423345565700242620ustar00rootroot00000000000000# this package import sdjson def test_parse_pass_1() -> None: # from http://json.org/JSON_checker/test/pass1.json JSON = r""" [ "JSON Test Pattern pass1", {"object with 1 member":["array with 1 element"]}, {}, [], -42, true, false, null, { "integer": 1234567890, "real": -9876.543210, "e": 0.123456789e-12, "E": 1.234567890E+34, "": 23456789012E66, "zero": 0, "one": 1, "space": " ", "quote": "\"", "backslash": "\\", "controls": "\b\f\n\r\t", "slash": "/ & \/", "alpha": "abcdefghijklmnopqrstuvwyz", "ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ", "digit": "0123456789", "0123456789": "digit", "special": "`1~!@#$%^&*()_+-={':[,]}|;.?", "hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A", "true": true, "false": false, "null": null, "array":[ ], "object":{ }, "address": "50 St. James Street", "url": "http://www.JSON.org/", "comment": "// /* */": " ", " s p a c e d " :[1,2 , 3 , 4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7], "jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}", "quotes": "" \u0022 %22 0x22 034 "", "\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?" : "A key can be any string" }, 0.5 ,98.6 , 99.44 , 1066, 1e1, 0.1e1, 1e-1, 1e00,2e+00,2e-00 ,"rosebud"] """ # test in/out equivalence and parsing res = sdjson.loads(JSON) out = sdjson.dumps(res) assert res == sdjson.loads(out) def test_parse_pass_2() -> None: # from http://json.org/JSON_checker/test/pass2.json JSON = r""" [[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]] """ # test in/out equivalence and parsing res = sdjson.loads(JSON) out = sdjson.dumps(res) assert res == sdjson.loads(out) def test_parse_pass_3() -> None: # from http://json.org/JSON_checker/test/pass3.json JSON = r""" { "JSON Test Pattern pass3": { "The outermost value": "must be an object or array.", "In this test": "It is an object." } } """ # test in/out equivalence and parsing res = sdjson.loads(JSON) out = sdjson.dumps(res) assert res == sdjson.loads(out) singledispatch-json-0.4.0/tests/stdlib_tests/test_recursion.py000066400000000000000000000047211423345565700247740ustar00rootroot00000000000000# stdlib from typing import Dict, List # 3rd party import pytest from coincidence.selectors import not_pypy # this package import sdjson class JSONTestObject: pass def test_listrecursion() -> None: x: List[List] = [] x.append(x) try: sdjson.dumps(x) except ValueError: pass else: pytest.fail("didn't raise ValueError on list recursion") x = [] y = [x] x.append(y) try: sdjson.dumps(x) except ValueError: pass else: pytest.fail("didn't raise ValueError on alternating list recursion") y = [] x = [y, y] # ensure that the marker is cleared sdjson.dumps(x) def test_dictrecursion() -> None: x: Dict[str, Dict] = {} x["test"] = x try: sdjson.dumps(x) except ValueError: pass else: pytest.fail("didn't raise ValueError on dict recursion") x = {} y = {'a': x, 'b': x} # ensure that the marker is cleared sdjson.dumps(x) def test_defaultrecursion() -> None: class RecursiveJSONEncoder(sdjson.JSONEncoder): recurse = False def default(self, o): if o is JSONTestObject: if self.recurse: return [JSONTestObject] else: return "JSONTestObject" return sdjson.JSONEncoder.default(self, o) enc = RecursiveJSONEncoder() assert enc.encode(JSONTestObject) == '"JSONTestObject"' enc.recurse = True try: enc.encode(JSONTestObject) except ValueError: pass else: pytest.fail("didn't raise ValueError on default recursion") def test_highly_nested_objects_decoding() -> None: # test that loading highly-nested objects doesn't segfault when C # accelerations are used. See #12017 with pytest.raises(RecursionError): sdjson.loads('{"a":' * 100000 + '1' + '}' * 100000) with pytest.raises(RecursionError): sdjson.loads('{"a":' * 100000 + "[1]" + '}' * 100000) with pytest.raises(RecursionError): sdjson.loads('[' * 100000 + '1' + ']' * 100000) @not_pypy("Breaks coverage tracing on PyPy") def test_highly_nested_objects_encoding() -> None: # See #12051 l: List[List] d: Dict[str, Dict] l, d = [], {} for x in range(100000): l, d = [l], {'k': d} with pytest.raises(RecursionError): sdjson.dumps(l) with pytest.raises(RecursionError): sdjson.dumps(d) @not_pypy("Breaks coverage tracing on PyPy") def test_endless_recursion() -> None: # See #12051 class EndlessJSONEncoder(sdjson.JSONEncoder): def default(self, o): """ If check_circular is False, this will keep adding another list. """ return [o] with pytest.raises(RecursionError): EndlessJSONEncoder(check_circular=False).encode(5j) singledispatch-json-0.4.0/tests/stdlib_tests/test_separators.py000066400000000000000000000021351423345565700251430ustar00rootroot00000000000000# stdlib import textwrap # 3rd party import pytest # this package import sdjson def test_separators() -> None: h = [ ["blorpie"], ["whoops"], [], "d-shtaeou", "d-nthiouh", "i-vhbjkhnth", {"nifty": 87}, {"field": "yes", "morefield": False}, ] expect = textwrap.dedent( """\ [ [ "blorpie" ] , [ "whoops" ] , [] , "d-shtaeou" , "d-nthiouh" , "i-vhbjkhnth" , { "nifty" : 87 } , { "field" : "yes" , "morefield" : false } ]""" ) d1 = sdjson.dumps(h) d2 = sdjson.dumps(h, indent=2, sort_keys=True, separators=(" ,", " : ")) h1 = sdjson.loads(d1) h2 = sdjson.loads(d2) assert h1 == h assert h2 == h assert d2 == expect def test_illegal_separators() -> None: h = {1: 2, 3: 4} with pytest.raises(TypeError): sdjson.dumps(h, separators=(b", ", ": ")) # type: ignore with pytest.raises(TypeError): sdjson.dumps(h, separators=(", ", b": ")) # type: ignore with pytest.raises(TypeError): sdjson.dumps(h, separators=(b", ", b": ")) # type: ignore singledispatch-json-0.4.0/tests/stdlib_tests/test_unicode.py000066400000000000000000000056711423345565700244160ustar00rootroot00000000000000# stdlib import codecs from collections import OrderedDict # 3rd party import pytest # this package import sdjson # test_encoding1 and test_encoding2 from 2.x are irrelevant (only str # is supported as input, not bytes). def test_encoding3() -> None: u = "ฮฑฮฉ" j = sdjson.dumps(u) assert j == '"\\u03b1\\u03a9"' def test_encoding4() -> None: u = "ฮฑฮฉ" j = sdjson.dumps([u]) assert j == '["\\u03b1\\u03a9"]' def test_encoding5() -> None: u = "ฮฑฮฉ" j = sdjson.dumps(u, ensure_ascii=False) assert j == f'"{u}"' def test_encoding6() -> None: u = "ฮฑฮฉ" j = sdjson.dumps([u], ensure_ascii=False) assert j == f'["{u}"]' def test_big_unicode_encode() -> None: u = '๐„ ' assert sdjson.dumps(u) == '"\\ud834\\udd20"' assert sdjson.dumps(u, ensure_ascii=False) == '"๐„ "' def test_big_unicode_decode() -> None: u = "z๐„ x" assert sdjson.loads('"' + u + '"') == u assert sdjson.loads('"z\\ud834\\udd20x"') == u def test_unicode_decode() -> None: for i in range(0, 0xd7ff): u = chr(i) s = f'"\\u{i:04x}"' assert sdjson.loads(s) == u def test_unicode_preservation() -> None: assert type(sdjson.loads('""')) == str assert type(sdjson.loads('"a"')) == str assert type(sdjson.loads('["a"]')[0]) == str def test_bytes_encode() -> None: with pytest.raises(TypeError): sdjson.dumps(b"hi") with pytest.raises(TypeError): sdjson.dumps([b"hi"]) def test_bytes_decode() -> None: for encoding, bom in [ ("utf-8", codecs.BOM_UTF8), ("utf-16be", codecs.BOM_UTF16_BE), ("utf-16le", codecs.BOM_UTF16_LE), ("utf-32be", codecs.BOM_UTF32_BE), ("utf-32le", codecs.BOM_UTF32_LE), ]: data = ["aยตโ‚ฌ๐„ "] encoded = sdjson.dumps(data).encode(encoding) assert sdjson.loads(bom + encoded) == data assert sdjson.loads(encoded) == data with pytest.raises(UnicodeDecodeError): sdjson.loads(b'["\x80"]') # RFC-7159 and ECMA-404 extend JSON to allow documents that # consist of only a string, which can present a special case # not covered by the encoding detection patterns specified in # RFC-4627 for utf-16-le (XX 00 XX 00). assert sdjson.loads('"โ˜€"'.encode("utf-16-le")) == 'โ˜€' # Encoding detection for small (<4) bytes objects # is implemented as a special case. RFC-7159 and ECMA-404 # allow single codepoint JSON documents which are only two # bytes in utf-16 encodings w/o BOM. assert sdjson.loads(b"5\x00") == 5 assert sdjson.loads(b"\x007") == 7 assert sdjson.loads(b"57") == 57 def test_object_pairs_hook_with_unicode() -> None: s = '{"xkd":1, "kcw":2, "art":3, "hxm":4, "qrt":5, "pad":6, "hoy":7}' p = [("xkd", 1), ("kcw", 2), ("art", 3), ("hxm", 4), ("qrt", 5), ("pad", 6), ("hoy", 7)] assert sdjson.loads(s, object_pairs_hook=lambda x: x) == p od = sdjson.loads(s, object_pairs_hook=OrderedDict) assert od == OrderedDict(p) assert type(od) == OrderedDict # the object_pairs_hook takes priority over the object_hook assert sdjson.loads(s, object_pairs_hook=OrderedDict, object_hook=lambda x: None) == OrderedDict(p) singledispatch-json-0.4.0/tests/test_custom_class.py000066400000000000000000000075731423345565700227670ustar00rootroot00000000000000""" Test custom encoder for a custom class """ # stdlib from abc import ABC # 3rd party from domdf_python_tools.paths import TemporaryPathPlus # this package import sdjson class CustomClassBase(ABC): def __str__(self) -> str: return self.__repr__() def __iter__(self): yield from self.__dict__.items() def __getstate__(self): return self.__dict__ def __setstate__(self, state): self.__init__(**state) # type: ignore def __copy__(self): return self.__class__(**self.__dict__) def __deepcopy__(self, memodict={}): return self.__copy__() class Character(CustomClassBase): def __init__(self, name, actor, armed=False): self.name = str(name) self.actor = str(actor) self.armed = bool(armed) def __repr__(self) -> str: return f"Character: {self.name} ({self.actor})" @property def __dict__(self): return dict(name=self.name, actor=self.actor, armed=self.armed) class Cheese(CustomClassBase): def __init__(self, name, properties=None): self.name = name if properties: self.properties = properties else: self.properties = [] def __repr__(self) -> str: return f"Cheese({self.name})" @property def __dict__(self): return dict( name=self.name, properties=self.properties, ) class Shop(CustomClassBase): """ Custom class to encode to JSON """ def __init__( self, name, address, is_open=True, staff=None, customers=None, current_stock=None, music=False, dancing=False, ): self.name = str(name) self.address = str(address) self.is_open = bool(is_open) self.music = bool(music) self.dancing = bool(dancing) if staff: self.staff = staff else: self.staff = [] if customers: self.customers = customers else: self.customers = [] if current_stock: self.current_stock = current_stock else: self.current_stock = [] def __repr__(self) -> str: return f"{self.name} ({'Open' if self.is_open else 'closed'})" @property def __dict__(self): return dict( name=self.name, address=self.address, is_open=self.is_open, music=self.music, dancing=self.dancing, staff=self.staff, customers=self.customers, current_stock=self.current_stock, ) def test_custom_class() -> None: # Create and register the custom encoders # In this example we create three separate encoders even though all three classes # actually share a common subclass. In real usage they might not be. @sdjson.encoders.register(Character) def encode_character(obj): return dict(obj) @sdjson.encoders.register(Cheese) def encode_cheese(obj): return dict(obj) @sdjson.encoders.register(Shop) def encode_shop(obj): return dict(obj) # Create instances of classes runny_camembert = Cheese("Camembert", ["Very runny"]) shopkeeper = Character("Mr Wensleydale", "Michael Palin") customer = Character("The Customer", "John Cleese") cheese_shop = Shop( "The National Cheese Emporium", address="""12 Some Street Some Town England""", staff=[shopkeeper], customers=[customer], current_stock=[runny_camembert], music=False, dancing=False, ) expected_json = ( '{"name": "The National Cheese Emporium", "address": "12 Some Street\\n' 'Some Town\\nEngland", "is_open": true, "music": false, "dancing": false, ' '"staff": [{"name": "Mr Wensleydale", "actor": "Michael Palin", "armed": false}], ' '"customers": [{"name": "The Customer", "actor": "John Cleese", "armed": false}], ' '"current_stock": [{"name": "Camembert", "properties": ["Very runny"]}]}' ) with TemporaryPathPlus() as tmpdir: tmpfile = tmpdir / "output.json" with open(tmpfile, 'w', encoding="UTF-8") as fp: sdjson.dump(cheese_shop, fp) with open(tmpfile, encoding="UTF-8") as fp: assert fp.read() == expected_json assert sdjson.dumps(cheese_shop) == expected_json # Cleanup sdjson.unregister_encoder(Character) sdjson.unregister_encoder(Cheese) sdjson.unregister_encoder(Shop) singledispatch-json-0.4.0/tests/test_custom_encoders.py000066400000000000000000000136551423345565700234620ustar00rootroot00000000000000""" Create several custom encoders and test that they work """ # stdlib import collections from datetime import date, datetime, time, timedelta from decimal import Decimal from fractions import Fraction # 3rd party import pytest import pytz # type: ignore[import] # this package import sdjson def test_decimal_float() -> None: # Create and register a custom encoder for Decimal that turns it into a float @sdjson.encoders.register(Decimal) def encode_decimal_float(obj): return float(obj) assert sdjson.dumps(Decimal(str(12.3456))) == "12.3456" # Cleanup sdjson.encoders.unregister(Decimal) def test_decimal_str() -> None: # Create and register a custom encoder for Decimal that turns it into a str @sdjson.encoders.register(Decimal) def encode_decimal_str(obj): return str(obj) assert sdjson.dumps(Decimal(str(12.3456))) == '"12.3456"' # Cleanup sdjson.encoders.unregister(Decimal) def test_fraction_float() -> None: # Create and register a custom encoder for Fraction that turns it into a float @sdjson.encoders.register(Fraction) def encode_fraction_float(obj): return float(obj) assert sdjson.dumps(Fraction(13, 10)) == "1.3" assert sdjson.dumps(Fraction(3, 4)) == "0.75" assert sdjson.dumps(Fraction(9, 11)) == "0.8181818181818182" assert sdjson.dumps(Fraction(140, 144)) == "0.9722222222222222" assert sdjson.dumps(Fraction(2, 7)) == "0.2857142857142857" # Cleanup sdjson.encoders.unregister(Fraction) def test_fraction_str() -> None: # Create and register a custom encoder for Fraction that turns it into a str @sdjson.encoders.register(Fraction) def encode_fraction_str(obj): return str(obj) assert sdjson.dumps(Fraction(13, 10)) == '"13/10"' assert sdjson.dumps(Fraction(3, 4)) == '"3/4"' assert sdjson.dumps(Fraction(9, 11)) == '"9/11"' assert sdjson.dumps(Fraction(140, 144)) == '"35/36"' assert sdjson.dumps(Fraction(2, 7)) == '"2/7"' # Cleanup sdjson.encoders.unregister(Fraction) def test_datetime_float() -> None: # Create and register a custom encoder for datetime that turns it into a float @sdjson.encoders.register(datetime) def encode_datetime_float(obj): return obj.timestamp() assert sdjson.dumps(datetime(1945, 5, 8, 19, 20, tzinfo=pytz.UTC)) == "-777876000.0" # Cleanup sdjson.encoders.unregister(datetime) def test_datetime_str() -> None: # Create and register a custom encoder for datetime that turns it into a str @sdjson.encoders.register(datetime) def encode_datetime_str(obj): return f"{obj:%Y/%m/%d %H:%M}" assert sdjson.dumps(datetime(1945, 5, 8, 19, 20)) == '"1945/05/08 19:20"' # Cleanup sdjson.encoders.unregister(datetime) def test_datetime_tuple() -> None: # Create and register a custom encoder for datetime that turns it into a timetuple @sdjson.encoders.register(datetime) def encode_datetime_tuple(obj): return obj.timetuple() assert sdjson.dumps(datetime(1945, 5, 8, 19, 20)) == "[1945, 5, 8, 19, 20, 0, 1, 128, -1]" # Cleanup sdjson.encoders.unregister(datetime) def test_timedelta_float() -> None: # Create and register a custom encoder for timedelta that turns it into a float @sdjson.encoders.register(timedelta) def encode_timedelta_float(obj): return obj.total_seconds() start_date = datetime(1945, 5, 8, 19, 20).replace(tzinfo=pytz.utc) end_date = datetime(2020, 5, 8, 9, 0).replace(tzinfo=pytz.utc) delta = end_date - start_date assert sdjson.dumps(delta) == "2366804400.0" # Cleanup sdjson.encoders.unregister(timedelta) # def test_date_float() -> None: # # Create and register a custom encoder for date that turns it into a float # @sdjson.encoders.register(date) # def encode_date_float(obj): # return obj.timestamp() # # assert sdjson.dumps(date(1945, 5, 8)) == "-777952800.0" # # # Cleanup # sdjson.encoders.unregister(date) def test_date_str() -> None: # Create and register a custom encoder for date that turns it into a str @sdjson.encoders.register(date) def encode_date_str(obj): return f"{obj:%Y/%m/%d}" assert sdjson.dumps(date(1945, 5, 8)) == '"1945/05/08"' # Cleanup sdjson.encoders.unregister(date) def test_date_tuple() -> None: # Create and register a custom encoder for date that turns it into a timetuple @sdjson.encoders.register(date) def encode_date_tuple(obj): return obj.timetuple() assert sdjson.dumps(date(1945, 5, 8)) == "[1945, 5, 8, 0, 0, 0, 1, 128, -1]" # Cleanup sdjson.encoders.unregister(date) def test_time_float() -> None: # Create and register a custom encoder for time that turns it into a float @sdjson.encoders.register(time) def encode_date_float(obj): return int(timedelta(hours=obj.hour, minutes=obj.minute, seconds=obj.second).total_seconds()) assert sdjson.dumps(time(9, 10, 11)) == "33011" # Cleanup sdjson.encoders.unregister(time) # Create and register a custom encoder for date that turns it into a float def test_time_str() -> None: # Create and register a custom encoder for time that turns it into a str @sdjson.encoders.register(time) def encode_time_str(obj): return f"{obj:%H:%M:%S}" assert sdjson.dumps(time(9, 10, 11)) == '"09:10:11"' # Cleanup sdjson.encoders.unregister(time) def test_time_tuple() -> None: # Create and register a custom encoder for time that turns it into a timetuple @sdjson.encoders.register(time) def encode_time_tuple(obj): return obj.hour, obj.minute, obj.second assert sdjson.dumps(time(9, 10, 11)) == "[9, 10, 11]" # Cleanup sdjson.encoders.unregister(time) @pytest.mark.xfail(reason="Not implemented in CPython yet.") def test_named_tuple() -> None: # Ref: https://bugs.python.org/issue30343 # https://github.com/python/cpython/pull/1558 Student = collections.namedtuple("Student", "name, age, teacher") try: @sdjson.encoders.register(Student) def encode_student(obj): return { "name": obj.name, "age": obj.age, "teacher": obj.teacher, } assert sdjson.dumps(Student("Alice", 12, "Sue")) == '{"name": "Alice", "age": 12, "teacher": "Sue"}' finally: # Cleanup sdjson.encoders.unregister(Student) singledispatch-json-0.4.0/tests/test_load.py000066400000000000000000000054151423345565700212000ustar00rootroot00000000000000""" Test dumping and loading some objects """ # stdlib from typing import Dict # 3rd party import pytest from domdf_python_tools.paths import PathPlus # this package import sdjson def write_then_read(obj, tmpdir: PathPlus): tmpfile = tmpdir / "output.json" with open(tmpfile, 'w', encoding="UTF-8") as fp: sdjson.dump(obj, fp) with open(tmpfile, encoding="UTF-8") as fp: return sdjson.load(fp) def test_bools(tmp_pathplus: PathPlus) -> None: assert write_then_read(True, tmp_pathplus) is True assert str(write_then_read(True, tmp_pathplus)) == "True" # Double check with string assert write_then_read(False, tmp_pathplus) is False assert str(write_then_read(False, tmp_pathplus)) == "False" # Double check with string def test_none(tmp_pathplus: PathPlus) -> None: assert write_then_read(None, tmp_pathplus) is None assert str(write_then_read(None, tmp_pathplus)) == "None" # Double check with string @pytest.mark.parametrize("value", [ 1, 1234, 12340000000, -1, -1234, -12340000000, ]) def test_int(value: int, tmp_pathplus: PathPlus) -> None: assert write_then_read(value, tmp_pathplus) == value @pytest.mark.parametrize( "value", [ 1.0, 1234.0, 12340000000.0, -1.0, -1234.0, -12340000000.0, 1.005, 1234.005, 12340000000.005, -1.005, -1234.005, -12340000000.005, ] ) def test_float(value: float, tmp_pathplus: PathPlus) -> None: assert write_then_read(value, tmp_pathplus) == value example_dict = {"True": True, "False": False, "String": "spam", "Integer": 1, "Float": 2.5} @pytest.mark.parametrize( "data, expected", [ pytest.param((True, False, 1, 2.5, "spam"), [True, False, 1, 2.5, "spam"], id="tuple"), pytest.param([True, False, 1, 2.5, "spam"], [True, False, 1, 2.5, "spam"], id="list"), pytest.param(example_dict, example_dict, id="dict"), ("egg and bacon", "egg and bacon"), ("egg sausage and bacon", "egg sausage and bacon"), ("egg and spam", "egg and spam"), ("egg bacon and spam", "egg bacon and spam"), ] ) def test_write_then_read(data, expected, tmp_pathplus: PathPlus): assert write_then_read(data, tmp_pathplus) == expected @pytest.mark.xfail @pytest.mark.parametrize("dictionary", [ {True: False, False: True}, {2: 3.0, 4.0: 5, False: 1, 6: True}, ]) def test_dict_failure(tmp_pathplus: PathPlus, dictionary: Dict) -> None: """ This test will fail because the boolean dictionary keys get read back in a lowercase strings """ assert write_then_read(dictionary, tmp_pathplus) == dictionary @pytest.mark.xfail def test_tuple_failure(tmp_pathplus: PathPlus) -> None: """ This test will fail because the tuple gets loaded back in as a list """ assert write_then_read((True, False, 1, 2.5, "spam"), tmp_pathplus) == (True, False, 1, 2.5, "spam") singledispatch-json-0.4.0/tests/test_multiple_files.py000066400000000000000000000010531423345565700232700ustar00rootroot00000000000000""" Test registering custom encoders in multiple files """ # stdlib from decimal import Decimal from fractions import Fraction # this package import sdjson def test_multiple_files() -> None: # this package from .glossia import talon, thorn # Test that we get the expected output when encoding a Decimal assert sdjson.dumps(Decimal(1)) == '"1"' # Test that we get the expected output when encoding a Fraction assert sdjson.dumps(Fraction(2, 3)) == '"2/3"' # Cleanup sdjson.encoders.unregister(Decimal) sdjson.encoders.unregister(Fraction) singledispatch-json-0.4.0/tests/test_overloading.py000066400000000000000000000022141423345565700225640ustar00rootroot00000000000000# stdlib import re from decimal import Decimal # 3rd party from coincidence import check_file_regression from pytest_regressions.file_regression import FileRegressionFixture # this package import sdjson def test_overloading(file_regression: FileRegressionFixture) -> None: try: # Create and register a custom encoder @sdjson.encoders.register(Decimal) def encoder_1(obj): return "Result from first registration" # Test that we get the expected output from the first encoder assert sdjson.dumps(Decimal(1)) == '"Result from first registration"' # Create and register a new custom encoder that overloads the previous one @sdjson.encoders.register(Decimal) def encoder_2(obj): return "Result from second registration" # Test that we get the expected output from the second encoder assert sdjson.dumps(Decimal(2)) == '"Result from second registration"' print(sdjson.encoders.registry.items()) check_file_regression(remove_memaddr(str(sdjson.encoders.registry.items())), file_regression) finally: # Cleanup sdjson.encoders.unregister(Decimal) def remove_memaddr(string): return re.sub("at 0x(.*)>", "at 0x...>", string) singledispatch-json-0.4.0/tests/test_overloading_/000077500000000000000000000000001423345565700223525ustar00rootroot00000000000000singledispatch-json-0.4.0/tests/test_overloading_/test_overloading.txt000066400000000000000000000001431423345565700264610ustar00rootroot00000000000000dict_items([(, .encoder_2 at 0x...>)]) singledispatch-json-0.4.0/tests/test_protocols.py000066400000000000000000000045701423345565700223060ustar00rootroot00000000000000# stdlib from abc import abstractmethod from typing import TypeVar # 3rd party import pytest from typing_extensions import Protocol, runtime_checkable # this package import sdjson T_co = TypeVar("T_co", covariant=True) # Any type covariant containers. @runtime_checkable class SupportsInt(Protocol): """ An ABC with one abstract method __int__. """ @abstractmethod def __int__(self) -> int: pass @runtime_checkable class SupportsFloat(Protocol): """ An ABC with one abstract method __float__. """ @abstractmethod def __float__(self) -> float: pass @runtime_checkable class SupportsBytes(Protocol): """ An ABC with one abstract method __bytes__. """ @abstractmethod def __bytes__(self) -> bytes: pass class SupportsBizBaz(Protocol): @abstractmethod def bizbaz(self): pass def test_protocols() -> None: class Integer: def __int__(self): return 42 class Float: def __float__(self): return 42.0 class Bytes: def __bytes__(self): return b"42" with pytest.raises(TypeError, match="Object of type '?Integer'? is not JSON serializable"): sdjson.dumps(Integer()) with pytest.raises(TypeError, match="Object of type '?Float'? is not JSON serializable"): sdjson.dumps(Float()) with pytest.raises(TypeError, match="Object of type '?Bytes'? is not JSON serializable"): sdjson.dumps(Bytes()) @sdjson.encoders.register(SupportsInt) def supports_int_encoder(obj): return int(obj) @sdjson.encoders.register(SupportsFloat) def supports_float_encoder(obj): return float(obj) @sdjson.encoders.register(SupportsBytes) def supports_bytes_encoder(obj): return bytes(obj) assert sdjson.dumps(Integer()) == "42" assert sdjson.dumps(Float()) == "42.0" # To prove the protocols don't take precedence assert sdjson.dumps(123) == "123" with pytest.raises(TypeError, match="Object of type '?bytes'? is not JSON serializable"): sdjson.dumps(Bytes()) @sdjson.encoders.register(bytes) def bytes_encoder(obj): return obj.decode("UTF-8") assert sdjson.dumps(Bytes()) == '"42"' sdjson.unregister_encoder(SupportsInt) sdjson.unregister_encoder(SupportsFloat) sdjson.unregister_encoder(SupportsBytes) sdjson.unregister_encoder(bytes) with pytest.raises(KeyError): sdjson.unregister_encoder(bytes) with pytest.raises(TypeError, match="Protocols must be @runtime_checkable"): sdjson.register_encoder(SupportsBizBaz, supports_bytes_encoder) singledispatch-json-0.4.0/tests/test_unregister.py000066400000000000000000000012011423345565700224350ustar00rootroot00000000000000# stdlib from decimal import Decimal # 3rd party import pytest # this package import sdjson def test_unregister() -> None: # Create and register a custom encoder for Decimal that turns it into a string @sdjson.encoders.register(Decimal) def encode_str(obj): return str(obj) # Test that we get the expected output from the first encoder assert sdjson.dumps(Decimal(1)) == '"1"' # Unregister that encoder sdjson.unregister_encoder(Decimal) # We should now get an error with pytest.raises(TypeError, match="Object of type [']*Decimal[']* is not JSON serializable") as e: sdjson.dumps(Decimal(2)) assert e.type is TypeError singledispatch-json-0.4.0/tox.ini000066400000000000000000000130611423345565700170150ustar00rootroot00000000000000# This file is managed by 'repo_helper'. # You may add new sections, but any changes made to the following sections will be lost: # * tox # * envlists # * testenv # * testenv:docs # * testenv:build # * testenv:lint # * testenv:mypy # * testenv:pyup # * testenv:coverage # * flake8 # * coverage:run # * coverage:report # * check-wheel-contents # * pytest [tox] envlist = py36 py37 py38 py39 py310 pypy36 pypy37 pypy38 pypy39 mypy build skip_missing_interpreters = True isolated_build = True requires = pip>=21 tox-envlist>=0.2.1 [envlists] test = py36, py37, py38, py39, py310, pypy36, pypy37, pypy38, pypy39 qa = mypy, lint cov = py36, coverage [testenv] setenv = PYTHONDEVMODE=1 PIP_DISABLE_PIP_VERSION_CHECK=1 deps = -r{toxinidir}/tests/requirements.txt commands = python --version python -m pytest --cov=sdjson -r aR tests/ {posargs} [testenv:docs] setenv = SHOW_TODOS = 1 basepython = python3.8 changedir = {toxinidir}/doc-source deps = -r{toxinidir}/doc-source/requirements.txt commands = sphinx-build -M {env:SPHINX_BUILDER:html} . ./build {posargs} [testenv:build] skip_install = True changedir = {toxinidir} deps = build[virtualenv]>=0.3.1 check-wheel-contents>=0.1.0 twine>=3.2.0 commands = python -m build --sdist --wheel "{toxinidir}" twine check dist/*.tar.gz dist/*.whl check-wheel-contents dist/ [testenv:lint] basepython = python3.6 changedir = {toxinidir} ignore_errors = True skip_install = True deps = flake8>=3.8.2 flake8-2020>=1.6.0 flake8-builtins>=1.5.3 flake8-docstrings>=1.5.0 flake8-dunder-all>=0.1.1 flake8-encodings>=0.1.0 flake8-github-actions>=0.1.0 flake8-noqa>=1.1.0 flake8-pyi>=20.10.0 flake8-pytest-style>=1.3.0 flake8-quotes>=3.3.0 flake8-slots>=0.1.0 flake8-sphinx-links>=0.0.4 flake8-strftime>=0.1.1 flake8-typing-imports>=1.10.0 git+https://github.com/domdfcoding/flake8-rst-docstrings-sphinx.git git+https://github.com/domdfcoding/flake8-rst-docstrings.git git+https://github.com/python-formate/flake8-unused-arguments.git@magic-methods pydocstyle>=6.0.0 pygments>=2.7.1 importlib_metadata<4.5.0; python_version<'3.8' commands = python3 -m flake8_rst_docstrings_sphinx sdjson tests --allow-toolbox {posargs} [testenv:mypy] basepython = python3.6 ignore_errors = True changedir = {toxinidir} deps = mypy==0.942 -r{toxinidir}/tests/requirements.txt -r{toxinidir}/stubs.txt commands = mypy sdjson tests {posargs} [testenv:pyup] basepython = python3.6 skip_install = True ignore_errors = True changedir = {toxinidir} deps = pyupgrade-directories commands = pyup_dirs sdjson tests --py36-plus --recursive [testenv:coverage] basepython = python3.6 skip_install = True ignore_errors = True whitelist_externals = /bin/bash passenv = COV_PYTHON_VERSION COV_PLATFORM COV_PYTHON_IMPLEMENTATION changedir = {toxinidir} deps = coverage>=5 coverage_pyver_pragma>=0.2.1 commands = /bin/bash -c "rm -rf htmlcov" coverage html /bin/bash -c "DISPLAY=:0 firefox 'htmlcov/index.html'" [flake8] max-line-length = 120 select = E111 E112 E113 E121 E122 E125 E127 E128 E129 E131 E133 E201 E202 E203 E211 E222 E223 E224 E225 E225 E226 E227 E228 E231 E241 E242 E251 E261 E262 E265 E271 E272 E303 E304 E306 E402 E502 E703 E711 E712 E713 E714 E721 W291 W292 W293 W391 W504 YTT101 YTT102 YTT103 YTT201 YTT202 YTT203 YTT204 YTT301 YTT302 YTT303 STRFTIME001 STRFTIME002 SXL001 PT001 PT002 PT003 PT006 PT007 PT008 PT009 PT010 PT011 PT012 PT013 PT014 PT015 PT016 PT017 PT018 PT019 PT020 PT021 RST201 RST202 RST203 RST204 RST205 RST206 RST207 RST208 RST210 RST211 RST212 RST213 RST214 RST215 RST216 RST217 RST218 RST219 RST299 RST301 RST302 RST303 RST304 RST305 RST306 RST399 RST401 RST499 RST900 RST901 RST902 RST903 Q001 Q002 Q003 A001 A002 A003 TYP001 TYP002 TYP003 TYP004 TYP005 TYP006 ENC001 ENC002 ENC003 ENC004 ENC011 ENC012 ENC021 ENC022 ENC023 ENC024 ENC025 ENC026 Y001,Y002 Y003 Y004 Y005 Y006 Y007 Y008 Y009 Y010 Y011 Y012 Y013 Y014 Y015 Y090 Y091 NQA001 NQA002 NQA003 NQA004 NQA005 NQA102 NQA103 E301 E302 E305 D100 D101 D102 D103 D104 D106 D201 D204 D207 D208 D209 D210 D211 D212 D213 D214 D215 D300 D301 D400 D402 D403 D404 D415 D417 DALL000 SLOT000 SLOT001 SLOT002 extend-exclude = doc-source,old,build,dist,__pkginfo__.py,setup.py,venv rst-directives = TODO autosummary-widths envvar extras-require license license-info rst-roles = choosealicense per-file-ignores = tests/*: D100 D101 D102 D103 D104 D106 D201 D204 D207 D208 D209 D210 D211 D212 D213 D214 D215 D300 D301 D400 D402 D403 D404 D415 D417 DALL000 SLOT000 SLOT001 SLOT002 */*.pyi: E301 E302 E305 D100 D101 D102 D103 D104 D106 D201 D204 D207 D208 D209 D210 D211 D212 D213 D214 D215 D300 D301 D400 D402 D403 D404 D415 D417 DALL000 SLOT000 SLOT001 SLOT002 pytest-parametrize-names-type = csv inline-quotes = " multiline-quotes = """ docstring-quotes = """ count = True min_python_version = 3.6.1 unused-arguments-ignore-abstract-functions = True unused-arguments-ignore-overload-functions = True unused-arguments-ignore-magic-methods = True unused-arguments-ignore-variadic-names = True [coverage:run] plugins = coverage_pyver_pragma [coverage:report] fail_under = 100 exclude_lines = raise AssertionError raise NotImplementedError if 0: if False: if TYPE_CHECKING: if typing.TYPE_CHECKING: if __name__ == .__main__.: [check-wheel-contents] ignore = W002 toplevel = sdjson package = sdjson [pytest] addopts = --color yes --durations 25 timeout = 300