[
  {
    "path": ".github/FUNDING.yml",
    "content": "\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "\nversion: 2\n\nupdates:\n- package-ecosystem: pip\n  directory: \"/\"\n  schedule:\n    interval: weekly\n- package-ecosystem: \"github-actions\"\n  directory: \"/\"\n  schedule:\n    interval: weekly"
  },
  {
    "path": ".github/workflows/bump_version.yaml",
    "content": "name: Bump version\n\non:\n  push:\n    branches:\n      - master\n\njobs:\n  bump-version:\n    if: \"!startsWith(github.event.head_commit.message, 'bump:')\"\n    runs-on: ubuntu-latest\n    name: \"Bump version and create changelog with commitizen\"\n    steps:\n      - name: Check out\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n          token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}\n      - name: Create bump and changelog\n        uses: commitizen-tools/commitizen-action@master\n        with:\n          github_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}\n          changelog_increment_filename: body.md\n      - name: Release\n        uses: softprops/action-gh-release@v2\n        with:\n          body_path: \"body.md\"\n          tag_name: ${{ env.REVISION }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}"
  },
  {
    "path": ".github/workflows/docs-preview.yaml",
    "content": "name: Deploy PR previews\n\non:\n  pull_request:\n    types:\n      - opened\n      - reopened\n      - synchronize\n      - closed\n\nconcurrency: preview-${{ github.ref }}\n\njobs:\n  deploy-preview:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Setup python\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.12'\n\n      - name: Install Dependencies\n        run: |\n          python -m pip install -U pip poetry\n          poetry install\n\n      - name: Build docs\n        run: |\n          poetry run mkdocs build\n\n      - name: Deploy preview\n        uses: rossjrw/pr-preview-action@v1\n        with:\n          source-dir: ./site/\n"
  },
  {
    "path": ".github/workflows/docs-publish.yaml",
    "content": "name: Publish documentation\n\non:\n  push:\n    branches: [\"master\"]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Setup python\n      uses: actions/setup-python@v5\n      with:\n        python-version: '3.12'\n\n    - name: Install Dependencies\n      run: |\n        python -m pip install -U pip poetry\n        poetry install\n\n    - name: Build docs\n      run: |\n        poetry run mkdocs build\n\n    - name: Push doc to Github Page\n      uses: peaceiris/actions-gh-pages@v4\n      with:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        PUBLISH_BRANCH: gh-pages\n        PUBLISH_DIR: ./site\n"
  },
  {
    "path": ".github/workflows/publish.yaml",
    "content": "name: Publish Package\n\non:\n  push:\n    tags:\n      - '*'\n  workflow_dispatch:\n    inputs:\n      release:\n        description: 'Release package'\n        required: true\n        default: false\n        type: boolean\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Setup python\n      uses: actions/setup-python@v5\n      with:\n        python-version: '3.12'\n\n    - name: Install Dependencies\n      run: |\n        python -m pip install -U pip poetry\n        poetry install --only ci-publish\n\n    - name: Publish\n      env:\n        TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }}\n        TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}\n      run: |\n        ./scripts/publish\n"
  },
  {
    "path": ".github/workflows/python-package.yml",
    "content": "# This workflow will install Python dependencies, run tests and lint with a variety of Python versions\n# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions\n\nname: Python package\n\non:\n  push:\n    branches: [\"master\", \"release/v3\"]\n  pull_request:\n    branches: [\"master\", \"release/v3\"]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\", \"3.12\"]\n    steps:\n    - uses: actions/checkout@v4\n    - name: Setup python\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python-version }}\n        architecture: x64\n\n    - name: Run docker-compose\n      uses: hoverkraft-tech/compose-action@v2.1.0\n      with:\n        compose-file: \"docker-compose.yaml\"\n    \n    - name: Build and test\n      env:\n        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}\n        PYTHON_VERSION: ${{ matrix.python-version }}\n      run: |\n        python -m pip install -U pip poetry\n        poetry install --all-extras\n        ./scripts/test\n"
  },
  {
    "path": ".gitignore",
    "content": "### Python template\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*.pyc\n*$py.class\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n\n# PyBuilder\ntarget/\n\n# pyenv\n.python-version\n\n# Environments\n.venv\nvenv/\nENV/\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n### VisualStudioCode template\n.vscode/\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n\n# Workspace files are user-specific\n*.sublime-workspace\n\n# Project files should be checked into the repository, unless a significant\n# proportion of contributors will probably not be using Sublime Text\n# *.sublime-project\n\n# SFTP configuration file\nsftp-config.json\n\n# Package control specific files\nPackage Control.last-run\nPackage Control.ca-list\nPackage Control.ca-bundle\nPackage Control.system-ca-bundle\nPackage Control.cache/\nPackage Control.ca-certs/\nPackage Control.merged-ca-bundle\nPackage Control.user-ca-bundle\noscrypto-ca-bundle.crt\nbh_unicode_properties.cache\n\n# Sublime-github package stores a github token in this file\n# https://packagecontrol.io/packages/sublime-github\nGitHub.sublime-settings\n\n### Vim template\n# Swap\n[._]*.s[a-v][a-z]\n[._]*.sw[a-p]\n[._]s[a-v][a-z]\n[._]sw[a-p]\n\n# Session\nSession.vim\n\n# Auto-generated tag files\ntags\n\n.pytest_cache/\n\n.ipython/\n\n# PyCharm\n.idea\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n### Fix\n\n- dependabot added. Documentation improved with mkdocstrings (#148)\n\n## v2.6.1 (2025-04-04)\n\n### Fix\n\n- use anyio instead of aiofiles for async file I/O (#434)\n\n## v2.6.0 (2024-06-19)\n\n### Feat\n\n- add verbose option for test_compatibility (#336)\n\n## v2.5.6 (2024-02-12)\n\n### Fix\n\n- bump fastavro to 1.9.3 and correct typing (#250)\n\n## v2.5.5 (2024-02-09)\n\n### Fix\n\n- **dependencies**: bump httpx version constraints to 0.26 (#244)\n\n## v2.5.4 (2024-01-12)\n\n### Fix\n\n- allow for minor version upgrades of httpx (#231)\n\n## v2.5.3 (2024-01-04)\n\n### Fix\n\n- order of arguments in SchemaVersion constructor (#227)\n\n## v2.5.2 (2023-12-11)\n\n### Fix\n\n- pin python version in publish workflow (#218)\n\n## v2.5.1 (2023-12-02)\n\n### Fix\n\n- remove print statement from serializer (#214)\n\n## v2.5.0 (2023-06-26)\n\n### Feat\n\n- Add support for generic httpx.Auth  (#174)\n\n## v2.4.4 (2023-06-26)\n\n### Fix\n\n- dependabot added. Documentation improved with mkdocstrings (#148)\n\n## [2.4.3] - 2023-04-19\n\n- include properly `py.typed` marker file\n\n### Fixed\n\n## [2.4.2] - 2023-04-18\n\n- `py.typed` marker file added [142](https://github.com/marcosschroh/python-schema-registry-client/pull/142)\n\n### Fixed\n\n## [2.4.1] - 2022-09-08\n\n- schema getters when fetching JSON schemas without cached results [138](https://github.com/marcosschroh/python-schema-registry-client/pull/138)\n\n### Fixed\n\n## [2.4.0] - 2022-04-22\n\n- `Auth` parameter added to simplify [auth credentials](https://marcosschroh.github.io/python-schema-registry-client/client/#auth) (`username` and `password`) [127](https://github.com/marcosschroh/python-schema-registry-client/pull/127)\n\n### Fixed\n\n## [2.2.2] - 2022-02-09\n\n- Support latest httpx version [117](https://github.com/marcosschroh/python-schema-registry-client/pull/117)\n\n### Fixed\n\n## [2.2.1] - 2021-12-29\n\n- Support for python 3.7+ and latest httpx added [116](https://github.com/marcosschroh/python-schema-registry-client/pull/116)\n\n### Added\n\n## [2.2.0] - 2021-11-24\n\n- `AsyncJsonMessageSerializer`, `AsyncAvroMessageSerializer` and `AsyncSchemaRegistryClient` added [102](https://github.com/marcosschroh/python-schema-registry-client/pull/102)\n\n### Fixed\n\n## [2.1.1] - 2021-11-15\n\n- `httpx` requirement updated [114](https://github.com/marcosschroh/python-schema-registry-client/pull/114)\n\n## [2.1.0] - 2021-11-12\n\n- new function `get_schema_subject_versions` [109](https://github.com/marcosschroh/python-schema-registry-client/pull/109)\n\n### Added\n\n- fix serializers API when faust is not installed [104](https://github.com/marcosschroh/python-schema-registry-client/pull/104)\n\n## [2.0.0] - 2021-10-14\n\n### Added\n\n- fix serializers API when faust is not installed [104](https://github.com/marcosschroh/python-schema-registry-client/pull/104)\n\n## [1.9.0] - 2021-10-07\n\n### Added\n\n- Support for json schemas added [100](https://github.com/marcosschroh/python-schema-registry-client/pull/100)\n\n## [1.8.2] - 2021-05-07\n\n### Fixed\n\n- Unpin fastavro dependency\n\n## [1.8.1] - 2021-02-26\n\n### Fixed\n\n- Type check [#95](https://github.com/marcosschroh/python-schema-registry-client/pull/95)\n- Logging levels [#94](https://github.com/marcosschroh/python-schema-registry-client/pull/94)\n\n## [1.8.0] - 2020-01-29\n\n### Added\n\n- support return_record_name [#89](https://github.com/marcosschroh/python-schema-registry-client/pull/89)\n- Update pinned fastavro version to match dataclasses-avroschema [#91](https://github.com/marcosschroh/python-schema-registry-client/pull/91)\n\n## [1.7.2] - 2020-12-22\n\n### Fixed\n\n- Checks if Schema is already registered before trying to register. This allows\n  Schema Registry to be readonly in production environment, with only CI/CD\n  being allowed to make changes.\n\n## [1.7.1] - 2020-12-07\n\n### Fixed\n\n- [faust] extra now depends on [faust-streaming fork](https://github.com/faust-streaming/faust)\n\n## [1.7.0] - 2020-10-17\n\n### Added\n\n- Integration with [dataclasses-avroschema](https://github.com/marcosschroh/dataclasses-avroschema) added to serializers\n\n### Fixed\n\n- Requirements updated: `fastavro==1.0.0.post1` and `mypy==0.782`\n\n## [1.6.1] - 2020-10-16\n\n### Fixed\n\n- Requirements updated: `fastavro==1.0.0.post1` and `mypy==0.782`\n\n## [1.6.0] - 2020-09-18\n\n### Added\n\n- Integration with [dataclasses-avroschema](https://github.com/marcosschroh/dataclasses-avroschema) added\n\n## [1.5.0] - 2020-09-12\n\n### Added\n\n- `AsyncSchemaRegistryClient` added\n\n## [1.4.7] - 2020-09-12\n\n### Fixed\n\n- Submit raw schema instead of the `fastavro-parsed` version [#77](https://github.com/marcosschroh/python-schema-registry-client/issues/77)\n\n## [1.4.6] - 2020-09-07\n\n### Fixed\n\n- `is_key` removed from signature methods\n- documentation updated\n\n## [1.4.5] - 2020-08-19\n\n### Fixed\n\n- Pin dependency versions\n\n## [1.4.4] - 2020-08-14\n\n### Fixed\n\n- Corrects `Accept headers` to conform to specification [#73](https://github.com/marcosschroh/python-schema-registry-client/pull/73)\n\n## [1.4.3] - 2020-08-13\n\n### Fixed\n\n- `requests` dependency removed [#70](https://github.com/marcosschroh/python-schema-registry-client/pull/70)\n\n## [1.4.2] - 2020-08-10\n\n### Fixed\n\n- Fix `client.register cache lookup` [#62](https://github.com/marcosschroh/python-schema-registry-client/pull/62)\n- Support for new release of `httpx`. For `httpx < 0.14.0` versions usage of `python-schema-registry-client==1.4.1`\n- Don't rely on httpx's private config values [#66](https://github.com/marcosschroh/python-schema-registry-client/pull/66)\n\n## [1.4.1] - 2020-07-14\n\n### Changed\n\n- `Avro` serialization for complex `faust` records that contains nested `records`, `Mapping` or `Sequences fixed` [#59](https://github.com/marcosschroh/python-schema-registry-client/issues/59)\n\n## [1.4.0] - 2020-05-07\n\n### Added\n\n- timeout and pool_limits added to client\n\n## [1.3.2] - 2020-05-06\n\n### Fixed\n\n- Allow SchemaRegistryClient to be picklable fixed #24\n\n## [1.3.1] - 2020-05-03\n\n### Changed\n\n- `requests` library has been replaced with `httpx`\n\n## [1.3.0] - 2020-04-25\n\n### Added\n\n- new properties added to AvroSchema: raw_schema, flat_schema and expanded_schema\n- documentation updated\n\n### Fixed\n\n## [1.2.10] - 2020-04-20\n\n- bad import check fixed causing faust crash\n\n### Fixed\n\n## [1.2.9] - 2020-04-20\n\n- fixed `Importing MessageSerializer` without Faust is Broken [#47](https://github.com/marcosschroh/python-schema-registry-client/issues/47)\n\n### Fixed\n\n## [1.2.8] - 2020-04-18\n\n- fix Base URL was overwritten [#46](https://github.com/marcosschroh/python-schema-registry-client/issues/46)\n\n### Changed\n\n## [1.2.7] - 2020-03-29\n\n- `faust serializer` updated in order to be compatible with latest Faust version\n\n### Fixed\n\n## [1.2.6] - 2020-03-13\n\n- Incorrect message on get_subjects fixed\n\n### Changed\n\n## [1.2.5] - 2020-02-19\n\n- is_key was removed from serializer, meaning that the subject itself will have to express wheter the schema is key or not. Related to [#40](https://github.com/marcosschroh/python-schema-registry-client/issues/40)\n- Requirements updated to latest versions: fastavro and requests\n\n### Changed\n\n## [1.2.4] - 2019-11-16\n\n- Faust requirement updated to <= 1.9.0\n\n### Fixed\n\n## [1.2.3] - 2019-11-02\n\n- fix force `fastavro` to parse always the schemas in order to avoid errors when a process B get the schema from the server that was previously processed by process A.\n\n### Changed\n\n## [1.2.2] - 2019-10-26\n\n- requirements updated\n\n### Changed\n\n## [1.2.1] - 2019-07-23\n\n- Typing added (mypy)\n\n### Added\n\n## [1.2.0] - 2019-07-22\n\n- Missing endpoints added:\n  - GET  /subjects\n  - GET /subjects/{subject}/versions\n  - DELETE /subjects/{subject}/versions/{version}\n\n### Added\n\n## [1.1.0] - 2019-07-19\n\n- Urls manager added to have more control over which endpoint the client is using\n\n### Added\n\n## [1.0.0] - 2019-07-17\n\n- Production ready\n- Move to FastAvro\n- Dependency `avro-python3` removed\n- Support for `logicalTypes` added\n- `AvroSchema` class added to manage schema parse and hasing\n- Tests added\n- Faker lib added to create fixtures\n- Documentation updated\n\n### Fixed\n\n## [0.3.1] - 2019-07-17\n\n- Error mapping proxy fixed when try to register a schema that contains `logicalTypes`\n\n### Added\n\n## [0.3.0] - 2019-07-11\n\n- Faust Serializer has been added\n- Optional Faust requirement added\n\n### Changed\n\n## [0.2.5] - 2019-06-10\n\n- Documentation about `MessageSerializer` and `Faust` Integration updated\n\n### Changed\n\n## [0.2.4] - 2019-06-05\n\n- Missing Compatibility levels added.\n- `ClienError` added to the documentation\n- Tests refactored\n\n### Fixed\n\n## [0.2.3] - 2019-05-31\n\n- HTTP code and `server_traceback` added to the `ClientError` when Schema Server returns a not success when a schema compatibility check is requested.\n\n### Fixed\n\n## [0.2.2] - 2019-05-29\n\n- Missing tests added.\n- Bug in register fixed.\n\n### Changed\n\n## [0.2.1] - 2019-05-27\n\n- Documentation updated.\n- Missing Python syntax highlighted added.\n\n### Added\n\n## [0.2.0] - 2019-05-23\n\n- Now all the tests are run against a `Schema Registry Server` and not a mock server. This allows us to be aware of when the server changes. The requirements to run the tests are Docker and `Docker Compose`\n\n### Changed\n\n## [0.1.1] - 2019-05-22\n\n- Http `Client` now is a subclass of `request.Session`\n\n### Added\n\n## [0.1.0] - 2019-05-22\n\n- Now is possible to inisialize SchemaRegistryClient with custom headers. This headers will be included on every requests.\n\n### Changed\n\n## [0.0.3] - 2019-05-22\n\n- small twaeks\n\n### Changed\n\n## [0.0.2] - 2019-05-19\n\n- First release\n- Http `Client` added.\n- `MessageSerializer` added.\n- tests added\n- Documentation added\n"
  },
  {
    "path": "Dockerfile",
    "content": "ARG PYTHON_VERSION\n\nFROM python:${PYTHON_VERSION}\nARG POETRY_VERSION=1.4.2\n\nRUN apt-get update && apt-get install -y netcat git && apt-get autoremove -y\nRUN pip install \"poetry==${POETRY_VERSION}\"\n\n# Create unprivileged user\nRUN adduser --disabled-password --gecos '' myuser\n\nCOPY wait_for_services.sh .\nCOPY /scripts scripts/\nCOPY setup.cfg .\nCOPY README.md .\nCOPY pyproject.toml .\nCOPY poetry.lock .\nCOPY .git .\nCOPY /schema_registry /schema_registry\nCOPY /tests /tests\n\n# create a file in order to have coverage\nRUN touch .coverage\nRUN poetry install --no-interaction --no-ansi --all-extras\n\nENTRYPOINT [\"./wait_for_services.sh\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\nCopyright (c) 2023, Marcos Schroh\n\nPermission 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:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE 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.\n"
  },
  {
    "path": "README.md",
    "content": "# Python Rest Client Schema Registry\n\n[![Python package](https://github.com/marcosschroh/python-schema-registry-client/actions/workflows/python-package.yml/badge.svg)](https://github.com/marcosschroh/python-schema-registry-client/actions/workflows/python-package.yml)\n[![GitHub license](https://img.shields.io/github/license/marcosschroh/python-schema-registry-client.svg)](https://github.com/marcosschroh/python-schema-registry-client/blob/master/LICENSE)\n[![codecov](https://codecov.io/gh/marcosschroh/python-schema-registry-client/branch/master/graph/badge.svg)](https://codecov.io/gh/marcosschroh/python-schema-registry-client)\n[![Python Version](https://img.shields.io/badge/python-3.8+-blue.svg)](https://img.shields.io/badge/python-3.8+-blue.svg)\n\nPython Rest Client to interact against [schema-registry](https://docs.confluent.io/current/schema-registry/index.html) confluent server to manage [Avro](https://docs.oracle.com/database/nosql-12.1.3.1/GettingStartedGuide/avroschemas.html) and [JSON](https://json-schema.org/) schemas resources.\n\n## Requirements\n\npython 3.8+\n\n## Installation\n\n```bash\npip install python-schema-registry-client\n```\n\nIf you want the `Faust` functionality:\n\n```bash\npip install python-schema-registry-client[faust]\n```\n\nNote that this will automatically add a dependency on the [faust-streaming](https://github.com/faust-streaming/faust) fork of faust. If you want to use the\nold faust version, simply install it manually and then install `python-schema-registry-client` without the `faust` extra enabled, the functionality will\nbe the same.\n\n## Client API, Serializer, Faust Integration and Schema Server description\n\n**Documentation**: [https://marcosschroh.github.io/python-schema-registry-client.io](https://marcosschroh.github.io/python-schema-registry-client)\n\n## Avro Schema Usage\n\n```python\nfrom schema_registry.client import SchemaRegistryClient, schema\n\nclient = SchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\ndeployment_schema = {\n    \"type\": \"record\",\n    \"namespace\": \"com.kubertenes\",\n    \"name\": \"AvroDeployment\",\n    \"fields\": [\n        {\"name\": \"image\", \"type\": \"string\"},\n        {\"name\": \"replicas\", \"type\": \"int\"},\n        {\"name\": \"port\", \"type\": \"int\"},\n    ],\n}\n\navro_schema = schema.AvroSchema(deployment_schema)\n\nschema_id = client.register(\"test-deployment\", avro_schema)\n```\n\nor async\n\n```python\nfrom schema_registry.client import AsyncSchemaRegistryClient, schema\n\nasync_client = AsyncSchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\ndeployment_schema = {\n    \"type\": \"record\",\n    \"namespace\": \"com.kubertenes\",\n    \"name\": \"AvroDeployment\",\n    \"fields\": [\n        {\"name\": \"image\", \"type\": \"string\"},\n        {\"name\": \"replicas\", \"type\": \"int\"},\n        {\"name\": \"port\", \"type\": \"int\"},\n    ],\n}\n\navro_schema = schema.AvroSchema(deployment_schema)\n\nschema_id = await async_client.register(\"test-deployment\", avro_schema)\n```\n\n## JSON Schema Usage\n\n```python\nfrom schema_registry.client import SchemaRegistryClient, schema\n\nclient = SchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\ndeployment_schema = {\n    \"definitions\" : {\n        \"JsonDeployment\" : {\n            \"type\" : \"object\",\n            \"required\" : [\"image\", \"replicas\", \"port\"],\n            \"properties\" : {\n                \"image\" :       {\"type\" : \"string\"},\n                \"replicas\" :    {\"type\" : \"integer\"},\n                \"port\" :        {\"type\" : \"integer\"}\n            }\n        }\n    },\n    \"$ref\" : \"#/definitions/JsonDeployment\"\n}\n\njson_schema = schema.JsonSchema(deployment_schema)\n\nschema_id = client.register(\"test-deployment\", json_schema)\n```\n\nor async\n\n```python\nfrom schema_registry.client import AsyncSchemaRegistryClient, schema\n\nasync_client = AsyncSchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\ndeployment_schema = {\n    \"definitions\" : {\n        \"JsonDeployment\" : {\n            \"type\" : \"object\",\n            \"required\" : [\"image\", \"replicas\", \"port\"],\n            \"properties\" : {\n                \"image\" :       {\"type\" : \"string\"},\n                \"replicas\" :    {\"type\" : \"integer\"},\n                \"port\" :        {\"type\" : \"integer\"}\n            }\n        }\n    },\n    \"$ref\" : \"#/definitions/JsonDeployment\"\n}\n\njson_schema = schema.JsonSchema(deployment_schema)\n\nschema_id = await async_client.register(\"test-deployment\", json_schema)\n```\n\n## Usage with dataclasses-avroschema for avro schemas\n\nYou can generate the `avro schema` directely from a python class using [dataclasses-avroschema](https://github.com/marcosschroh/dataclasses-avroschema)\nand use it in the API for `register schemas`, `check versions` and `test compatibility`:\n\n```python\nimport dataclasses\n\nfrom dataclasses_avroschema import AvroModel, types\n\nfrom schema_registry.client import SchemaRegistryClient\n\nclient = SchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\n\n@dataclasses.dataclass\nclass UserAdvance(AvroModel):\n    name: str\n    age: int\n    pets: typing.List[str] = dataclasses.field(default_factory=lambda: [\"dog\", \"cat\"])\n    accounts: typing.Dict[str, int] = dataclasses.field(default_factory=lambda: {\"key\": 1})\n    has_car: bool = False\n    favorite_colors: types.Enum = types.Enum([\"BLUE\", \"YELLOW\", \"GREEN\"], default=\"BLUE\")\n    country: str = \"Argentina\"\n    address: str = None\n\n# register the schema\nschema_id = client.register(subject, UserAdvance.avro_schema())\n\nprint(schema_id)\n# >>> 12\n\nresult = client.check_version(subject, UserAdvance.avro_schema())\nprint(result)\n# >>> SchemaVersion(subject='dataclasses-avroschema-subject-2', schema_id=12, schema=1, version={\"type\":\"record\" ...')\n\ncompatibility = client.test_compatibility(subject, UserAdvance.avro_schema())\nprint(compatibility)\n\n# >>> True\n```\n\n## Usage with pydantic for json schemas\n\nYou can generate the json schema directely from a python class using pydantic and use it in the API for register schemas, check versions and test compatibility:\n\n```python\nimport typing\n\nfrom enum import Enum\n\nfrom pydantic import BaseModel\n\nfrom schema_registry.client import SchemaRegistryClient\n\nclient = SchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\nclass ColorEnum(str, Enum):\n  BLUE = \"BLUE\"\n  YELLOW = \"YELLOW\"\n  GREEN = \"GREEN\"\n\n\nclass UserAdvance(BaseModel):\n    name: str\n    age: int\n    pets: typing.List[str] = [\"dog\", \"cat\"]\n    accounts: typing.Dict[str, int] = {\"key\": 1}\n    has_car: bool = False\n    favorite_colors: ColorEnum = ColorEnum.BLUE\n    country: str = \"Argentina\"\n    address: str = None\n\n# register the schema\nschema_id = client.register(subject, UserAdvance.model_json_schema(), schema_type=\"JSON\")\n\nprint(schema_id)\n# >>> 12\n\nresult = client.check_version(subject, UserAdvance.model_json_schema(), schema_type=\"JSON\")\nprint(result)\n# >>> SchemaVersion(subject='pydantic-jsonschema-subject', schema_id=12, schema=1, version=<schema_registry.client.schema.JsonSchema object at 0x7f40354550a0>)\n\ncompatibility = client.test_compatibility(subject, UserAdvance.model_json_schema(), schema_type=\"JSON\")\nprint(compatibility)\n\n# >>> True\n```\n\n## Serializers\n\nYou can use `AvroMessageSerializer` to encode/decode messages in `avro`\n\n```python\nfrom schema_registry.client import SchemaRegistryClient, schema\nfrom schema_registry.serializers import AvroMessageSerializer\n\n\nclient = SchemaRegistryClient(\"http://127.0.0.1:8081\")\navro_message_serializer = AvroMessageSerializer(client)\n\navro_user_schema = schema.AvroSchema({\n    \"type\": \"record\",\n    \"namespace\": \"com.example\",\n    \"name\": \"AvroUsers\",\n    \"fields\": [\n        {\"name\": \"first_name\", \"type\": \"string\"},\n        {\"name\": \"last_name\", \"type\": \"string\"},\n        {\"name\": \"age\", \"type\": \"int\"},\n\n    ],\n})\n\n# We want to encode the user_record with avro_user_schema\nuser_record = {\n    \"first_name\": \"my_first_name\",\n    \"last_name\": \"my_last_name\",\n    \"age\": 20,\n}\n\n# Encode the record\nmessage_encoded = avro_message_serializer.encode_record_with_schema(\n    \"user\", avro_user_schema, user_record)\n\nprint(message_encoded)\n# >>> b'\\x00\\x00\\x00\\x00\\x01\\x1amy_first_name\\x18my_last_name('\n```\n\nor with `json schemas`\n\n```python\nfrom schema_registry.client import SchemaRegistryClient, schema\nfrom schema_registry.serializers import JsonMessageSerializer\n\n\nclient = SchemaRegistryClient(\"http://127.0.0.1:8081\")\njson_message_serializer = JsonMessageSerializer(client)\n\njson_schema = schema.JsonSchema({\n  \"definitions\" : {\n    \"record:python.test.basic.basic\" : {\n      \"description\" : \"basic schema for tests\",\n      \"type\" : \"object\",\n      \"required\" : [ \"number\", \"name\" ],\n      \"properties\" : {\n        \"number\" : {\n          \"oneOf\" : [ {\n            \"type\" : \"integer\"\n          }, {\n            \"type\" : \"null\"\n          } ]\n        },\n        \"name\" : {\n          \"oneOf\" : [ {\n            \"type\" : \"string\"\n          } ]\n        }\n      }\n    }\n  },\n  \"$ref\" : \"#/definitions/record:python.test.basic.basic\"\n})\n\n# Encode the record\nbasic_record = {\n    \"number\": 10,\n    \"name\": \"a_name\",\n}\n\nmessage_encoded = json_message_serializer.encode_record_with_schema(\n    \"basic\", json_schema, basic_record)\n\nprint(message_encoded)\n# >>> b'\\x00\\x00\\x00\\x00\\x02{\"number\": 10, \"name\": \"a_name\"}'\n```\n\n## When use this library\n\nUsually, we have a situation like this:\n\n![Confluent Architecture](docs/img/confluent_architecture.png)\n\nSo, our producers/consumers have to serialize/deserialize messages every time that they send/receive from Kafka topics. In this picture, we can imagine a `Faust` application receiving messages (encoded with an Avro schema) and we want to deserialize them, so we can ask the `schema server` to do that for us. In this scenario, the `MessageSerializer` is perfect.\n\nAlso, could be a use case that we would like to have an Application only to administrate `Avro Schemas` (register, update compatibilities, delete old schemas, etc.), so the `SchemaRegistryClient` is perfect.\n\n## Development\n\n[Poetry](https://python-poetry.org/docs/) is needed to install the dependencies and develope locally\n\n1. Install dependencies: `poetry install --all-extras`\n2. Code linting: `./scripts/format`\n3. Run tests: `./scripts/test`\n\nFor commit messages we use [commitizen](https://commitizen-tools.github.io/commitizen/) in order to standardize a way of committing rules\n\n*Note*: The tests are run against the `Schema Server` using `docker compose`, so you will need\n`Docker` and `Docker Compose` installed.\n\nIn a terminal run `docker-compose up`. Then in a different terminal run the tests:\n\n```bash\n./scripts/test\n```\n\nAll additional args will be passed to pytest, for example:\n\n```bash\n./scripts/test ./tests/client/\n```\n\n### Tests usind the python shell\n\nTo perform tests using the python shell you can run the project using `docker-compose`.\n\n1. Execute `docker-compose up`. Then, the `schema registry server` will run on `http://127.0.0.1:8081`, then you can interact against it using the `SchemaRegistryClient`:\n1. Use the python interpreter (get a python shell typing `python` in your command line)\n1. Play with the `schema server`\n\n```python\nfrom schema_registry.client import SchemaRegistryClient, schema\n\nclient = SchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\n# do some operations with the client...\ndeployment_schema = {\n    \"type\": \"record\",\n    \"namespace\": \"com.kubertenes\",\n    \"name\": \"AvroDeployment\",\n    \"fields\": [\n        {\"name\": \"image\", \"type\": \"string\"},\n        {\"name\": \"replicas\", \"type\": \"int\"},\n        {\"name\": \"port\", \"type\": \"int\"},\n    ],\n}\n\navro_schema = schema.AvroSchema(deployment_schema)\nclient.register(\"test-deployment\", avro_schema)\n# >>>> Out[5]: 1\n```\n\nThen, you can check the schema using your browser going to the url `http://127.0.0.1:8081/schemas/ids/1`\n"
  },
  {
    "path": "docker-compose.yaml",
    "content": "version: '3.5'\nservices:\n  zookeeper:\n    image: \"confluentinc/cp-zookeeper\"\n    hostname: zookeeper\n    ports:\n      - 32181:32181\n    environment:\n      - ZOOKEEPER_CLIENT_PORT=32181\n  kafka:\n    # pinned due to https://github.com/confluentinc/kafka-images/issues/127\n    image: confluentinc/cp-kafka:7.0.0\n    hostname: kafka\n    container_name: kafka\n    ports:\n    - 9092:9092\n    - 29092:29092\n    depends_on:\n    - zookeeper\n    environment:\n      - KAFKA_ZOOKEEPER_CONNECT=zookeeper:32181\n      - KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1\n      - KAFKAD_LISTENERS=PLAINTEXT_HOST://localhost:29092,PLAINTEXT://kafka:9092\n      - KAFKA_ADVERTISED_LISTENERS=PLAINTEXT_HOST://localhost:29092,PLAINTEXT://kafka:9092\n      - KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT\n      - KAFKA_BROKER_ID=1\n  schema-registry-server:\n    image: confluentinc/cp-schema-registry\n    hostname: schema-registry-server\n    container_name: schema-registry-server\n    depends_on:\n      - kafka\n      - zookeeper\n    ports:\n      - 8081:8081\n    environment:\n      - SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS=kafka:9092\n      - SCHEMA_REGISTRY_KAFKASTORE_CONNECTION_URL=zookeeper:32181\n      - SCHEMA_REGISTRY_HOST_NAME=schema-registry-server\n      - SCHEMA_REGISTRY_DEBUG=true\n      - SCHEMA_REGISTRY_ACCESS_CONTROL_ALLOW_ORIGIN=*\n\n"
  },
  {
    "path": "docs/client.md",
    "content": "# Schema Registry Client\n\nThe `Schema Registry Client` consumes the API exposed by the `schema-registry` to operate resources that are `avro` and `json` schemas.\n\nYou probably won't use this but is good to know that exists. The `MessageSerializer` is whom interact with the `SchemaRegistryClient`\n\n::: schema_registry.client.SchemaRegistryClient\n    options:\n        show_root_heading: true\n        docstring_section_style: table\n        show_signature_annotations: false\n\n::: schema_registry.client.AsyncSchemaRegistryClient\n    options:\n        show_root_heading: true\n        docstring_section_style: table\n        show_signature_annotations: false\n\n## Auth\n\nCredentials can be supplied in `two` different ways: using the `url` or the `schema_registry.client.Auth`.\n\n```python title=\"Credentials using Auth\"\nfrom schema_registry.client import SchemaRegistryClient, Auth\n\nSchemaRegistryClient(\n    url=\"https://user_url:secret_url@127.0.0.1:65534\",\n    auth=Auth(username=\"secret-user\", password=\"secret\"),\n)\n```\n\n```python title=\"Credentials using the url\"\nfrom schema_registry.client import SchemaRegistryClient\n\nusername=\"secret-username\"\npassword=\"secret\"\n\nSchemaRegistryClient({\"url\": f\"https://{username}:{password}@127.0.0.1:65534\"})\n```\n\n!!! note\n    This auth methods are the same for `AsyncSchemaRegistryClient`\n"
  },
  {
    "path": "docs/exceptions.md",
    "content": "# ClientError\n\nAn instance of ClientError is returned when an error occurs.\n\n```python\nclass ClientError(Exception):\n    \"\"\" Error thrown by Schema Registry clients \"\"\"\n\n    def __init__(self, message, http_code=None, server_traceback=None):\n        self.message = message\n        self.server_traceback = server_traceback\n        self.http_code = http_code\n        super(ClientError, self).__init__(self.__str__())\n\n    def __repr__(self):\n        return f\"ClientError(error={self.message})\"\n\n    def __str__(self):\n        return self.message\n```\n\nFor example:\n\n```python\ntry:\n    # Try to get the compatibility level of a subject that does not exist\n    # The Schema Registry Server returns 404\n    compatibility = client.get_compatibility(\"subject-does-not-exists\")\nexcept Exception as error:\n    # Print 404\n    print(error.http_code)\n```\n"
  },
  {
    "path": "docs/faust.md",
    "content": "# How to use it with Faust\n\nThis section describe how integrate this library with [Faust](https://faust.readthedocs.io/en/latest/)\n\n## Schemas, Custom Codecs and Serializers\n\nBecause we want to be sure that the message that we encode are valid, we can use [Avro](https://docs.oracle.com/database/nosql-12.1.3.1/GettingStartedGuide/avroschemas.html) or [JSON](https://json-schema.org/) schemas. Also, [Introduction to Schemas in Apache Kafka with the Confluent Schema Registry](https://medium.com/@stephane.maarek/introduction-to-schemas-in-apache-kafka-with-the-confluent-schema-registry-3bf55e401321) is a good post to start with `schemas`.\nAvro and JSON can be used to define the data schema for a record's value. This schema describes the fields allowed in the value, along with their data types.\n\nIn order to use `avro schemas` or `json schemas` with `Faust`, we need to define a custom codec and a custom serializer able to talk with the `schema-registry`, and to do that, we will use the `MessageSerializer`.\n\nFor serializing `avro schemas` we should use the `FaustSerializer`. For serializing `json schemas` we should use the `FaustJsonSerializer`.\n\nFor our demonstration, let's imagine that we have the following `avro schema`:\n\n```json\n{\n    \"type\": \"record\",\n    \"namespace\": \"com.example\",\n    \"name\": \"AvroUsers\",\n    \"fields\": [\n        {\"name\": \"first_name\", \"type\": \"string\"},\n        {\"name\": \"last_name\", \"type\": \"string\"}\n    ]\n}\n```\n\nLet's register the custom `codec`\n\n```python title=\"Trivial Usage\"\n# codecs.codec.py\nfrom schema_registry.client import SchemaRegistryClient, schema\nfrom schema_registry.serializers.faust import FaustSerializer\n\n# create an instance of the `SchemaRegistryClient`\nclient = SchemaRegistryClient(url=settings.SCHEMA_REGISTRY_URL)\n\n# schema that we want to use. For this example we\n# are using a dict, but this schema could be located in a file called avro_user_schema.avsc\navro_user_schema = schema.AvroSchema({\n    \"type\": \"record\",\n    \"namespace\": \"com.example\",\n    \"name\": \"AvroUsers\",\n    \"fields\": [\n        {\"name\": \"first_name\", \"type\": \"string\"},\n        {\"name\": \"last_name\", \"type\": \"string\"}\n    ]\n})\n\navro_user_serializer = FaustSerializer(client, \"users\", avro_user_schema)\n\n# function used to register the codec\ndef avro_user_codec():\n    return avro_user_serializer\n```\n\nand add in `setup.py` the following code in order to tell faust where to find the custom codecs.\n\n```python title=\"setup.py\"\nsetup(\n    ...\n    entry_points={\n        'console_scripts': [\n            'example = example.app:main',\n        ],\n        'faust.codecs': [\n            'avro_users = example.codecs.avro:avro_user_codec',\n        ],\n    },\n)\n```\n\nor if you are using `poetry` an a [`pyproject.toml`](https://python-poetry.org/docs/pyproject/) you can add in `pyproject.toml` the following code to tell faust where to find the custom codecs:\n\n```toml title=\"pyproject.toml\"\n[tool.poetry.scripts]\nexample = \"example.app:main\"\n\n[tool.poetry.plugins.\"faust.codecs\"]\navro_users = \"example.codecs.avro:avro_user_codec\"\n```\n\nNow the final step is to integrate the faust model with the AvroSerializer.\n\n```python title=\"user.models.py\"\nimport faust\n\n\nclass UserModel(faust.Record, serializer='avro_users'):\n    first_name: str\n    last_name: str\n```\n\nNow our application is able to send and receive message using arvo schemas!!!! :-)\n\n```python title=\"application\"\nimport logging\n\nfrom your_project.app import app\nfrom .codecs.codec import avro_user_serializer\nfrom .models import UserModel\n\nusers_topic = app.topic('avro_users', partitions=1, value_type=UserModel)\n\nlogger = logging.getLogger(__name__)\n\n\n@app.agent(users_topic)\nasync def users(users):\n    async for user in users:\n        logger.info(\"Event received in topic avro_users\")\n        logger.info(f\"First Name: {user.first_name}, last name {user.last_name}\")\n\n\n@app.timer(5.0, on_leader=True)\nasync def publish_users():\n    logger.info('PUBLISHING ON LEADER FOR USERS APP!')\n    user = {\"first_name\": \"foo\", \"last_name\": \"bar\"}\n    await users.send(value=user, value_serializer=avro_user_serializer)\n```\n\nThe full example is [here](https://github.com/marcosschroh/faust-docker-compose-example/blob/master/faust-project/example/codecs/avro.py)\n\n### Usage with dataclasses-avroschema for avro schemas\n\nYou can also use this funcionality with [dataclasses-avroschema](https://github.com/marcosschroh/dataclasses-avroschema) and you won't have to provide the avro schema.\nThe only thing that you need to do is add the `AvroModel` class and use its methods:\n\n```python title=\"user.models.py\"\nimport faust\n\nfrom dataclasses_avroschema.faust import AvroRecord\n\n\nclass UserModel(AvroRecord, serializer='avro_users'):\n    first_name: str\n    last_name: str\n\n\n# codecs.codec.py\nfrom schema_registry.client import SchemaRegistryClient, schema\nfrom schema_registry.serializers.faust import FaustSerializer\n\nfrom users.models import UserModel\n\n# create an instance of the `SchemaRegistryClient`\nclient = SchemaRegistryClient(url=settings.SCHEMA_REGISTRY_URL)\n\navro_user_serializer = FaustSerializer(client, \"users\", UserModel.avro_schema())  # usign the method avro_schema to get the avro schema representation\n\n# function used to register the codec\ndef avro_user_codec():\n    return avro_user_serializer\n```\n\n### Usage with pydantic for json schemas\nYou can also use this funcionality with [dataclasses-pydantic](https://github.com/samuelcolvin/pydantic) and you won't have to provide the json schema.\nThe only thing that you need to do is add the `BaseModel` class and use its methods:\n\n```python title=\"users.models.py\"\nimport faust\n\nfrom pydantic import BaseModel\n\n\nclass UserModel(faust.Record, BaseModel, serializer='json_users'):\n    first_name: str\n    last_name: str\n\n\n# codecs.codec.py\nfrom schema_registry.client import SchemaRegistryClient, schema\nfrom schema_registry.serializers.faust import FaustJsonSerializer\n\nfrom users.models import UserModel\n\n# create an instance of the `SchemaRegistryClient`\nclient = SchemaRegistryClient(url=settings.SCHEMA_REGISTRY_URL)\n\njson_user_serializer = FaustJsonSerializer(client, \"users\", UserModel.model_json_schema())  # using the method model_json_schema to get the json schema representation\n\n# function used to register the codec\ndef json_user_codec():\n    return json_user_serializer\n```\n"
  },
  {
    "path": "docs/index.md",
    "content": "# Python Rest Client Schema Registry\n\n[![Python package](https://github.com/marcosschroh/python-schema-registry-client/actions/workflows/python-package.yml/badge.svg)](https://github.com/marcosschroh/python-schema-registry-client/actions/workflows/python-package.yml)\n[![GitHub license](https://img.shields.io/github/license/marcosschroh/python-schema-registry-client.svg)](https://github.com/marcosschroh/python-schema-registry-client/blob/master/LICENSE)\n[![codecov](https://codecov.io/gh/marcosschroh/python-schema-registry-client/branch/master/graph/badge.svg)](https://codecov.io/gh/marcosschroh/python-schema-registry-client)\n[![Python Version](https://img.shields.io/badge/python-3.8+-blue.svg)](https://img.shields.io/badge/python-3.8+-blue.svg)\n\nPython Rest Client to interact against [schema-registry](https://docs.confluent.io/current/schema-registry/index.html) confluent server to manage [Avro](https://docs.oracle.com/database/nosql-12.1.3.1/GettingStartedGuide/avroschemas.html) and [JSON](https://json-schema.org/) schemas resources.\n\n## Requirements\n\npython 3.8+\n\n## Installation\n\n```bash\npip install python-schema-registry-client\n```\n\nIf you want the `Faust` functionality:\n\n```bash\npip install python-schema-registry-client[faust]\n```\n\n## Usage\n\n```python title=\"Trival Usage with avro\"\nfrom schema_registry.client import SchemaRegistryClient, schema\n\nclient = SchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\ndeployment_schema = {\n    \"type\": \"record\",\n    \"namespace\": \"com.kubertenes\",\n    \"name\": \"AvroDeployment\",\n    \"fields\": [\n        {\"name\": \"image\", \"type\": \"string\"},\n        {\"name\": \"replicas\", \"type\": \"int\"},\n        {\"name\": \"port\", \"type\": \"int\"},\n    ],\n}\n\navro_schema = schema.AvroSchema(deployment_schema)\n\nschema_id = client.register(\"test-deployment\", avro_schema)\n```\n\n```python title=\"Trival Usage Async with avro\"\nfrom schema_registry.client import AsyncSchemaRegistryClient, schema\n\nasync_client = AsyncSchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\ndeployment_schema = {\n    \"type\": \"record\",\n    \"namespace\": \"com.kubertenes\",\n    \"name\": \"AvroDeployment\",\n    \"fields\": [\n        {\"name\": \"image\", \"type\": \"string\"},\n        {\"name\": \"replicas\", \"type\": \"int\"},\n        {\"name\": \"port\", \"type\": \"int\"},\n    ],\n}\n\navro_schema = schema.AvroSchema(deployment_schema)\n\nschema_id = await async_client.register(\"test-deployment\", avro_schema)\n```\n\n```python title=\"Trival Usage with json schemas\"\nfrom schema_registry.client import SchemaRegistryClient, schema\n\nclient = SchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\ndeployment_schema = {\n    \"definitions\" : {\n        \"JsonDeployment\" : {\n            \"type\" : \"object\",\n            \"required\" : [\"image\", \"replicas\", \"port\"],\n            \"properties\" : {\n                \"image\" :       {\"type\" : \"string\"},\n                \"replicas\" :    {\"type\" : \"integer\"},\n                \"port\" :        {\"type\" : \"integer\"}\n            }\n        }\n    },\n    \"$ref\" : \"#/definitions/JsonDeployment\"\n}\n\njson_schema = schema.JsonSchema(deployment_schema)\n\nschema_id = client.register(\"test-deployment\", json_schema)\n```\n\n```python title=\"Trival Usage Asynv with json schemas\"\nfrom schema_registry.client import AsyncSchemaRegistryClient, schema\n\nasync_client = AsyncSchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\ndeployment_schema = {\n    \"definitions\" : {\n        \"JsonDeployment\" : {\n            \"type\" : \"object\",\n            \"required\" : [\"image\", \"replicas\", \"port\"],\n            \"properties\" : {\n                \"image\" :       {\"type\" : \"string\"},\n                \"replicas\" :    {\"type\" : \"integer\"},\n                \"port\" :        {\"type\" : \"integer\"}\n            }\n        }\n    },\n    \"$ref\" : \"#/definitions/JsonDeployment\"\n}\n\njson_schema = schema.JsonSchema(deployment_schema)\n\nschema_id = await async_client.register(\"test-deployment\", json_schema)\n```\n\n## Usage with dataclasses-avroschema\n\nYou can generate the `avro schema` and `json schemas` directely from a python class using [dataclasses-avroschema](https://github.com/marcosschroh/dataclasses-avroschema)\nand use it in the API for `register schemas`, `check versions` and `test compatibility`:\n\n```python title=\"Trival Usage with dataclasses-avroschema\"\nimport dataclasses\nfrom enum import Enum\nimport typing\n\nfrom dataclasses_avroschema import AvroModel\nfrom schema_registry.client import SchemaRegistryClient\n\nclient = SchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\n\nclass ColorEnum(str, Enum):\n    BLUE = \"BLUE\"\n    YELLOW = \"YELLOW\"\n    GREEN = \"GREEN\"\n\n\n@dataclasses.dataclass\nclass UserAdvance(AvroModel):\n    name: str\n    age: int\n    pets: typing.List[str] = dataclasses.field(default_factory=lambda: [\"dog\", \"cat\"])\n    accounts: typing.Dict[str, int] = dataclasses.field(default_factory=lambda: {\"key\": 1})\n    has_car: bool = False\n    favorite_colors: ColorEnum = ColorEnum.BLUE\n    country: str = \"Argentina\"\n    address: str = None\n\nsubject = \"subject\"\n\n# register the schema\nschema_id = client.register(subject, UserAdvance.avro_schema())\n\nprint(schema_id)\n# >>> 12\n\nresult = client.check_version(subject, UserAdvance.avro_schema())\nprint(result)\n# >>> SchemaVersion(subject='dataclasses-avroschema-subject-2', schema_id=12, schema=1, version={\"type\":\"record\" ...')\n\ncompatible = client.test_compatibility(subject, UserAdvance.avro_schema())\nprint(compatible)\n# >>> True\n```\n\n!!! note\n    You can generate json schemas with `dataclasses-avroschema` adding the *[pydantic batteries](https://marcosschroh.github.io/dataclasses-avroschema/pydantic/)*\n\n## Usage with pydantic for json schemas\n\nYou can generate the json schema directely from a python class using pydantic and use it in the API for register schemas, check versions and test compatibility:\n\n```python title=\"Trival Usage with pydantic\"\nimport typing\nfrom enum import Enum\n\nfrom pydantic import BaseModel\nfrom schema_registry.client import SchemaRegistryClient\n\nclient = SchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\nclass ColorEnum(str, Enum):\n  BLUE = \"BLUE\"\n  YELLOW = \"YELLOW\"\n  GREEN = \"GREEN\"\n\n\nclass UserAdvance(BaseModel):\n    name: str\n    age: int\n    pets: typing.List[str] = [\"dog\", \"cat\"]\n    accounts: typing.Dict[str, int] = {\"key\": 1}\n    has_car: bool = False\n    favorite_colors: ColorEnum = ColorEnum.BLUE\n    country: str = \"Argentina\"\n    address: str = None\n\nsubject = \"subject\"\n\n# register the schema\nschema_id = client.register(subject, UserAdvance.model_json_schema(), schema_type=\"JSON\")\n\nprint(schema_id)\n# >>> 12\n\nresult = client.check_version(subject, UserAdvance.model_json_schema(), schema_type=\"JSON\")\nprint(result)\n# >>> SchemaVersion(subject='pydantic-jsonschema-subject', schema_id=12, schema=1, version=<schema_registry.client.schema.JsonSchema object at 0x7f40354550a0>)\n\ncompatible = client.test_compatibility(subject, UserAdvance.model_json_schema(), schema_type=\"JSON\")\nprint(compatible)\n# >>> True\n```\n\n## When use this library\n\nUsually, we have a situacion like this:\n\n![Confluent Architecture](img/confluent_architecture.png)\n\nSo, our producers/consumers have to serialize/deserialize messages every time that they send/receive from Kafka topics. In this picture, we can imagine a `Faust` application receiving messages (encoded with an Avro schema) and we want to deserialize them, so we can ask the `schema server` to do that for us. In this scenario, the `MessageSerializer` is perfect.\n\nAlso, could be a use case that we would like to have an Application only to administrate `Avro Schemas` (register, update compatibilities, delete old schemas, etc.), so the `SchemaRegistryClient` is perfect.\n\n## Development\n\n[Poetry](https://python-poetry.org/docs/) is needed to install the dependencies and develope locally\n\n1. Install dependencies: `poetry install --all-extras`\n2. Code linting: `./scripts/format`\n3. Run tests: `./scripts/test`\n\nFor commit messages we use [commitizen](https://commitizen-tools.github.io/commitizen/) in order to standardize a way of committing rules\n\n*Note*: The tests are run against the `Schema Server` using `docker compose`, so you will need\n`Docker` and `Docker Compose` installed.\n\nIn a terminal run `docker-compose up`. Then in a different terminal run the tests:\n\n```bash\n./scripts/test\n```\n\nAll additional args will be passed to pytest, for example:\n\n```bash\n./scripts/test ./tests/client/\n```\n\n### Tests usind the python shell\n\nTo perform tests using the python shell you can run the project using `docker-compose`.\n\n1. Build: `docker-compose build --build-arg PYTHON_VERSION=$PYTHON_VERSION`\n2. Execute `docker-compose up`. Then, the `schema registry server` will run on `http://127.0.0.1:8081`, then you can interact against it using the `SchemaRegistryClient`:\n3. Use the python interpreter (get a python shell typing `python` in your command line)\n4. Play with the `schema server`\n\n```python\nfrom schema_registry.client import SchemaRegistryClient, schema\n\nclient = SchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\n# do some operations with the client...\ndeployment_schema = {\n    \"type\": \"record\",\n    \"namespace\": \"com.kubertenes\",\n    \"name\": \"AvroDeployment\",\n    \"fields\": [\n        {\"name\": \"image\", \"type\": \"string\"},\n        {\"name\": \"replicas\", \"type\": \"int\"},\n        {\"name\": \"port\", \"type\": \"int\"},\n    ],\n}\n\navro_schema = schema.AvroSchema(deployment_schema)\nclient.register(\"test-deployment\", avro_schema)\n# >>>> Out[5]: 1\n```\n\nThen, you can check the schema using your browser going to the url `http://127.0.0.1:8081/schemas/ids/1`\n"
  },
  {
    "path": "docs/schemaregistry_server.md",
    "content": "# Schema Registry Server\n\nThis section provides you just an introduction about the `Schema Server`.\n\nSchema Registry provides a serving layer for your metadata. It provides a RESTful interface for storing and retrieving Avro or JSON schemas. It stores a versioned history of all schemas, provides multiple compatibility settings and allows evolution of schemas according to the configured compatibility settings and expanded Avro or JSON support. It provides serializers that plug into Apache Kafka® clients that handle schema storage and retrieval for Kafka messages that are sent in the Avro or JSON format.\n\nSchema Registry is a distributed storage layer for Avro or JSON Schemas which uses Kafka as its underlying storage mechanism. Some key design decisions:\n\n1. Assigns globally unique ID to each registered schema. Allocated IDs are guaranteed to be monotonically increasing but not necessarily consecutive.\n2. Kafka provides the durable backend, and functions as a write-ahead changelog for the state of Schema Registry and the schemas it contains.\n3. Schema Registry is designed to be distributed, with single-primary architecture, and ZooKeeper/Kafka coordinates primary election (based on the configuration).\n\n## API\n\n### Schemas\n\n`GET /schemas/ids/{int: id}` - Get the schema string identified by the input ID\n\n### Subjects\n\n`GET /subjects` - Get a list of registered subjects.\n\n`GET /subjects/(string: subject)/versions` - Get a list of versions registered under the specified subject\n\n`GET /schemas/ids/{int: id}/versions` - Get the subject-version pairs identified by the input ID.\n\n`DELETE /subjects/(string: subject)` - Deletes the specified subject and its associated compatibility level if registered. It is recommended to use this API only when a topic needs to be recycled or in development environment.\n\n`GET /subjects/(string: subject)/versions/(versionId: version)` - Get a specific version of the schema registered under this subject *Check response*\n\n`GET /subjects/(string: subject)/versions/(versionId: version)/schema` - Get the avro or json schema for the specified version of this subject. The unescaped schema only is returned. *[Missing]*\n\n`POST /subjects/(string: subject)/versions` - Register a new schema under the specified subject and receive a schema id\n\n`POST /subjects/(string: subject)` - Check if a schema has already been registered under the specified subject. If so, this returns the schema string along with its globally unique identifier, its version under this subject and the subject name.\n\n`DELETE /subjects/(string: subject)/versions/(versionId: version)` - Deletes a specific version of the schema registered under this subject. This only deletes the version and the schema ID remains intact making it still possible to decode data using the schema ID. This API is recommended to be used only in development environments or under extreme circumstances where-in, its required to delete a previously registered schema for compatibility purposes or re-register previously registered schema. *[Missing]*\n\n### Compatibility\n\n`POST /compatibility/subjects/(string: subject)/versions/(versionId: version)` - Test input schema against a particular version of a subject's schema for compatibility. Note that the compatibility level applied for the check is the configured compatibility level for the subject (http:get:: /config/(string: subject)). If this subject's compatibility level was never changed, then the global compatibility level applies (http:get:: /config).\n\nThese are the compatibility types:\n\n*BACKWARD*: (default) consumers using the new schema can read data written by producers using the latest registered schema\n\n*BACKWARD_TRANSITIVE*: consumers using the new schema can read data written by producers using all previously registered schemas\n\n*FORWARD*: consumers using the latest registered schema can read data written by producers using the new schema\n\n*FORWARD_TRANSITIVE*: consumers using all previously registered schemas can read data written by producers using the new schema\n\n*FULL*: the new schema is forward and backward compatible with the latest registered schema\n\n*FULL_TRANSITIVE*: the new schema is forward and backward compatible with all previously registered schemas\n\n*NONE*: schema compatibility checks are disabled\n\n### Config\n\n`GET /config` - Get global compatibility level.\n\n`PUT /config` - Update global compatibility level. *[Missing]*\n\n`GET /config/(string: subject)` - Get compatibility level for a subject. *[Missing]*\n\n`PUT /config/(string: subject)` - Update compatibility level for the specified subject.\n\nToo know more about the API go [here](https://docs.confluent.io/current/schema-registry/develop/api.html)\n"
  },
  {
    "path": "docs/schemas.md",
    "content": "# Schemas\n\n## BaseSchema\n\n`BaseSchema` an abstract base class from which `AvroSchema` and `JsonSchema` inherit.\nRequires concrete classes implement the following methods.\n\n```python\n@abstractmethod\ndef parse_schema(self, schema: typing.Dict) -> typing.Dict:\n    pass\n\n@staticmethod\n@abstractmethod\ndef load(fp: str) -> BaseSchema:\n    \"\"\"Parse a schema from a file path\"\"\"\n    pass\n\n@staticmethod\n@abstractmethod\nasync def async_load(fp: str) -> BaseSchema:\n    \"\"\"Parse a schema from a file path\"\"\"\n    pass\n\n@property\n@abstractmethod\ndef name(self) -> typing.Optional[str]:\n    pass\n\n@property\n@abstractmethod\ndef schema_type(self) -> str:\n    pass\n```\n\n## AvroSchema\n\n`AvroSchema` parses strings into avro schemas to assure validation. Properties:\n\n`raw_schema`: The input string that will be parsed\n\n`schema`: Result of parsing the raw_schema with `fastavro`\n\n`flat_schema`: Parsed schema without `__fastavro_parsed` flag\n\n`expanded_schema`: Parsed schema where all named types are expanded to their real schema\n\n## JsonSchema\n\n`JsonSchema` parses strings into json schemas to assure validation. Properties:\n\n`raw_schema`: The input string that will be parsed\n\n`schema`: Result of parsing the raw_schema with `jsonschema.Draft7Validator.check_schema`\n\n## SchemaVersion\n\n`SchemaVersion` is a `namedtuple` that contains the `subject`, `schema_id`, `version` and either `AvroSchema` or `JsonSchema`.\n\nThe `SchemaVersion` is returned by `get_schema` and `check_version` client methods\n"
  },
  {
    "path": "docs/serializer.md",
    "content": "# Serializers\n\nTo serialize and deserialize messages you can use `AvroMessageSerializer` and `JsonMessageSerializer`. They interact with the `SchemaRegistryClient` to get `avro Schemas`  and `json schemas` in order to process messages.\n\n*If you want to run the following examples run `docker-compose up` and the `schema registry server` will run on `http://127.0.0.1:8081`*\n\n!!! warning\n    The `AvroMessageSerializer` uses the same `protocol` as confluent, meaning that the event will contain the schema id in the payload.\n    If you produce an event with the `AvroMessageSerializer` you have to consume it with the `AvroMessageSerializer` as well, otherwise you have to\n    implement the parser on the consumer side.\n\n::: schema_registry.serializers.AvroMessageSerializer\n    options:\n        show_root_heading: true\n        docstring_section_style: table\n        show_signature_annotations: true\n\n::: schema_registry.serializers.JsonMessageSerializer\n    options:\n        show_root_heading: true\n        docstring_section_style: table\n        show_signature_annotations: false\n        show_base_classes: true\n\n## Async implementations\n\n`JsonMessageSerializer`, `AvroMessageSerializer` and `SchemaRegistryClient` have their asynchronous\ncounterparts `AsyncJsonMessageSerializer`, `AsyncAvroMessageSerializer` and `AsyncSchemaRegistryClient` and all\nexamples above should work if you replace them with their async variations\n\n::: schema_registry.serializers.AsyncAvroMessageSerializer\n    options:\n        show_root_heading: true\n        docstring_section_style: table\n        show_signature_annotations: false\n\n::: schema_registry.serializers.AsyncJsonMessageSerializer\n    options:\n        show_root_heading: true\n        docstring_section_style: table\n        show_signature_annotations: false\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: Python Schema Registry Client\nsite_description: avro, kafka, client, faust, schema\n\ntheme:\n  name: 'material'\n  palette:\n  - scheme: default\n    primary: blue grey\n    accent: indigo\n    toggle:\n      icon: material/lightbulb\n      name: Switch to dark mode\n  - scheme: slate\n    primary: blue grey\n    accent: indigo\n    toggle:\n      icon: material/lightbulb-outline\n      name: Switch to light mode\n  features:\n  - search.suggest\n  - search.highlight\n  - content.tabs.link\n  - content.code.annotate\n\nrepo_name: marcosschroh/python-schema-registry-client\nrepo_url: https://github.com/marcosschroh/python-schema-registry-client\n\nnav:\n  - Introduction: 'index.md'\n  - SchemaRegistryClient Api: 'client.md'\n  - Serializers: 'serializer.md'\n  - Faust integration: 'faust.md'\n  - Schemas: 'schemas.md'\n  - Exceptions: 'exceptions.md'\n  - Schema Registry Server: 'schemaregistry_server.md'\n\nmarkdown_extensions:\n  - pymdownx.highlight\n  - pymdownx.inlinehilite\n  - pymdownx.superfences\n  - pymdownx.snippets\n  - pymdownx.critic\n  - pymdownx.caret\n  - pymdownx.keys\n  - pymdownx.mark\n  - pymdownx.tilde\n  - pymdownx.details\n  - tables\n  - attr_list\n  - md_in_html\n  - admonition\n  - codehilite\n\nplugins:\n  - autorefs\n  - mkdocstrings\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.poetry]\nname = \"python-schema-registry-client\"\nversion = \"2.6.1\"\ndescription = \"Python Rest Client to interact against Schema Registry confluent server\"\nauthors = [\"Marcos Schroh <schrohm@gmail.com>\"]\nlicense = \"MIT\"\nreadme = \"README.md\"\npackages = [{include = \"schema_registry\"}]\nclassifiers = [\n    \"Intended Audience :: Developers\",\n    \"License :: OSI Approved :: MIT License\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.8\",\n    \"Programming Language :: Python :: 3.9\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3 :: Only\",\n    \"Topic :: Software Development\",\n]\n\n[tool.poetry.dependencies]\npython = \"^3.8\"\nfastavro = \"^1.7.3\"\njsonschema = \"^4.17.3\"\nhttpx = \">=0.28,<0.29\"\nanyio = \">=2,<5\"\nfaust-streaming = {version = \">=0.10.11,<0.12.0\", optional = true}\n\n[tool.poetry.group.dev.dependencies]\nmypy = \"^1\"\nruff = \">=0.8,<0.10\"\npytest = \">=7,<9\"\npytest-cov = \">=4,<6\"\npytest-mock = \"^3.10.0\"\npytest-asyncio = \">=0.21,<0.24\"\ndataclasses-avroschema = {version = \">=0.57,<0.62\", extras = [\"pydantic\", \"faker\"]}\ncodecov = \"^2.1.13\"\ntypes-jsonschema = \"^4.17.0.7\"\n\n[tool.poetry.group.docs.dependencies]\nmkdocs = \"^1\"\nmkdocs-material = \"^9\"\nmkdocstrings = {extras = [\"python\"], version = \">=0.21.2,<0.27.0\"}\n\n[tool.poetry.group.ci-publish.dependencies]\ncommitizen = \"^3\"\n\n[tool.poetry.extras]\nfaust = [\"faust-streaming\"]\n\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n\n[tool.mypy]\nallow_empty_bodies = true\n\n[tool.ruff]\nline-length = 120\nexclude = [\n    \".bzr\",\n    \".direnv\",\n    \".eggs\",\n    \".git\",\n    \".hg\",\n    \".mypy_cache\",\n    \".nox\",\n    \".pants.d\",\n    \".ruff_cache\",\n    \".svn\",\n    \".tox\",\n    \".venv\",\n    \"__pypackages__\",\n    \"_build\",\n    \"buck-out\",\n    \"build\",\n    \"dist\",\n    \"node_modules\",\n    \".venv\",\n]\n\n[tool.ruff.lint]\nfixable = [\"ALL\"]\nunfixable = []\nselect = [\n    \"B\",  # flake8-bugbear\n    \"C\",  # flake8-comprehensions\n    \"D100\",  # pydocstyle: public module docstring\n    \"D101\",  # pydocstyle: docstring in public class\n    \"E\",  # pycodestyle errors\n    \"F\",  # pyflakes\n    \"I\",  # isort\n    \"S\",  # bandit\n    \"W\",  # pycodestyle warnings\n]\n\n[tool.ruff.lint.per-file-ignores]\n\"tests/*.py\" = [\n    \"D\",\n    \"S\",\n]\n\n[tool.ruff.lint.pydocstyle]\nconvention = \"google\"\n\n[tool.commitizen]\nversion_provider = \"poetry\"\ntag_format = \"v$version\"\nupdate_changelog_on_bump = true\n"
  },
  {
    "path": "schema_registry/__init__.py",
    "content": ""
  },
  {
    "path": "schema_registry/client/__init__.py",
    "content": "from . import errors, schema  # noqa\nfrom .client import AsyncSchemaRegistryClient, SchemaRegistryClient  # noqa\n\n__all__ = [\"SchemaRegistryClient\", \"AsyncSchemaRegistryClient\"]\n"
  },
  {
    "path": "schema_registry/client/client.py",
    "content": "\"\"\"Store client to interact with Schema Registry HTTP API.\"\"\"\n\nimport json\nimport logging\nimport os\nimport ssl\nimport typing\nfrom abc import abstractmethod\nfrom collections import defaultdict\nfrom urllib.parse import urlparse\n\nimport certifi\nimport httpx\nfrom httpx import USE_CLIENT_DEFAULT, Auth, BasicAuth\nfrom httpx._client import UseClientDefault\nfrom httpx._types import TimeoutTypes\n\nfrom . import status, utils\nfrom .errors import ClientError\nfrom .paths import paths\nfrom .schema import BaseSchema, SchemaFactory, SubjectVersion\nfrom .urls import UrlManager\n\nlogger = logging.getLogger(__name__)\n\n\ndef get_response_and_status_code(\n    response: httpx.Response,\n) -> typing.Tuple[typing.Any, int]:\n    \"\"\"Returns a tuple containing response json and status code.\n\n    Args:\n        response: Http response object\n\n    Returns:\n       A tuple of the JSON response and the status code\n    \"\"\"\n    return (\n        response.json(),\n        response.status_code,\n    )\n\n\nclass BaseClient:\n    \"\"\"A client that talks to a Schema Registry over HTTP.\n\n    Args:\n        url: Url to schema registry or dictionary containing client configuration.\n        ca_location: File or directory path to CA certificate(s) for verifying the Schema Registry key.\n        cert_location: Path to public key used for authentication.\n        key_location: Path to private key used for authentication.\n        key_password: Key password\n        extra_headers: Extra headers to add on every requests.\n        timeout: The timeout configuration to use when sending requests.\n        pool_limits: The connection pool configuration to use when determining the maximum number of concurrently\n            open HTTP connections.\n        auth: Auth credentials.\n    \"\"\"\n\n    def __init__(\n        self,\n        url: typing.Union[str, typing.Dict],\n        ca_location: typing.Optional[str] = None,\n        cert_location: typing.Optional[str] = None,\n        key_location: typing.Optional[str] = None,\n        key_password: typing.Optional[str] = None,\n        extra_headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Optional[httpx.Timeout] = None,\n        pool_limits: typing.Optional[httpx.Limits] = None,\n        auth: typing.Optional[Auth] = None,\n    ) -> None:\n        if isinstance(url, str):\n            conf = {\n                utils.URL: url,\n                utils.SSL_CA_LOCATION: ca_location,\n                utils.SSL_CERTIFICATE_LOCATION: cert_location,\n                utils.SSL_KEY_LOCATION: key_location,\n                utils.SSL_KEY_PASSWORD: key_password,\n            }\n        else:\n            conf = url\n\n        self.conf = conf\n        schema_server_url = conf.get(utils.URL, \"\")\n        self.url_manager = UrlManager(schema_server_url, paths)  # type: ignore\n        self.extra_headers = extra_headers\n        self.timeout = timeout\n        self.pool_limits = pool_limits\n        self.auth = auth\n        self.client_kwargs = self._get_client_kwargs()\n\n        # Cache Schemas: subj => { schema => id }\n        self.subject_to_schema_ids: typing.Dict[str, typing.Dict[str, int]] = defaultdict(dict)\n\n        # Cache Schemas: subj => { schema => version }\n        self.subject_to_schema_versions: typing.Dict[str, typing.Dict[str, typing.Union[str, int]]] = defaultdict(dict)\n\n        # Cache Schemas: id => avro_schema\n        self.id_to_schema: typing.Dict[int, BaseSchema] = {}\n\n    def __eq__(self, obj: typing.Any) -> bool:\n        return self.conf == obj.conf and self.extra_headers == obj.extra_headers\n\n    @staticmethod\n    def _schema_from_result(result: typing.Dict) -> BaseSchema:\n        schema: str = result[\"schema\"]\n        schema_type = result.get(\"schemaType\", utils.AVRO_SCHEMA_TYPE)\n        return SchemaFactory.create_schema(schema, schema_type)\n\n    def _configure_auth(self) -> Auth:\n        # Check first if the credentials are sent in Auth\n        if self.auth is not None:\n            return self.auth\n\n        # This part should be deprecated with a new mayor version. Url should be only a string\n        url = self.conf[\"url\"]\n        auth_provider = self.conf.pop(\"basic.auth.credentials.source\", \"URL\").upper()  # type: ignore\n\n        if auth_provider not in utils.VALID_AUTH_PROVIDERS:\n            raise ValueError(\n                f\"\"\"\n                schema.registry.basic.auth.credentials.source\n                must be one of {utils.VALID_AUTH_PROVIDERS}\n                \"\"\"\n            )\n\n        if auth_provider == \"USER_INFO\":\n            logger.warning(\"Deprecation warning: This will be deprecated in future versions. Use httpx.Auth instead\")\n            auth = BasicAuth(*self.conf.pop(\"basic.auth.user.info\", \"\").split(\":\"))  # type: ignore\n        else:\n            # Credentials might be in the url.\n            parsed_url = urlparse(url)\n            auth = BasicAuth(parsed_url.username or \"\", parsed_url.password or \"\")\n\n        # remove ignore after mypy fix https://github.com/python/mypy/issues/4805\n        return auth  # type: ignore\n\n    @staticmethod\n    def _configure_client_tls(\n        conf: dict,\n    ) -> typing.Optional[typing.Union[typing.Tuple[str, str], typing.Tuple[str, str, str]]]:\n        cert = conf.get(utils.SSL_CERTIFICATE_LOCATION)\n        certificate = cert\n\n        if cert is not None:\n            key_path = conf.get(utils.SSL_KEY_LOCATION)\n            key_password = conf.get(utils.SSL_KEY_PASSWORD)\n            certificate = (cert,)\n\n            if key_path is not None:\n                certificate = (\n                    cert,\n                    key_path,\n                )\n\n                if key_password is not None:\n                    certificate += (key_password,)\n\n        return certificate\n\n    def _get_client_kwargs(self) -> typing.Dict:\n        auth = self._configure_auth()\n\n        client_kwargs: typing.Dict = {\n            \"auth\": auth,\n        }\n\n        certificate = self._configure_client_tls(self.conf)\n\n        if certificate:\n            global_ca_location = self.conf.get(utils.SSL_CA_LOCATION, certifi.where())\n            ctx = ssl.create_default_context(\n                cafile=os.environ.get(\"SSL_CERT_FILE\", global_ca_location),\n                capath=os.environ.get(\"SSL_CERT_DIR\"),\n            )\n\n            ctx.load_cert_chain(*certificate)\n            client_kwargs[\"verify\"] = ctx\n\n        # If these values haven't been explicitly defined let httpx sort out\n        # the default values.\n        if self.extra_headers is not None:\n            client_kwargs[\"headers\"] = self.extra_headers\n\n        if self.timeout is not None:\n            client_kwargs[\"timeout\"] = self.timeout\n\n        if self.pool_limits is not None:\n            client_kwargs[\"limits\"] = self.pool_limits\n        return client_kwargs\n\n    def prepare_headers(\n        self,\n        body: typing.Optional[typing.Dict] = None,\n        headers: typing.Optional[typing.Dict] = None,\n    ) -> typing.Dict[str, str]:\n        \"\"\"Combines parameters to form a HTTP Header.\n\n        Args:\n            body: Body of the future request. Defaults to None.\n            headers: Additional information to combine. Defaults to None.\n\n        Returns:\n            The header in a dictionnary format\n        \"\"\"\n        _headers = {\"Accept\": utils.ACCEPT_HEADERS}\n\n        if body:\n            _headers[\"Content-Type\"] = utils.HEADERS\n\n        if headers:\n            _headers.update(headers)\n\n        return _headers\n\n    def _cache_subject_to_schema_ids(self, subject: str, schema: BaseSchema, value: int) -> None:\n        self.subject_to_schema_ids[subject][str(schema)] = value\n\n    def _cache_subject_to_schema_versions(\n        self, subject: str, schema: BaseSchema, value: typing.Union[str, int]\n    ) -> None:\n        self.subject_to_schema_versions[subject][str(schema)] = value\n\n    def _cache_schema(\n        self,\n        schema: BaseSchema,\n        schema_id: int,\n        subject: typing.Optional[str] = None,\n        version: typing.Union[str, int, None] = None,\n    ) -> None:\n        if schema_id in self.id_to_schema:\n            schema = self.id_to_schema[schema_id]\n        else:\n            self.id_to_schema[schema_id] = schema\n\n        if subject:\n            self._cache_subject_to_schema_ids(subject, schema, schema_id)\n            if version:\n                self._cache_subject_to_schema_versions(subject, schema, version)\n\n    @abstractmethod\n    def request(\n        self,\n        url: str,\n        method: str = \"GET\",\n        body: typing.Optional[typing.Dict] = None,\n        params: typing.Optional[typing.Dict] = None,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> typing.Union[tuple, httpx.Response, typing.Coroutine[typing.Any, typing.Any, typing.Any]]:\n        _headers = self.prepare_headers(body=body, headers=headers)\n        with httpx.Client(**self.client_kwargs) as client:\n            response = client.request(method, url, headers=_headers, json=body, params=params, timeout=timeout)\n        return response\n\n\nclass SchemaRegistryClient(BaseClient):\n    \"\"\"A client that talks to a Schema Registry over HTTP.\n\n    !!! Example\n        ```python title=\"Usage\"\n        from schema_registry.client import SchemaRegistryClient, schema\n\n        client = SchemaRegistryClient(url=\"http://127.0.0.1:8081\")\n\n        deployment_schema = {\n            \"type\": \"record\",\n            \"namespace\": \"com.kubertenes\",\n            \"name\": \"AvroDeployment\",\n            \"fields\": [\n                {\"name\": \"image\", \"type\": \"string\"},\n                {\"name\": \"replicas\", \"type\": \"int\"},\n                {\"name\": \"port\", \"type\": \"int\"},\n            ],\n        }\n\n        avro_schema = schema.AvroSchema(deployment_schema)\n        schema_id = client.register(\"test-deployment\", avro_schema)\n        ```\n\n    Args:\n        url: Url to schema registry or dictionary containing client configuration.\n        ca_location: File or directory path to CA certificate(s) for verifying the Schema Registry key.\n        cert_location: Path to public key used for authentication.\n        key_location: Path to private key used for authentication.\n        key_password: Key password\n        extra_headers: Extra headers to add on every requests.\n        timeout: The timeout configuration to use when sending requests.\n        pool_limits: The connection pool configuration to use when determining the maximum number of concurrently\n            open HTTP connections.\n        auth: Auth credentials.\n    \"\"\"\n\n    def request(\n        self,\n        url: str,\n        method: str = \"GET\",\n        body: typing.Optional[typing.Dict] = None,\n        params: typing.Optional[typing.Dict] = None,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> httpx.Response:\n        if method not in utils.VALID_METHODS:\n            raise ClientError(f\"Method {method} is invalid; valid methods include {utils.VALID_METHODS}\")\n\n        _headers = self.prepare_headers(body=body, headers=headers)\n        with httpx.Client(**self.client_kwargs) as client:\n            response = client.request(method, url, headers=_headers, json=body, params=params, timeout=timeout)\n        return response\n\n    def register(\n        self,\n        subject: str,\n        schema: typing.Union[BaseSchema, str, typing.Dict[str, typing.Any]],\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n        schema_type: typing.Literal[\"AVRO\", \"JSON\"] = utils.AVRO_SCHEMA_TYPE,\n    ) -> int:\n        \"\"\"Register a schema for a subject.\n\n        Schema can be avro or json, and can be presented as a parsed schema or a string.\n        If schema is a string, the `schema_type` kwarg must be used to indicate\n        what type of schema the string is (`AVRO` by default).\n        If the schema is already parsed, the schema_type is inferred directly from the parsed schema.\n        Multiple instances of the same schema will result in cache misses.\n\n        Args:\n            subject: subject name\n            schema: Avro or JSON schema to be registered\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n            schema_type: The type of schema to parse if `schema` is a string. Default \"AVRO\"\n\n        Returns:\n            schema_id\n        \"\"\"\n        if isinstance(schema, str) or isinstance(schema, dict):\n            schema = SchemaFactory.create_schema(schema, schema_type)\n\n        schema_id = self.subject_to_schema_ids[subject].get(str(schema))\n        if schema_id is not None:\n            return schema_id\n\n        # Check if schema is already registered. This should normally work even if\n        # the schema registry we're talking to is readonly, enabling a setup where\n        # only CI/CD can do changes to Schema Registry, and production code has readonly\n        # access\n\n        response = self.check_version(subject, schema, headers=headers, timeout=timeout)\n        if response is not None:\n            return response.schema_id\n\n        url, method = self.url_manager.url_for(\"register\", subject=subject)\n        body = {\n            \"schema\": json.dumps(schema.raw_schema),\n            \"schemaType\": schema.schema_type,\n        }\n\n        (\n            result,\n            code,\n        ) = get_response_and_status_code(self.request(url, method=method, body=body, headers=headers, timeout=timeout))\n        msg = None\n        if code in (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN):\n            msg = \"Unauthorized access\"\n        elif code == status.HTTP_409_CONFLICT:\n            msg = \"Incompatible schema\"\n        elif code == status.HTTP_422_UNPROCESSABLE_ENTITY:\n            msg = \"Invalid schema\"\n        elif not status.is_success(code):\n            msg = \"Unable to register schema\"\n\n        if msg is not None:\n            raise ClientError(message=msg, http_code=code, server_traceback=result)\n\n        schema_id = result[\"id\"]\n        self._cache_schema(schema, schema_id, subject)\n\n        return schema_id\n\n    def get_subjects(\n        self,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> list:\n        \"\"\"Get list of all registered subjects in your Schema Registry.\n\n        GET /subjects/(string: subject)\n\n        Args:\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            List of registered subjects.\n        \"\"\"\n        url, method = self.url_manager.url_for(\"get_subjects\")\n        result, code = get_response_and_status_code(self.request(url, method=method, headers=headers, timeout=timeout))\n\n        if status.is_success(code):\n            return result\n\n        raise ClientError(\"Unable to get subjects\", http_code=code, server_traceback=result)\n\n    def delete_subject(\n        self,\n        subject: str,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> list:\n        \"\"\"Deletes the specified subject and its associated compatibility level if registered.\n\n        It is recommended to use this API only when a topic needs to be\n        recycled or in development environments.\n\n        DELETE /subjects/(string: subject)\n\n        Args:\n            subject: subject name\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            List version of the schema deleted under this subject\n        \"\"\"\n        url, method = self.url_manager.url_for(\"delete_subject\", subject=subject)\n        result, code = get_response_and_status_code(self.request(url, method=method, headers=headers, timeout=timeout))\n\n        if status.is_success(code):\n            return result\n        elif code == status.HTTP_404_NOT_FOUND:\n            return []\n\n        raise ClientError(\"Unable to delete subject\", http_code=code, server_traceback=result)\n\n    def get_by_id(\n        self,\n        schema_id: int,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> typing.Optional[BaseSchema]:\n        \"\"\"Retrieve a parsed avro schema by id or None if not found.\n\n        GET /schemas/ids/{int: id}\n\n        Args:\n            schema_id: Schema Id\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            Avro or JSON schema\n        \"\"\"\n        if schema_id in self.id_to_schema:\n            return self.id_to_schema[schema_id]\n\n        url, method = self.url_manager.url_for(\"get_by_id\", schema_id=schema_id)\n\n        result, code = get_response_and_status_code(self.request(url, method=method, headers=headers, timeout=timeout))\n        if code == status.HTTP_404_NOT_FOUND:\n            logger.info(f\"Schema {schema_id} not found: {code}\")\n            return None\n        elif status.is_success(code):\n            schema = self._schema_from_result(result)\n            self._cache_schema(schema, schema_id)\n            return schema\n\n        raise ClientError(\n            f\"Received bad schema (id {schema_id})\",\n            http_code=code,\n            server_traceback=result,\n        )\n\n    def get_schema_subject_versions(\n        self,\n        schema_id: int,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> typing.Optional[typing.List[SubjectVersion]]:\n        \"\"\"Get the subject-version pairs identified by the input ID.\n\n        GET /schemas/ids/{int: id}/versions\n\n        Args:\n            schema_id: Schema Id\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            List of Subject/Version pairs where Schema Id is registered\n        \"\"\"\n        url, method = self.url_manager.url_for(\"get_schema_subject_versions\", schema_id=schema_id)\n        result, code = get_response_and_status_code(self.request(url, method=method, headers=headers, timeout=timeout))\n        if code == status.HTTP_404_NOT_FOUND:\n            logger.warning(f\"Schema {schema_id} not found: {code}\")\n            return None\n        elif status.is_success(code):\n            ret = []\n            for entry in result:\n                ret.append(SubjectVersion(entry[\"subject\"], entry[\"version\"]))\n            return ret\n\n        raise ClientError(\n            f\"Received bad schema (id {schema_id})\",\n            http_code=code,\n            server_traceback=result,\n        )\n\n    def get_schema(\n        self,\n        subject: str,\n        version: typing.Union[int, str] = \"latest\",\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> typing.Optional[utils.SchemaVersion]:\n        \"\"\"Get a specific version of the schema registered under this subject.\n\n        GET /subjects/(string: subject)/versions/(versionId: version)\n\n        Args:\n            subject: subject name\n            version: version id. If is None, the latest schema is returned\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            The SchemaVersion utils.SchemaVersion if response was succeeded\n        \"\"\"\n        url, method = self.url_manager.url_for(\"get_schema\", subject=subject, version=version)\n\n        result, code = get_response_and_status_code(self.request(url, method=method, headers=headers, timeout=timeout))\n        if code == status.HTTP_404_NOT_FOUND:\n            logger.info(f\"Schema version {version} under subjet {subject} not found: {code}\")\n            return None\n        elif code == status.HTTP_422_UNPROCESSABLE_ENTITY:\n            logger.info(f\"Invalid version {version}: {code}\")\n            return None\n        elif not status.is_success(code):\n            logger.info(f\"Not success version {version}: {code}\")\n            return None\n\n        schema_id = result.get(\"id\")\n        if schema_id in self.id_to_schema:\n            schema = self.id_to_schema[schema_id]\n        else:\n            schema = self._schema_from_result(result)\n\n        version = result[\"version\"]\n        self._cache_schema(schema, schema_id, subject, version)\n\n        return utils.SchemaVersion(subject=subject, schema_id=schema_id, schema=schema, version=version)\n\n    def get_versions(\n        self,\n        subject: str,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> list:\n        \"\"\"Get a list of versions registered under the specified subject.\n\n        GET subjects/{subject}/versions\n\n        Args:\n            subject: subject name\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            List  version of the schema registered under this subject\n        \"\"\"\n        url, method = self.url_manager.url_for(\"get_versions\", subject=subject)\n\n        result, code = get_response_and_status_code(self.request(url, method=method, headers=headers, timeout=timeout))\n        if status.is_success(code):\n            return result\n        elif code == status.HTTP_404_NOT_FOUND:\n            logger.info(f\"Subject {subject} not found\")\n            return []\n\n        raise ClientError(\n            f\"Unable to get the versions for subject {subject}\",\n            http_code=code,\n            server_traceback=result,\n        )\n\n    def delete_version(\n        self,\n        subject: str,\n        version: typing.Union[int, str] = \"latest\",\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> typing.Optional[int]:\n        \"\"\"Deletes a specific version of the schema registered under this subject.\n\n        This only deletes the version and the schema ID remains intact making\n        it still possible to decode data using the schema ID.\n        This API is recommended to be used only in development environments or\n        under extreme circumstances where-in, its required to delete a previously\n        registered schema for compatibility purposes or re-register previously registered schema.\n\n        DELETE /subjects/(string: subject)/versions/(versionId: version)\n\n        Args:\n            subject: subject name\n            version: Version of the schema to be deleted.\n                Valid values for versionId are between [1,2^31-1] or the string \"latest\".\n                \"latest\" deletes the last registered schema under the specified subject.\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            Version of the schema deleted. If the subject or version does not exist.\n        \"\"\"\n        url, method = self.url_manager.url_for(\"delete_version\", subject=subject, version=version)\n\n        result, code = get_response_and_status_code(self.request(url, method=method, headers=headers, timeout=timeout))\n\n        if status.is_success(code):\n            return result\n        elif status.is_client_error(code):\n            return None\n\n        raise ClientError(\"Unable to delete the version\", http_code=code, server_traceback=result)\n\n    def check_version(\n        self,\n        subject: str,\n        schema: typing.Union[BaseSchema, str, typing.Dict[str, typing.Any]],\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n        schema_type: typing.Literal[\"AVRO\", \"JSON\"] = utils.AVRO_SCHEMA_TYPE,\n    ) -> typing.Optional[utils.SchemaVersion]:\n        \"\"\"Check if a schema has already been registered under the specified subject.\n\n        If so, this returns the schema string along with its globally unique identifier,\n        its version under this subject and the subject name.\n\n        POST /subjects/(string: subject)\n\n        Args:\n            subject: subject name\n            schema: Avro or JSON schema headers typing.Dict: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n            schema_type: The type of schema to parse if `schema` is a string. Default \"AVRO\"\n\n        Returns:\n            SchemaVersion If schema exist\n        \"\"\"\n        if isinstance(schema, str) or isinstance(schema, dict):\n            schema = SchemaFactory.create_schema(schema, schema_type)\n\n        version = self.subject_to_schema_versions[subject].get(str(schema))\n        schema_id = self.subject_to_schema_ids[subject].get(str(schema))\n\n        if all((version, schema_id)):\n            return utils.SchemaVersion(subject=subject, schema_id=schema_id, version=version, schema=schema)\n\n        url, method = self.url_manager.url_for(\"check_version\", subject=subject)\n        body = {\n            \"schema\": json.dumps(schema.raw_schema),\n            \"schemaType\": schema.schema_type,\n        }\n        result, code = get_response_and_status_code(\n            self.request(url, method=method, body=body, headers=headers, timeout=timeout)\n        )\n        if code == status.HTTP_404_NOT_FOUND:\n            logger.info(f\"Schema {schema.name} under subject {subject} not found: {code}\")\n            return None\n        elif status.is_success(code):\n            schema_id = result[\"id\"]\n            version = result.get(\"version\")\n            self._cache_schema(schema, schema_id, subject, version)  # type: ignore\n\n            return utils.SchemaVersion(\n                subject=subject,\n                schema_id=schema_id,\n                version=version,\n                schema=result.get(\"schema\"),\n            )\n\n        raise ClientError(\"Unable to get version of a schema\", http_code=code, server_traceback=result)\n\n    def test_compatibility(\n        self,\n        subject: str,\n        schema: typing.Union[BaseSchema, str, typing.Dict[str, typing.Any]],\n        version: typing.Union[int, str] = \"latest\",\n        verbose: bool = False,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n        schema_type: typing.Literal[\"AVRO\", \"JSON\"] = utils.AVRO_SCHEMA_TYPE,\n    ) -> typing.Union[bool, typing.Dict[str, typing.Any]]:\n        \"\"\"Test the compatibility of a candidate parsed schema for a given subject.\n\n        By default the latest version is checked against.\n\n        POST /compatibility/subjects/(string: subject)/versions/(versionId: version)\n\n        Args:\n            subject: subject name\n            schema: Avro or JSON schema headers typing.Dict: Extra headers to add on the requests\n            version: The schema version to test compatibility against\n            verbose: Whether or not to return the errors in case of incompatibility\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n            schema_type: The type of schema to parse if `schema` is a string. Default \"AVRO\"\n\n        Returns:\n            If verbose if False: return a boolean wether the schema is compatible with the latest version for a subject\n            If verbose is True: return the API reponse with both the compatibility boolean and the possible errors\n        \"\"\"\n        url, method = self.url_manager.url_for(\"test_compatibility\", subject=subject, version=version)\n\n        if isinstance(schema, str) or isinstance(schema, dict):\n            schema = SchemaFactory.create_schema(schema, schema_type)\n\n        body = {\n            \"schema\": json.dumps(schema.raw_schema),\n            \"schemaType\": schema.schema_type,\n        }\n        result, code = get_response_and_status_code(\n            self.request(url, method=method, body=body, headers=headers, params={\"verbose\": verbose}, timeout=timeout)\n        )\n\n        if code == status.HTTP_404_NOT_FOUND:\n            logger.info(f\"Subject or version not found: {code}\")\n            return False\n        elif code == status.HTTP_422_UNPROCESSABLE_ENTITY:\n            logger.info(f\"Unprocessable entity. Invalid subject or schema: {code}\")\n        elif status.is_success(code):\n            if verbose:\n                return result\n            else:\n                return result.get(\"is_compatible\")\n\n        raise ClientError(\"Unable to check the compatibility\", http_code=code, server_traceback=result)\n\n    def update_compatibility(\n        self,\n        level: str,\n        subject: typing.Optional[str] = None,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> bool:\n        \"\"\"Update the compatibility level.\n\n        If subject is None, the compatibility level is global.\n\n        PUT /config/(string: subject)\n\n        Args:\n            level: one of BACKWARD, BACKWARD_TRANSITIVE, FORWARD, FORWARD_TRANSITIVE,\n                FULL, FULL_TRANSITIVE, NONE\n            subject: Option subject\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            Whether the compatibility was updated\n\n        Raises:\n            ClientError: if the request was unsuccessful or an invalid\n        \"\"\"\n        if level not in utils.VALID_LEVELS:\n            raise ClientError(f\"Invalid level specified: {level}\")\n\n        url, method = self.url_manager.url_for(\"update_compatibility\", subject=subject)\n        body = {\"compatibility\": level}\n\n        result, code = get_response_and_status_code(\n            self.request(url, method=method, body=body, headers=headers, timeout=timeout)\n        )\n\n        if status.is_success(code):\n            return True\n\n        raise ClientError(f\"Unable to update level: {level}.\", http_code=code, server_traceback=result)\n\n    def get_compatibility(\n        self,\n        subject: typing.Optional[str] = None,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> str:\n        \"\"\"Get the current compatibility level for a subject.\n\n        Args:\n            subject: subject name\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            One of BACKWARD, BACKWARD_TRANSITIVE, FORWARD, FORWARD_TRANSITIVE,\n                FULL, FULL_TRANSITIVE, NONE\n\n        Raises:\n            ClientError: if the request was unsuccessful or an invalid\n                compatibility level was returned\n        \"\"\"\n        url, method = self.url_manager.url_for(\"get_compatibility\", subject=subject)\n        result, code = get_response_and_status_code(self.request(url, method=method, headers=headers, timeout=timeout))\n\n        if status.is_success(code):\n            compatibility = result.get(\"compatibilityLevel\")\n            if compatibility not in utils.VALID_LEVELS:\n                if compatibility is None:\n                    error_msg_suffix = \"No compatibility was returned\"\n                else:\n                    error_msg_suffix = str(compatibility)\n                raise ClientError(\n                    f\"Invalid compatibility level received: {error_msg_suffix}\",\n                    http_code=code,\n                    server_traceback=result,\n                )\n\n            return compatibility\n\n        raise ClientError(\n            f\"Unable to fetch compatibility level. Error code: {code}\",\n            http_code=code,\n            server_traceback=result,\n        )\n\n\nclass AsyncSchemaRegistryClient(BaseClient):\n    \"\"\"A client that talks to a Schema Registry over HTTP.\n\n    Args:\n        url: Url to schema registry or dictionary containing client configuration.\n        ca_location: File or directory path to CA certificate(s) for verifying the Schema Registry key.\n        cert_location: Path to public key used for authentication.\n        key_location: Path to private key used for authentication.\n        key_password: Key password\n        extra_headers: Extra headers to add on every requests.\n        timeout: The timeout configuration to use when sending requests.\n        pool_limits: The connection pool configuration to use when determining the maximum number of concurrently open\n            HTTP connections.\n        auth: Auth credentials.\n    \"\"\"\n\n    async def request(\n        self,\n        url: str,\n        method: str = \"GET\",\n        body: typing.Optional[typing.Dict] = None,\n        params: typing.Optional[typing.Dict] = None,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> httpx.Response:\n        if method not in utils.VALID_METHODS:\n            raise ClientError(f\"Method {method} is invalid; valid methods include {utils.VALID_METHODS}\")\n\n        _headers = self.prepare_headers(body=body, headers=headers)\n        async with httpx.AsyncClient(**self.client_kwargs) as client:\n            response = await client.request(method, url, headers=_headers, json=body, params=params, timeout=timeout)\n\n        return response\n\n    async def register(\n        self,\n        subject: str,\n        schema: typing.Union[BaseSchema, str, typing.Dict[str, typing.Any]],\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n        schema_type: typing.Literal[\"AVRO\", \"JSON\"] = utils.AVRO_SCHEMA_TYPE,\n    ) -> int:\n        \"\"\"Register a schema with the registry under the given subject and receive a schema id.\n\n        Schema can be avro or json, and can be presented as a parsed schema or a string.\n        If schema is a string, the `schema_type` kwarg must be used to indicate what type of schema the string is\n        (\"AVRO\" by default).\n        If the schema is already parsed, the schema_type is inferred directly from the parsed schema.\n        Multiple instances of the same schema will result in cache misses.\n\n        POST /subjects/(string: subject)/versions\n\n        Args:\n            subject: subject name\n            schema: Avro or JSON schema to be registered\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n            schema_type typing.Union[AVRO, JSON]: The type of schema to parse if `schema` is a string. Default \"AVRO\"\n\n        Returns:\n            schema_id\n        \"\"\"\n        if isinstance(schema, str) or isinstance(schema, dict):\n            schema = SchemaFactory.create_schema(schema, schema_type)\n\n        schema_id = self.subject_to_schema_ids[subject].get(str(schema))\n        if schema_id is not None:\n            return schema_id\n\n        # Check if schema is already registered. This should normally work even if\n        # the schema registry we're talking to is readonly, enabling a setup where\n        # only CI/CD can do changes to Schema Registry, and production code has readonly\n        # access\n\n        response = await self.check_version(subject, schema, headers=headers, timeout=timeout)\n\n        if response is not None:\n            return response.schema_id\n\n        url, method = self.url_manager.url_for(\"register\", subject=subject)\n        body = {\n            \"schema\": json.dumps(schema.raw_schema),\n            \"schemaType\": schema.schema_type,\n        }\n\n        result, code = get_response_and_status_code(\n            await self.request(url, method=method, body=body, headers=headers, timeout=timeout)\n        )\n\n        msg = None\n        if code in (status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN):\n            msg = \"Unauthorized access\"\n        elif code == status.HTTP_409_CONFLICT:\n            msg = \"Incompatible Avro schema\"\n        elif code == status.HTTP_422_UNPROCESSABLE_ENTITY:\n            msg = \"Invalid Avro schema\"\n        elif not status.is_success(code):\n            msg = \"Unable to register schema\"\n\n        if msg is not None:\n            raise ClientError(message=msg, http_code=code, server_traceback=result)\n\n        schema_id = result[\"id\"]\n        self._cache_schema(schema, schema_id, subject)\n\n        return schema_id\n\n    async def get_subjects(\n        self,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> list:\n        \"\"\"Get list of all registered subjects in your Schema Registry.\n\n        GET /subjects/(string: subject)\n\n        Args:\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            List of registered subjects.\n        \"\"\"\n        url, method = self.url_manager.url_for(\"get_subjects\")\n        result, code = get_response_and_status_code(\n            await self.request(url, method=method, headers=headers, timeout=timeout)\n        )\n\n        if status.is_success(code):\n            return result\n\n        raise ClientError(\"Unable to get subjects\", http_code=code, server_traceback=result)\n\n    async def delete_subject(\n        self,\n        subject: str,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> list:\n        \"\"\"Deletes the specified subject and its associated compatibility level if registered.\n\n        It is recommended to use this API only when a topic needs to be\n        recycled or in development environments.\n\n        DELETE /subjects/(string: subject)\n\n        Args:\n            subject: subject name\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            List version of the schema deleted under this subject\n        \"\"\"\n        url, method = self.url_manager.url_for(\"delete_subject\", subject=subject)\n        result, code = get_response_and_status_code(\n            await self.request(url, method=method, headers=headers, timeout=timeout)\n        )\n\n        if status.is_success(code):\n            return result\n        elif code == status.HTTP_404_NOT_FOUND:\n            return []\n\n        raise ClientError(\"Unable to delete subject\", http_code=code, server_traceback=result)\n\n    async def get_by_id(\n        self,\n        schema_id: int,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> typing.Optional[BaseSchema]:\n        \"\"\"Retrieve a parsed avro schema by id or None if not found.\n\n        GET /schemas/ids/{int: id}\n\n        Args:\n            schema_id: Schema Id\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            Avro or JSON schema\n        \"\"\"\n        if schema_id in self.id_to_schema:\n            return self.id_to_schema[schema_id]\n\n        url, method = self.url_manager.url_for(\"get_by_id\", schema_id=schema_id)\n\n        result, code = get_response_and_status_code(\n            await self.request(url, method=method, headers=headers, timeout=timeout)\n        )\n        if code == status.HTTP_404_NOT_FOUND:\n            logger.info(f\"Schema {schema_id} not found: {code}\")\n            return None\n        elif status.is_success(code):\n            schema = self._schema_from_result(result)\n            self._cache_schema(schema, schema_id)\n            return schema\n\n        raise ClientError(\n            f\"Received bad schema (id {schema_id})\",\n            http_code=code,\n            server_traceback=result,\n        )\n\n    async def get_schema(\n        self,\n        subject: str,\n        version: typing.Union[int, str] = \"latest\",\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> typing.Optional[utils.SchemaVersion]:\n        \"\"\"Get a specific version of the schema registered under this subject.\n\n        GET /subjects/(string: subject)/versions/(versionId: version)\n\n        Args:\n            subject: subject name\n            version: version id. If is None, the latest schema is returned\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            SchemaVersion (nametupled): (subject, schema_id, schema, version)\n\n            None: If server returns a not success response:\n                404: Schema not found\n                422: Unprocessable entity\n                ~ (200 - 299): Not success\n        \"\"\"\n        url, method = self.url_manager.url_for(\"get_schema\", subject=subject, version=version)\n\n        result, code = get_response_and_status_code(\n            await self.request(url, method=method, headers=headers, timeout=timeout)\n        )\n\n        if code == status.HTTP_404_NOT_FOUND:\n            logger.info(f\"Schema version {version} under subjet {subject} not found: {code}\")\n            return None\n        elif code == status.HTTP_422_UNPROCESSABLE_ENTITY:\n            logger.info(f\"Invalid version {version}: {code}\")\n            return None\n        elif not status.is_success(code):\n            logger.info(f\"Not success version {version}: {code}\")\n            return None\n\n        schema_id = result.get(\"id\")\n        if schema_id in self.id_to_schema:\n            schema = self.id_to_schema[schema_id]\n        else:\n            schema = self._schema_from_result(result)\n\n        version = result[\"version\"]\n        self._cache_schema(schema, schema_id, subject, version)\n\n        return utils.SchemaVersion(subject=subject, schema_id=schema_id, schema=schema, version=version)\n\n    async def get_schema_subject_versions(\n        self,\n        schema_id: int,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> typing.Optional[typing.List[SubjectVersion]]:\n        \"\"\"Get the subject-version pairs identified by the input ID.\n\n        GET /schemas/ids/{int: id}/versions\n\n        Args:\n            schema_id: Schema Id\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            List of Subject/Version pairs where Schema Id is registered\n        \"\"\"\n        url, method = self.url_manager.url_for(\"get_schema_subject_versions\", schema_id=schema_id)\n        result, code = get_response_and_status_code(\n            await self.request(url, method=method, headers=headers, timeout=timeout)\n        )\n\n        if code == status.HTTP_404_NOT_FOUND:\n            logger.warning(f\"Schema {schema_id} not found: {code}\")\n            return None\n        elif status.is_success(code):\n            ret = []\n            for entry in result:\n                ret.append(SubjectVersion(entry[\"subject\"], entry[\"version\"]))\n            return ret\n\n        raise ClientError(\n            f\"Received bad schema (id {schema_id})\",\n            http_code=code,\n            server_traceback=result,\n        )\n\n    async def get_versions(\n        self,\n        subject: str,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> list:\n        \"\"\"Get a list of versions registered under the specified subject.\n\n        GET subjects/{subject}/versions\n\n        Args:\n            subject: subject name\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            List version of the schema registered under this subject\n        \"\"\"\n        url, method = self.url_manager.url_for(\"get_versions\", subject=subject)\n\n        result, code = get_response_and_status_code(\n            await self.request(url, method=method, headers=headers, timeout=timeout)\n        )\n        if status.is_success(code):\n            return result\n        elif code == status.HTTP_404_NOT_FOUND:\n            logger.info(f\"Subject {subject} not found\")\n            return []\n\n        raise ClientError(\n            f\"Unable to get the versions for subject {subject}\",\n            http_code=code,\n            server_traceback=result,\n        )\n\n    async def delete_version(\n        self,\n        subject: str,\n        version: typing.Union[int, str] = \"latest\",\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> typing.Optional[int]:\n        \"\"\"Deletes a specific version of the schema registered under this subject.\n\n        This only deletes the version and the schema ID remains intact making\n        it still possible to decode data using the schema ID.\n        This API is recommended to be used only in development environments or\n        under extreme circumstances where-in, its required to delete a previously\n        registered schema for compatibility purposes or re-register previously registered schema.\n\n        DELETE /subjects/(string: subject)/versions/(versionId: version)\n\n        Args:\n            subject: subject name\n            version: Version of the schema to be deleted.\n                Valid values for versionId are between [1,2^31-1] or the string \"latest\".\n                \"latest\" deletes the last registered schema under the specified subject.\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            Version of the schema deleted. If the subject or version does not exist.\n        \"\"\"\n        url, method = self.url_manager.url_for(\"delete_version\", subject=subject, version=version)\n\n        result, code = get_response_and_status_code(\n            await self.request(url, method=method, headers=headers, timeout=timeout)\n        )\n\n        if status.is_success(code):\n            return result\n        elif status.is_client_error(code):\n            return None\n\n        raise ClientError(\"Unable to delete the version\", http_code=code, server_traceback=result)\n\n    async def check_version(\n        self,\n        subject: str,\n        schema: typing.Union[BaseSchema, str, typing.Dict[str, typing.Any]],\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n        schema_type: typing.Literal[\"AVRO\", \"JSON\"] = utils.AVRO_SCHEMA_TYPE,\n    ) -> typing.Optional[utils.SchemaVersion]:\n        \"\"\"Check if a schema has already been registered under the specified subject.\n\n        If so, this returns the schema string along with its globally unique identifier,\n        its version under this subject and the subject name.\n\n        POST /subjects/(string: subject)\n\n        Args:\n            subject: subject name\n            schema: Avro or JSON schema\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n            schema_type: The type of schema to parse if `schema` is a string. Default \"AVRO\"\n\n        Returns:\n            SchemaVersion If schema exist\n        \"\"\"\n        schemas_to_version = self.subject_to_schema_versions[subject]\n\n        if isinstance(schema, str) or isinstance(schema, dict):\n            schema = SchemaFactory.create_schema(schema, schema_type)\n\n        version = schemas_to_version.get(str(schema))\n\n        schemas_to_id = self.subject_to_schema_ids[subject]\n        schema_id = schemas_to_id.get(str(schema))\n\n        if all((version, schema_id)):\n            return utils.SchemaVersion(subject=subject, schema_id=schema_id, version=version, schema=schema)\n\n        url, method = self.url_manager.url_for(\"check_version\", subject=subject)\n        body = {\n            \"schema\": json.dumps(schema.raw_schema),\n            \"schemaType\": schema.schema_type,\n        }\n\n        result, code = get_response_and_status_code(\n            await self.request(url, method=method, body=body, headers=headers, timeout=timeout)\n        )\n        if code == status.HTTP_404_NOT_FOUND:\n            logger.info(f\"Schema {schema.name} under subject {subject} not found: {code}\")\n            return None\n        elif status.is_success(code):\n            schema_id = result[\"id\"]\n            version = result.get(\"version\")\n            self._cache_schema(schema, schema_id, subject, version)  # type: ignore\n\n            return utils.SchemaVersion(\n                subject=subject,\n                schema_id=schema_id,\n                version=version,\n                schema=result.get(\"schema\"),\n            )\n\n        raise ClientError(\"Unable to get version of a schema\", http_code=code, server_traceback=result)\n\n    async def test_compatibility(\n        self,\n        subject: str,\n        schema: typing.Union[BaseSchema, str, typing.Dict[str, typing.Any]],\n        version: typing.Union[int, str] = \"latest\",\n        verbose: bool = False,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n        schema_type: typing.Literal[\"AVRO\", \"JSON\"] = utils.AVRO_SCHEMA_TYPE,\n    ) -> typing.Union[bool, typing.Dict[str, typing.Any]]:\n        \"\"\"Test the compatibility of a candidate parsed schema for a given subject.\n\n        By default the latest version is checked against.\n\n        POST /compatibility/subjects/(string: subject)/versions/(versionId: version)\n\n        Args:\n            subject: subject name\n            schema: Avro or JSON schema\n            version: The schema version to test compatibility against\n            verbose: Whether or not to return the errors in case of incompatibility\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n            schema_type: The type of schema to parse if `schema` is a string. Default \"AVRO\"\n\n        Returns:\n            If verbose if False: return a boolean wether the schema is compatible with the latest version for a subject\n            If verbose is True: return the API reponse with both the compatibility boolean and the possible errors\n        \"\"\"\n        url, method = self.url_manager.url_for(\"test_compatibility\", subject=subject, version=version)\n\n        if isinstance(schema, str) or isinstance(schema, dict):\n            schema = SchemaFactory.create_schema(schema, schema_type)\n\n        body = {\n            \"schema\": json.dumps(schema.raw_schema),\n            \"schemaType\": schema.schema_type,\n        }\n        result, code = get_response_and_status_code(\n            await self.request(\n                url, method=method, body=body, headers=headers, params={\"verbose\": verbose}, timeout=timeout\n            )\n        )\n\n        if code == status.HTTP_404_NOT_FOUND:\n            logger.info(f\"Subject or version not found: {code}\")\n            return False\n        elif code == status.HTTP_422_UNPROCESSABLE_ENTITY:\n            logger.info(f\"Unprocessable entity. Invalid subject or schema: {code}\")\n            return False\n        elif status.is_success(code):\n            if verbose:\n                return result\n            else:\n                return result.get(\"is_compatible\")\n\n        raise ClientError(\"Unable to check the compatibility\", http_code=code, server_traceback=result)\n\n    async def update_compatibility(\n        self,\n        level: str,\n        subject: typing.Optional[str] = None,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> bool:\n        \"\"\"Update the compatibility level.\n\n        If subject is None, the compatibility level is global.\n\n        PUT /config/(string: subject)\n\n        Args:\n            level: one of BACKWARD, BACKWARD_TRANSITIVE, FORWARD, FORWARD_TRANSITIVE,\n                FULL, FULL_TRANSITIVE, NONE\n            subject: Option subject\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            Whether the compatibility was updated\n\n        Raises:\n            ClientError: if the request was unsuccessful or an invalid\n        \"\"\"\n        if level not in utils.VALID_LEVELS:\n            raise ClientError(f\"Invalid level specified: {level}\")\n\n        url, method = self.url_manager.url_for(\"update_compatibility\", subject=subject)\n        body = {\"compatibility\": level}\n\n        result, code = get_response_and_status_code(\n            await self.request(url, method=method, body=body, headers=headers, timeout=timeout)\n        )\n\n        if status.is_success(code):\n            return True\n\n        raise ClientError(f\"Unable to update level: {level}.\", http_code=code, server_traceback=result)\n\n    async def get_compatibility(\n        self,\n        subject: typing.Optional[str] = None,\n        headers: typing.Optional[typing.Dict] = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> str:\n        \"\"\"Get the current compatibility level for a subject.\n\n        Args:\n            subject: subject name\n            headers: Extra headers to add on the requests\n            timeout: The timeout configuration to use when sending requests. Default USE_CLIENT_DEFAULT\n\n        Returns:\n            One of BACKWARD, BACKWARD_TRANSITIVE, FORWARD, FORWARD_TRANSITIVE,\n                FULL, FULL_TRANSITIVE, NONE\n\n        Raises:\n            ClientError: if the request was unsuccessful or an invalid\n                compatibility level was returned\n        \"\"\"\n        url, method = self.url_manager.url_for(\"get_compatibility\", subject=subject)\n        result, code = get_response_and_status_code(\n            await self.request(url, method=method, headers=headers, timeout=timeout)\n        )\n\n        if status.is_success(code):\n            compatibility = result.get(\"compatibilityLevel\")\n            if compatibility not in utils.VALID_LEVELS:\n                if compatibility is None:\n                    error_msg_suffix = \"No compatibility was returned\"\n                else:\n                    error_msg_suffix = str(compatibility)\n                raise ClientError(\n                    f\"Invalid compatibility level received: {error_msg_suffix}\",\n                    http_code=code,\n                    server_traceback=result,\n                )\n\n            return compatibility\n\n        raise ClientError(\n            f\"Unable to fetch compatibility level. Error code: {code}\",\n            http_code=code,\n            server_traceback=result,\n        )\n"
  },
  {
    "path": "schema_registry/client/errors.py",
    "content": "\"\"\"Exception exposed by the client module.\"\"\"\n\nimport typing\n\n\nclass ClientError(Exception):\n    \"\"\"Error thrown by Schema Registry client.\"\"\"\n\n    def __init__(\n        self,\n        message: str,\n        http_code: typing.Optional[int] = None,\n        server_traceback: typing.Optional[str] = None,\n    ) -> None:\n        \"\"\"Base class for all exceptions that occur when interacting with the registry API.\n\n        Args:\n            message: Message description\n            http_code: HTTP Code returned by the registry API. Defaults to None.\n            server_traceback: Server's traceback. Defaults to None.\n        \"\"\"\n        self.message = message\n        self.server_traceback = server_traceback\n        self.http_code = http_code\n        super().__init__(message)\n\n    def __repr__(self) -> str:\n        return f\"ClientError(error={self.message})\"\n\n    def __str__(self) -> str:\n        return self.message\n"
  },
  {
    "path": "schema_registry/client/paths.py",
    "content": "\"\"\"Available urls supported with the client.\"\"\"\n\npaths = [\n    (\"get_subjects\", \"subjects\", \"GET\"),\n    (\"get_versions\", \"subjects/{subject}/versions\", \"GET\"),\n    (\"delete_version\", \"subjects/{subject}/versions/{version}\", \"DELETE\"),\n    (\"register\", \"subjects/{subject}/versions\", \"POST\"),\n    (\"delete_subject\", \"subjects/{subject}\", \"DELETE\"),\n    (\"get_schema\", \"subjects/{subject}/versions/{version}\", \"GET\"),\n    (\"check_version\", \"subjects/{subject}\", \"POST\"),\n    (\"get_by_id\", \"schemas/ids/{schema_id}\", \"GET\"),\n    (\"get_schema_subject_versions\", \"schemas/ids/{schema_id}/versions\", \"GET\"),\n    (\n        \"test_compatibility\",\n        \"compatibility/subjects/{subject}/versions/{version}\",\n        \"POST\",\n    ),\n    (\"update_compatibility\", \"config/{subject}\", \"PUT\"),\n    (\"get_compatibility\", \"config/{subject}\", \"GET\"),\n]\n"
  },
  {
    "path": "schema_registry/client/schema.py",
    "content": "\"\"\"Class to wrap json or avro raw schema with generic methods.\"\"\"\n\nfrom __future__ import annotations\n\nimport json\nimport typing\nfrom abc import ABC, abstractmethod\nfrom dataclasses import dataclass\n\nimport anyio\nimport fastavro\nimport jsonschema\n\nfrom schema_registry.client.utils import AVRO_SCHEMA_TYPE, JSON_SCHEMA_TYPE\n\n\nclass BaseSchema(ABC):\n    \"\"\"Abstract class for schema wrapper\"\"\"\n\n    def __init__(self, schema: typing.Union[str, typing.Dict[str, typing.Any]]) -> None:\n        if isinstance(schema, str):\n            schema = json.loads(schema)\n        self.raw_schema = typing.cast(typing.Dict, schema)\n        self.schema = self.parse_schema(self.raw_schema)\n        self.generate_hash()\n\n    @abstractmethod\n    def parse_schema(self, schema: typing.Dict) -> typing.Dict:\n        pass\n\n    @staticmethod\n    @abstractmethod\n    def load(fp: str) -> BaseSchema:\n        \"\"\"Parse a schema from a file path.\"\"\"\n\n    @staticmethod\n    @abstractmethod\n    async def async_load(fp: str) -> BaseSchema:\n        \"\"\"Parse a schema from a file path.\"\"\"\n\n    @property\n    @abstractmethod\n    def name(self) -> typing.Optional[str]:\n        pass\n\n    @property\n    @abstractmethod\n    def schema_type(self) -> str:\n        pass\n\n    def generate_hash(self) -> None:\n        self._hash = hash(json.dumps(self.schema))\n\n    def __hash__(self) -> int:\n        return self._hash\n\n    def __str__(self) -> str:\n        return str(self.schema)\n\n    def __eq__(self, other: typing.Any) -> bool:\n        if not isinstance(other, BaseSchema):\n            return NotImplemented\n        return self.__hash__() == other.__hash__()\n\n\nclass AvroSchema(BaseSchema):\n    \"\"\"Integrate BaseSchema for Avro schema.\"\"\"\n\n    def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None:\n        self._expanded_schema: typing.Optional[typing.Dict] = None\n        self._flat_schema: typing.Optional[typing.Dict] = None\n\n        super().__init__(*args, **kwargs)\n\n    @property\n    def name(self) -> typing.Optional[str]:\n        return self.schema.get(\"name\")\n\n    @property\n    def schema_type(self) -> str:\n        return AVRO_SCHEMA_TYPE\n\n    @property\n    def expanded_schema(self) -> typing.Dict:\n        \"\"\"Returns a schema where all named types are expanded to their real schema.\n\n        Returns:\n            expanded_schema (typing.Dict): Schema parsed expanded\n        \"\"\"\n        if self._expanded_schema is None:\n            # NOTE: Dict expected when we pass a dict\n            self._expanded_schema = typing.cast(typing.Dict, fastavro.schema.expand_schema(self.raw_schema))\n        return self._expanded_schema\n\n    @property\n    def flat_schema(self) -> typing.Dict:\n        \"\"\"Parse the schema removing the fastavro write_hint flag __fastavro_parsed.\n\n        Returns:\n            flat_schema (typing.Dict): Schema parsed without the write hint\n        \"\"\"\n        if self._flat_schema is None:\n            # NOTE: Dict expected when we pass a dict\n            self._flat_schema = typing.cast(\n                typing.Dict,\n                fastavro.parse_schema(self.raw_schema, _write_hint=False, _force=True),\n            )\n\n        return self._flat_schema\n\n    def parse_schema(self, schema: typing.Dict) -> typing.Dict:\n        # NOTE: Dict expected when we pass a dict\n        return typing.cast(typing.Dict, fastavro.parse_schema(schema, _force=True))\n\n    @staticmethod\n    def load(fp: str) -> AvroSchema:\n        \"\"\"Parse an avro schema from a file path.\"\"\"\n        with open(fp, mode=\"r\") as f:\n            content = f.read()\n            return AvroSchema(content)\n\n    @staticmethod\n    async def async_load(fp: str) -> AvroSchema:\n        \"\"\"Parse an avro schema from a file path.\"\"\"\n        async with await anyio.open_file(fp, mode=\"r\") as f:\n            content = await f.read()\n            return AvroSchema(content)\n\n\nclass JsonSchema(BaseSchema):\n    \"\"\"Integrate BaseSchema for JSON schema.\"\"\"\n\n    @property\n    def name(self) -> typing.Optional[str]:\n        return self.schema.get(\"title\", self.schema.get(\"$id\", self.schema.get(\"$ref\")))\n\n    @property\n    def schema_type(self) -> str:\n        return JSON_SCHEMA_TYPE\n\n    def parse_schema(self, schema: typing.Dict) -> typing.Dict:\n        jsonschema.Draft7Validator.check_schema(schema)\n        return schema\n\n    @staticmethod\n    def load(fp: str) -> BaseSchema:\n        \"\"\"Parse a json schema from a file path.\"\"\"\n        with open(fp, mode=\"r\") as f:\n            content = f.read()\n            return JsonSchema(content)\n\n    @staticmethod\n    async def async_load(fp: str) -> BaseSchema:\n        \"\"\"Parse a json schema from a file path.\"\"\"\n        async with await anyio.open_file(fp, mode=\"r\") as f:\n            content = await f.read()\n            return JsonSchema(content)\n\n\nclass SchemaFactory:\n    \"\"\"Factory to generate Schema wrapper from the given schema and schema_type.\"\"\"\n\n    @staticmethod\n    def create_schema(\n        schema: typing.Union[str, typing.Dict[str, typing.Any]], schema_type: str\n    ) -> typing.Union[JsonSchema, AvroSchema]:\n        if schema_type == JSON_SCHEMA_TYPE:\n            return JsonSchema(schema)\n        elif schema_type == AVRO_SCHEMA_TYPE:\n            return AvroSchema(schema)\n        else:\n            raise ValueError(f\"Unsupported schema type '{schema_type}'. Supported schemas are 'AVRO' and 'JSON'.\")\n\n\n@dataclass\nclass SubjectVersion(object):\n    \"\"\"Represents information extracted from the registry.\"\"\"\n\n    subject: str\n    version: int\n"
  },
  {
    "path": "schema_registry/client/status.py",
    "content": "\"\"\"HTTP codes\nSee RFC 2616 - https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html\nAnd RFC 6585 - https://tools.ietf.org/html/rfc6585\nAnd RFC 4918 - https://tools.ietf.org/html/rfc4918\n\"\"\"\n\nHTTP_100_CONTINUE = 100\nHTTP_101_SWITCHING_PROTOCOLS = 101\nHTTP_200_OK = 200\nHTTP_201_CREATED = 201\nHTTP_202_ACCEPTED = 202\nHTTP_203_NON_AUTHORITATIVE_INFORMATION = 203\nHTTP_204_NO_CONTENT = 204\nHTTP_205_RESET_CONTENT = 205\nHTTP_206_PARTIAL_CONTENT = 206\nHTTP_207_MULTI_STATUS = 207\nHTTP_300_MULTIPLE_CHOICES = 300\nHTTP_301_MOVED_PERMANENTLY = 301\nHTTP_302_FOUND = 302\nHTTP_303_SEE_OTHER = 303\nHTTP_304_NOT_MODIFIED = 304\nHTTP_305_USE_PROXY = 305\nHTTP_306_RESERVED = 306\nHTTP_307_TEMPORARY_REDIRECT = 307\nHTTP_400_BAD_REQUEST = 400\nHTTP_401_UNAUTHORIZED = 401\nHTTP_402_PAYMENT_REQUIRED = 402\nHTTP_403_FORBIDDEN = 403\nHTTP_404_NOT_FOUND = 404\nHTTP_405_METHOD_NOT_ALLOWED = 405\nHTTP_406_NOT_ACCEPTABLE = 406\nHTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407\nHTTP_408_REQUEST_TIMEOUT = 408\nHTTP_409_CONFLICT = 409\nHTTP_410_GONE = 410\nHTTP_411_LENGTH_REQUIRED = 411\nHTTP_412_PRECONDITION_FAILED = 412\nHTTP_413_REQUEST_ENTITY_TOO_LARGE = 413\nHTTP_414_REQUEST_URI_TOO_LONG = 414\nHTTP_415_UNSUPPORTED_MEDIA_TYPE = 415\nHTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416\nHTTP_417_EXPECTATION_FAILED = 417\nHTTP_422_UNPROCESSABLE_ENTITY = 422\nHTTP_423_LOCKED = 423\nHTTP_424_FAILED_DEPENDENCY = 424\nHTTP_428_PRECONDITION_REQUIRED = 428\nHTTP_429_TOO_MANY_REQUESTS = 429\nHTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431\nHTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451\nHTTP_500_INTERNAL_SERVER_ERROR = 500\nHTTP_501_NOT_IMPLEMENTED = 501\nHTTP_502_BAD_GATEWAY = 502\nHTTP_503_SERVICE_UNAVAILABLE = 503\nHTTP_504_GATEWAY_TIMEOUT = 504\nHTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505\nHTTP_507_INSUFFICIENT_STORAGE = 507\nHTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511\n\n\ndef is_informational(code: int) -> bool:\n    return HTTP_100_CONTINUE <= code < HTTP_200_OK\n\n\ndef is_success(code: int) -> bool:\n    return HTTP_200_OK <= code < HTTP_300_MULTIPLE_CHOICES\n\n\ndef is_redirect(code: int) -> bool:\n    return HTTP_300_MULTIPLE_CHOICES <= code < HTTP_400_BAD_REQUEST\n\n\ndef is_client_error(code: int) -> bool:\n    return HTTP_400_BAD_REQUEST <= code < HTTP_500_INTERNAL_SERVER_ERROR\n\n\ndef is_server_error(code: int) -> bool:\n    return HTTP_500_INTERNAL_SERVER_ERROR <= code <= 599\n"
  },
  {
    "path": "schema_registry/client/urls.py",
    "content": "\"\"\"Module to handle client registry url.\"\"\"\n\nimport typing\nimport urllib\nfrom collections import defaultdict\n\n\nclass UrlManager:\n    \"\"\"Instantiate url related and Path related to the registry client.\"\"\"\n\n    def __init__(self, base_url: str, paths: typing.List[typing.Tuple[str, str, str]]) -> None:\n        parsed_url = urllib.parse.urlparse(base_url)\n\n        assert parsed_url.scheme in (  # noqa: S101\n            \"http\",\n            \"https\",\n        ), f\"The url {base_url} has invalid schema. Use http or https. For example http://{base_url}\"\n\n        # this is the absolute url to the server\n        # make sure that url ends with /\n        if not base_url.endswith(\"/\"):\n            base_url += \"/\"\n\n        self.base_url = base_url\n\n        self.paths = {path.name: path for path in (Path(path) for path in paths)}\n\n    @property\n    def url(self) -> str:\n        \"\"\"Return base url.\"\"\"\n        return self.base_url\n\n    def url_for(self, func: str, **kwargs: typing.Any) -> tuple:\n        \"\"\"Generate a url for a given function.\"\"\"\n        path = self.paths[func]\n        url = path.generate_url(**kwargs)\n\n        return urllib.parse.urljoin(self.base_url, url), path.method\n\n\nclass Path:\n    \"\"\"Associate an action related to a path & method.\"\"\"\n\n    def __init__(self, path: typing.Tuple[str, str, str]) -> None:\n        self.func = path[0]\n        self.url = path[1]\n        self.method = path[2]\n\n    @property\n    def name(self) -> str:\n        return self.func\n\n    def generate_url(self, **kwargs: typing.Any) -> str:\n        parameters = {key: value for key, value in kwargs.items() if value}\n\n        return self.url.format_map(defaultdict(str, **parameters))\n"
  },
  {
    "path": "schema_registry/client/utils.py",
    "content": "\"\"\"Regroups global constants.\"\"\"\n\nimport typing\nfrom collections import namedtuple\n\nSchemaVersion = namedtuple(\"SchemaVersion\", \"subject schema_id schema version\")\n\nBACKWARD = \"BACKWARD\"\nBACKWARD_TRANSITIVE = \"BACKWARD_TRANSITIVE\"\nFORWARD = \"FORWARD\"\nFORWARD_TRANSITIVE = \"FORWARD_TRANSITIVE\"\nFULL = \"FULL\"\nFULL_TRANSITIVE = \"FULL_TRANSITIVE\"\nNONE = \"NONE\"\n\nVALID_LEVELS = (\n    BACKWARD,\n    BACKWARD_TRANSITIVE,\n    FORWARD,\n    FORWARD_TRANSITIVE,\n    FULL,\n    FULL_TRANSITIVE,\n    NONE,\n)\nVALID_METHODS = (\"GET\", \"POST\", \"PUT\", \"DELETE\")\nVALID_AUTH_PROVIDERS = (\n    \"URL\",\n    \"USER_INFO\",\n)\n\nHEADER_AVRO_JSON = \"application/x-avro-json\"\nHEADER_AVRO = \"application/avro\"\nHEADER_APPLICATION_JSON = \"application/json\"\nHEADERS = \"application/vnd.schemaregistry.v1+json\"\nACCEPT_HEADERS = \"application/vnd.schemaregistry.v1+json, application/vnd.schemaregistry+json, application/json\"\n\nURL = \"url\"\nSSL_CA_LOCATION = \"ssl.ca.location\"\nSSL_CERTIFICATE_LOCATION = \"ssl.certificate.location\"\nSSL_KEY_LOCATION = \"ssl.key.location\"\nSSL_KEY_PASSWORD = \"ssl.key.password\"  # noqa: S105\n\nAVRO_SCHEMA_TYPE: typing.Literal[\"AVRO\"] = \"AVRO\"\nJSON_SCHEMA_TYPE: typing.Literal[\"JSON\"] = \"JSON\"\n"
  },
  {
    "path": "schema_registry/py.typed",
    "content": ""
  },
  {
    "path": "schema_registry/serializers/__init__.py",
    "content": "from schema_registry.serializers.message_serializer import AsyncAvroMessageSerializer  # noqa\nfrom schema_registry.serializers.message_serializer import AsyncJsonMessageSerializer  # noqa\nfrom schema_registry.serializers.message_serializer import AsyncMessageSerializer  # noqa\nfrom schema_registry.serializers.message_serializer import AvroMessageSerializer  # noqa\nfrom schema_registry.serializers.message_serializer import JsonMessageSerializer  # noqa\nfrom schema_registry.serializers.message_serializer import MessageSerializer  # noqa\n"
  },
  {
    "path": "schema_registry/serializers/errors.py",
    "content": "\"\"\"Exception exposed by the serializers module.\"\"\"\n\n\nclass SerializerError(Exception):\n    \"\"\"Generic error from serializer package.\"\"\"\n\n    def __init__(self, message: str) -> None:\n        self.message = message\n        super().__init__(message)\n\n    def __repr__(self) -> str:\n        return f\"{self.__class__.__name__}(error={self.message})\"\n\n    def __str__(self) -> str:\n        return self.message\n"
  },
  {
    "path": "schema_registry/serializers/faust.py",
    "content": "\"\"\"Faust serializers.\"\"\"\n\nimport typing\nfrom collections.abc import Mapping, Sequence\n\nfrom schema_registry.client import SchemaRegistryClient\nfrom schema_registry.client.schema import AvroSchema, BaseSchema, JsonSchema\nfrom schema_registry.serializers import (\n    AvroMessageSerializer,\n    JsonMessageSerializer,\n    MessageSerializer,\n)\n\ntry:\n    from faust import Codec, Record\nexcept ImportError as ex:\n    raise Exception(\"Cannot use Faust serializers Faust is not installed.\") from ex\n\n\nclass Serializer(Codec):\n    \"\"\"Generic serializer for Faust.\"\"\"\n\n    def __init__(\n        self,\n        schema_subject: str,\n        schema: BaseSchema,\n        message_serializer: MessageSerializer,\n    ):\n        self.schema_subject = schema_subject\n        self.schema = schema\n        self.message_serializer = message_serializer\n\n        Codec.__init__(self)\n\n    def _loads(self, event: bytes) -> typing.Optional[typing.Dict]:\n        return self.message_serializer.decode_message(event)\n\n    def _dumps(self, payload: typing.Dict[str, typing.Any]) -> bytes:\n        \"\"\"Given a parsed avro schema, encode a record for the given topic.\n\n        The record is expected to be a dictionary.\n        The schema is registered with the subject of 'topic-value'\n        \"\"\"\n        payload = self.clean_payload(payload)\n\n        return self.message_serializer.encode_record_with_schema(self.schema_subject, self.schema, payload)\n\n    @staticmethod\n    def _clean_item(item: typing.Any) -> typing.Any:\n        if isinstance(item, Record):\n            return Serializer._clean_item(item.to_representation())\n        elif isinstance(item, str):\n            # str is also a sequence, need to make sure we don't iterate over it.\n            return item\n        elif isinstance(item, Mapping):\n            return type(item)({key: Serializer._clean_item(value) for key, value in item.items()})  # type: ignore\n        elif isinstance(item, Sequence):\n            return type(item)(Serializer._clean_item(value) for value in item)  # type: ignore\n        return item\n\n    @staticmethod\n    def clean_payload(\n        payload: typing.Dict[str, typing.Any],\n    ) -> typing.Dict[str, typing.Any]:\n        \"\"\"Try to clean payload retrieve by faust.Record.to_representation.\n\n        All values inside payload should be native types and not faust.Record\n        On Faust versions <=1.9.0 Record.to_representation always returns a dict with native types\n        as a values which are compatible with fastavro.\n        On Faust 1.10.0 <= versions Record.to_representation always returns a dic but values\n        can also be faust.Record, so fastavro is incapable of serialize them.\n\n        Args:\n            payload (dict): Payload to clean\n\n        Returns:\n            dict that represents the clean payload\n        \"\"\"\n        return Serializer._clean_item(payload)\n\n\ndef avro_serializer_factory(\n    schema_registry_client: SchemaRegistryClient,\n    schema_subject: str,\n    schema: typing.Union[AvroSchema, str, typing.Dict[str, typing.Any]],\n    return_record_name: bool = False,\n) -> \"Serializer\":  # type: ignore # noqa: F821\n    if isinstance(schema, str) or isinstance(schema, dict):\n        schema = AvroSchema(schema)\n\n    return Serializer(\n        schema_subject,\n        schema,\n        AvroMessageSerializer(schema_registry_client, return_record_name=return_record_name),\n    )\n\n\ndef json_serializer_factory(\n    schema_registry_client: SchemaRegistryClient,\n    schema_subject: str,\n    schema: typing.Union[JsonSchema, str, typing.Dict[str, typing.Any]],\n    return_record_name: bool = False,\n) -> \"Serializer\":  # type: ignore # noqa: F821\n    if isinstance(schema, str) or isinstance(schema, dict):\n        schema = JsonSchema(schema)\n\n    return Serializer(\n        schema_subject,\n        schema,\n        JsonMessageSerializer(schema_registry_client, return_record_name=return_record_name),\n    )\n\n\nFaustSerializer = avro_serializer_factory\nFaustJsonSerializer = json_serializer_factory\n"
  },
  {
    "path": "schema_registry/serializers/message_serializer.py",
    "content": "\"\"\"Defines serializer for serlizing and deserializing messages\"\"\"\n\nimport io\nimport json\nimport logging\nimport struct\nimport sys\nimport traceback\nimport typing\nfrom abc import ABC, abstractmethod\n\nfrom fastavro import schemaless_reader, schemaless_writer\nfrom fastavro.types import Schema\nfrom jsonschema import validate\n\nfrom schema_registry.client import (\n    AsyncSchemaRegistryClient,\n    SchemaRegistryClient,\n    schema,\n    utils,\n)\nfrom schema_registry.client.errors import ClientError\nfrom schema_registry.client.schema import BaseSchema\n\nfrom .errors import SerializerError\n\nlog = logging.getLogger(__name__)\n\nMAGIC_BYTE = 0\n\n\nclass ContextStringIO(io.BytesIO):\n    \"\"\"Wrapper to allow use of StringIO via 'with' constructs.\"\"\"\n\n    def __enter__(self) -> \"ContextStringIO\":\n        return self\n\n    def __exit__(self, *args: typing.Any) -> None:\n        self.close()\n\n\nclass MessageSerializer(ABC):\n    \"\"\"A helper class that can serialize and deserialize messages asynchronously.\n\n    Args:\n        schemaregistry_client: Http Client\n        reader_schema: Specify a schema to decode the message\n        return_record_name: If the record name should be returned\n    \"\"\"\n\n    def __init__(\n        self,\n        schemaregistry_client: SchemaRegistryClient,\n        reader_schema: typing.Optional[schema.AvroSchema] = None,\n        return_record_name: bool = False,\n    ):\n        self.schemaregistry_client = schemaregistry_client\n        self.id_to_decoder_func = {}  # type: typing.Dict\n        self.id_to_writers = {}  # type: typing.Dict\n        self.reader_schema = reader_schema\n        self.return_record_name = return_record_name\n\n    @property\n    @abstractmethod\n    def _serializer_schema_type(self) -> typing.Literal[\"AVRO\", \"JSON\"]: ...\n\n    @abstractmethod\n    def _get_encoder_func(self, schema: BaseSchema) -> typing.Callable: ...\n\n    @abstractmethod\n    def _get_decoder_func(self, payload: ContextStringIO, writer_schema: BaseSchema) -> typing.Callable: ...\n\n    def encode_record_with_schema(\n        self, subject: str, schema: BaseSchema, record: typing.Dict[str, typing.Any]\n    ) -> bytes:\n        \"\"\"Given a parsed avro schema, encode a record for the given subject.\n\n        The schema is registered with the subject of 'topic-value'\n        The record is expected to be a dictionary.\n\n        Args:\n            subject: Subject name\n            schema: Avro Schema\n            record: An object to serialize\n\n        Returns:\n            Encoded record with schema ID as bytes\n        \"\"\"\n        # Try to register the schema\n        schema_id = self.schemaregistry_client.register(subject, schema, schema_type=self._serializer_schema_type)\n\n        # cache writer\n        if not self.id_to_writers.get(schema_id):\n            self.id_to_writers[schema_id] = self._get_encoder_func(schema)\n\n        return self.encode_record_with_schema_id(schema_id, record)\n\n    def encode_record_with_schema_id(self, schema_id: int, record: dict) -> bytes:\n        \"\"\"Encode a record with a given schema id. The record must be a python dictionary.\n\n        Args:\n            schema_id: integer ID\n            record: An object to serialize\n\n        Returns:\n            Decoder function\n        \"\"\"\n        # use slow avro\n        if schema_id not in self.id_to_writers:\n            try:\n                schema = self.schemaregistry_client.get_by_id(schema_id)\n                if not schema:\n                    raise SerializerError(\"Schema does not exist\")\n                self.id_to_writers[schema_id] = self._get_encoder_func(schema)\n            except ClientError as err:\n                exc_type, exc_value, exc_traceback = sys.exc_info()\n                raise SerializerError(repr(traceback.format_exception(exc_type, exc_value, exc_traceback))) from err\n\n        writer = self.id_to_writers[schema_id]\n        with ContextStringIO() as outf:\n            # Write the magic byte and schema ID in network byte order (big endian)\n            outf.write(struct.pack(\">bI\", MAGIC_BYTE, schema_id))\n\n            # write the record to the rest of the buffer\n            writer(record, outf)\n\n            return outf.getvalue()\n\n    def decode_message(self, message: typing.Optional[bytes]) -> typing.Optional[dict]:\n        \"\"\"Decode a message from kafka that has been encoded for use with the schema registry.\n\n        Args:\n            message: message key or value to be decoded\n\n        Returns:\n            Decoded message contents.\n        \"\"\"\n        if message is None:\n            return None\n\n        if len(message) <= 5:\n            raise SerializerError(\"message is too small to decode\")\n\n        with ContextStringIO(message) as payload:\n            magic, schema_id = struct.unpack(\">bI\", payload.read(5))\n            if magic != MAGIC_BYTE:\n                raise SerializerError(\"message does not start with magic byte\")\n\n            if schema_id in self.id_to_decoder_func:\n                return self.id_to_decoder_func[schema_id](payload)\n\n            try:\n                writer_schema = self.schemaregistry_client.get_by_id(schema_id)\n            except ClientError as e:\n                raise SerializerError(f\"unable to fetch schema with id {schema_id}: {e}\") from e\n\n            if writer_schema is None:\n                raise SerializerError(f\"unable to fetch schema with id {schema_id}\")\n\n            decoder_func = self._get_decoder_func(payload, writer_schema)\n            self.id_to_decoder_func[schema_id] = decoder_func\n\n            return decoder_func(payload)\n\n\nclass AvroMessageSerializer(MessageSerializer):\n    \"\"\"AvroMessageSerializer to serialize and deserialize messages.\n\n    !!! Example\n        ```python\n        from schema_registry.client import SchemaRegistryClient, schema\n        from schema_registry.serializers import AvroMessageSerializer\n\n\n        client = SchemaRegistryClient(\"http://127.0.0.1:8081\")\n        avro_message_serializer = AvroMessageSerializer(client)\n\n        avro_user_schema = schema.AvroSchema({\n            \"type\": \"record\",\n            \"namespace\": \"com.example\",\n            \"name\": \"AvroUsers\",\n            \"fields\": [\n                {\"name\": \"first_name\", \"type\": \"string\"},\n                {\"name\": \"last_name\", \"type\": \"string\"},\n                {\"name\": \"age\", \"type\": \"int\"},\n\n            ],\n        })\n\n        # We want to encode the user_record with avro_user_schema\n        user_record = {\n            \"first_name\": \"my_first_name\",\n            \"last_name\": \"my_last_name\",\n            \"age\": 20,\n        }\n\n        # Encode the record\n        message_encoded = avro_message_serializer.encode_record_with_schema(\n            \"user\", avro_user_schema, user_record)\n\n        # this is because the message encoded reserved 5 bytes for the schema_id\n        assert len(message_encoded) > 5\n        assert isinstance(message_encoded, bytes)\n\n        # Decode the message\n        message_decoded = avro_message_serializer.decode_message(message_encoded)\n        assert message_decoded == user_record\n\n        # Now if we send a bad record\n        bad_record = {\n            \"first_name\": \"my_first_name\",\n            \"last_name\": \"my_last_name\",\n            \"age\": \"my_age\"\n        }\n\n        avro_message_serializer.encode_record_with_schema(\n            \"user\", avro_user_schema, bad_record)\n\n        # >>> TypeError: an integer is required on field age\n        ```\n\n    Args:\n        schemaregistry_client: Http Client\n        reader_schema: Specify a schema to decode the message\n        return_record_name: If the record name should be returned\n    \"\"\"\n\n    @property\n    def _serializer_schema_type(self) -> typing.Literal[\"AVRO\", \"JSON\"]:\n        return utils.AVRO_SCHEMA_TYPE\n\n    def _get_encoder_func(self, schema: typing.Union[BaseSchema]) -> typing.Callable:\n        return lambda record, fp: schemaless_writer(fp, schema.schema, record)\n\n    def _get_decoder_func(self, payload: ContextStringIO, writer_schema: BaseSchema) -> typing.Callable:\n        return lambda payload: schemaless_reader(\n            payload,\n            writer_schema.schema,\n            typing.cast(Schema, self.reader_schema),\n            self.return_record_name,\n        )\n\n\nclass JsonMessageSerializer(MessageSerializer):\n    \"\"\"JsonMessageSerializer to serialize and deserialize messages.\n\n    !!! Example\n\n        ```python\n        from schema_registry.client import SchemaRegistryClient, schema\n        from schema_registry.serializers import JsonMessageSerializer\n\n\n        client = SchemaRegistryClient(\"http://127.0.0.1:8081\")\n        json_message_serializer = JsonMessageSerializer(client)\n\n        json_schema = schema.JsonSchema({\n        \"definitions\" : {\n            \"record:python.test.basic.basic\" : {\n            \"description\" : \"basic schema for tests\",\n            \"type\" : \"object\",\n            \"required\" : [ \"number\", \"name\" ],\n            \"properties\" : {\n                \"number\" : {\n                \"oneOf\" : [ {\n                    \"type\" : \"integer\"\n                }, {\n                    \"type\" : \"null\"\n                } ]\n                },\n                \"name\" : {\n                \"oneOf\" : [ {\n                    \"type\" : \"string\"\n                } ]\n                }\n            }\n            }\n        },\n        \"$ref\" : \"#/definitions/record:python.test.basic.basic\"\n        })\n\n        # Encode the record\n        basic_record = {\n            \"number\": 10,\n            \"name\": \"a_name\",\n        }\n\n        message_encoded = json_message_serializer.encode_record_with_schema(\n            \"basic\", json_schema, basic_record)\n\n        # this is because the message encoded reserved 5 bytes for the schema_id\n        assert len(message_encoded) > 5\n        assert isinstance(message_encoded, bytes)\n\n        # Decode the message\n        message_decoded = json_message_serializer.decode_message(message_encoded)\n        assert message_decoded == basic_record\n        ```\n\n    Args:\n        schemaregistry_client: Http Client\n        reader_schema: Specify a schema to decode the message\n        return_record_name: If the record name should be returned\n    \"\"\"\n\n    @property\n    def _serializer_schema_type(self) -> typing.Literal[\"AVRO\", \"JSON\"]:\n        return utils.JSON_SCHEMA_TYPE\n\n    def _get_encoder_func(self, schema: typing.Union[BaseSchema]) -> typing.Callable:\n        def json_encoder_func(record: dict, fp: ContextStringIO) -> typing.Any:\n            validate(record, schema.schema)\n            fp.write(json.dumps(record).encode())\n\n        return json_encoder_func\n\n    def _get_decoder_func(self, payload: ContextStringIO, writer_schema: BaseSchema) -> typing.Callable:\n        def json_decoder_func(payload: typing.IO) -> typing.Any:\n            obj = json.load(payload)\n            validate(obj, writer_schema.schema)\n            return obj\n\n        return json_decoder_func\n\n\nclass AsyncMessageSerializer(ABC):\n    \"\"\"AsyncMessageSerializer to serialize and deserialize messages asynchronously.\n\n    Args:\n        schemaregistry_client: Http Client\n        reader_schema: Specify a schema to decode the message\n        return_record_name: If the record name should be returned\n    \"\"\"\n\n    def __init__(\n        self,\n        schemaregistry_client: AsyncSchemaRegistryClient,\n        reader_schema: typing.Optional[schema.AvroSchema] = None,\n        return_record_name: bool = False,\n    ):\n        self.schemaregistry_client = schemaregistry_client\n        self.id_to_decoder_func = {}  # type: typing.Dict\n        self.id_to_writers = {}  # type: typing.Dict\n        self.reader_schema = reader_schema\n        self.return_record_name = return_record_name\n\n    @property\n    @abstractmethod\n    def _serializer_schema_type(self) -> typing.Literal[\"AVRO\", \"JSON\"]: ...\n\n    @abstractmethod\n    def _get_encoder_func(self, schema: BaseSchema) -> typing.Callable: ...\n\n    @abstractmethod\n    def _get_decoder_func(self, payload: ContextStringIO, writer_schema: BaseSchema) -> typing.Callable: ...\n\n    async def encode_record_with_schema(self, subject: str, schema: typing.Union[BaseSchema], record: dict) -> bytes:\n        \"\"\"Given a parsed avro schema, encode a record for the given subject.\n\n        The record is expected to be a dictionary.\n        The schema is registered with the subject of 'topic-value'\n\n        Args:\n            subject: Subject name\n            schema: Avro Schema\n            record: An object to serialize\n\n        Returns:\n            Encoded record with schema ID\n        \"\"\"\n        # Try to register the schema\n        schema_id = await self.schemaregistry_client.register(subject, schema, schema_type=self._serializer_schema_type)\n\n        # cache writer\n        if not self.id_to_writers.get(schema_id):\n            self.id_to_writers[schema_id] = self._get_encoder_func(schema)\n\n        return await self.encode_record_with_schema_id(schema_id, record)\n\n    async def encode_record_with_schema_id(self, schema_id: int, record: dict) -> bytes:\n        \"\"\"Encode a record with a given schema id. The record must be a python dictionary.\n\n        Args:\n            schema_id: integer ID\n            record: An object to serialize\n\n        Returns:\n            Decoder function\n        \"\"\"\n        # use slow avro\n        if schema_id not in self.id_to_writers:\n            try:\n                schema = await self.schemaregistry_client.get_by_id(schema_id)\n\n                if not schema:\n                    raise SerializerError(\"Schema does not exist\")\n                self.id_to_writers[schema_id] = self._get_encoder_func(schema)\n            except ClientError as err:\n                exc_type, exc_value, exc_traceback = sys.exc_info()\n                raise SerializerError(repr(traceback.format_exception(exc_type, exc_value, exc_traceback))) from err\n\n        writer = self.id_to_writers[schema_id]\n        with ContextStringIO() as outf:\n            # Write the magic byte and schema ID in network byte order (big endian)\n            outf.write(struct.pack(\">bI\", MAGIC_BYTE, schema_id))\n\n            # write the record to the rest of the buffer\n            writer(record, outf)\n\n            return outf.getvalue()\n\n    async def decode_message(self, message: typing.Optional[bytes]) -> typing.Optional[dict]:\n        \"\"\"Decode a message from kafka that has been encoded for use with the schema registry.\n\n        Args:\n            message: message key or value to be decoded\n\n        Returns:\n            Decoded message\n        \"\"\"\n        if message is None:\n            return None\n\n        if len(message) <= 5:\n            raise SerializerError(\"message is too small to decode\")\n\n        with ContextStringIO(message) as payload:\n            magic, schema_id = struct.unpack(\">bI\", payload.read(5))\n            if magic != MAGIC_BYTE:\n                raise SerializerError(\"message does not start with magic byte\")\n\n            if schema_id in self.id_to_decoder_func:\n                return self.id_to_decoder_func[schema_id](payload)\n\n            try:\n                writer_schema = await self.schemaregistry_client.get_by_id(schema_id)\n            except ClientError as e:\n                raise SerializerError(f\"unable to fetch schema with id {schema_id}: {e}\") from e\n\n            if writer_schema is None:\n                raise SerializerError(f\"unable to fetch schema with id {schema_id}\")\n\n            decoder_func = self._get_decoder_func(payload, writer_schema)\n            self.id_to_decoder_func[schema_id] = decoder_func\n\n            return decoder_func(payload)\n\n\nclass AsyncAvroMessageSerializer(AsyncMessageSerializer):\n    \"\"\"AsyncAvroMessageSerializer to serialize and deserialize messages asynchronously.\n\n    Args:\n        schemaregistry_client: Http Client\n        reader_schema: Specify a schema to decode the message\n        return_record_name: If the record name should be returned\n    \"\"\"\n\n    @property\n    def _serializer_schema_type(self) -> typing.Literal[\"AVRO\", \"JSON\"]:\n        return utils.AVRO_SCHEMA_TYPE\n\n    def _get_encoder_func(self, schema: typing.Union[BaseSchema]) -> typing.Callable:\n        return lambda record, fp: schemaless_writer(fp, schema.schema, record)\n\n    def _get_decoder_func(self, payload: ContextStringIO, writer_schema: BaseSchema) -> typing.Callable:\n        return lambda payload: schemaless_reader(\n            payload,\n            writer_schema.schema,\n            typing.cast(Schema, self.reader_schema),\n            self.return_record_name,\n        )\n\n\nclass AsyncJsonMessageSerializer(AsyncMessageSerializer):\n    \"\"\"AsyncJsonMessageSerializer to serialize and deserialize messages asynchronously.\n\n    Args:\n        schemaregistry_client: Http Client\n        reader_schema: Specify a schema to decode the message\n        return_record_name: If the record name should be returned\n    \"\"\"\n\n    @property\n    def _serializer_schema_type(self) -> typing.Literal[\"AVRO\", \"JSON\"]:\n        return utils.JSON_SCHEMA_TYPE\n\n    def _get_encoder_func(self, schema: typing.Union[BaseSchema]) -> typing.Callable:\n        def json_encoder_func(record: dict, fp: ContextStringIO) -> typing.Any:\n            validate(record, schema.schema)\n            fp.write(json.dumps(record).encode())\n\n        return json_encoder_func\n\n    def _get_decoder_func(self, payload: ContextStringIO, writer_schema: BaseSchema) -> typing.Callable:\n        def json_decoder_func(payload: typing.IO) -> typing.Any:\n            obj = json.load(payload)\n            validate(obj, writer_schema.schema)\n            return obj\n\n        return json_decoder_func\n"
  },
  {
    "path": "scripts/README.md",
    "content": "# Development Scripts\n\n* `scripts/run-tests` - Run the test suite\n* `scripts/lint` - Run the code linting\n* `scripts/publish` - Publish the latest version to PyPI and deploy github pages\n* `scripts/clean` - Clean annoying files, remove docker containers and network\n* `scripts/create-tag` - Create git tag un publish it\n* `scripts/install` - Install dependencies\n"
  },
  {
    "path": "scripts/clean",
    "content": "#!/bin/bash -e\n\nif [ -d 'dist' ] ; then\n    rm -rf dist\nfi\n\nif [ -d 'site' ] ; then\n    rm -rf site\nfi\n\nif [ -d 'python_schema_registry_client.egg-info' ] ; then\n    rm -rf python_schema_registry_client.egg-info\nfi\n\n# delete python cache\nfind . -iname '*.pyc' -delete\nfind . -iname '__pycache__' -delete\n\ndocker-compose stop\nyes | docker-compose rm\n"
  },
  {
    "path": "scripts/format",
    "content": "#!/bin/bash -e\n\nset -x\n\npoetry run ruff format schema_registry tests\npoetry run ruff check schema_registry tests --fix\n"
  },
  {
    "path": "scripts/publish",
    "content": "#!/bin/bash -e\n\npoetry publish --build -u $TWINE_USERNAME -p $TWINE_PASSWORD\n"
  },
  {
    "path": "scripts/test",
    "content": "#!/bin/bash -e\n\nset -o errexit\n\nexport SCHEMA_REGISTRY_URL=http://localhost:8081\nexport KAFKA_BOOSTRAP_SERVER_NAME=127.0.0.1\nexport KAFKA_BOOSTRAP_SERVER_PORT=9092\nexport SCHEMA_REGISTRY_SERVER=127.0.0.1\nexport SCHEMA_REGISTRY_SERVER_PORT=8081\n\n./scripts/wait_for_services\n\npoetry run pytest --cov=schema_registry ${1} --cov-fail-under=87\npoetry run ruff check schema_registry tests\npoetry run ruff format --check schema_registry tests\npoetry run mypy schema_registry\npoetry run codecov --token=$CODECOV_TOKEN\n"
  },
  {
    "path": "scripts/wait_for_services",
    "content": "#!/bin/bash\nset -e\n\ncmd=\"$@\"\n\nuntil nc -vz ${KAFKA_BOOSTRAP_SERVER_NAME} ${KAFKA_BOOSTRAP_SERVER_PORT}; do\n  >&2 echo \"Waiting for Kafka to be ready... - sleeping\"\n  sleep 2\ndone\n\n>&2 echo \"Kafka is up\"\n\nuntil nc -vz ${SCHEMA_REGISTRY_SERVER} ${SCHEMA_REGISTRY_SERVER_PORT}; do\n  >&2 echo \"Waiting for Schema Registry to be ready... - sleeping\"\n  sleep 2\ndone\n\n>&2 echo \"Schema Registry is up\"\n\necho \"Executing command ${cmd}\"\nexec $cmd\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/avro_schemas/adv_schema.avsc",
    "content": "{\n    \"type\": \"record\",\n    \"doc\": \"advanced schema for tests\",\n    \"name\": \"advanced\",\n    \"namespace\": \"python.test.advanced\",\n    \"fields\": [\n        {\n            \"name\": \"number\",\n            \"doc\": \"age\",\n            \"type\": [\n                \"long\",\n                \"null\"\n            ]\n        },\n        {\n            \"name\": \"name\",\n            \"doc\": \"a name\",\n            \"type\": [\n                \"string\"\n            ]\n        },\n        {\n            \"name\": \"friends\",\n            \"doc\": \"friends\",\n            \"type\" : {\n                \"type\": \"map\",\n                \"values\" : {\n                    \"name\": \"basicPerson\",\n                    \"type\": \"record\",\n                    \"namespace\": \"python.test.advanced\",\n                    \"fields\": [\n                        {\n                            \"name\": \"number\",\n                            \"doc\": \"friend age\",\n                            \"type\": [\n                                \"long\",\n                                \"null\"\n                            ]\n                        },\n                        {\n                            \"name\": \"name\",\n                            \"doc\": \"friend name\",\n                            \"type\": [\n                                \"string\"\n                            ]\n                        }\n                    ]\n                }\n            }\n        },\n        {\n            \"name\" : \"family\",\n            \"doc\" : \"family\",\n            \"type\" : {\n                \"namespace\" : \"python.test.advanced\",\n                \"type\" : \"map\",\n                \"values\" : \"basicPerson\"\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "tests/avro_schemas/basic_schema.avsc",
    "content": "{\n    \"name\": \"basic\",\n    \"type\": \"record\",\n    \"doc\": \"basic schema for tests\",\n    \"namespace\": \"python.test.basic\",\n    \"fields\": [\n        {\n            \"name\": \"number\",\n            \"doc\": \"age\",\n            \"type\": [\n                \"long\",\n                \"null\"\n            ]\n        },\n        {\n            \"name\": \"name\",\n            \"doc\": \"a name\",\n            \"type\": [\n                \"string\"\n            ]\n        }\n    ]\n}"
  },
  {
    "path": "tests/avro_schemas/invalid_schema.avsc",
    "content": "{\n  \"type\" : \"record\",\n  \"name\" : \"string_key\",\n  \"namespace\" : \"OrbitDbProducer\",\n  \"fields\" : [ {\n    \"name\" : \"key\",\n    \"type\" : \"array\",\n    \"items\": \"string\"\n  } ]\n}\n"
  },
  {
    "path": "tests/avro_schemas/logical_types_schema.avsc",
    "content": "{\n  \"type\": \"record\",\n  \"name\": \"visit\",\n  \"fields\": [\n    {\n      \"name\": \"metadata\",\n      \"type\": {\n        \"type\": \"record\",\n        \"name\": \"metadata_record\",\n        \"fields\": [\n          {\n            \"name\": \"timestamp\",\n            \"type\": {\n              \"type\": \"long\",\n              \"logicalType\": \"timestamp-millis\"\n            }\n          },\n          {\n            \"name\": \"total\",\n            \"type\": {\n              \"type\": \"bytes\",\n              \"logicalType\": \"decimal\",\n              \"precision\": 4,\n              \"scale\": 2\n            }\n          }\n        ]\n      }\n    }\n  ]\n}"
  },
  {
    "path": "tests/avro_schemas/nested_schema.avsc",
    "content": "{\n    \"name\": \"Customer\",\n    \"namespace\": \"com.questanalytics.core\",\n    \"type\": \"record\",\n    \"fields\": [\n        {\n            \"name\": \"uid\",\n            \"type\": \"int\",\n            \"default\": 10\n        },\n        {\n            \"name\": \"order\",\n            \"type\": {\n                \"name\": \"OrderRecord\",\n                \"namespace\": \"com.questanalytics.core\",\n                \"type\": \"record\",\n                \"fields\": [{\n                    \"name\": \"uid\",\n                    \"type\": \"int\",\n                    \"default\": 10\n                }]\n            }\n        },\n        {\n            \"name\": \"name\",\n            \"type\": \"string\",\n            \"default\": \"bond\"\n        }\n    ]\n}"
  },
  {
    "path": "tests/avro_schemas/order_schema.avsc",
    "content": "{\n    \"type\": \"record\",\n    \"name\": \"Order\",\n    \"aliases\": [\"Order\"],\n    \"fields\": [\n        {\"name\": \"uid\", \"type\": \"int\"}\n    ]\n}\n   "
  },
  {
    "path": "tests/avro_schemas/primitive_float.avsc",
    "content": "{\n    \"type\": \"float\"\n}\n"
  },
  {
    "path": "tests/avro_schemas/primitive_string.avsc",
    "content": "{\n    \"type\": \"string\"\n}\n"
  },
  {
    "path": "tests/avro_schemas/user_v1.avsc",
    "content": "{\n \"type\": \"record\",\n \"name\": \"UserKey\",\n \"aliases\": [\"User\"],\n \"fields\": [\n     {\"name\": \"name\", \"type\": \"string\"}\n ]\n}\n"
  },
  {
    "path": "tests/avro_schemas/user_v2.avsc",
    "content": "{\n \"type\": \"record\",\n \"name\": \"User\",\n \"aliases\": [\"UserKey\"],\n \"fields\": [\n     {\"name\": \"name\", \"type\": \"string\"},\n     {\"name\": \"favorite_number\",  \"type\": [\"int\", \"null\"], \"default\": 42},\n     {\"name\": \"favorite_color\", \"type\": [\"string\", \"null\"], \"default\": \"purple\"}\n ]\n}\n"
  },
  {
    "path": "tests/certificates/cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIICljCCAX4CCQD4pDS7tmqFojANBgkqhkiG9w0BAQsFADANMQswCQYDVQQGEwJO\nTDAeFw0yMDA0MDcxODQ2MzhaFw0yMDA1MDcxODQ2MzhaMA0xCzAJBgNVBAYTAk5M\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzYk0Qw/BYxWgL0EXQ30J\n32tI09UgHX8/HmmNV1TAT+28lkXQRH0TWeTLm6unODqSkE+v7g5LujQB4hTw6jcK\nIyuFVneBhEvb6OJ/iTWY0/mzHcj0zOgxjjwJKCwcmjFSWqPJD+4icXnTBimYxwaR\noxCOd8qxFmTr30mF1xxqowKPTbxb1CGcDvGLANjg6VQEjKVKJpTrJVKIdjLamULJ\nD/xOO6IJCojCz1lInxm0HrQZuLuPPABXLq3PI1cgnXfOOBMdpsTgSw1Xc9DwaF7T\n+37oVYnyGS6/aoCOLukAiomjqC/pXjYK51XsNFmHsBJwdmK021xrBcBIKh9+BYAw\nwwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBgB3gLtj5g50V4hAxmTJAKgbUTYeTR\nWIK85dfQFktdLPxxzZEPs63j7AONvQgU3bb85T8hzJH7XK5egBvJzrJ+uHnfeKrK\nJYO0WILOCuMIhImghVMUVFtDL+OX07hps9ctX28FZ+8oOI+kx0gvhR4j6/cS9Oj0\nXpfKfz772NbeYiuKjGYApO4kTAOKiW177ji8bVKkghmiSSRVIqoZ1Yf7kig0Y33Z\nvuw1foBbHiBhNZyCiq8fkg6Xa8x+D1rZVWOdO+YZRYDkeh2YSzYNx2FrdcEkU8Xl\nqFTRu7mqJwKo8mJNs9seZmZ6qUwUZRlrgEVxHPGpzND/9rkW6B34K6bX\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "tests/certificates/key.pem",
    "content": "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQI6ITVHlk0uCICAggA\nMB0GCWCGSAFlAwQBKgQQmO730QfVVhfwCGbXs8dlZQSCBNCzhN5cg5IL4bM5EiyV\nmXAi7aSdrGMEPByT2Q0hXCtym0HaIvEpgTglX/zKX34JH8v/TjlVNKk6fBk2AvcQ\n7pgB1EEX/7MgjjWZAejm9YGDNy8w6uqx81Zxtrq/Ex2QvSkHhkHoykYsAPCHn19t\nJYSWWnaiDEfq6YW6WCwRRSxxL3xygVM0nAU396tyI/tWYNkbG4DdmYC2E83N54Pu\nrt97AXgFzBQhQ2OI3D8GyhhSHkjYHnGtQgjOnUOXBfKch334XA7nOvRkkiodAxdw\nLTwK99IZPK+vHr950iKQ6mss0HuFFigoKWXw/0udL9nGYBGE/xuc9Xc7koFSXQJ2\nj/O4cq4KhWGtWGyY/d79ywIqVR2IIHUsnggmB8wbJwinZDs89ieh5P7kwSrAIYst\nxD1eLIBnQ7SDWFA3FfNYKwtYHP/ylCuYzPm/K5LEw0YJOD7hU8g6k+LF37QJJDZS\nQ8y+JSfM70p1KamEJhqvXjur/AUnNVHHVbuenyVpK9UylMwT5O/jkwZlTE18nN1B\nv43k4qJ1lbR6e3eW1htKQzsxBHP8Vvx3BV4DR8yFbJE/IDRXuTAWMSlaYGMBqQpR\nPsEJZiJDw6ySWp7YKc6wX5NJ5AfQkVf/qPbgv/8NQ9yFHvKtNfMAlU7WQ6GFVxo/\nxs5T+GbrfMMceab/FMh7I14E6q3R5tD7iCa3V9r1GZz/TjrNbolybyVcfUkuAhKV\nW3itvzYYUPvGAoWOIcfWt4/yuFheOa/CF9DsXA3k78BAMJrm+eJtQ93VCh9xNuD/\n2V6qeamuZ6Ck6U/UaDke1UMBAWa3MLQO4+lKBayDi9FvowzRHL1eyCv7BFCy6B9o\nkY7bNyY2vVeALpW9fFbn5i21giZdVByfVvBTva0GFmckUVAOomVwSR0UWmOolBTG\nqnkyuQTlAQeIfPY73CrMXKqpnR6JvzT+ROBZvcoN91p3VtbQxsJZaJ7ARlgLFALl\nMW59mbNoJv9Ch6Sam0NPfKDQuMxviiy3wJ6F8hASyGEHC5qmBFgmRDqyGsLROZqB\nafyQ8Nr+WniHnE5AiLbTxNqfaiYzK3oQGhlwD5S9s9XNChF6vxhcXg4vBOJ36gTF\nBmrgDzAeh8fzcCzwSmMSSkUFmY6JSZWFUx/PXfLo/rJUVhG9Xyyl8qbM8j4//h/W\nLCot/aXzvkXQxewZ1XLq6eNTf/h2BcEusgYoKTgPwZg0QlFqv2UZUC4EI3rDg87h\n89RDk3PIFJWmhVqmjUwTtHA3UTDvyw0C+AMWYE7CDNSKRCsvA9zy3F5kfJj+6fUA\nrFY2JOuSJekzjysDstIWuLvoTidxgIRUgrWDpZsK1v+K8pH8G/Kgc7Rul2F2WxIe\nzX1EK/6A6K+N+Ripcu3ARtm/pHqYw/RQYbKHSX7un9mvBgcrQ3Bd8tzh65r91qnU\nbx7qnEFY3c9dOOEkZpY5+Wdx+o/tzZqfig3sJjReaa42lUT3sAaj1y8O5aydY7/v\nsoC2Cl0hSZBmNQE1Hg7AlCAhDinbBMLM6kNycs+Kz/3LHY6mo7Zl1JktCr+w/4M6\nBJRt02IsV4novcdnXLX+lR6fm8npoG2bWKuvikWDstKt4hxhm20g+6c1V8p4U3u2\nFwfzQ2ItEd4Q5Lofz2VLYkyEaw==\n-----END ENCRYPTED PRIVATE KEY-----\n"
  },
  {
    "path": "tests/client/__init__.py",
    "content": ""
  },
  {
    "path": "tests/client/async_client/__init__.py",
    "content": ""
  },
  {
    "path": "tests/client/async_client/test_http_client.py",
    "content": "# import pickle\nimport os\nfrom base64 import b64encode\n\nimport httpx\nimport pytest\n\nfrom schema_registry.client import AsyncSchemaRegistryClient, utils\n\n\n@pytest.mark.asyncio\nasync def test_invalid_cert():\n    with pytest.raises(FileNotFoundError):\n        AsyncSchemaRegistryClient(url=\"https://127.0.0.1:65534\", cert_location=\"/path/to/cert\")\n\n\ndef test_cert_with_key(certificates):\n    client = AsyncSchemaRegistryClient(\n        url=\"https://127.0.0.1:65534\",\n        cert_location=certificates[\"certificate\"],\n        key_location=certificates[\"key\"],\n        key_password=certificates[\"password\"],\n    )\n\n    assert client.conf[utils.SSL_CERTIFICATE_LOCATION] == certificates[\"certificate\"]\n    assert client.conf[utils.SSL_KEY_LOCATION] == certificates[\"key\"]\n    assert client.conf[utils.SSL_KEY_PASSWORD] == certificates[\"password\"]\n\n\ndef test_custom_headers():\n    extra_headers = {\"custom-serialization\": utils.HEADER_AVRO_JSON}\n\n    client = AsyncSchemaRegistryClient(url=\"https://127.0.0.1:65534\", extra_headers=extra_headers)\n    assert extra_headers == client.extra_headers\n\n\n@pytest.mark.asyncio\nasync def test_override_headers(avro_deployment_schema, response_klass, async_mock):\n    extra_headers = {\"custom-serialization\": utils.HEADER_AVRO_JSON}\n    async_client = AsyncSchemaRegistryClient(url=os.getenv(\"SCHEMA_REGISTRY_URL\"), extra_headers=extra_headers)\n\n    response = await async_client.request(\"https://example.com\")\n    assert response.request.headers.get(\"custom-serialization\") == utils.HEADER_AVRO_JSON\n\n    subject = \"test\"\n    override_header = {\"custom-serialization\": utils.HEADER_AVRO}\n\n    mock = async_mock(\n        httpx.AsyncClient,\n        \"request\",\n        returned_value=response_klass(200, content={\"id\": 1}),\n    )\n\n    with mock:\n        await async_client.register(subject, avro_deployment_schema, headers=override_header)\n\n        prepare_headers = async_client.prepare_headers(body=\"1\")\n        prepare_headers[\"custom-serialization\"] = utils.HEADER_AVRO\n\n        mock.assert_called_with(headers=prepare_headers)\n\n\ndef test_cert_path():\n    client = AsyncSchemaRegistryClient(url=\"https://127.0.0.1:65534\", ca_location=True)\n\n    assert client.conf[utils.SSL_CA_LOCATION]\n\n\ndef test_init_with_dict(certificates):\n    client = AsyncSchemaRegistryClient(\n        {\n            \"url\": \"https://127.0.0.1:65534\",\n            \"ssl.certificate.location\": certificates[\"certificate\"],\n            \"ssl.key.location\": certificates[\"key\"],\n            \"ssl.key.password\": \"test\",\n        }\n    )\n    assert \"https://127.0.0.1:65534/\" == client.url_manager.url\n\n\ndef test_empty_url():\n    with pytest.raises(AssertionError):\n        AsyncSchemaRegistryClient({\"url\": \"\"})\n\n\ndef test_invalid_type_url():\n    with pytest.raises(AttributeError):\n        AsyncSchemaRegistryClient(url=1)\n\n\ndef test_invalid_type_url_dict():\n    with pytest.raises(AttributeError):\n        AsyncSchemaRegistryClient({\"url\": 1})\n\n\n@pytest.mark.asyncio\nasync def test_basic_auth_url():\n    username = \"secret-user\"\n    password = \"secret\"\n    client = AsyncSchemaRegistryClient({\"url\": f\"https://{username}:{password}@127.0.0.1:65534\"})\n    userpass = b\":\".join((httpx._utils.to_bytes(username), httpx._utils.to_bytes(password)))\n    token = b64encode(userpass).decode()\n\n    response = await client.request(\"https://example.com\")\n    assert response.request.headers.get(\"Authorization\") == f\"Basic {token}\"\n\n\n@pytest.mark.asyncio\nasync def test_basic_auth_user_info():\n    username = \"secret-user\"\n    password = \"secret\"\n    client = AsyncSchemaRegistryClient(\n        {\n            \"url\": \"https://user_url:secret_url@127.0.0.1:65534\",\n            \"basic.auth.credentials.source\": \"user_info\",\n            \"basic.auth.user.info\": f\"{username}:{password}\",\n        }\n    )\n\n    userpass = b\":\".join((httpx._utils.to_bytes(username), httpx._utils.to_bytes(password)))\n    token = b64encode(userpass).decode()\n\n    response = await client.request(\"https://example.com\")\n    assert response.request.headers.get(\"Authorization\") == f\"Basic {token}\"\n\n\n@pytest.mark.asyncio\nasync def test_auth():\n    username = \"secret-user\"\n    password = \"secret\"\n    client = AsyncSchemaRegistryClient(\n        url=\"https://user_url:secret_url@127.0.0.1:65534\",\n        auth=httpx.BasicAuth(username=username, password=password),\n    )\n\n    userpass = b\":\".join((httpx._utils.to_bytes(username), httpx._utils.to_bytes(password)))\n    token = b64encode(userpass).decode()\n    response = await client.request(\"https://example.com\")\n    assert response.request.headers.get(\"Authorization\") == f\"Basic {token}\"\n\n\n@pytest.mark.asyncio\nasync def test_custom_auth():\n    class CustomAuth(httpx.Auth):\n        def __init__(self, token):\n            self.token = token\n\n        def auth_flow(self, request):\n            # Send the request, with a custom `Authorization` header.\n            request.headers[\"Authorization\"] = f\"Bearer {self.token}\"\n            yield request\n\n    token = \"token\"\n    client = AsyncSchemaRegistryClient(url=\"https://@127.0.0.1:65534\", auth=CustomAuth(token))\n\n    response = await client.request(\"https://example.com\")\n    assert response.request.headers.get(\"Authorization\") == f\"Bearer {token}\"\n\n\ndef test_basic_auth_invalid():\n    with pytest.raises(ValueError):\n        AsyncSchemaRegistryClient(\n            {\n                \"url\": \"https://user_url:secret_url@127.0.0.1:65534\",\n                \"basic.auth.credentials.source\": \"VAULT\",\n            }\n        )\n"
  },
  {
    "path": "tests/client/async_client/test_schema.py",
    "content": "import fastavro\nimport jsonschema\nimport pytest\n\nfrom schema_registry.client import schema\nfrom tests import data_gen\n\n\ndef test_avro_schema_from_string():\n    parsed = schema.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n\n    assert isinstance(parsed, schema.AvroSchema)\n\n\n@pytest.mark.asyncio\nasync def test_avro_schema_from_file():\n    parsed = await schema.AvroSchema.async_load(data_gen.get_schema_path(\"adv_schema.avsc\"))\n    assert isinstance(parsed, schema.AvroSchema)\n\n\n@pytest.mark.asyncio\nasync def test_avro_schema_load_parse_error():\n    with pytest.raises(fastavro.schema.UnknownType):\n        await schema.AvroSchema.async_load(data_gen.get_schema_path(\"invalid_schema.avsc\"))\n\n\ndef test_json_schema_from_string():\n    parsed = schema.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n\n    assert isinstance(parsed, schema.JsonSchema)\n\n\n@pytest.mark.asyncio\nasync def test_json_schema_from_file():\n    parsed = await schema.JsonSchema.async_load(data_gen.get_schema_path(\"adv_schema.json\"))\n    assert isinstance(parsed, schema.JsonSchema)\n\n\n@pytest.mark.asyncio\nasync def test_json_schema_load_parse_error():\n    with pytest.raises(jsonschema.exceptions.SchemaError):\n        await schema.JsonSchema.async_load(data_gen.get_schema_path(\"invalid_schema.json\"))\n"
  },
  {
    "path": "tests/client/async_client/test_schema_compatibility.py",
    "content": "import httpx\nimport pytest\n\nfrom schema_registry.client import errors, schema, utils\nfrom tests import data_gen\nfrom tests.conftest import RequestLoggingAsyncSchemaRegistryClient\n\n\n@pytest.mark.asyncio\nasync def test_avro_compatibility(async_client, avro_user_schema_v3):\n    \"\"\"Test the compatibility of a new User Schema against the User schema version 2.\"\"\"\n    subject = \"test-avro-user-schema\"\n    version_2 = schema.AvroSchema(data_gen.AVRO_USER_V2)\n    await async_client.register(subject, version_2)\n\n    compatibility = await async_client.test_compatibility(subject, avro_user_schema_v3)\n    assert compatibility\n\n\n@pytest.mark.asyncio\nasync def test_avro_compatibility_dataclasses_avroschema(\n    async_client, dataclass_avro_schema, dataclass_avro_schema_advance\n):\n    \"\"\"Test the compatibility of a new User Schema against the User schema version 2.\"\"\"\n    subject = \"dataclasses-avroschema-subject\"\n    await async_client.register(subject, dataclass_avro_schema.avro_schema())\n\n    compatibility = await async_client.test_compatibility(subject, dataclass_avro_schema_advance.avro_schema())\n    assert compatibility\n\n\n@pytest.mark.asyncio\nasync def test_avro_update_compatibility_for_subject(async_client):\n    \"\"\"The latest User V2 schema is  BACKWARD and FORWARDFULL compatibility (FULL).\n    So, we can ipdate compatibility level for the specified subject.\n    \"\"\"\n    assert await async_client.update_compatibility(\"FULL\", \"test-avro-user-schema\")\n\n\n@pytest.mark.asyncio\nasync def test_avro_update_global_compatibility(async_client):\n    \"\"\"The latest User V2 schema is  BACKWARD and FORWARDFULL compatibility (FULL).\n    So, we can ipdate compatibility level for the specified subject.\n    \"\"\"\n    assert await async_client.update_compatibility(\"FULL\")\n\n\n@pytest.mark.asyncio\nasync def test_avro_update_compatibility_fail(async_client, response_klass, async_mock):\n    http_code = 404\n    mock = async_mock(httpx.AsyncClient, \"request\", returned_value=response_klass(http_code))\n\n    with mock:\n        with pytest.raises(errors.ClientError) as excinfo:\n            await async_client.update_compatibility(\"FULL\", \"test-avro-user-schema\")\n\n            assert excinfo.http_code == http_code\n\n\n@pytest.mark.asyncio\nasync def test_avro_get_compatibility_for_subject(async_client):\n    \"\"\"Test latest compatibility for test-avro-user-schema subject\"\"\"\n    assert await async_client.get_compatibility(\"test-avro-user-schema\") == \"FULL\"\n\n\n@pytest.mark.asyncio\nasync def test_avro_get_global_compatibility(async_client):\n    \"\"\"Test latest compatibility for test-avro-user-schema subject\"\"\"\n    assert await async_client.get_compatibility() is not None\n\n\n@pytest.mark.asyncio\nasync def test_json_compatibility(async_client, json_user_schema_v3):\n    \"\"\"Test the compatibility of a new User Schema against the User schema version 2.\"\"\"\n    subject = \"test-json-user-schema\"\n    version_2 = schema.JsonSchema(data_gen.JSON_USER_V2)\n    await async_client.register(subject, version_2)\n\n    compatibility = await async_client.test_compatibility(subject, json_user_schema_v3)\n    assert compatibility\n\n\n@pytest.mark.asyncio\nasync def test_json_compatibility_dataclasses_jsonschema(\n    async_client: RequestLoggingAsyncSchemaRegistryClient,\n    dataclass_json_schema,\n    dataclass_json_schema_advance,\n):\n    \"\"\"Test the compatibility of a new User Schema against the User schema version 2.\"\"\"\n    subject = \"dataclasses-jsonschema-subject\"\n    await async_client.register(\n        subject,\n        dataclass_json_schema.model_json_schema(),\n        schema_type=utils.JSON_SCHEMA_TYPE,\n    )\n\n    compatibility = await async_client.test_compatibility(\n        subject,\n        dataclass_json_schema_advance.model_json_schema(),\n        schema_type=utils.JSON_SCHEMA_TYPE,\n    )\n\n    assert compatibility\n\n\n@pytest.mark.asyncio\nasync def test_json_update_compatibility_for_subject(async_client):\n    \"\"\"The latest User V2 schema is  BACKWARD and FORWARDFULL compatibility (FULL).\n    So, we can ipdate compatibility level for the specified subject.\n    \"\"\"\n    assert await async_client.update_compatibility(\"FULL\", \"test-json-user-schema\")\n\n\n@pytest.mark.asyncio\nasync def test_json_update_global_compatibility(async_client):\n    \"\"\"The latest User V2 schema is  BACKWARD and FORWARDFULL compatibility (FULL).\n    So, we can ipdate compatibility level for the specified subject.\n    \"\"\"\n    assert await async_client.update_compatibility(\"FULL\")\n\n\n@pytest.mark.asyncio\nasync def test_json_update_compatibility_fail(async_client, response_klass, async_mock):\n    http_code = 404\n    mock = async_mock(httpx.AsyncClient, \"request\", returned_value=response_klass(http_code))\n\n    with mock:\n        with pytest.raises(errors.ClientError) as excinfo:\n            await async_client.update_compatibility(\"FULL\", \"test-json-user-schema\")\n\n            assert excinfo.http_code == http_code\n\n\n@pytest.mark.asyncio\nasync def test_json_get_compatibility_for_subject(async_client):\n    \"\"\"Test latest compatibility for test-json-user-schema subject\"\"\"\n    assert await async_client.get_compatibility(\"test-json-user-schema\") == \"FULL\"\n\n\n@pytest.mark.asyncio\nasync def test_json_get_global_compatibility(async_client):\n    \"\"\"Test latest compatibility for test-json-user-schema subject\"\"\"\n    assert await async_client.get_compatibility() is not None\n"
  },
  {
    "path": "tests/client/async_client/test_schema_delete.py",
    "content": "import pytest\n\nfrom schema_registry.client import schema\nfrom tests import data_gen\n\n\n@pytest.mark.asyncio\nasync def test_avro_delete_subject(async_client, avro_user_schema_v3):\n    subject = \"avro-subject-to-delete\"\n    versions = [\n        schema.AvroSchema(data_gen.AVRO_USER_V1),\n        schema.AvroSchema(data_gen.AVRO_USER_V2),\n    ]\n\n    for version in versions:\n        await async_client.register(subject, version)\n\n    assert len(await async_client.delete_subject(subject)) == len(versions)\n\n\n@pytest.mark.asyncio\nasync def test_json_delete_subject(async_client, json_user_schema_v3):\n    subject = \"json-subject-to-delete\"\n    versions = [schema.JsonSchema(data_gen.JSON_USER_V2), json_user_schema_v3]\n\n    for version in versions:\n        await async_client.register(subject, version)\n\n    assert len(await async_client.delete_subject(subject)) == len(versions)\n\n\n@pytest.mark.asyncio\nasync def test_delete_subject_does_not_exist(async_client):\n    assert not await async_client.delete_subject(\"a-random-subject\")\n"
  },
  {
    "path": "tests/client/async_client/test_schema_getters.py",
    "content": "import pytest\n\nfrom schema_registry.client import schema as schema_loader\nfrom tests import data_gen\n\n\n@pytest.mark.asyncio\nasync def test_avro_getters(async_client):\n    subject = \"test-avro-basic-schema\"\n    parsed_basic = schema_loader.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    await async_client.register(subject, parsed_basic)\n    schema = await async_client.get_by_id(1)\n    assert schema is not None\n\n    subject = \"subject-does-not-exist\"\n    latest = await async_client.get_schema(subject)\n    assert latest is None\n\n    schema_id = await async_client.register(subject, parsed_basic)\n    latest = await async_client.get_schema(subject)\n    fetched = await async_client.get_by_id(schema_id)\n\n    assert fetched == parsed_basic\n\n\n@pytest.mark.asyncio\nasync def test_avro_get_subjects(async_client, avro_user_schema_v3, avro_country_schema):\n    subject_user = \"test-avro-user-schema\"\n    subject_country = \"test-avro-country\"\n\n    await async_client.register(\"test-avro-user-schema\", avro_user_schema_v3)\n    await async_client.register(\"test-avro-country\", avro_country_schema)\n\n    subjects = await async_client.get_subjects()\n\n    assert subject_user in subjects\n    assert subject_country in subjects\n\n\n@pytest.mark.asyncio\nasync def test_json_getters(async_client):\n    subject = \"test-json-basic-schema\"\n    parsed_basic = schema_loader.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n    await async_client.register(subject, parsed_basic)\n    schema = await async_client.get_by_id(1)\n    assert schema is not None\n\n    subject = \"subject-does-not-exist\"\n    latest = await async_client.get_schema(subject)\n    assert latest is None\n\n    schema_id = await async_client.register(subject, parsed_basic)\n    latest = await async_client.get_schema(subject)\n    fetched = await async_client.get_by_id(schema_id)\n\n    assert fetched == parsed_basic\n\n\n@pytest.mark.asyncio\nasync def test_json_get_subjects(async_client, json_user_schema_v3, json_country_schema):\n    subject_user = \"test-json-user-schema\"\n    subject_country = \"test-json-country\"\n\n    await async_client.register(\"test-json-user-schema\", json_user_schema_v3)\n    await async_client.register(\"test-json-country\", json_country_schema)\n\n    subjects = await async_client.get_subjects()\n\n    assert subject_user in subjects\n    assert subject_country in subjects\n"
  },
  {
    "path": "tests/client/async_client/test_schema_registration.py",
    "content": "import pytest\n\nfrom schema_registry.client import schema, utils\nfrom tests import data_gen\n\n\ndef assertLatest(self, meta_tuple, sid, schema, version):\n    self.assertNotEqual(sid, -1)\n    self.assertNotEqual(version, -1)\n    self.assertEqual(meta_tuple[0], sid)\n    self.assertEqual(meta_tuple[1], schema)\n    self.assertEqual(meta_tuple[2], version)\n\n\n@pytest.mark.asyncio\nasync def test_avro_register(async_client):\n    parsed = schema.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    schema_id = await async_client.register(\"test-avro-basic-schema\", parsed)\n\n    assert schema_id > 0\n    assert len(async_client.id_to_schema) == 1\n\n    schema_versions = await async_client.get_schema_subject_versions(schema_id)\n    assert schema_versions[0].subject == \"test-avro-basic-schema\"\n\n\n@pytest.mark.asyncio\nasync def test_avro_register_json_data(async_client, avro_deployment_schema):\n    schema_id = await async_client.register(\"test-avro-deployment\", avro_deployment_schema)\n    assert schema_id > 0\n\n\n@pytest.mark.asyncio\nasync def test_avro_register_with_custom_headers(async_client, avro_country_schema):\n    headers = {\"custom-serialization\": \"application/x-avro-json\"}\n    schema_id = await async_client.register(\"test-avro-country\", avro_country_schema, headers=headers)\n    assert schema_id > 0\n\n\n@pytest.mark.asyncio\nasync def test_avro_register_with_logical_types(async_client):\n    parsed = schema.AvroSchema(data_gen.AVRO_LOGICAL_TYPES_SCHEMA)\n    schema_id = await async_client.register(\"test-logical-types-schema\", parsed)\n\n    assert schema_id > 0\n    assert len(async_client.id_to_schema) == 1\n\n\n@pytest.mark.asyncio\nasync def test_avro_multi_subject_register(async_client):\n    parsed = schema.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    schema_id = await async_client.register(\"test-avro-basic-schema\", parsed)\n    assert schema_id > 0\n\n    # register again under different subject\n    dupe_id = await async_client.register(\"test-avro-basic-schema-backup\", parsed)\n    assert schema_id == dupe_id\n    assert len(async_client.id_to_schema) == 1\n\n    schema_versions = await async_client.get_schema_subject_versions(schema_id)\n    schema_versions.sort(key=lambda x: x.subject)\n    assert schema_versions[0].subject == \"test-avro-basic-schema\"\n    assert schema_versions[1].subject == \"test-avro-basic-schema-backup\"\n    # The schema version we get here has a tendency to vary with the\n    # number of times the schema has been soft-deleted, so only verifying\n    # it's an int and > 0\n    assert isinstance(schema_versions[1].version, int)\n    assert schema_versions[1].version > 0\n\n\n@pytest.mark.asyncio\nasync def test_avro_dupe_register(async_client):\n    parsed = schema.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    subject = \"test-avro-basic-schema\"\n    schema_id = await async_client.register(subject, parsed)\n\n    # Verify we had a check version call\n    async_client.assert_url_suffix(0, \"/subjects/%s\" % subject)\n    async_client.assert_method(0, \"POST\")\n    # Verify that we had a register call\n    async_client.assert_url_suffix(1, \"/subjects/%s/versions\" % subject)\n    async_client.assert_method(1, \"POST\")\n    assert len(async_client.request_calls) == 2\n\n    assert schema_id > 0\n    latest = await async_client.get_schema(subject)\n\n    async_client.assert_url_suffix(2, \"/subjects/%s/versions/latest\" % subject)\n    async_client.assert_method(2, \"GET\")\n    assert len(async_client.request_calls) == 3\n\n    # register again under same subject\n    dupe_id = await async_client.register(subject, parsed)\n    assert schema_id == dupe_id\n\n    # Served from cache\n    assert len(async_client.request_calls) == 3\n\n    dupe_latest = await async_client.get_schema(subject)\n    assert latest == dupe_latest\n\n\n@pytest.mark.asyncio\nasync def test_avro_multi_register(async_client):\n    \"\"\"Register two different schemas under the same subject with backwards compatibility.\"\"\"\n    version_1 = schema.AvroSchema(data_gen.AVRO_USER_V1)\n    version_2 = schema.AvroSchema(data_gen.AVRO_USER_V2)\n    subject = \"test-avro-user-schema\"\n\n    id1 = await async_client.register(subject, version_1)\n    latest_schema_1 = await async_client.get_schema(subject)\n    await async_client.check_version(subject, version_1)\n\n    id2 = await async_client.register(subject, version_2)\n    latest_schema_2 = await async_client.get_schema(subject)\n    await async_client.check_version(subject, version_2)\n\n    assert id1 != id2\n    assert latest_schema_1 != latest_schema_2\n    # ensure version is higher\n    assert latest_schema_1.version < latest_schema_2.version\n\n    await async_client.register(subject, version_1)\n    latest_schema_3 = await async_client.get_schema(subject)\n\n    assert latest_schema_2 == latest_schema_3\n\n\n@pytest.mark.asyncio\nasync def test_register_dataclass_avro_schema(async_client, dataclass_avro_schema):\n    subject = \"dataclasses-avroschema-subject\"\n    schema_id = await async_client.register(subject, dataclass_avro_schema.avro_schema())\n\n    assert schema_id > 0\n    assert len(async_client.id_to_schema) == 1\n\n    subjects = await async_client.get_subjects()\n\n    assert subject in subjects\n\n\n@pytest.mark.asyncio\nasync def test_json_register(async_client):\n    parsed = schema.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n    schema_id = await async_client.register(\"test-json-basic-schema\", parsed)\n\n    assert schema_id > 0\n    assert len(async_client.id_to_schema) == 1\n\n\n@pytest.mark.asyncio\nasync def test_json_register_json_data(async_client, json_deployment_schema):\n    schema_id = await async_client.register(\"test-json-deployment\", json_deployment_schema)\n    assert schema_id > 0\n\n\n@pytest.mark.asyncio\nasync def test_json_register_with_custom_headers(async_client, json_country_schema):\n    headers = {\"custom-serialization\": \"application/x-avro-json\"}\n    schema_id = await async_client.register(\"test-json-country\", json_country_schema, headers=headers)\n    assert schema_id > 0\n\n\n@pytest.mark.asyncio\nasync def test_json_multi_subject_register(async_client):\n    parsed = schema.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n    schema_id = await async_client.register(\"test-json-basic-schema\", parsed)\n    assert schema_id > 0\n\n    # register again under different subject\n    dupe_id = await async_client.register(\"test-json-basic-schema-backup\", parsed)\n    assert schema_id == dupe_id\n    assert len(async_client.id_to_schema) == 1\n\n\n@pytest.mark.asyncio\nasync def test_json_dupe_register(async_client):\n    parsed = schema.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n    subject = \"test-json-basic-schema\"\n    schema_id = await async_client.register(subject, parsed)\n\n    # Verify we had a check version call\n    async_client.assert_url_suffix(0, \"/subjects/%s\" % subject)\n    async_client.assert_method(0, \"POST\")\n    # Verify that we had a register call\n    async_client.assert_url_suffix(1, \"/subjects/%s/versions\" % subject)\n    async_client.assert_method(1, \"POST\")\n    assert len(async_client.request_calls) == 2\n\n    assert schema_id > 0\n    latest = await async_client.get_schema(subject)\n\n    async_client.assert_url_suffix(2, \"/subjects/%s/versions/latest\" % subject)\n    async_client.assert_method(2, \"GET\")\n    assert len(async_client.request_calls) == 3\n\n    # register again under same subject\n    dupe_id = await async_client.register(subject, parsed)\n    assert schema_id == dupe_id\n\n    # Served from cache\n    assert len(async_client.request_calls) == 3\n\n    dupe_latest = await async_client.get_schema(subject)\n    assert latest == dupe_latest\n\n\n@pytest.mark.asyncio\nasync def test_json_multi_register(async_client, json_user_schema_v3):\n    \"\"\"Register two different schemas under the same subject with backwards compatibility.\"\"\"\n    version_1 = schema.JsonSchema(data_gen.JSON_USER_V2)\n    version_2 = json_user_schema_v3\n    subject = \"test-json-user-schema\"\n\n    id1 = await async_client.register(subject, version_1)\n    latest_schema_1 = await async_client.get_schema(subject)\n    await async_client.check_version(subject, version_1)\n\n    id2 = await async_client.register(subject, version_2)\n    latest_schema_2 = await async_client.get_schema(subject)\n    await async_client.check_version(subject, version_2)\n\n    assert id1 != id2\n    assert latest_schema_1 != latest_schema_2\n    # ensure version is higher\n    assert latest_schema_1.version < latest_schema_2.version\n\n    await async_client.register(subject, version_1)\n    latest_schema_3 = await async_client.get_schema(subject)\n\n    assert latest_schema_2 == latest_schema_3\n\n\n@pytest.mark.asyncio\nasync def test_register_dataclass_json_schema(async_client, dataclass_json_schema):\n    subject = \"dataclasses-jsonschema-subject\"\n    schema_id = await async_client.register(\n        subject,\n        dataclass_json_schema.model_json_schema(),\n        schema_type=utils.JSON_SCHEMA_TYPE,\n    )\n\n    assert schema_id > 0\n    assert len(async_client.id_to_schema) == 1\n\n    subjects = await async_client.get_subjects()\n\n    assert subject in subjects\n"
  },
  {
    "path": "tests/client/async_client/test_schema_version.py",
    "content": "import pytest\n\nfrom schema_registry.client import utils\n\n\n@pytest.mark.asyncio\nasync def test_avro_version_does_not_exists(async_client, avro_country_schema):\n    assert await async_client.check_version(\"test-avro-schema-version\", avro_country_schema) is None\n\n\n@pytest.mark.asyncio\nasync def test_avro_get_versions(async_client, avro_country_schema):\n    subject = \"test-avro-schema-version\"\n    await async_client.register(subject, avro_country_schema)\n    versions = await async_client.get_versions(subject)\n\n    assert versions\n\n\n@pytest.mark.asyncio\nasync def test_avro_get_versions_does_not_exist(async_client):\n    assert not await async_client.get_versions(\"random-subject\")\n\n\n@pytest.mark.asyncio\nasync def test_avro_check_version(async_client, avro_country_schema):\n    subject = \"test-avro-schema-version\"\n    schema_id = await async_client.register(subject, avro_country_schema)\n    result = await async_client.check_version(subject, avro_country_schema)\n\n    assert subject == result.subject\n    assert schema_id == result.schema_id\n    assert isinstance(result.version, int)\n    assert isinstance(result.schema, str)\n\n\n@pytest.mark.asyncio\nasync def test_avro_check_version_dataclasses_avroschema(async_client, dataclass_avro_schema):\n    subject = \"dataclasses-avroschema-subject\"\n    schema_id = await async_client.register(subject, dataclass_avro_schema.avro_schema())\n    result = await async_client.check_version(subject, dataclass_avro_schema.avro_schema())\n\n    assert subject == result.subject\n    assert schema_id == result.schema_id\n\n\n@pytest.mark.asyncio\nasync def test_avro_delete_version(async_client, avro_country_schema):\n    subject = \"test-avro-schema-version\"\n    await async_client.register(subject, avro_country_schema)\n    versions = await async_client.get_versions(subject)\n    latest_version = versions[-1]\n\n    assert latest_version == await async_client.delete_version(subject, latest_version)\n\n\n@pytest.mark.asyncio\nasync def test_avro_delete_version_does_not_exist(async_client, avro_country_schema):\n    subject = \"test-avro-schema-version\"\n    await async_client.register(subject, avro_country_schema)\n\n    assert not await async_client.delete_version(\"random-subject\")\n    assert not await async_client.delete_version(subject, \"random-version\")\n\n\n@pytest.mark.asyncio\nasync def test_json_version_does_not_exists(async_client, json_country_schema):\n    assert await async_client.check_version(\"test-json-schema-version\", json_country_schema) is None\n\n\n@pytest.mark.asyncio\nasync def test_json_get_versions(async_client, json_country_schema):\n    subject = \"test-json-schema-version\"\n    await async_client.register(subject, json_country_schema)\n    versions = await async_client.get_versions(subject)\n\n    assert versions\n\n\n@pytest.mark.asyncio\nasync def test_json_get_versions_does_not_exist(async_client):\n    assert not await async_client.get_versions(\"random-subject\")\n\n\n@pytest.mark.asyncio\nasync def test_json_check_version(async_client, json_country_schema):\n    subject = \"test-json-schema-version\"\n    schema_id = await async_client.register(subject, json_country_schema)\n    result = await async_client.check_version(subject, json_country_schema)\n\n    assert subject == result.subject\n    assert schema_id == result.schema_id\n\n\n@pytest.mark.asyncio\nasync def test_json_check_version_dataclasses_avroschema(async_client, dataclass_json_schema):\n    subject = \"dataclasses-jsonschema-subject\"\n    schema_id = await async_client.register(\n        subject,\n        dataclass_json_schema.model_json_schema(),\n        schema_type=utils.JSON_SCHEMA_TYPE,\n    )\n    result = await async_client.check_version(\n        subject,\n        dataclass_json_schema.model_json_schema(),\n        schema_type=utils.JSON_SCHEMA_TYPE,\n    )\n\n    assert subject == result.subject\n    assert schema_id == result.schema_id\n\n\n@pytest.mark.asyncio\nasync def test_json_delete_version(async_client, json_country_schema):\n    subject = \"test-json-schema-version\"\n    await async_client.register(subject, json_country_schema)\n    versions = await async_client.get_versions(subject)\n    latest_version = versions[-1]\n\n    assert latest_version == await async_client.delete_version(subject, latest_version)\n\n\n@pytest.mark.asyncio\nasync def test_json_delete_version_does_not_exist(async_client, json_country_schema):\n    subject = \"test-json-schema-version\"\n    await async_client.register(subject, json_country_schema)\n\n    assert not await async_client.delete_version(\"random-subject\")\n    assert not await async_client.delete_version(subject, \"random-version\")\n"
  },
  {
    "path": "tests/client/sync_client/__init__.py",
    "content": ""
  },
  {
    "path": "tests/client/sync_client/test_http_client.py",
    "content": "import pickle\nfrom base64 import b64encode\n\nimport httpx\nimport pytest\nfrom httpx import USE_CLIENT_DEFAULT\n\nfrom schema_registry.client import SchemaRegistryClient, schema, utils\nfrom tests import data_gen\n\n\ndef test_invalid_cert():\n    with pytest.raises(FileNotFoundError):\n        SchemaRegistryClient(url=\"https://127.0.0.1:65534\", cert_location=\"/path/to/cert\")\n\n\ndef test_cert_with_key(certificates):\n    client = SchemaRegistryClient(\n        url=\"https://127.0.0.1:65534\",\n        cert_location=certificates[\"certificate\"],\n        key_location=certificates[\"key\"],\n        key_password=certificates[\"password\"],\n    )\n\n    assert client.conf[utils.SSL_CERTIFICATE_LOCATION] == certificates[\"certificate\"]\n    assert client.conf[utils.SSL_KEY_LOCATION] == certificates[\"key\"]\n    assert client.conf[utils.SSL_KEY_PASSWORD] == certificates[\"password\"]\n\n\ndef test_pickelable(client):\n    unpickled_client = pickle.loads(pickle.dumps(client))\n\n    assert client == unpickled_client\n\n    # make sure that is possible to do client operations with unpickled_client\n    subject = \"test-avro-basic-schema\"\n    parsed = schema.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    unpickled_client.get_subjects()\n    schema_id = unpickled_client.register(subject, parsed)\n\n    assert schema_id > 0\n    assert unpickled_client.delete_subject(subject)\n\n\ndef test_custom_headers():\n    extra_headers = {\"custom-serialization\": utils.HEADER_AVRO_JSON}\n\n    client = SchemaRegistryClient(url=\"https://127.0.0.1:65534\", extra_headers=extra_headers)\n    assert extra_headers == client.extra_headers\n\n\ndef test_custom_httpx_config():\n    \"\"\"Test the SchemaRegistryClient creation with custom httpx config.\"\"\"\n    timeout = httpx.Timeout(10.0, connect=60.0)\n    pool_limits = httpx.Limits(max_keepalive_connections=5, max_connections=10)\n\n    client = SchemaRegistryClient(\n        url=\"https://127.0.0.1:65534\",\n        timeout=timeout,\n        pool_limits=pool_limits,\n    )\n\n    assert client.timeout == timeout\n    assert client.pool_limits == pool_limits\n\n\ndef test_override_headers(client, avro_deployment_schema, mocker, response_klass):\n    extra_headers = {\"custom-serialization\": utils.HEADER_AVRO_JSON}\n    client = SchemaRegistryClient(\"https://127.0.0.1:65534\", extra_headers=extra_headers)\n\n    response = client.request(\"https://example.com\")\n    assert response.request.headers.get(\"custom-serialization\") == utils.HEADER_AVRO_JSON\n\n    subject = \"test\"\n    override_header = {\"custom-serialization\": utils.HEADER_AVRO}\n\n    request_patch = mocker.patch.object(httpx.Client, \"request\", return_value=response_klass(200, content={\"id\": 1}))\n    client.register(subject, avro_deployment_schema, headers=override_header)\n\n    prepare_headers = client.prepare_headers(body=\"1\")\n    prepare_headers[\"custom-serialization\"] = utils.HEADER_AVRO\n\n    request_patch.assert_called_once_with(\n        \"POST\",\n        mocker.ANY,\n        headers=prepare_headers,\n        params=None,\n        json=mocker.ANY,\n        timeout=USE_CLIENT_DEFAULT,\n    )\n\n\ndef test_cert_path():\n    client = SchemaRegistryClient(url=\"https://127.0.0.1:65534\", ca_location=True)\n\n    assert client.conf[utils.SSL_CA_LOCATION]\n\n\ndef test_init_with_dict(certificates):\n    client = SchemaRegistryClient(\n        {\n            \"url\": \"https://127.0.0.1:65534\",\n            \"ssl.certificate.location\": certificates[\"certificate\"],\n            \"ssl.key.location\": certificates[\"key\"],\n            \"ssl.key.password\": certificates[\"password\"],\n        }\n    )\n    assert \"https://127.0.0.1:65534/\" == client.url_manager.url\n\n\ndef test_empty_url():\n    with pytest.raises(AssertionError):\n        SchemaRegistryClient({\"url\": \"\"})\n\n\ndef test_invalid_type_url():\n    with pytest.raises(AttributeError):\n        SchemaRegistryClient(url=1)\n\n\ndef test_invalid_type_url_dict():\n    with pytest.raises(AttributeError):\n        SchemaRegistryClient({\"url\": 1})\n\n\ndef test_invalid_url():\n    with pytest.raises(AssertionError):\n        SchemaRegistryClient({\"url\": \"example.com:65534\"})\n\n\ndef test_basic_auth_url():\n    username = \"secret-user\"\n    password = \"secret\"\n    client = SchemaRegistryClient({\"url\": f\"https://{username}:{password}@127.0.0.1:65534\"})\n    userpass = b\":\".join((httpx._utils.to_bytes(username), httpx._utils.to_bytes(password)))\n    token = b64encode(userpass).decode()\n    response = client.request(\"https://example.com\")\n    assert response.request.headers.get(\"Authorization\") == f\"Basic {token}\"\n\n\ndef test_basic_auth_user_info():\n    username = \"secret-user\"\n    password = \"secret\"\n    client = SchemaRegistryClient(\n        {\n            \"url\": \"https://user_url:secret_url@127.0.0.1:65534\",\n            \"basic.auth.credentials.source\": \"user_info\",\n            \"basic.auth.user.info\": f\"{username}:{password}\",\n        }\n    )\n\n    userpass = b\":\".join((httpx._utils.to_bytes(username), httpx._utils.to_bytes(password)))\n    token = b64encode(userpass).decode()\n    response = client.request(\"https://example.com\")\n    assert response.request.headers.get(\"Authorization\") == f\"Basic {token}\"\n\n\ndef test_auth():\n    username = \"secret-user\"\n    password = \"secret\"\n    client = SchemaRegistryClient(\n        url=\"https://user_url:secret_url@127.0.0.1:65534\",\n        auth=httpx.BasicAuth(username=username, password=password),\n    )\n\n    userpass = b\":\".join((httpx._utils.to_bytes(username), httpx._utils.to_bytes(password)))\n    token = b64encode(userpass).decode()\n    response = client.request(\"https://example.com\")\n    assert response.request.headers.get(\"Authorization\") == f\"Basic {token}\"\n\n\ndef test_custom_auth():\n    class CustomAuth(httpx.Auth):\n        def __init__(self, token):\n            self.token = token\n\n        def auth_flow(self, request):\n            # Send the request, with a custom `Authorization` header.\n            request.headers[\"Authorization\"] = f\"Bearer {self.token}\"\n            yield request\n\n    token = \"token\"\n    client = SchemaRegistryClient(url=\"https://127.0.0.1:65534\", auth=CustomAuth(token))\n\n    response = client.request(\"https://example.com\")\n    assert response.request.headers.get(\"Authorization\") == f\"Bearer {token}\"\n\n\ndef test_basic_auth_invalid():\n    with pytest.raises(ValueError):\n        SchemaRegistryClient(\n            {\n                \"url\": \"https://user_url:secret_url@127.0.0.1:65534\",\n                \"basic.auth.credentials.source\": \"VAULT\",\n            }\n        )\n"
  },
  {
    "path": "tests/client/sync_client/test_schema.py",
    "content": "import fastavro\nimport jsonschema\nimport pytest\n\nfrom schema_registry.client import schema, utils\nfrom tests import data_gen\n\n\ndef test_avro_schema_from_string():\n    parsed = schema.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    assert isinstance(parsed, schema.AvroSchema)\n\n\ndef test_avro_schema_from_file():\n    parsed = schema.AvroSchema.load(data_gen.get_schema_path(\"adv_schema.avsc\"))\n    assert isinstance(parsed, schema.AvroSchema)\n\n\ndef test_avro_schema_load_parse_error():\n    with pytest.raises(fastavro.schema.UnknownType):\n        schema.AvroSchema.load(data_gen.get_schema_path(\"invalid_schema.avsc\"))\n\n\ndef test_avro_schema_type_property():\n    parsed = schema.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    assert parsed.schema_type == utils.AVRO_SCHEMA_TYPE\n\n\ndef test_expanded_schema(client):\n    advance_schema = schema.AvroSchema(data_gen.AVRO_ADVANCED_SCHEMA)\n    expanded = {\n        \"type\": \"record\",\n        \"doc\": \"advanced schema for tests\",\n        \"name\": \"python.test.advanced.advanced\",\n        \"fields\": [\n            {\"name\": \"number\", \"doc\": \"age\", \"type\": [\"long\", \"null\"]},\n            {\"name\": \"name\", \"doc\": \"a name\", \"type\": [\"string\"]},\n            {\n                \"doc\": \"friends\",\n                \"name\": \"friends\",\n                \"type\": {\n                    \"type\": \"map\",\n                    \"values\": {\n                        \"type\": \"record\",\n                        \"name\": \"python.test.advanced.basicPerson\",\n                        \"fields\": [\n                            {\n                                \"doc\": \"friend age\",\n                                \"name\": \"number\",\n                                \"type\": [\"long\", \"null\"],\n                            },\n                            {\"doc\": \"friend name\", \"name\": \"name\", \"type\": [\"string\"]},\n                        ],\n                    },\n                },\n            },\n            {\n                \"name\": \"family\",\n                \"doc\": \"family\",\n                \"type\": {\n                    \"type\": \"map\",\n                    \"values\": {\n                        \"type\": \"record\",\n                        \"name\": \"python.test.advanced.basicPerson\",\n                        \"fields\": [\n                            {\n                                \"doc\": \"friend age\",\n                                \"name\": \"number\",\n                                \"type\": [\"long\", \"null\"],\n                            },\n                            {\"doc\": \"friend name\", \"name\": \"name\", \"type\": [\"string\"]},\n                        ],\n                    },\n                },\n            },\n        ],\n    }\n\n    assert advance_schema.expanded_schema == expanded\n\n\ndef test_flat_schema(client):\n    advance_schema = schema.AvroSchema(data_gen.AVRO_ADVANCED_SCHEMA)\n    subject = \"test-avro-advance-schema\"\n    client.register(subject, advance_schema)\n\n    schema_version = client.get_schema(subject)\n    parsed_schema = schema_version.schema\n    parsed_schema.schema.pop(\"__fastavro_parsed\")\n    parsed_schema.schema.pop(\"__named_schemas\")\n\n    assert schema_version.schema.flat_schema == parsed_schema.schema\n\n\ndef test_json_schema_from_string():\n    parsed = schema.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n    assert isinstance(parsed, schema.JsonSchema)\n\n\ndef test_json_schema_from_file():\n    parsed = schema.JsonSchema.load(data_gen.get_schema_path(\"adv_schema.json\"))\n    assert isinstance(parsed, schema.JsonSchema)\n\n\ndef test_json_schema_load_parse_error():\n    with pytest.raises(jsonschema.exceptions.SchemaError):\n        schema.JsonSchema.load(data_gen.get_schema_path(\"invalid_schema.json\"))\n\n\ndef test_json_schema_type_property():\n    parsed = schema.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n    assert parsed.schema_type == \"JSON\"\n"
  },
  {
    "path": "tests/client/sync_client/test_schema_compatibility.py",
    "content": "import httpx\nimport pytest\n\nfrom schema_registry.client import errors, schema, utils\nfrom tests import data_gen\n\n\ndef test_avro_compatibility(client, avro_user_schema_v3):\n    \"\"\"Test the compatibility of a new User Schema against the User schema version 2.\"\"\"\n    subject = \"test-avro-user-schema\"\n    version_2 = schema.AvroSchema(data_gen.AVRO_USER_V2)\n    client.register(subject, version_2)\n\n    compatibility = client.test_compatibility(subject, avro_user_schema_v3)\n    assert compatibility is True\n\n\ndef test_avro_compatibility_dataclasses_avroschema(client, dataclass_avro_schema, dataclass_avro_schema_advance):\n    \"\"\"Test the compatibility of a new User Schema against the User schema version 2.\"\"\"\n    subject = \"dataclasses-avroschema-subject\"\n    client.register(subject, dataclass_avro_schema.avro_schema())\n\n    compatibility = client.test_compatibility(subject, dataclass_avro_schema_advance.avro_schema())\n    assert compatibility is True\n\n\ndef test_avro_update_compatibility_for_subject(client):\n    \"\"\"The latest User V2 schema is  BACKWARD and FORWARDFULL compatibility (FULL).\n\n    So, we can update compatibility level for the specified subject.\n    \"\"\"\n    assert client.update_compatibility(\"FULL\", \"test-avro-user-schema\")\n\n\ndef test_avro_update_global_compatibility(client):\n    \"\"\"The latest User V2 schema is  BACKWARD and FORWARDFULL compatibility (FULL).\n\n    So, we can update compatibility level for the specified subject.\n    \"\"\"\n    assert client.update_compatibility(\"FULL\")\n\n\ndef test_avro_update_compatibility_fail(client, response_klass, mocker):\n    http_code = 404\n    mocker.patch.object(httpx.Client, \"request\", return_value=response_klass(http_code))\n\n    with pytest.raises(errors.ClientError) as excinfo:\n        client.update_compatibility(\"FULL\", \"test-avro-user-schema\")\n\n        assert excinfo.http_code == http_code\n\n\ndef test_avro_get_compatibility_for_subject(client):\n    \"\"\"Test latest compatibility for test-avro-user-schema subject.\"\"\"\n    assert client.get_compatibility(\"test-avro-user-schema\") == \"FULL\"\n\n\ndef test_avro_get_global_compatibility(client):\n    \"\"\"Test latest compatibility for test-avro-user-schema subject.\"\"\"\n    assert client.get_compatibility() is not None\n\n\ndef test_avro_compatibility_non_verbose(client, avro_user_schema_v3):\n    \"\"\"Test the compatibility with the verbose option set to False\"\"\"\n    subject = \"test-avro-user-schema\"\n    version_2 = schema.AvroSchema(data_gen.AVRO_USER_V2)\n    client.register(subject, version_2)\n\n    compatibility = client.test_compatibility(subject, avro_user_schema_v3, verbose=False)\n    assert isinstance(compatibility, bool)\n\n\ndef test_avro_compatibility_verbose(client, avro_user_schema_v3):\n    \"\"\"Test the compatibility with the verbose option set to True\"\"\"\n    subject = \"test-avro-user-schema\"\n    version_2 = schema.AvroSchema(data_gen.AVRO_USER_V2)\n    client.register(subject, version_2)\n\n    compatibility = client.test_compatibility(subject, avro_user_schema_v3, verbose=True)\n    assert isinstance(compatibility, dict)\n    assert compatibility[\"is_compatible\"] is True\n    assert isinstance(compatibility[\"messages\"], list)\n\n\ndef test_json_compatibility(client, json_user_schema_v3):\n    \"\"\"Test the compatibility of a new User Schema against the User schema version 2.\"\"\"\n    subject = \"test-json-user-schema\"\n    version_2 = schema.JsonSchema(data_gen.JSON_USER_V2)\n    client.register(subject, version_2)\n\n    compatibility = client.test_compatibility(subject, json_user_schema_v3)\n\n    assert compatibility is True\n\n\ndef test_json_compatibility_dataclasses_jsonschema(client, dataclass_json_schema, dataclass_json_schema_advance):\n    \"\"\"Test the compatibility of a new User Schema against the User schema version 2.\"\"\"\n    subject = \"dataclasses-jsonschema-subject\"\n    client.register(\n        subject,\n        dataclass_json_schema.model_json_schema(),\n        schema_type=utils.JSON_SCHEMA_TYPE,\n    )\n\n    compatibility = client.test_compatibility(\n        subject,\n        dataclass_json_schema_advance.model_json_schema(),\n        schema_type=utils.JSON_SCHEMA_TYPE,\n    )\n\n    assert compatibility is True\n\n\ndef test_json_update_compatibility_for_subject(client):\n    \"\"\"The latest User V2 schema is  BACKWARD and FORWARDFULL compatibility (FULL).\n\n    So, we can update compatibility level for the specified subject.\n    \"\"\"\n    assert client.update_compatibility(\"FULL\", \"test-json-user-schema\")\n\n\ndef test_json_update_global_compatibility(client):\n    \"\"\"The latest User V2 schema is  BACKWARD and FORWARDFULL compatibility (FULL).\n\n    So, we can update compatibility level for the specified subject.\n    \"\"\"\n    assert client.update_compatibility(\"FULL\")\n\n\ndef test_json_update_compatibility_fail(client, response_klass, mocker):\n    http_code = 404\n    mocker.patch.object(httpx.Client, \"request\", return_value=response_klass(http_code))\n\n    with pytest.raises(errors.ClientError) as excinfo:\n        client.update_compatibility(\"FULL\", \"test-json-user-schema\")\n\n        assert excinfo.http_code == http_code\n\n\ndef test_json_get_compatibility_for_subject(client):\n    \"\"\"Test latest compatibility for test-json-user-schema subject.\"\"\"\n    assert client.get_compatibility(\"test-json-user-schema\") == \"FULL\"\n\n\ndef test_json_get_global_compatibility(client):\n    \"\"\"Test latest compatibility for test-json-user-schema subject.\"\"\"\n    assert client.get_compatibility() is not None\n"
  },
  {
    "path": "tests/client/sync_client/test_schema_delete.py",
    "content": "from schema_registry.client import schema\nfrom tests import data_gen\n\n\ndef test_avro_delete_subject(client, avro_user_schema_v3):\n    subject = \"avro-subject-to-delete\"\n    versions = [\n        schema.AvroSchema(data_gen.AVRO_USER_V1),\n        schema.AvroSchema(data_gen.AVRO_USER_V2),\n    ]\n\n    for version in versions:\n        client.register(subject, version)\n\n    assert len(client.delete_subject(subject)) == len(versions)\n\n\ndef test_json_delete_subject(client, json_user_schema_v3):\n    subject = \"json-subject-to-delete\"\n    versions = [schema.JsonSchema(data_gen.JSON_USER_V2), json_user_schema_v3]\n\n    for version in versions:\n        client.register(subject, version)\n\n    assert len(client.delete_subject(subject)) == len(versions)\n\n\ndef test_delete_subject_does_not_exist(client):\n    assert not client.delete_subject(\"a-random-subject\")\n"
  },
  {
    "path": "tests/client/sync_client/test_schema_getters.py",
    "content": "from schema_registry.client import schema as schema_loader\nfrom tests import data_gen\n\n\ndef test_avro_getters(client):\n    subject = \"test-avro-basic-schema\"\n    parsed_basic = schema_loader.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    client.register(subject, parsed_basic)\n    schema = client.get_by_id(1)\n    assert schema is not None\n\n    subject = \"subject-does-not-exist\"\n    latest = client.get_schema(subject)\n    assert latest is None\n\n    schema_id = client.register(subject, parsed_basic)\n    latest = client.get_schema(subject)\n    fetched = client.get_by_id(schema_id)\n\n    assert fetched == parsed_basic\n\n\ndef test_avro_get_subjects(client, avro_user_schema_v3, avro_country_schema):\n    subject_user = \"test-avro-user-schema\"\n    subject_country = \"test-avro-country\"\n\n    client.register(\"test-avro-user-schema\", avro_user_schema_v3)\n    client.register(\"test-avro-country\", avro_country_schema)\n\n    subjects = client.get_subjects()\n\n    assert subject_user in subjects\n    assert subject_country in subjects\n\n\ndef test_json_getters(client):\n    subject = \"test-json-basic-schema\"\n    parsed_basic = schema_loader.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n    client.register(subject, parsed_basic)\n    schema = client.get_by_id(1)\n    assert schema is not None\n\n    subject = \"subject-does-not-exist\"\n    latest = client.get_schema(subject)\n    assert latest is None\n\n    schema_id = client.register(subject, parsed_basic)\n    latest = client.get_schema(subject)\n    fetched = client.get_by_id(schema_id)\n\n    assert fetched == parsed_basic\n\n\ndef test_json_get_subjects(client, json_user_schema_v3, json_country_schema):\n    subject_user = \"test-json-user-schema\"\n    subject_country = \"test-json-country\"\n\n    client.register(\"test-json-user-schema\", json_user_schema_v3)\n    client.register(\"test-json-country\", json_country_schema)\n\n    subjects = client.get_subjects()\n\n    assert subject_user in subjects\n    assert subject_country in subjects\n"
  },
  {
    "path": "tests/client/sync_client/test_schema_registration.py",
    "content": "from schema_registry.client import schema, utils\nfrom tests import data_gen\nfrom tests.conftest import RequestLoggingSchemaRegistryClient\n\n\ndef assertLatest(self, meta_tuple, sid, schema, version):\n    self.assertNotEqual(sid, -1)\n    self.assertNotEqual(version, -1)\n    self.assertEqual(meta_tuple[0], sid)\n    self.assertEqual(meta_tuple[1], schema)\n    self.assertEqual(meta_tuple[2], version)\n\n\ndef test_avro_register(client):\n    parsed = schema.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    schema_id = client.register(\"test-avro-basic-schema\", parsed)\n\n    assert schema_id > 0\n    assert len(client.id_to_schema) == 1\n\n    schema_versions = client.get_schema_subject_versions(schema_id)\n    assert schema_versions[0].subject == \"test-avro-basic-schema\"\n\n\ndef test_avro_register_json_data(client, avro_deployment_schema):\n    schema_id = client.register(\"test-avro-deployment\", avro_deployment_schema)\n    assert schema_id > 0\n\n\ndef test_avro_register_with_custom_headers(client, avro_country_schema):\n    headers = {\"custom-serialization\": \"application/x-avro-json\"}\n    schema_id = client.register(\"test-avro-country\", avro_country_schema, headers=headers)\n    assert schema_id > 0\n\n\ndef test_avro_register_with_logical_types(client):\n    parsed = schema.AvroSchema(data_gen.AVRO_LOGICAL_TYPES_SCHEMA)\n    schema_id = client.register(\"test-logical-types-schema\", parsed)\n\n    assert schema_id > 0\n    assert len(client.id_to_schema) == 1\n\n\ndef test_avro_multi_subject_register(client: RequestLoggingSchemaRegistryClient):\n    parsed = schema.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    schema_id = client.register(\"test-avro-basic-schema\", parsed)\n    assert schema_id > 0\n\n    # register again under different subject\n    dupe_id = client.register(\"test-avro-basic-schema-backup\", parsed)\n    assert schema_id == dupe_id\n    assert len(client.id_to_schema) == 1\n\n    schema_versions = client.get_schema_subject_versions(schema_id)\n    schema_versions.sort(key=lambda x: x.subject)\n    assert schema_versions[0].subject == \"test-avro-basic-schema\"\n    assert schema_versions[1].subject == \"test-avro-basic-schema-backup\"\n    # The schema version we get here has a tendency to vary with the\n    # number of times the schema has been soft-deleted, so only verifying\n    # it's an int and > 0\n    assert isinstance(schema_versions[1].version, int)\n    assert schema_versions[1].version > 0\n\n\ndef test_avro_dupe_register(client):\n    parsed = schema.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    subject = \"test-avro-basic-schema\"\n    schema_id = client.register(subject, parsed)\n\n    # Verify we had a check version call\n    client.assert_url_suffix(0, \"/subjects/%s\" % subject)\n    client.assert_method(0, \"POST\")\n    # Verify that we had a register call\n    client.assert_url_suffix(1, \"/subjects/%s/versions\" % subject)\n    client.assert_method(1, \"POST\")\n    assert len(client.request_calls) == 2\n\n    assert schema_id > 0\n    latest = client.get_schema(subject)\n\n    client.assert_url_suffix(2, \"/subjects/%s/versions/latest\" % subject)\n    client.assert_method(2, \"GET\")\n    assert len(client.request_calls) == 3\n\n    # register again under same subject\n    dupe_id = client.register(subject, parsed)\n    assert schema_id == dupe_id\n\n    # Served from cache\n    assert len(client.request_calls) == 3\n\n    dupe_latest = client.get_schema(subject)\n    assert latest == dupe_latest\n\n\ndef test_avro_multi_register(client):\n    \"\"\"Register two different schemas under the same subject with backwards compatibility.\"\"\"\n    version_1 = schema.AvroSchema(data_gen.AVRO_USER_V1)\n    version_2 = schema.AvroSchema(data_gen.AVRO_USER_V2)\n    subject = \"test-avro-user-schema\"\n\n    id1 = client.register(subject, version_1)\n    latest_schema_1 = client.get_schema(subject)\n    client.check_version(subject, version_1)\n\n    id2 = client.register(subject, version_2)\n    latest_schema_2 = client.get_schema(subject)\n    client.check_version(subject, version_2)\n\n    assert id1 != id2\n    assert latest_schema_1 != latest_schema_2\n    # ensure version is higher\n    assert latest_schema_1.version < latest_schema_2.version\n\n    client.register(subject, version_1)\n    latest_schema_3 = client.get_schema(subject)\n\n    assert latest_schema_2 == latest_schema_3\n\n\ndef test_register_dataclass_avro_schema(client, dataclass_avro_schema):\n    subject = \"dataclasses-avroschema-subject\"\n    schema_id = client.register(subject, dataclass_avro_schema.avro_schema())\n\n    assert schema_id > 0\n    assert len(client.id_to_schema) == 1\n\n    subjects = client.get_subjects()\n\n    assert subject in subjects\n\n\ndef test_json_register(client):\n    parsed = schema.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n    schema_id = client.register(\"test-json-basic-schema\", parsed)\n\n    assert schema_id > 0\n    assert len(client.id_to_schema) == 1\n\n\ndef test_json_register_json_data(client, json_deployment_schema):\n    schema_id = client.register(\"test-json-deployment\", json_deployment_schema)\n    assert schema_id > 0\n\n\ndef test_json_register_with_custom_headers(client, json_country_schema):\n    headers = {\"custom-serialization\": \"application/x-avro-json\"}\n    schema_id = client.register(\"test-json-country\", json_country_schema, headers=headers)\n    assert schema_id > 0\n\n\ndef test_json_multi_subject_register(client):\n    parsed = schema.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n    schema_id = client.register(\"test-json-basic-schema\", parsed)\n    assert schema_id > 0\n\n    # register again under different subject\n    dupe_id = client.register(\"test-json-basic-schema-backup\", parsed)\n    assert schema_id == dupe_id\n    assert len(client.id_to_schema) == 1\n\n\ndef test_json_dupe_register(client):\n    parsed = schema.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n    subject = \"test-json-basic-schema\"\n    schema_id = client.register(subject, parsed)\n\n    # Verify we had a check version call\n    client.assert_url_suffix(0, \"/subjects/%s\" % subject)\n    client.assert_method(0, \"POST\")\n    # Verify that we had a register call\n    client.assert_url_suffix(1, \"/subjects/%s/versions\" % subject)\n    client.assert_method(1, \"POST\")\n    assert len(client.request_calls) == 2\n\n    assert schema_id > 0\n    latest = client.get_schema(subject)\n\n    client.assert_url_suffix(2, \"/subjects/%s/versions/latest\" % subject)\n    client.assert_method(2, \"GET\")\n    assert len(client.request_calls) == 3\n\n    # register again under same subject\n    dupe_id = client.register(subject, parsed)\n    assert schema_id == dupe_id\n\n    # Served from cache\n    assert len(client.request_calls) == 3\n\n    dupe_latest = client.get_schema(subject)\n    assert latest == dupe_latest\n\n\ndef test_json_multi_register(client, json_user_schema_v3):\n    \"\"\"Register two different schemas under the same subject with backwards compatibility.\"\"\"\n    version_1 = schema.JsonSchema(data_gen.JSON_USER_V2)\n    version_2 = json_user_schema_v3\n    subject = \"test-json-user-schema\"\n\n    id1 = client.register(subject, version_1)\n    latest_schema_1 = client.get_schema(subject)\n    client.check_version(subject, version_1)\n\n    id2 = client.register(subject, version_2)\n    latest_schema_2 = client.get_schema(subject)\n    client.check_version(subject, version_2)\n\n    assert id1 != id2\n    assert latest_schema_1 != latest_schema_2\n    # ensure version is higher\n    assert latest_schema_1.version < latest_schema_2.version\n\n    client.register(subject, version_1)\n    latest_schema_3 = client.get_schema(subject)\n\n    assert latest_schema_2 == latest_schema_3\n\n\ndef test_register_dataclass_json_schema(client, dataclass_json_schema):\n    subject = \"dataclasses-jsonschema-subject\"\n    schema_id = client.register(\n        subject,\n        dataclass_json_schema.model_json_schema(),\n        schema_type=utils.JSON_SCHEMA_TYPE,\n    )\n\n    assert schema_id > 0\n    assert len(client.id_to_schema) == 1\n\n    subjects = client.get_subjects()\n\n    assert subject in subjects\n"
  },
  {
    "path": "tests/client/sync_client/test_schema_version.py",
    "content": "from schema_registry.client import utils\n\n\ndef test_avro_version_does_not_exists(client, avro_country_schema):\n    assert client.check_version(\"test-avro-schema-version\", avro_country_schema) is None\n\n\ndef test_avro_get_versions(client, avro_country_schema):\n    subject = \"test-avro-schema-version\"\n    client.register(subject, avro_country_schema)\n    versions = client.get_versions(subject)\n\n    assert versions\n\n\ndef test_avro_get_versions_does_not_exist(client):\n    assert not client.get_versions(\"random-subject\")\n\n\ndef test_avro_check_version(client, avro_country_schema):\n    subject = \"test-avro-schema-version\"\n    schema_id = client.register(subject, avro_country_schema)\n    result = client.check_version(subject, avro_country_schema)\n\n    assert subject == result.subject\n    assert schema_id == result.schema_id\n    assert isinstance(result.version, int)\n    assert isinstance(result.schema, str)\n\n\ndef test_avro_check_version_dataclasses_avroschema(client, dataclass_avro_schema):\n    subject = \"dataclasses-avroschema-subject\"\n    schema_id = client.register(subject, dataclass_avro_schema.avro_schema())\n    result = client.check_version(subject, dataclass_avro_schema.avro_schema())\n\n    assert subject == result.subject\n    assert schema_id == result.schema_id\n\n\ndef test_avro_delete_version(client, avro_country_schema):\n    subject = \"test-avro-schema-version\"\n    client.register(subject, avro_country_schema)\n    versions = client.get_versions(subject)\n    latest_version = versions[-1]\n\n    assert latest_version == client.delete_version(subject, latest_version)\n\n\ndef test_avro_delete_version_does_not_exist(client, avro_country_schema):\n    subject = \"test-avro-schema-version\"\n    client.register(subject, avro_country_schema)\n\n    assert not client.delete_version(\"random-subject\")\n    assert not client.delete_version(subject, \"random-version\")\n\n\ndef test_json_version_does_not_exists(client, json_country_schema):\n    assert client.check_version(\"test-json-schema-version\", json_country_schema) is None\n\n\ndef test_json_get_versions(client, json_country_schema):\n    subject = \"test-json-schema-version\"\n    client.register(subject, json_country_schema)\n    versions = client.get_versions(subject)\n\n    assert versions\n\n\ndef test_json_get_versions_does_not_exist(client):\n    assert not client.get_versions(\"random-subject\")\n\n\ndef test_json_check_version(client, json_country_schema):\n    subject = \"test-json-schema-version\"\n    schema_id = client.register(subject, json_country_schema)\n    result = client.check_version(subject, json_country_schema)\n\n    assert subject == result.subject\n    assert schema_id == result.schema_id\n\n\ndef test_json_check_version_dataclasses_jsonschema(client, dataclass_json_schema):\n    subject = \"dataclasses-jsonschema-subject\"\n    schema_id = client.register(\n        subject,\n        dataclass_json_schema.model_json_schema(),\n        schema_type=utils.JSON_SCHEMA_TYPE,\n    )\n    result = client.check_version(\n        subject,\n        dataclass_json_schema.model_json_schema(),\n        schema_type=utils.JSON_SCHEMA_TYPE,\n    )\n\n    assert subject == result.subject\n    assert schema_id == result.schema_id\n\n\ndef test_json_delete_version(client, json_country_schema):\n    subject = \"test-json-schema-version\"\n    client.register(subject, json_country_schema)\n    versions = client.get_versions(subject)\n    latest_version = versions[-1]\n\n    assert latest_version == client.delete_version(subject, latest_version)\n\n\ndef test_json_delete_version_does_not_exist(client, json_country_schema):\n    subject = \"test-json-schema-version\"\n    client.register(subject, json_country_schema)\n\n    assert not client.delete_version(\"random-subject\")\n    assert not client.delete_version(subject, \"random-version\")\n"
  },
  {
    "path": "tests/client/test_urls.py",
    "content": "import urllib\n\nimport pytest\n\nfrom schema_registry.client import urls\nfrom schema_registry.client.paths import paths\n\nBASE_URLS = (\"http://localhost:8081\", \"http://localhost:8082/api/schema-registry/\")\n\n\ndef test_fail_url_manager_creation():\n    base_url = \"localhost:8081\"\n\n    with pytest.raises(AssertionError):\n        urls.UrlManager(base_url, [])\n\n\n@pytest.mark.parametrize(\"base_url\", BASE_URLS)\ndef test_url_with_path(base_url):\n    paths = [(\"get_cars\", \"cars/{car_id}\", \"GET\"), (\"create_car\", \"cars\", \"POST\")]\n\n    url_manager = urls.UrlManager(base_url, paths)\n    url, method = url_manager.url_for(\"get_cars\")\n\n    assert base_url in url\n\n\n@pytest.mark.parametrize(\"base_url\", BASE_URLS)\ndef test_urls_generation(base_url):\n    local_paths = [(\"get_cars\", \"cars/{car_id}\", \"GET\"), (\"create_car\", \"cars\", \"POST\")]\n\n    url_manager = urls.UrlManager(base_url, local_paths)\n    url, method = url_manager.url_for(\"get_cars\")\n\n    assert url == urllib.parse.urljoin(base_url, \"cars/\")\n    assert method == \"GET\"\n\n    url, method = url_manager.url_for(\"get_cars\", car_id=10)\n    assert url == urllib.parse.urljoin(base_url, \"cars/10\")\n    assert method == \"GET\"\n\n    url, method = url_manager.url_for(\"create_car\")\n    assert url == urllib.parse.urljoin(base_url, \"cars\")\n    assert method == \"POST\"\n\n\n@pytest.mark.parametrize(\"base_url\", BASE_URLS)\ndef test_client_paths(base_url):\n    url_manager = urls.UrlManager(base_url, paths)\n\n    for func, _path, _ in paths:\n        kwargs = {\"subject\": \"my-subject\", \"version\": 1}\n        url, method = url_manager.url_for(func, **kwargs)\n\n        assert base_url in url\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "import dataclasses\nimport enum\nimport logging\nimport os\nimport typing\nfrom collections import namedtuple\n\nimport pydantic\nimport pytest\nimport pytest_asyncio\nfrom dataclasses_avroschema import AvroModel\nfrom httpx._client import USE_CLIENT_DEFAULT, TimeoutTypes, UseClientDefault\n\nfrom schema_registry.client import (\n    AsyncSchemaRegistryClient,\n    SchemaRegistryClient,\n    errors,\n    schema,\n    utils,\n)\nfrom schema_registry.serializers import (\n    AsyncAvroMessageSerializer,\n    AsyncJsonMessageSerializer,\n    AvroMessageSerializer,\n    JsonMessageSerializer,\n)\n\nlogger = logging.getLogger(__name__)\n\nCERTIFICATES_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), \"certificates\")\n\nflat_schemas = {\n    \"avro_deployment_schema\": {\n        \"type\": \"record\",\n        \"namespace\": \"com.kubertenes\",\n        \"name\": \"AvroDeployment\",\n        \"fields\": [\n            {\"name\": \"image\", \"type\": \"string\"},\n            {\"name\": \"replicas\", \"type\": \"int\"},\n            {\"name\": \"port\", \"type\": \"int\"},\n        ],\n    },\n    \"avro_country_schema\": {\n        \"type\": \"record\",\n        \"namespace\": \"com.example\",\n        \"name\": \"AvroSomeSchema\",\n        \"fields\": [{\"name\": \"country\", \"type\": \"string\"}],\n    },\n    \"avro_user_schema_v3\": {\n        \"type\": \"record\",\n        \"name\": \"User\",\n        \"aliases\": [\"UserKey\"],\n        \"fields\": [\n            {\"name\": \"name\", \"type\": \"string\"},\n            {\"name\": \"favorite_number\", \"type\": [\"int\", \"null\"], \"default\": 42},\n            {\"name\": \"favorite_color\", \"type\": [\"string\", \"null\"], \"default\": \"purple\"},\n            {\"name\": \"country\", \"type\": [\"null\", \"string\"], \"default\": None},\n        ],\n    },\n    \"json_deployment_schema\": {\n        \"definitions\": {\n            \"record:com.kubertenes.JsonDeployment\": {\n                \"type\": \"object\",\n                \"required\": [\"image\", \"replicas\", \"port\"],\n                \"additionalProperties\": True,\n                \"properties\": {\n                    \"image\": {\"type\": \"string\"},\n                    \"replicas\": {\"type\": \"integer\"},\n                    \"port\": {\"type\": \"integer\"},\n                },\n            }\n        },\n        \"$ref\": \"#/definitions/record:com.kubertenes.JsonDeployment\",\n    },\n    \"json_country_schema\": {\n        \"definitions\": {\n            \"record:com.example.JsonSomeSchema\": {\n                \"type\": \"object\",\n                \"required\": [\"country\"],\n                \"additionalProperties\": True,\n                \"properties\": {\"country\": {\"type\": \"string\"}},\n            }\n        },\n        \"$ref\": \"#/definitions/record:com.example.JsonSomeSchema\",\n    },\n    \"json_user_schema_v3\": {\n        \"definitions\": {\n            \"record:User\": {\n                \"type\": \"object\",\n                \"required\": [\"name\", \"favorite_number\", \"favorite_color\", \"country\"],\n                \"additionalProperties\": {\n                    \"default\": \"null\",\n                    \"oneOf\": [{\"type\": \"null\"}, {\"type\": \"string\"}],\n                },\n                \"properties\": {\n                    \"name\": {\"type\": \"string\"},\n                    \"favorite_number\": {\n                        \"default\": 42,\n                        \"oneOf\": [{\"type\": \"integer\"}, {\"type\": \"null\"}],\n                    },\n                    \"favorite_color\": {\n                        \"default\": \"purple\",\n                        \"oneOf\": [{\"type\": \"string\"}, {\"type\": \"null\"}],\n                    },\n                    \"country\": {\n                        \"default\": None,\n                        \"oneOf\": [{\"type\": \"null\"}, {\"type\": \"string\"}],\n                    },\n                },\n            }\n        },\n        \"$ref\": \"#/definitions/record:User\",\n    },\n}\n\n\nclass Response:\n    def __init__(self, status_code, content=None):\n        self.status_code = status_code\n\n        if content is None:\n            content = {}\n\n        self.content = content\n\n    def json(self):\n        return self.content\n\n\n@pytest.fixture\ndef response_klass():\n    return Response\n\n\nRequestArgs = namedtuple(\"RequestArgs\", [\"url\", \"method\", \"body\", \"headers\", \"params\", \"timeout\"])\n\n\nclass Color(str, enum.Enum):\n    BLUE = \"BLUE\"\n    YELLOW = \"YELLOW\"\n    GREEN = \"GREEN\"\n\n\nclass RequestLoggingAssertMixin(object):\n    def assert_url_suffix(self, call_no: int, url_suffix: str) -> None:\n        args = self.request_calls[call_no]\n        assert args.url.startswith(self.conf[utils.URL])\n        assert args.url[len(self.conf[utils.URL]) :] == url_suffix\n\n    def assert_method(self, call_no: int, method: str) -> None:\n        assert self.request_calls[call_no].method == method\n\n\nclass RequestLoggingSchemaRegistryClient(SchemaRegistryClient, RequestLoggingAssertMixin):\n    def __init__(self, url, *args, **kwargs):\n        self.request_calls = []\n        super(SchemaRegistryClient, self).__init__(url, *args, **kwargs)\n\n    def request(\n        self,\n        url: str,\n        method: str = \"GET\",\n        body: dict = None,\n        params: dict = None,\n        headers: dict = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> tuple:\n        self.request_calls.append(RequestArgs(url, method, body, headers, params, timeout))\n        return super().request(url, method, body, headers=headers, params=params, timeout=timeout)\n\n\n@pytest.fixture\ndef client():\n    url = os.getenv(\"SCHEMA_REGISTRY_URL\", \"http://localhost:8081\")\n    client = RequestLoggingSchemaRegistryClient(url)\n    yield client\n\n    subjects = {\n        \"test-avro-basic-schema\",\n        \"test-json-basic-schema\",\n        \"test-avro-deployment\",\n        \"test-json-deployment\",\n        \"test-avro-country\",\n        \"test-json-country\",\n        \"test-avro-basic-schema-backup\",\n        \"test-json-basic-schema-backup\",\n        \"test-avro-advance-schema\",\n        \"test-json-advance-schema\",\n        \"test-avro-user-schema\",\n        \"test-json-user-schema\",\n        \"subject-does-not-exist\",\n        \"test-logical-types-schema\",\n        \"test-avro-schema-version\",\n        \"test-json-schema-version\",\n        \"test-avro-nested-schema\",\n        \"test-json-nested-schema\",\n        \"test-dataclasses-avroschema\",\n        \"test-dataclasses-jsonschema\",\n        \"test-union-field-avroschema\",\n    }\n\n    # Executing the clean up. Delete all the subjects between tests.\n    for subject in subjects:\n        try:\n            client.delete_subject(subject)\n        except errors.ClientError as exc:\n            logger.info(exc.message)\n\n\n@pytest.fixture\ndef schemas():\n    return flat_schemas\n\n\n@pytest.fixture\ndef avro_deployment_schema():\n    return schema.AvroSchema(flat_schemas.get(\"avro_deployment_schema\"))\n\n\n@pytest.fixture\ndef avro_country_schema():\n    return schema.AvroSchema(flat_schemas.get(\"avro_country_schema\"))\n\n\n@pytest.fixture\ndef avro_user_schema_v3():\n    \"\"\"The user V2 is:\n    {\n        \"type\": \"record\",\n        \"name\": \"User\",\n        \"aliases\": [\"UserKey\"],\n        \"fields\": [\n            {\"name\": \"name\", \"type\": \"string\"},\n            {\"name\": \"favorite_number\",  \"type\": [\"int\", \"null\"], \"default\": 42},\n            {\"name\": \"favorite_color\", \"type\": [\"string\", \"null\"], \"default\": \"purple\"}\n        ]\n    }\n    \"\"\"\n    return schema.AvroSchema(flat_schemas.get(\"avro_user_schema_v3\"))\n\n\n@pytest.fixture\ndef json_deployment_schema():\n    return schema.JsonSchema(flat_schemas.get(\"json_deployment_schema\"))\n\n\n@pytest.fixture\ndef json_country_schema():\n    return schema.JsonSchema(flat_schemas.get(\"json_country_schema\"))\n\n\n@pytest.fixture\ndef json_user_schema_v3():\n    return schema.JsonSchema(flat_schemas.get(\"json_user_schema_v3\"))\n\n\n@pytest.fixture\ndef avro_message_serializer(client):\n    return AvroMessageSerializer(client)\n\n\n@pytest.fixture\ndef json_message_serializer(client):\n    return JsonMessageSerializer(client)\n\n\n@pytest.fixture\ndef async_avro_message_serializer(async_client):\n    return AsyncAvroMessageSerializer(async_client)\n\n\n@pytest.fixture\ndef async_json_message_serializer(async_client):\n    return AsyncJsonMessageSerializer(async_client)\n\n\n@pytest.fixture\ndef certificates():\n    return {\n        \"certificate\": os.path.join(CERTIFICATES_DIR, \"cert.pem\"),\n        \"key\": os.path.join(CERTIFICATES_DIR, \"key.pem\"),\n        \"password\": \"test\",\n    }\n\n\nclass AsyncMock:\n    def __init__(self, module, func, returned_value=None):\n        self.module = module\n        self.func = func\n        self.returned_value = returned_value\n        self.original_object = getattr(module, func)\n        self.args_called_with = None\n        self.kwargs_called_with = None\n\n    def __enter__(self):\n        setattr(self.module, self.func, self.mock)\n\n    def __exit__(self, *args):\n        setattr(self.module, self.func, self.original_object)\n\n    def assert_called_with(self, **kwargs):\n        for key, value in kwargs.items():\n            assert self.kwargs_called_with[key] == value\n\n    async def mock(self, *args, **kwargs):\n        self.args_called_with = args\n        self.kwargs_called_with = kwargs\n\n        return self.returned_value\n\n\n@pytest.fixture\ndef async_mock():\n    return AsyncMock\n\n\nclass RequestLoggingAsyncSchemaRegistryClient(AsyncSchemaRegistryClient, RequestLoggingAssertMixin):\n    def __init__(self, url, *args, **kwargs):\n        self.request_calls = []\n        super(AsyncSchemaRegistryClient, self).__init__(url, *args, **kwargs)\n\n    async def request(\n        self,\n        url: str,\n        method: str = \"GET\",\n        body: dict = None,\n        params: dict = None,\n        headers: dict = None,\n        timeout: typing.Union[TimeoutTypes, UseClientDefault] = USE_CLIENT_DEFAULT,\n    ) -> tuple:\n        self.request_calls.append(RequestArgs(url, method, body, headers, params, timeout))\n        return await super().request(url, method, body, headers=headers, params=params, timeout=timeout)\n\n\n@pytest_asyncio.fixture\nasync def async_client():\n    url = os.getenv(\"SCHEMA_REGISTRY_URL\")\n    client = RequestLoggingAsyncSchemaRegistryClient(url)\n    yield client\n\n    subjects = {\n        \"test-avro-basic-schema\",\n        \"test-json-basic-schema\",\n        \"test-avro-deployment\",\n        \"test-json-deployment\",\n        \"test-avro-country\",\n        \"test-json-country\",\n        \"test-avro-basic-schema-backup\",\n        \"test-json-basic-schema-backup\",\n        \"test-avro-advance-schema\",\n        \"test-json-advance-schema\",\n        \"test-avro-user-schema\",\n        \"test-json-user-schema\",\n        \"subject-does-not-exist\",\n        \"test-logical-types-schema\",\n        \"test-avro-schema-version\",\n        \"test-json-schema-version\",\n        \"test-avro-nested-schema\",\n        \"test-json-nested-schema\",\n        \"test-dataclasses-avroschema\",\n        \"test-dataclasses-jsonschema\",\n        \"test-union-field-avroschema\",\n        \"test-union-field-jsonschema\",\n    }\n\n    # Executing the clean up. Delete all the subjects between tests.\n    for subject in subjects:\n        try:\n            await client.delete_subject(subject)\n        except errors.ClientError as exc:\n            logger.info(exc.message)\n\n\n@pytest.fixture\ndef dataclass_avro_schema():\n    @dataclasses.dataclass\n    class UserAdvance(AvroModel):\n        name: str\n        age: int\n        pets: typing.List[str] = dataclasses.field(default_factory=lambda: [\"dog\", \"cat\"])\n        accounts: typing.Dict[str, int] = dataclasses.field(default_factory=lambda: {\"key\": 1})\n        has_car: bool = False\n\n    return UserAdvance\n\n\n@pytest.fixture\ndef dataclass_avro_schema_advance():\n    @dataclasses.dataclass\n    class UserAdvance(AvroModel):\n        name: str\n        age: int\n        pets: typing.List[str] = dataclasses.field(default_factory=lambda: [\"dog\", \"cat\"])\n        accounts: typing.Dict[str, int] = dataclasses.field(default_factory=lambda: {\"key\": 1})\n        has_car: bool = False\n        favorite_colors: Color = Color.BLUE\n        address: str = None\n\n    return UserAdvance\n\n\n@pytest.fixture\ndef dataclass_json_schema():\n    class UserAdvance(pydantic.BaseModel):\n        model_config = pydantic.ConfigDict(json_schema_extra={\"additionalProperties\": {\"type\": \"string\"}})\n\n        name: str\n        age: int\n        pets: typing.List[str] = pydantic.Field(default_factory=lambda: [\"dog\", \"cat\"])\n        accounts: typing.Dict[str, int] = pydantic.Field(default_factory=lambda: {\"key\": 1})\n        has_car: bool = False\n\n    return UserAdvance\n\n\n@pytest.fixture\ndef dataclass_json_schema_advance():\n    class UserAdvance(pydantic.BaseModel):\n        model_config = pydantic.ConfigDict(json_schema_extra={\"additionalProperties\": {\"type\": \"string\"}})\n\n        name: str\n        age: int\n        pets: typing.List[str] = pydantic.Field(default_factory=lambda: [\"dog\", \"cat\"])\n        accounts: typing.Dict[str, int] = pydantic.Field(default_factory=lambda: {\"key\": 1})\n        has_car: bool = False\n        address: str = None\n\n    return UserAdvance\n"
  },
  {
    "path": "tests/data_gen.py",
    "content": "import datetime\nimport os\nimport os.path\n\nimport faker\n\nfake = faker.Faker()\nepoch = datetime.datetime.utcfromtimestamp(0)\n\nAVRO_SCHEMAS_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), \"avro_schemas\")\nJSON_SCHEMAS_DIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), \"json_schemas\")\n\n\ndef unix_time_millis(dt):\n    return (dt - epoch).total_seconds() * 1000.0\n\n\ndef get_schema_path(fname):\n    ext = os.path.splitext(fname)[1]\n    if ext == \".avsc\":\n        return os.path.join(AVRO_SCHEMAS_DIR, fname)\n\n    if ext == \".json\":\n        return os.path.join(JSON_SCHEMAS_DIR, fname)\n\n    raise ValueError(\n        f\"File format '{ext}' not supported. Schemas files must have extensions of either avro (.avsc) or json (.json).\"\n    )\n\n\ndef load_schema_file(fname):\n    fname = get_schema_path(fname)\n    with open(fname) as f:\n        return f.read()\n\n\ndef create_basic_item(i):\n    return {\"name\": fake.first_name(), \"number\": fake.pyint(max_value=100)}\n\n\ndef create_adv_item(i):\n    friends = map(create_basic_item, range(1, 3))\n    family = map(create_basic_item, range(1, 3))\n    basic = create_basic_item(i)\n    basic[\"family\"] = {bi[\"name\"]: bi for bi in family}\n    basic[\"friends\"] = {bi[\"name\"]: bi for bi in friends}\n\n    return basic\n\n\ndef create_logical_item():\n    return {\n        \"metadata\": {\n            \"timestamp\": fake.past_datetime(tzinfo=datetime.timezone.utc),\n            \"total\": fake.pydecimal(left_digits=2, right_digits=2),\n        }\n    }\n\n\ndef create_nested_schema():\n    return {\n        \"name\": fake.first_name(),\n        \"uid\": fake.pyint(min_value=0, max_value=9999, step=1),\n        \"order\": {\"uid\": fake.pyint(min_value=0, max_value=9999, step=1)},\n    }\n\n\nAVRO_BASIC_SCHEMA = load_schema_file(os.path.join(AVRO_SCHEMAS_DIR, \"basic_schema.avsc\"))\nAVRO_ADVANCED_SCHEMA = load_schema_file(os.path.join(AVRO_SCHEMAS_DIR, \"adv_schema.avsc\"))\nAVRO_BASIC_ITEMS = map(create_basic_item, range(1, 20))\nAVRO_USER_V1 = load_schema_file(os.path.join(AVRO_SCHEMAS_DIR, \"user_v1.avsc\"))\nAVRO_USER_V2 = load_schema_file(os.path.join(AVRO_SCHEMAS_DIR, \"user_v2.avsc\"))\nAVRO_LOGICAL_TYPES_SCHEMA = load_schema_file(os.path.join(AVRO_SCHEMAS_DIR, \"logical_types_schema.avsc\"))\nAVRO_ADVANCED_ITEMS = map(create_adv_item, range(1, 20))\nAVRO_NESTED_SCHEMA = load_schema_file(os.path.join(AVRO_SCHEMAS_DIR, \"nested_schema.avsc\"))\nAVRO_ORDER_SCHEMA = load_schema_file(os.path.join(AVRO_SCHEMAS_DIR, \"order_schema.avsc\"))\n\nJSON_BASIC_SCHEMA = load_schema_file(os.path.join(JSON_SCHEMAS_DIR, \"basic_schema.json\"))\nJSON_ADVANCED_SCHEMA = load_schema_file(os.path.join(JSON_SCHEMAS_DIR, \"adv_schema.json\"))\nJSON_BASIC_ITEMS = map(create_basic_item, range(1, 20))\nJSON_USER_V1 = load_schema_file(os.path.join(JSON_SCHEMAS_DIR, \"user_v1.json\"))\nJSON_USER_V2 = load_schema_file(os.path.join(JSON_SCHEMAS_DIR, \"user_v2.json\"))\nJSON_ADVANCED_ITEMS = map(create_adv_item, range(1, 20))\nJSON_NESTED_SCHEMA = load_schema_file(os.path.join(JSON_SCHEMAS_DIR, \"nested_schema.json\"))\nJSON_ORDER_SCHEMA = load_schema_file(os.path.join(JSON_SCHEMAS_DIR, \"order_schema.json\"))\n\n\ndef cleanup(files):\n    for f in files:\n        try:\n            os.remove(f)\n        except OSError:\n            pass\n"
  },
  {
    "path": "tests/json_schemas/adv_schema.json",
    "content": "{\r\n  \"definitions\" : {\r\n    \"record.python.test.advanced.advanced\" : {\r\n      \"description\" : \"advanced schema for tests\",\r\n      \"type\" : \"object\",\r\n      \"required\" : [ \"number\", \"name\", \"friends\", \"family\" ],\r\n      \"additionalProperties\" : false,\r\n      \"properties\" : {\r\n        \"number\" : {\r\n          \"oneOf\" : [ {\r\n            \"type\" : \"integer\",\r\n            \"minimum\" : -9223372036854775808,\r\n            \"maximum\" : 9223372036854775807\r\n          }, {\r\n            \"type\" : \"null\"\r\n          } ]\r\n        },\r\n        \"name\" : {\r\n          \"oneOf\" : [ {\r\n            \"type\" : \"string\"\r\n          } ]\r\n        },\r\n        \"friends\" : {\r\n          \"type\" : \"object\",\r\n          \"additionalProperties\" : {\r\n            \"$ref\" : \"#/definitions/record.python.test.advanced.basicPerson\"\r\n          }\r\n        },\r\n        \"family\" : {\r\n          \"type\" : \"object\",\r\n          \"additionalProperties\" : {\r\n            \"$ref\" : \"#/definitions/record.python.test.advanced.basicPerson\"\r\n          }\r\n        }\r\n      }\r\n    },\r\n    \"record.python.test.advanced.basicPerson\" : {\r\n      \"type\" : \"object\",\r\n      \"required\" : [ \"number\", \"name\" ],\r\n      \"additionalProperties\" : false,\r\n      \"properties\" : {\r\n        \"number\" : {\r\n          \"oneOf\" : [ {\r\n            \"type\" : \"integer\",\r\n            \"minimum\" : -9223372036854775808,\r\n            \"maximum\" : 9223372036854775807\r\n          }, {\r\n            \"type\" : \"null\"\r\n          } ]\r\n        },\r\n        \"name\" : {\r\n          \"oneOf\" : [ {\r\n            \"type\" : \"string\"\r\n          } ]\r\n        }\r\n      }\r\n    }\r\n  },\r\n  \"$ref\" : \"#/definitions/record.python.test.advanced.advanced\"\r\n}"
  },
  {
    "path": "tests/json_schemas/basic_schema.json",
    "content": "{\r\n  \"definitions\" : {\r\n    \"record:python.test.basic.basic\" : {\r\n      \"description\" : \"basic schema for tests\",\r\n      \"type\" : \"object\",\r\n      \"required\" : [ \"number\", \"name\" ],\r\n      \"properties\" : {\r\n        \"number\" : {\r\n          \"oneOf\" : [ {\r\n            \"type\" : \"integer\"\r\n          }, {\r\n            \"type\" : \"null\"\r\n          } ]\r\n        },\r\n        \"name\" : {\r\n          \"oneOf\" : [ {\r\n            \"type\" : \"string\"\r\n          } ]\r\n        }\r\n      }\r\n    }\r\n  },\r\n  \"$ref\" : \"#/definitions/record:python.test.basic.basic\"\r\n}"
  },
  {
    "path": "tests/json_schemas/invalid_schema.json",
    "content": "{\r\n  \"type\": \"object\",\r\n  \"properties\": {\r\n    \"firstName\": {\r\n      \"type\": \"invalidType\"\r\n    },\r\n    \"lastName\": {\r\n      \"type\": \"string\"\r\n    },\r\n    \"email\": {\r\n      \"type\": \"string\",\r\n      \"format\": \"email\"\r\n    },\r\n    \"gender\": {\r\n      \"type\": \"string\",\r\n      \"enum\": [\"Male\", \"Female\"]\r\n    },\r\n    \"active\": {\r\n      \"type\": \"boolean\"\r\n    },\r\n    \"weight\": {\r\n      \"type\": \"number\"\r\n    },\r\n    \"height\": {\r\n      \"type\": \"integer\"\r\n    },\r\n    \"dateOfBirth\": {\r\n      \"type\": \"string\",\r\n      \"format\": \"date-time\"\r\n    }\r\n  },\r\n  \"required\": [ \"firstName\", \"email\" ],\r\n  \"additionalProperties\": false\r\n}"
  },
  {
    "path": "tests/json_schemas/nested_schema.json",
    "content": "{\r\n  \"definitions\" : {\r\n    \"record:com.questanalytics.core.Customer\" : {\r\n      \"type\" : \"object\",\r\n      \"required\" : [ \"uid\", \"order\", \"name\" ],\r\n      \"properties\" : {\r\n        \"uid\" : {\r\n          \"default\" : \"NONE\",\r\n          \"type\" : \"integer\"\r\n        },\r\n        \"order\" : {\r\n          \"default\" : \"NONE\",\r\n          \"$ref\" : \"#/definitions/record:com.questanalytics.core.OrderRecord\"\r\n        },\r\n        \"name\" : {\r\n          \"default\" : \"NONE\",\r\n          \"type\" : \"string\"\r\n        }\r\n      }\r\n    },\r\n    \"record:com.questanalytics.core.OrderRecord\" : {\r\n      \"type\" : \"object\",\r\n      \"required\" : [ \"uid\" ],\r\n      \"properties\" : {\r\n        \"uid\" : {\r\n          \"default\" : \"NONE\",\r\n          \"type\" : \"integer\"\r\n        }\r\n      }\r\n    }\r\n  },\r\n  \"$ref\" : \"#/definitions/record:com.questanalytics.core.Customer\"\r\n}"
  },
  {
    "path": "tests/json_schemas/order_schema.json",
    "content": "{\r\n  \"definitions\" : {\r\n    \"record:Order\" : {\r\n      \"type\" : \"object\",\r\n      \"required\" : [ \"uid\" ],\r\n      \"additionalProperties\" : false,\r\n      \"properties\" : {\r\n        \"uid\" : {\r\n          \"type\" : \"integer\"\r\n        }\r\n      }\r\n    }\r\n  },\r\n  \"$ref\" : \"#/definitions/record:Order\"\r\n}"
  },
  {
    "path": "tests/json_schemas/user_v1.json",
    "content": "{\r\n  \"definitions\" : {\r\n    \"record:UserKey\" : {\r\n      \"type\" : \"object\",\r\n      \"required\" : [ \"name\" ],\r\n      \"additionalProperties\" : true,\r\n      \"properties\" : {\r\n        \"name\" : {\r\n          \"type\" : \"string\"\r\n        }\r\n      }\r\n    }\r\n  },\r\n  \"$ref\" : \"#/definitions/record:UserKey\"\r\n}"
  },
  {
    "path": "tests/json_schemas/user_v2.json",
    "content": "{\r\n  \"definitions\" : {\r\n    \"record:User\" : {\r\n      \"type\" : \"object\",\r\n      \"required\" : [ \"name\", \"favorite_number\", \"favorite_color\" ],\r\n      \"additionalProperties\" : {\r\n        \"default\" : \"null\",\r\n        \"oneOf\" : [{\"type\" : \"null\"},{\"type\" : \"string\"}]\r\n      },\r\n      \"properties\" : {\r\n        \"name\" : {\r\n          \"type\" : \"string\"\r\n        },\r\n        \"favorite_number\" : {\r\n          \"default\" : 42,\r\n          \"oneOf\" : [{\"type\" : \"integer\"}, {\"type\" : \"null\"}]\r\n        },\r\n        \"favorite_color\" : {\r\n          \"default\" : \"purple\",\r\n          \"oneOf\" : [{\"type\" : \"string\"},{\"type\" : \"null\"}]\r\n        }\r\n      }\r\n    }\r\n  },\r\n  \"$ref\" : \"#/definitions/record:User\"\r\n}"
  },
  {
    "path": "tests/serializer/__init__.py",
    "content": ""
  },
  {
    "path": "tests/serializer/test_async_message_serializer.py",
    "content": "import math\nimport struct\n\nimport jsonschema\nimport pytest\n\nfrom schema_registry.client import schema\nfrom tests import data_gen\n\npytestmark = pytest.mark.asyncio\n\n\nasync def assertAvroMessageIsSame(message, expected, schema_id, async_avro_message_serializer):\n    assert message\n    assert len(message) > 5\n\n    magic, sid = struct.unpack(\">bI\", message[0:5])\n    assert magic == 0\n    assert sid == schema_id\n\n    decoded = await async_avro_message_serializer.decode_message(message)\n    assert decoded\n    assert decoded == expected\n\n\nasync def test_avro_encode_with_schema_id(async_client, async_avro_message_serializer):\n    basic = schema.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    subject = \"test-avro-basic-schema\"\n    schema_id = await async_client.register(subject, basic)\n\n    records = data_gen.AVRO_BASIC_ITEMS\n    for record in records:\n        message = await async_avro_message_serializer.encode_record_with_schema_id(schema_id, record)\n        await assertAvroMessageIsSame(message, record, schema_id, async_avro_message_serializer)\n\n    adv = schema.AvroSchema(data_gen.AVRO_ADVANCED_SCHEMA)\n    subject = \"test-avro-advance-schema\"\n    adv_schema_id = await async_client.register(subject, adv)\n\n    assert adv_schema_id != schema_id\n\n    records = data_gen.AVRO_ADVANCED_ITEMS\n    for record in records:\n        message = await async_avro_message_serializer.encode_record_with_schema_id(adv_schema_id, record)\n        await assertAvroMessageIsSame(message, record, adv_schema_id, async_avro_message_serializer)\n\n\nasync def test_avro_encode_logical_types(async_client, async_avro_message_serializer):\n    logical_types_schema = schema.AvroSchema(data_gen.AVRO_LOGICAL_TYPES_SCHEMA)\n    subject = \"test-logical-types-schema\"\n    schema_id = await async_client.register(subject, logical_types_schema)\n\n    record = data_gen.create_logical_item()\n    message = await async_avro_message_serializer.encode_record_with_schema_id(schema_id, record)\n    decoded = await async_avro_message_serializer.decode_message(message)\n\n    decoded_datetime = decoded[\"metadata\"][\"timestamp\"]\n    decoded_total = decoded[\"metadata\"][\"total\"]\n\n    record_datetime = record[\"metadata\"][\"timestamp\"]\n    record_total = record[\"metadata\"][\"total\"]\n\n    assert math.floor(record_datetime.timestamp()) <= math.floor(decoded_datetime.timestamp())\n    assert record_total == decoded_total\n\n\nasync def test_avro_encode_decode_with_schema_from_json(async_avro_message_serializer, avro_deployment_schema):\n    deployment_record = {\n        \"image\": \"registry.gitlab.com/my-project:1.0.0\",\n        \"replicas\": 1,\n        \"port\": 8080,\n    }\n\n    message_encoded = await async_avro_message_serializer.encode_record_with_schema(\n        \"avro-deployment\", avro_deployment_schema, deployment_record\n    )\n\n    assert message_encoded\n    assert len(message_encoded) > 5\n    assert isinstance(message_encoded, bytes)\n\n    # now decode the message\n    message_decoded = await async_avro_message_serializer.decode_message(message_encoded)\n    assert message_decoded == deployment_record\n\n\n# async def test_encode_with_schema_string(async_avro_message_serializer):\n#     deployment_record = {\"image\": \"registry.gitlab.com/my-project:1.0.0\", \"replicas\": 1, \"port\": 8080}\n#     schema = \"\"\"{\n#         \"type\": \"record\",\n#         \"namespace\": \"com.kubertenes.v2\",\n#         \"name\": \"AvroDeploymentV2\",\n#         \"fields\": [\n#             {\"name\": \"image\", \"type\": \"string\"},\n#             {\"name\": \"replicas\", \"type\": \"int\"},\n#             {\"name\": \"host\", \"type\": \"string\", \"default\": \"localhost\"},\n#             {\"name\": \"port\", \"type\": \"int\"}\n#         ]\n#     }\"\"\"\n\n#     message_encoded = async_avro_message_serializer.encode_record_with_schema(\n#         \"avro-deployment\", schema, deployment_record\n#     )\n\n#     assert message_encoded\n#     assert len(message_encoded) > 5\n#     assert isinstance(message_encoded, bytes)\n\n#     # now decode the message\n#     message_decoded = async_avro_message_serializer.decode_message(message_encoded)\n#     assert message_decoded == deployment_record\n\n\nasync def test_avro_fail_encode_with_schema(async_avro_message_serializer, avro_deployment_schema):\n    bad_record = {\n        \"image\": \"registry.gitlab.com/my-project:1.0.0\",\n        \"replicas\": \"1\",\n        \"port\": \"8080\",\n    }\n\n    with pytest.raises(TypeError):\n        await async_avro_message_serializer.encode_record_with_schema(\n            \"avro-deployment\", avro_deployment_schema, bad_record\n        )\n\n\nasync def test_avro_encode_record_with_schema(async_client, async_avro_message_serializer):\n    topic = \"test\"\n    basic = schema.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    subject = \"test-avro-value\"\n    schema_id = await async_client.register(subject, basic)\n    records = data_gen.AVRO_BASIC_ITEMS\n\n    for record in records:\n        message = await async_avro_message_serializer.encode_record_with_schema(topic, basic, record)\n        await assertAvroMessageIsSame(message, record, schema_id, async_avro_message_serializer)\n\n\nasync def test_avro_decode_none(async_avro_message_serializer):\n    \"\"\" \"null/None messages should decode to None\"\"\"\n    assert await async_avro_message_serializer.decode_message(None) is None\n\n\nasync def assertJsonMessageIsSame(message, expected, schema_id, async_json_message_serializer):\n    assert message\n    assert len(message) > 5\n\n    magic, sid = struct.unpack(\">bI\", message[0:5])\n    assert magic == 0\n    assert sid == schema_id\n\n    decoded = await async_json_message_serializer.decode_message(message)\n    assert decoded\n    assert decoded == expected\n\n\nasync def test_json_encode_with_schema_id(async_client, async_json_message_serializer):\n    basic = schema.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n    subject = \"test-json-basic-schema\"\n    schema_id = await async_client.register(subject, basic)\n\n    records = data_gen.JSON_BASIC_ITEMS\n    for record in records:\n        message = await async_json_message_serializer.encode_record_with_schema_id(schema_id, record)\n        await assertJsonMessageIsSame(message, record, schema_id, async_json_message_serializer)\n\n    adv = schema.JsonSchema(data_gen.JSON_ADVANCED_SCHEMA)\n    subject = \"test-json-advance-schema\"\n    adv_schema_id = await async_client.register(subject, adv)\n\n    assert adv_schema_id != schema_id\n\n    records = data_gen.JSON_ADVANCED_ITEMS\n    for record in records:\n        message = await async_json_message_serializer.encode_record_with_schema_id(adv_schema_id, record)\n        await assertJsonMessageIsSame(message, record, adv_schema_id, async_json_message_serializer)\n\n\nasync def test_json_encode_decode_with_schema_from_json(async_json_message_serializer, json_deployment_schema):\n    deployment_record = {\n        \"image\": \"registry.gitlab.com/my-project:1.0.0\",\n        \"replicas\": 1,\n        \"port\": 8080,\n    }\n\n    message_encoded = await async_json_message_serializer.encode_record_with_schema(\n        \"json-deployment\", json_deployment_schema, deployment_record\n    )\n\n    assert message_encoded\n    assert len(message_encoded) > 5\n    assert isinstance(message_encoded, bytes)\n\n    # now decode the message\n    message_decoded = await async_json_message_serializer.decode_message(message_encoded)\n    assert message_decoded == deployment_record\n\n\nasync def test_json_fail_encode_with_schema(async_json_message_serializer, json_deployment_schema):\n    bad_record = {\n        \"image\": \"registry.gitlab.com/my-project:1.0.0\",\n        \"replicas\": \"1\",\n        \"port\": \"8080\",\n    }\n\n    with pytest.raises(jsonschema.exceptions.ValidationError):\n        await async_json_message_serializer.encode_record_with_schema(\n            \"json-deployment\", json_deployment_schema, bad_record\n        )\n\n\nasync def test_json_encode_record_with_schema(async_client, async_json_message_serializer):\n    topic = \"test\"\n    basic = schema.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n    subject = \"test-json-value\"\n    schema_id = await async_client.register(subject, basic)\n    records = data_gen.JSON_BASIC_ITEMS\n\n    for record in records:\n        message = await async_json_message_serializer.encode_record_with_schema(topic, basic, record)\n        await assertJsonMessageIsSame(message, record, schema_id, async_json_message_serializer)\n\n\nasync def test_json_decode_none(async_json_message_serializer):\n    \"\"\"null/None messages should decode to None.\"\"\"\n    assert await async_json_message_serializer.decode_message(None) is None\n"
  },
  {
    "path": "tests/serializer/test_faust_serializer.py",
    "content": "import typing\n\nimport faust\nimport pydantic\nfrom dataclasses_avroschema.faust import AvroRecord\n\nfrom schema_registry.client import schema\nfrom schema_registry.serializers import AvroMessageSerializer, JsonMessageSerializer\nfrom schema_registry.serializers import faust as serializer\nfrom tests import data_gen\n\n\ndef test_create_avro_faust_serializer(client, avro_country_schema):\n    schema_subject = \"test-avro-country\"\n    faust_serializer = serializer.FaustSerializer(client, schema_subject, avro_country_schema)\n\n    assert isinstance(faust_serializer.message_serializer, AvroMessageSerializer)\n    assert faust_serializer.schema_subject == schema_subject\n    assert faust_serializer.schema == avro_country_schema\n    assert faust_serializer.message_serializer.schemaregistry_client == client\n\n\ndef test_avro_dumps_load_message(client, avro_country_schema):\n    faust_serializer = serializer.FaustSerializer(client, \"test-avro-country\", avro_country_schema)\n\n    record = {\"country\": \"Argentina\"}\n    message_encoded = faust_serializer._dumps(record)\n\n    assert message_encoded\n    assert len(message_encoded) > 5\n    assert isinstance(message_encoded, bytes)\n\n    message_decoded = faust_serializer._loads(message_encoded)\n    assert message_decoded == record\n\n\ndef test_avro_nested_schema(client):\n    nested_schema = schema.AvroSchema(data_gen.AVRO_NESTED_SCHEMA)\n    faust_serializer = serializer.FaustSerializer(client, \"test-avro-nested-schema\", nested_schema)\n\n    record = data_gen.create_nested_schema()\n    message_encoded = faust_serializer._dumps(record)\n\n    assert message_encoded\n    assert len(message_encoded) > 5\n    assert isinstance(message_encoded, bytes)\n\n    message_decoded = faust_serializer._loads(message_encoded)\n    assert message_decoded == record\n\n\ndef test_avro_dumps_load_with_register_codec(client, avro_country_schema):\n    payload = {\"country\": \"Argentina\"}\n    country_serializer = serializer.FaustSerializer(client, \"test-avro-country\", avro_country_schema)\n\n    faust.serializers.codecs.register(\"country_avro_serializer\", country_serializer)\n\n    class CountryRecord(faust.Record, serializer=\"country_avro_serializer\"):\n        country: str\n\n    country_record = CountryRecord(**payload)\n    message_encoded = country_record.dumps()\n\n    assert message_encoded\n    assert len(message_encoded) > 5\n    assert isinstance(message_encoded, bytes)\n\n    message_decoded = CountryRecord.loads(message_encoded)\n\n    assert message_decoded == country_record\n\n\ndef test_avro_nested_schema_with_register_codec(client):\n    nested_schema = schema.AvroSchema(data_gen.AVRO_NESTED_SCHEMA)\n    order_schema = schema.AvroSchema(data_gen.AVRO_ORDER_SCHEMA)\n\n    customer_serializer = serializer.FaustSerializer(client, \"test-avro-nested-schema\", nested_schema)\n    order_serializer = serializer.FaustSerializer(client, \"test-avro-order-schema\", order_schema)\n\n    faust.serializers.codecs.register(\"customer_avro_serializer\", customer_serializer)\n    faust.serializers.codecs.register(\"order_avro_serializer\", order_serializer)\n\n    class Order(faust.Record, serializer=\"order_avro_serializer\"):\n        uid: int\n\n    class Customer(faust.Record, serializer=\"customer_avro_serializer\"):\n        name: str\n        uid: int\n        order: Order\n\n    payload = data_gen.create_nested_schema()\n\n    customer = Customer(**payload)\n\n    message_encoded = customer.dumps()\n\n    assert message_encoded\n    assert len(message_encoded) > 5\n    assert isinstance(message_encoded, bytes)\n\n    message_decoded = Customer.loads(message_encoded)\n    assert message_decoded == customer\n\n\ndef test_avro_dumps_load_message_dataclasses_avro_schema(client):\n    class AdvanceUserModel(AvroRecord):\n        first_name: str\n        last_name: str\n        age: int\n\n    faust_serializer = serializer.FaustSerializer(client, \"test-dataclasses-avroschema\", AdvanceUserModel.avro_schema())\n\n    record = {\n        \"first_name\": \"Juan\",\n        \"last_name\": \"Perez\",\n        \"age\": 20,\n    }\n\n    message_encoded = faust_serializer._dumps(record)\n\n    assert message_encoded\n    assert len(message_encoded) > 5\n    assert isinstance(message_encoded, bytes)\n\n    message_decoded = faust_serializer._loads(message_encoded)\n    assert message_decoded == record\n\n\ndef test_avro_dumps_load_message_union_avro_schema(client):\n    class FirstMemberRecord(AvroRecord):\n        name: str = \"\"\n\n    class SecondMemberRecord(AvroRecord):\n        name: str = \"\"\n\n    class UnionFieldAvroModel(AvroRecord):\n        a_name: typing.Union[FirstMemberRecord, SecondMemberRecord, None]\n\n    avro_name = \"test-union-field-avroschema\"\n    avro_schema = UnionFieldAvroModel.avro_schema()\n\n    faust_serializer = serializer.FaustSerializer(client, avro_name, avro_schema, return_record_name=True)\n\n    record = {\"a_name\": (\"FirstMemberRecord\", {\"name\": \"jj\"})}\n\n    message_encoded = faust_serializer._dumps(record)\n\n    assert message_encoded\n\n    message_decoded = faust_serializer._loads(message_encoded)\n    assert message_decoded == record\n\n\ndef test_create_json_faust_serializer(client, json_country_schema):\n    schema_subject = \"test-json-country\"\n    faust_serializer = serializer.FaustJsonSerializer(client, schema_subject, json_country_schema)\n\n    assert isinstance(faust_serializer.message_serializer, JsonMessageSerializer)\n    assert faust_serializer.schema_subject == schema_subject\n    assert faust_serializer.schema == json_country_schema\n    assert faust_serializer.message_serializer.schemaregistry_client == client\n\n\ndef test_json_dumps_load_message(client, json_country_schema):\n    faust_serializer = serializer.FaustJsonSerializer(client, \"test-json-country\", json_country_schema)\n\n    record = {\"country\": \"Argentina\"}\n    message_encoded = faust_serializer._dumps(record)\n\n    assert message_encoded\n    assert len(message_encoded) > 5\n    assert isinstance(message_encoded, bytes)\n\n    message_decoded = faust_serializer._loads(message_encoded)\n    assert message_decoded == record\n\n\ndef test_json_nested_schema(client):\n    nested_schema = schema.JsonSchema(data_gen.JSON_NESTED_SCHEMA)\n    faust_serializer = serializer.FaustJsonSerializer(client, \"test-json-nested-schema\", nested_schema)\n\n    record = data_gen.create_nested_schema()\n    message_encoded = faust_serializer._dumps(record)\n\n    assert message_encoded\n    assert len(message_encoded) > 5\n    assert isinstance(message_encoded, bytes)\n\n    message_decoded = faust_serializer._loads(message_encoded)\n    assert message_decoded == record\n\n\ndef test_json_dumps_load_with_register_codec(client, json_country_schema):\n    payload = {\"country\": \"Argentina\"}\n    country_serializer = serializer.FaustJsonSerializer(client, \"test-json-country\", json_country_schema)\n\n    faust.serializers.codecs.register(\"country_json_serializer\", country_serializer)\n\n    class CountryRecord(faust.Record, serializer=\"country_json_serializer\"):\n        country: str\n\n    country_record = CountryRecord(**payload)\n    message_encoded = country_record.dumps()\n\n    assert message_encoded\n    assert len(message_encoded) > 5\n    assert isinstance(message_encoded, bytes)\n\n    message_decoded = CountryRecord.loads(message_encoded)\n\n    assert message_decoded == country_record\n\n\ndef test_json_nested_schema_with_register_codec(client):\n    nested_schema = schema.JsonSchema(data_gen.JSON_NESTED_SCHEMA)\n    order_schema = schema.JsonSchema(data_gen.JSON_ORDER_SCHEMA)\n\n    customer_serializer = serializer.FaustJsonSerializer(client, \"test-json-nested-schema\", nested_schema)\n    order_serializer = serializer.FaustJsonSerializer(client, \"test-json-order-schema\", order_schema)\n\n    faust.serializers.codecs.register(\"customer_json_serializer\", customer_serializer)\n    faust.serializers.codecs.register(\"order_json_serializer\", order_serializer)\n\n    class Order(AvroRecord, serializer=\"order_json_serializer\"):\n        uid: int\n\n    class Customer(AvroRecord, serializer=\"customer_json_serializer\"):\n        name: str\n        uid: int\n        order: Order\n\n    payload = data_gen.create_nested_schema()\n\n    customer = Customer(**payload)\n\n    message_encoded = customer.dumps()\n\n    assert message_encoded\n    assert len(message_encoded) > 5\n    assert isinstance(message_encoded, bytes)\n\n    message_decoded = Customer.loads(message_encoded)\n    assert message_decoded == customer\n\n\ndef test_json_dumps_load_message_dataclasses_json_schema(client):\n    class AdvanceUserModel(AvroRecord, pydantic.BaseModel):\n        first_name: str\n        last_name: str\n        age: int\n\n    faust_serializer = serializer.FaustJsonSerializer(\n        client, \"test-dataclasses-jsonschema\", AdvanceUserModel.model_json_schema()\n    )\n\n    record = {\n        \"first_name\": \"Juan\",\n        \"last_name\": \"Perez\",\n        \"age\": 20,\n    }\n\n    message_encoded = faust_serializer._dumps(record)\n\n    assert message_encoded\n    assert len(message_encoded) > 5\n    assert isinstance(message_encoded, bytes)\n\n    message_decoded = faust_serializer._loads(message_encoded)\n    assert message_decoded == record\n\n\ndef test_json_dumps_load_message_union_json_schema(client):\n    class FirstMemberRecord(pydantic.BaseModel):\n        name: str = \"\"\n\n    class SecondMemberRecord(pydantic.BaseModel):\n        name: str = \"\"\n\n    class UnionFieldJsonModel(pydantic.BaseModel):\n        a_name: typing.Union[FirstMemberRecord, SecondMemberRecord, None]\n\n    json_name = \"test-union-field-jsonschema\"\n    json_schema = UnionFieldJsonModel.model_json_schema()\n\n    faust_serializer = serializer.FaustJsonSerializer(client, json_name, json_schema, return_record_name=True)\n\n    record = {\"a_name\": {\"name\": \"jj\"}}\n\n    message_encoded = faust_serializer._dumps(record)\n\n    assert message_encoded\n\n    message_decoded = faust_serializer._loads(message_encoded)\n    assert message_decoded == record\n"
  },
  {
    "path": "tests/serializer/test_faust_serializer_clean_payload.py",
    "content": "import typing\n\nfrom faust import Record\n\nfrom schema_registry.serializers import faust as serializer\n\n\nclass DummyRecord(Record):\n    item: typing.Any\n\n\ndef test_avro_simple_record(client, avro_country_schema):\n    schema_subject = \"test-avro-country\"\n    faust_serializer = serializer.FaustSerializer(client, schema_subject, avro_country_schema)\n\n    result = {\n        \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n        \"item\": \"test\",\n    }\n\n    dummy = DummyRecord(\"test\")\n    assert result == faust_serializer.clean_payload(dummy)\n\n\ndef test_avro_nested_record(client, avro_country_schema):\n    schema_subject = \"test-avro-country\"\n    faust_serializer = serializer.FaustSerializer(client, schema_subject, avro_country_schema)\n\n    result = {\n        \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n        \"item\": {\n            \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n            \"item\": \"test\",\n        },\n    }\n\n    dummy = DummyRecord(DummyRecord(\"test\"))\n    assert result == faust_serializer.clean_payload(dummy)\n\n\ndef test_avro_list_of_records(client, avro_country_schema):\n    schema_subject = \"test-avro-country\"\n    faust_serializer = serializer.FaustSerializer(client, schema_subject, avro_country_schema)\n\n    result = {\n        \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n        \"item\": [\n            {\n                \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n                \"item\": \"test\",\n            },\n            {\n                \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n                \"item\": \"test\",\n            },\n        ],\n    }\n\n    dummy = DummyRecord([DummyRecord(\"test\"), DummyRecord(\"test\")])\n    assert result == faust_serializer.clean_payload(dummy)\n\n\ndef test_avro_map_of_records(client, avro_country_schema):\n    schema_subject = \"test-avro-country\"\n    faust_serializer = serializer.FaustSerializer(client, schema_subject, avro_country_schema)\n\n    result = {\n        \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n        \"item\": {\n            \"key1\": {\n                \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n                \"item\": \"test\",\n            },\n            \"key2\": {\n                \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n                \"item\": \"test\",\n            },\n        },\n    }\n\n    dummy = DummyRecord({\"key1\": DummyRecord(\"test\"), \"key2\": DummyRecord(\"test\")})\n    assert result == faust_serializer.clean_payload(dummy)\n\n\ndef test_json_simple_record(client, json_country_schema):\n    schema_subject = \"test-avro-country\"\n    faust_serializer = serializer.FaustJsonSerializer(client, schema_subject, json_country_schema)\n\n    result = {\n        \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n        \"item\": \"test\",\n    }\n\n    dummy = DummyRecord(\"test\")\n    assert result == faust_serializer.clean_payload(dummy)\n\n\ndef test_json_nested_record(client, json_country_schema):\n    schema_subject = \"test-avro-country\"\n    faust_serializer = serializer.FaustJsonSerializer(client, schema_subject, json_country_schema)\n\n    result = {\n        \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n        \"item\": {\n            \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n            \"item\": \"test\",\n        },\n    }\n\n    dummy = DummyRecord(DummyRecord(\"test\"))\n    assert result == faust_serializer.clean_payload(dummy)\n\n\ndef test_json_list_of_records(client, json_country_schema):\n    schema_subject = \"test-avro-country\"\n    faust_serializer = serializer.FaustJsonSerializer(client, schema_subject, json_country_schema)\n\n    result = {\n        \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n        \"item\": [\n            {\n                \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n                \"item\": \"test\",\n            },\n            {\n                \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n                \"item\": \"test\",\n            },\n        ],\n    }\n\n    dummy = DummyRecord([DummyRecord(\"test\"), DummyRecord(\"test\")])\n    assert result == faust_serializer.clean_payload(dummy)\n\n\ndef test_json_map_of_records(client, json_country_schema):\n    schema_subject = \"test-avro-country\"\n    faust_serializer = serializer.FaustJsonSerializer(client, schema_subject, json_country_schema)\n\n    result = {\n        \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n        \"item\": {\n            \"key1\": {\n                \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n                \"item\": \"test\",\n            },\n            \"key2\": {\n                \"__faust\": {\"ns\": \"tests.serializer.test_faust_serializer_clean_payload.DummyRecord\"},\n                \"item\": \"test\",\n            },\n        },\n    }\n\n    dummy = DummyRecord({\"key1\": DummyRecord(\"test\"), \"key2\": DummyRecord(\"test\")})\n    assert result == faust_serializer.clean_payload(dummy)\n"
  },
  {
    "path": "tests/serializer/test_message_serializer.py",
    "content": "import math\nimport struct\n\nimport jsonschema\nimport pytest\n\nfrom schema_registry.client import schema\nfrom tests import data_gen\n\n\ndef assertAvroMessageIsSame(message, expected, schema_id, avro_message_serializer):\n    assert message\n    assert len(message) > 5\n\n    magic, sid = struct.unpack(\">bI\", message[0:5])\n    assert magic == 0\n    assert sid == schema_id\n\n    decoded = avro_message_serializer.decode_message(message)\n    assert decoded\n    assert decoded == expected\n\n\ndef test_avro_encode_with_schema_id(client, avro_message_serializer):\n    basic = schema.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    subject = \"test-avro-basic-schema\"\n    schema_id = client.register(subject, basic)\n\n    records = data_gen.AVRO_BASIC_ITEMS\n    for record in records:\n        message = avro_message_serializer.encode_record_with_schema_id(schema_id, record)\n        assertAvroMessageIsSame(message, record, schema_id, avro_message_serializer)\n\n    adv = schema.AvroSchema(data_gen.AVRO_ADVANCED_SCHEMA)\n    subject = \"test-avro-advance-schema\"\n    adv_schema_id = client.register(subject, adv)\n\n    assert adv_schema_id != schema_id\n\n    records = data_gen.AVRO_ADVANCED_ITEMS\n    for record in records:\n        message = avro_message_serializer.encode_record_with_schema_id(adv_schema_id, record)\n        assertAvroMessageIsSame(message, record, adv_schema_id, avro_message_serializer)\n\n\ndef test_avro_encode_logical_types(client, avro_message_serializer):\n    logical_types_schema = schema.AvroSchema(data_gen.AVRO_LOGICAL_TYPES_SCHEMA)\n    subject = \"test-logical-types-schema\"\n    schema_id = client.register(subject, logical_types_schema)\n\n    record = data_gen.create_logical_item()\n    message = avro_message_serializer.encode_record_with_schema_id(schema_id, record)\n    decoded = avro_message_serializer.decode_message(message)\n\n    decoded_datetime = decoded[\"metadata\"][\"timestamp\"]\n    decoded_total = decoded[\"metadata\"][\"total\"]\n\n    record_datetime = record[\"metadata\"][\"timestamp\"]\n    record_total = record[\"metadata\"][\"total\"]\n\n    assert math.floor(record_datetime.timestamp()) <= math.floor(decoded_datetime.timestamp())\n    assert record_total == decoded_total\n\n\ndef test_avro_encode_decode_with_schema_from_json(avro_message_serializer, avro_deployment_schema):\n    deployment_record = {\n        \"image\": \"registry.gitlab.com/my-project:1.0.0\",\n        \"replicas\": 1,\n        \"port\": 8080,\n    }\n\n    message_encoded = avro_message_serializer.encode_record_with_schema(\n        \"avro-deployment\", avro_deployment_schema, deployment_record\n    )\n\n    assert message_encoded\n    assert len(message_encoded) > 5\n    assert isinstance(message_encoded, bytes)\n\n    # now decode the message\n    message_decoded = avro_message_serializer.decode_message(message_encoded)\n    assert message_decoded == deployment_record\n\n\n# def test_encode_with_schema_string(avro_message_serializer):\n#     deployment_record = {\"image\": \"registry.gitlab.com/my-project:1.0.0\", \"replicas\": 1, \"port\": 8080}\n#     schema = \"\"\"{\n#         \"type\": \"record\",\n#         \"namespace\": \"com.kubertenes.v2\",\n#         \"name\": \"AvroDeploymentV2\",\n#         \"fields\": [\n#             {\"name\": \"image\", \"type\": \"string\"},\n#             {\"name\": \"replicas\", \"type\": \"int\"},\n#             {\"name\": \"host\", \"type\": \"string\", \"default\": \"localhost\"},\n#             {\"name\": \"port\", \"type\": \"int\"}\n#         ]\n#     }\"\"\"\n\n#     message_encoded = avro_message_serializer.encode_record_with_schema(\n#         \"avro-deployment\", schema, deployment_record\n#     )\n\n#     assert message_encoded\n#     assert len(message_encoded) > 5\n#     assert isinstance(message_encoded, bytes)\n\n#     # now decode the message\n#     message_decoded = avro_message_serializer.decode_message(message_encoded)\n#     assert message_decoded == deployment_record\n\n\ndef test_avro_fail_encode_with_schema(avro_message_serializer, avro_deployment_schema):\n    bad_record = {\n        \"image\": \"registry.gitlab.com/my-project:1.0.0\",\n        \"replicas\": \"1\",\n        \"port\": \"8080\",\n    }\n\n    with pytest.raises(TypeError):\n        avro_message_serializer.encode_record_with_schema(\"avro-deployment\", avro_deployment_schema, bad_record)\n\n\ndef test_avro_encode_record_with_schema(client, avro_message_serializer):\n    topic = \"test\"\n    basic = schema.AvroSchema(data_gen.AVRO_BASIC_SCHEMA)\n    subject = \"test-avro-value\"\n    schema_id = client.register(subject, basic)\n    records = data_gen.AVRO_BASIC_ITEMS\n\n    for record in records:\n        message = avro_message_serializer.encode_record_with_schema(topic, basic, record)\n        assertAvroMessageIsSame(message, record, schema_id, avro_message_serializer)\n\n\ndef test_avro_decode_none(avro_message_serializer):\n    \"\"\" \"null/None messages should decode to None\"\"\"\n    assert avro_message_serializer.decode_message(None) is None\n\n\ndef assertJsonMessageIsSame(message, expected, schema_id, json_message_serializer):\n    assert message\n    assert len(message) > 5\n\n    magic, sid = struct.unpack(\">bI\", message[0:5])\n    assert magic == 0\n    assert sid == schema_id\n\n    decoded = json_message_serializer.decode_message(message)\n    assert decoded\n    assert decoded == expected\n\n\ndef test_json_encode_with_schema_id(client, json_message_serializer):\n    basic = schema.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n    subject = \"test-json-basic-schema\"\n    schema_id = client.register(subject, basic)\n\n    records = data_gen.JSON_BASIC_ITEMS\n    for record in records:\n        message = json_message_serializer.encode_record_with_schema_id(schema_id, record)\n        assertJsonMessageIsSame(message, record, schema_id, json_message_serializer)\n\n    adv = schema.JsonSchema(data_gen.JSON_ADVANCED_SCHEMA)\n    subject = \"test-json-advance-schema\"\n    adv_schema_id = client.register(subject, adv)\n\n    assert adv_schema_id != schema_id\n\n    records = data_gen.JSON_ADVANCED_ITEMS\n    for record in records:\n        message = json_message_serializer.encode_record_with_schema_id(adv_schema_id, record)\n        assertJsonMessageIsSame(message, record, adv_schema_id, json_message_serializer)\n\n\ndef test_json_encode_decode_with_schema_from_json(json_message_serializer, json_deployment_schema):\n    deployment_record = {\n        \"image\": \"registry.gitlab.com/my-project:1.0.0\",\n        \"replicas\": 1,\n        \"port\": 8080,\n    }\n\n    message_encoded = json_message_serializer.encode_record_with_schema(\n        \"json-deployment\", json_deployment_schema, deployment_record\n    )\n\n    assert message_encoded\n    assert len(message_encoded) > 5\n    assert isinstance(message_encoded, bytes)\n\n    # now decode the message\n    message_decoded = json_message_serializer.decode_message(message_encoded)\n    assert message_decoded == deployment_record\n\n\ndef test_json_fail_encode_with_schema(json_message_serializer, json_deployment_schema):\n    bad_record = {\n        \"image\": \"registry.gitlab.com/my-project:1.0.0\",\n        \"replicas\": \"1\",\n        \"port\": \"8080\",\n    }\n\n    with pytest.raises(jsonschema.exceptions.ValidationError):\n        json_message_serializer.encode_record_with_schema(\"json-deployment\", json_deployment_schema, bad_record)\n\n\ndef test_json_encode_record_with_schema(client, json_message_serializer):\n    topic = \"test\"\n    basic = schema.JsonSchema(data_gen.JSON_BASIC_SCHEMA)\n    subject = \"test-json-value\"\n    schema_id = client.register(subject, basic)\n    records = data_gen.JSON_BASIC_ITEMS\n\n    for record in records:\n        message = json_message_serializer.encode_record_with_schema(topic, basic, record)\n        assertJsonMessageIsSame(message, record, schema_id, json_message_serializer)\n\n\ndef test_json_decode_none(json_message_serializer):\n    \"\"\"null/None messages should decode to None.\"\"\"\n    assert json_message_serializer.decode_message(None) is None\n"
  }
]