Repository: vitalik/django-ninja Branch: master Commit: 0b67d47637a9 Files: 279 Total size: 3.4 MB Directory structure: gitextract_4zhr2mua/ ├── .dockerignore ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── feature_request.md │ │ └── question.md │ ├── dependabot.yml │ └── workflows/ │ ├── close-old-issues.yml │ ├── docs.yml │ ├── publish.yml │ ├── test.yml │ └── test_full.yml ├── .gitignore ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── docs/ │ ├── docs/ │ │ ├── chat.md │ │ ├── extra.css │ │ ├── guides/ │ │ │ ├── api-docs.md │ │ │ ├── async-support.md │ │ │ ├── authentication.md │ │ │ ├── decorators.md │ │ │ ├── errors.md │ │ │ ├── input/ │ │ │ │ ├── body.md │ │ │ │ ├── file-params.md │ │ │ │ ├── filtering.md │ │ │ │ ├── form-params.md │ │ │ │ ├── operations.md │ │ │ │ ├── path-params.md │ │ │ │ ├── query-params.md │ │ │ │ └── request-parsers.md │ │ │ ├── response/ │ │ │ │ ├── config-pydantic.md │ │ │ │ ├── django-pydantic-create-schema.md │ │ │ │ ├── django-pydantic.md │ │ │ │ ├── index.md │ │ │ │ ├── pagination.md │ │ │ │ ├── response-renderers.md │ │ │ │ └── temporal_response.md │ │ │ ├── routers.md │ │ │ ├── testing.md │ │ │ ├── throttling.md │ │ │ ├── urls.md │ │ │ └── versioning.md │ │ ├── help.md │ │ ├── index.md │ │ ├── javascripts/ │ │ │ └── ask-ai-button.js │ │ ├── motivation.md │ │ ├── proposals/ │ │ │ ├── cbv.md │ │ │ ├── index.md │ │ │ └── v1.md │ │ ├── reference/ │ │ │ ├── api.md │ │ │ ├── csrf.md │ │ │ ├── management-commands.md │ │ │ ├── operations-parameters.md │ │ │ └── settings.md │ │ ├── releases.md │ │ ├── tutorial/ │ │ │ ├── index.md │ │ │ ├── other/ │ │ │ │ ├── crud.md │ │ │ │ └── video.md │ │ │ ├── step2.md │ │ │ └── step3.md │ │ └── whatsnew_v1.md │ ├── mkdocs.yml │ ├── requirements.txt │ └── src/ │ ├── index001.py │ └── tutorial/ │ ├── authentication/ │ │ ├── apikey01.py │ │ ├── apikey02.py │ │ ├── apikey03.py │ │ ├── basic01.py │ │ ├── bearer01.py │ │ ├── bearer02.py │ │ ├── code001.py │ │ ├── code002.py │ │ ├── global01.py │ │ ├── multiple01.py │ │ └── schema01.py │ ├── body/ │ │ ├── code01.py │ │ ├── code02.py │ │ └── code03.py │ ├── form/ │ │ ├── code01.py │ │ ├── code02.py │ │ └── code03.py │ ├── path/ │ │ ├── code01.py │ │ ├── code010.py │ │ └── code02.py │ └── query/ │ ├── code01.py │ ├── code010.py │ ├── code02.py │ └── code03.py ├── mypy.ini ├── ninja/ │ ├── __init__.py │ ├── compatibility/ │ │ ├── __init__.py │ │ ├── files.py │ │ ├── streaming.py │ │ └── util.py │ ├── conf.py │ ├── constants.py │ ├── decorators.py │ ├── errors.py │ ├── files.py │ ├── filter_schema.py │ ├── main.py │ ├── management/ │ │ ├── __init__.py │ │ ├── commands/ │ │ │ ├── __init__.py │ │ │ └── export_openapi_schema.py │ │ └── utils.py │ ├── openapi/ │ │ ├── __init__.py │ │ ├── docs.py │ │ ├── schema.py │ │ ├── urls.py │ │ └── views.py │ ├── operation.py │ ├── orm/ │ │ ├── __init__.py │ │ ├── factory.py │ │ ├── fields.py │ │ ├── metaclass.py │ │ └── shortcuts.py │ ├── pagination.py │ ├── params/ │ │ ├── __init__.py │ │ ├── functions.py │ │ └── models.py │ ├── parser.py │ ├── patch_dict.py │ ├── py.typed │ ├── renderers.py │ ├── responses.py │ ├── router.py │ ├── schema.py │ ├── security/ │ │ ├── __init__.py │ │ ├── apikey.py │ │ ├── base.py │ │ ├── http.py │ │ └── session.py │ ├── signature/ │ │ ├── __init__.py │ │ ├── details.py │ │ └── utils.py │ ├── static/ │ │ └── ninja/ │ │ ├── redoc.standalone.js │ │ ├── swagger-ui-bundle.js │ │ ├── swagger-ui-init.js │ │ └── swagger-ui.css │ ├── streaming.py │ ├── templates/ │ │ └── ninja/ │ │ ├── favicons.html │ │ ├── redoc.html │ │ ├── redoc_cdn.html │ │ ├── swagger.html │ │ └── swagger_cdn.html │ ├── testing/ │ │ ├── __init__.py │ │ └── client.py │ ├── throttling.py │ ├── types.py │ └── utils.py ├── pyproject.toml ├── scripts/ │ └── build-docs.sh └── tests/ ├── conftest.py ├── demo_project/ │ ├── db.sqlite3 │ ├── demo/ │ │ ├── __init__.py │ │ ├── asgi.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── manage.py │ ├── multi_param/ │ │ ├── __init__.py │ │ ├── api.py │ │ ├── asgi.py │ │ ├── manage.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ └── someapp/ │ ├── __init__.py │ ├── admin.py │ ├── api.py │ ├── models.py │ └── views.py ├── env-matrix/ │ ├── Dockerfile │ ├── Dockerfile.backup │ ├── README.md │ ├── create_docker.py │ ├── docker-compose.yml │ ├── install_env.sh │ └── run.sh ├── main.py ├── mypy_test.py ├── pytest.ini ├── test_add_decorator.py ├── test_add_decorator_async.py ├── test_alias.py ├── test_annotated.py ├── test_api_instance.py ├── test_app.py ├── test_async.py ├── test_auth.py ├── test_auth_async.py ├── test_auth_global.py ├── test_auth_inheritance_routers.py ├── test_auth_routers.py ├── test_body.py ├── test_compatibility.py ├── test_conf.py ├── test_csrf.py ├── test_csrf_async.py ├── test_decorators.py ├── test_discriminator.py ├── test_django_models.py ├── test_docs/ │ ├── __init__.py │ ├── test_auth.py │ ├── test_body.py │ ├── test_form.py │ ├── test_index.py │ ├── test_path.py │ └── test_query.py ├── test_enum.py ├── test_errors.py ├── test_exceptions.py ├── test_export_openapi_schema.py ├── test_files.py ├── test_filter_schema.py ├── test_forms.py ├── test_forms_and_files.py ├── test_inheritance_routers.py ├── test_lists.py ├── test_misc.py ├── test_models.py ├── test_openapi_docs.py ├── test_openapi_extra.py ├── test_openapi_params.py ├── test_openapi_schema.py ├── test_orm_metaclass.py ├── test_orm_relations.py ├── test_orm_schemas.py ├── test_pagination.py ├── test_pagination_async.py ├── test_pagination_cursor.py ├── test_pagination_router.py ├── test_params_models.py ├── test_parser.py ├── test_patch_dict.py ├── test_path.py ├── test_pydantic_migrate.py ├── test_query.py ├── test_query_schema.py ├── test_renderer.py ├── test_request.py ├── test_response.py ├── test_response_cookies.py ├── test_response_multiple.py ├── test_response_params.py ├── test_reverse.py ├── test_router_add_router.py ├── test_router_defaults.py ├── test_router_path_params.py ├── test_router_reuse.py ├── test_schema.py ├── test_schema_context.py ├── test_serialization_context.py ├── test_server.py ├── test_signature_details.py ├── test_status.py ├── test_streaming.py ├── test_test_client.py ├── test_throttling.py ├── test_union.py ├── test_utils.py ├── test_with_django/ │ ├── __init__.py │ ├── schema_fixtures/ │ │ ├── test-multi-body-file.json │ │ ├── test-multi-body-form-file.json │ │ ├── test-multi-body-form.json │ │ ├── test-multi-body.json │ │ ├── test-multi-cookie.json │ │ ├── test-multi-form-body-file.json │ │ ├── test-multi-form-body.json │ │ ├── test-multi-form-file.json │ │ ├── test-multi-form.json │ │ ├── test-multi-header.json │ │ ├── test-multi-path.json │ │ └── test-multi-query.json │ └── test_multi_param_parsing.py ├── test_wraps.py └── util.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ *.pyc .venv* .vscode .mypy_cache .coverage htmlcov dist test.py ================================================ FILE: .github/FUNDING.yml ================================================ # polar: django-ninja custom: ["https://www.buymeacoffee.com/djangoninja"] ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: "[BUG] " labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **Versions (please complete the following information):** - Python version: [e.g. 3.6] - Django version: [e.g. 4.0] - Django-Ninja version: [e.g. 0.16.2] - Pydantic version: [e.g. 1.9.0] Note you can quickly get this by runninng in `./manage.py shell` this line: ``` import django; import pydantic; import ninja; django.__version__; ninja.__version__; pydantic.__version__ ``` ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. ================================================ FILE: .github/ISSUE_TEMPLATE/question.md ================================================ --- name: Question about: Having troubles implementing something ? title: '' labels: '' assignees: '' --- Please describe what you are trying to achieve Please include code examples (like models code, schemes code, view function) to help understand the issue ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: monthly ================================================ FILE: .github/workflows/close-old-issues.yml ================================================ name: Close Old Issues on: workflow_dispatch: inputs: days_old: description: 'Close issues older than N days' required: true default: '365' type: number dry_run: description: 'Dry run mode (preview only, do not close)' required: true default: true type: boolean comment_on_close: description: 'Add comment when closing issues' required: true default: true type: boolean jobs: close-old-issues: runs-on: ubuntu-latest permissions: issues: write steps: - name: Close or list old issues uses: actions/github-script@v8 with: script: | const daysOld = ${{ inputs.days_old }}; const dryRun = ${{ inputs.dry_run }}; const addComment = ${{ inputs.comment_on_close }}; const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - daysOld); console.log(`Looking for issues older than ${daysOld} days (before ${cutoffDate.toISOString()})`); console.log(`Dry run mode: ${dryRun ? 'YES (no changes will be made)' : 'NO (issues will be closed)'}`); console.log('---'); let page = 1; let issuesClosed = 0; let issuesToClose = []; while (true) { const issues = await github.rest.issues.listForRepo({ owner: context.repo.owner, repo: context.repo.repo, state: 'open', sort: 'created', direction: 'asc', per_page: 100, page: page }); if (issues.data.length === 0) { break; } for (const issue of issues.data) { // Skip pull requests if (issue.pull_request) { continue; } const createdAt = new Date(issue.created_at); const updatedAt = new Date(issue.updated_at); // Check if issue is old enough based on last update if (updatedAt < cutoffDate) { const daysOldCalculated = Math.floor((Date.now() - updatedAt.getTime()) / (1000 * 60 * 60 * 24)); issuesToClose.push({ number: issue.number, title: issue.title, created_at: createdAt.toISOString().split('T')[0], updated_at: updatedAt.toISOString().split('T')[0], days_old: daysOldCalculated, url: issue.html_url }); } } page++; } console.log(`\nFound ${issuesToClose.length} issue(s) to close:\n`); for (const issue of issuesToClose) { console.log(`#${issue.number}: ${issue.title}`); console.log(` Created: ${issue.created_at}`); console.log(` Last updated: ${issue.updated_at} (${issue.days_old} days ago)`); console.log(` URL: ${issue.url}`); console.log(''); if (!dryRun) { try { // Add a comment before closing (if enabled) if (addComment) { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, body: `This issue has been automatically closed due to inactivity (no updates for ${issue.days_old} days). If you believe this issue is still relevant, please feel free to reopen it or create a new issue.` }); } // Close the issue await github.rest.issues.update({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, state: 'closed', state_reason: 'not_planned' }); issuesClosed++; console.log(` ✓ Closed issue #${issue.number}`); } catch (error) { console.error(` ✗ Failed to close issue #${issue.number}: ${error.message}`); } } } console.log('\n---'); if (dryRun) { console.log(`DRY RUN: Would close ${issuesToClose.length} issue(s)`); console.log('To actually close these issues, run this workflow again with dry_run set to false'); } else { console.log(`Successfully closed ${issuesClosed} out of ${issuesToClose.length} issue(s)`); } // Set output for summary core.summary .addHeading(dryRun ? 'Dry Run Results' : 'Close Old Issues Results') .addRaw(`**Mode:** ${dryRun ? '🔍 Dry Run (Preview Only)' : '✅ Live Run'}\n`) .addRaw(`**Cutoff Date:** Issues last updated before ${cutoffDate.toISOString().split('T')[0]}\n`) .addRaw(`**Days Old Threshold:** ${daysOld} days\n`) .addRaw(`**Issues ${dryRun ? 'Found' : 'Closed'}:** ${dryRun ? issuesToClose.length : issuesClosed}\n\n`); if (issuesToClose.length > 0) { core.summary.addHeading('Issues', 3); const tableData = issuesToClose.map(issue => [ `#${issue.number}`, issue.title.substring(0, 80) + (issue.title.length > 80 ? '...' : ''), issue.updated_at, `${issue.days_old} days`, `[View](${issue.url})` ]); core.summary.addTable([ ['Issue', 'Title', 'Last Updated', 'Age', 'Link'], ...tableData ]); } else { core.summary.addRaw('\nNo issues found matching the criteria.'); } await core.summary.write(); ================================================ FILE: .github/workflows/docs.yml ================================================ name: Docs on: workflow_dispatch: jobs: docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Docs update run: git push origin master:docs ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish on: release: types: [published] workflow_dispatch: jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v5 - name: Install Flit run: pip install flit - name: Install Dependencies run: flit install --symlink - name: Publish env: # FLIT_USERNAME: ${{ secrets.FLIT_USERNAME }} # FLIT_PASSWORD: ${{ secrets.FLIT_PASSWORD }} FLIT_USERNAME: __token__ FLIT_PASSWORD: ${{ secrets.PYPI_TOKEN }} run: flit publish ================================================ FILE: .github/workflows/test.yml ================================================ name: Test Coverage on: push: branches: - master jobs: test_coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.12' - name: Install Flit run: pip install flit "django>=5.1" - name: Install Dependencies run: flit install --symlink - name: Test run: pytest --cov=ninja --cov-report=xml tests - name: Coverage uses: codecov/codecov-action@v4.4.1 ================================================ FILE: .github/workflows/test_full.yml ================================================ name: Full Test on: push: workflow_dispatch: pull_request: types: [assigned, opened, synchronize, reopened] jobs: test: runs-on: ubuntu-22.04 strategy: matrix: python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14'] django-version: ['<3.2', '<3.3', '<4.2', '<4.3', '<5.1', '<5.2', '<5.3', '<6.1'] exclude: - python-version: '3.7' django-version: '<5.1' - python-version: '3.8' django-version: '<5.1' - python-version: '3.9' django-version: '<5.1' - python-version: '3.12' django-version: '<3.2' - python-version: '3.12' django-version: '<3.3' - python-version: '3.13' django-version: '<3.2' - python-version: '3.13' django-version: '<3.3' # as of oct 2025 looks like django < 5.2 does not support python 3.14 - python-version: '3.14' django-version: '<3.2' - python-version: '3.14' django-version: '<3.3' - python-version: '3.14' django-version: '<4.2' - python-version: '3.14' django-version: '<4.3' - python-version: '3.14' django-version: '<5.1' - python-version: '3.14' django-version: '<5.2' steps: - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install core run: pip install "Django${{ matrix.django-version }}" "pydantic<3" - name: Install tests run: pip install pytest pytest-asyncio pytest-django psycopg2-binary - name: Test run: pytest coverage: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v5 with: python-version: 3.12 - name: Install Flit run: pip install flit "django>=5.2" - name: Install Dependencies run: flit install --symlink - name: Test run: pytest --cov=ninja codestyle: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Set up Python uses: actions/setup-python@v5 with: python-version: 3.12 - name: Install Flit run: pip install flit - name: Install Dependencies run: flit install --symlink - name: Ruff format run: ruff format --check ninja tests - name: Ruff lint run: ruff check ninja tests - name: mypy run: mypy ninja tests/mypy_test.py ================================================ FILE: .gitignore ================================================ *.pyc .venv* .vscode .mypy_cache .coverage htmlcov /coverage.xml dist test.py docs/site .DS_Store .idea .python-version *.local.md ================================================ FILE: .pre-commit-config.yaml ================================================ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.2.0 hooks: - id: check-yaml # - id: end-of-file-fixer # - id: trailing-whitespace - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.7.1 hooks: - id: mypy additional_dependencies: ["django-stubs", "pydantic"] exclude: (tests|docs)/ - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.4.2 hooks: - id: ruff-format - id: ruff args: [--fix, --exit-non-zero-on-fix] ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Django Ninja uses Flit to build, package and publish the project. to install it use: ``` pip install flit ``` Once you have it - to install all dependencies required for development and testing use this command: ``` flit install --deps develop --symlink ``` Once done you can check if all works with ``` pytest . ``` or using Makefile: ``` make test ``` Now you are ready to make your contribution When you're done please make sure you to test your functionality and check the coverage of your contribution. ``` pytest --cov=ninja --cov-report term-missing tests ``` or using Makefile: ``` make test-cov ``` ## Code style Django Ninja uses `ruff`, and `mypy` for style checks. Run `pre-commit install` to create a git hook to fix your styles before you commit. Alternatively, manually check your code with: ``` ruff format --check ninja tests ruff check ninja tests mypy ninja ``` or using Makefile: ``` make lint ``` Or reformat your code with: ``` ruff format ninja tests ruff check ninja tests --fix ``` or using Makefile: ``` make fmt ``` ## Docs Please do not forget to document your contribution Django Ninja uses `mkdocs`: ``` cd docs/ mkdocs serve ``` and go to browser to see changes in real time ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2025 Vitaliy Kucheryaviy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ .DEFAULT_GOAL := help .PHONY: help help: @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' .PHONY: install install: ## Install dependencies flit install --deps develop --symlink .PHONY: lint lint: ## Run code linters ruff format --check ninja tests ruff check ninja tests mypy ninja .PHONY: fmt format fmt format: ## Run code formatters ruff format ninja tests ruff check --fix ninja tests .PHONY: test test: ## Run tests pytest . .PHONY: test-cov test-cov: ## Run tests with coverage pytest --cov=ninja --cov-report term-missing tests .PHONY: docs docs: ## Serve documentation locally pip install -r docs/requirements.txt cd docs && mkdocs serve -a localhost:8090 ================================================ FILE: README.md ================================================ SCR-20230123-m1t ^ Please read ^

Fast to learn, fast to code, fast to run

![Test](https://github.com/vitalik/django-ninja/actions/workflows/test_full.yml/badge.svg) ![Coverage](https://img.shields.io/codecov/c/github/vitalik/django-ninja) [![PyPI version](https://badge.fury.io/py/django-ninja.svg)](https://badge.fury.io/py/django-ninja) [![Downloads](https://static.pepy.tech/personalized-badge/django-ninja?period=month&units=international_system&left_color=black&right_color=brightgreen&left_text=downloads/month)](https://pepy.tech/project/django-ninja) # Django Ninja - Fast Django REST Framework **Django Ninja** is a web framework for building APIs with **Django** and Python 3.6+ **type hints**. **Key features:** - **Easy**: Designed to be easy to use and intuitive. - **FAST execution**: Very high performance thanks to **Pydantic** and **async support**. - **Fast to code**: Type hints and automatic docs lets you focus only on business logic. - **Standards-based**: Based on the open standards for APIs: **OpenAPI** (previously known as Swagger) and **JSON Schema**. - **Django friendly**: (obviously) has good integration with the Django core and ORM. - **Production ready**: Used by multiple companies on live projects (If you use django-ninja and would like to publish your feedback, please email ppr.vitaly@gmail.com). ![Django Ninja REST Framework](docs/docs/img/benchmark.png) **Documentation**: https://django-ninja.dev --- ## Installation ``` pip install django-ninja ``` ## Usage In your django project next to urls.py create new `api.py` file: ```Python from ninja import NinjaAPI api = NinjaAPI() @api.get("/add") def add(request, a: int, b: int): return {"result": a + b} ``` Now go to `urls.py` and add the following: ```Python hl_lines="3 7" ... from .api import api urlpatterns = [ path("admin/", admin.site.urls), path("api/", api.urls), # <---------- ! ] ``` **That's it !** Now you've just created an API that: - receives an HTTP GET request at `/api/add` - takes, validates and type-casts GET parameters `a` and `b` - decodes the result to JSON - generates an OpenAPI schema for defined operation ### Interactive API docs Now go to http://127.0.0.1:8000/api/docs You will see the automatic interactive API documentation (provided by Swagger UI or Redoc): ![Swagger UI](docs/docs/img/index-swagger-ui.png) ## Sponsors sendcloud-logo Become a sponsor ## What next? - Read the full documentation here - https://django-ninja.dev - To support this project, please give star it on Github. ![github star](docs/docs/img/github-star.png) - Share it [via Twitter](https://twitter.com/intent/tweet?text=Check%20out%20Django%20Ninja%20-%20Fast%20Django%20REST%20Framework%20-%20https%3A%2F%2Fdjango-ninja.dev) - If you already using django-ninja, please share your feedback to ppr.vitaly@gmail.com ================================================ FILE: docs/docs/chat.md ================================================ # Ask AI Please feel free to share any questions or describe any problems you're encountering. Simply enter your text in the chat, and I'll be happy to assist you.
================================================ FILE: docs/docs/extra.css ================================================ .doc-module code { white-space: nowrap; } /* Ask AI Button Styles */ .ask-ai-button-container { display: flex; align-items: center; margin-left: auto; margin-right: 1rem; } .ask-ai-button { background-color: white !important; color: #4caf50 !important; /* Material green-500 */ padding: 0.4rem 0.5rem !important; border-radius: 0.25rem; font-weight: 500; text-decoration: none; font-size: 0.9rem; border: 2px solid #4caf50; transition: all 0.2s ease-in-out; white-space: nowrap; margin-left: 8px; } .ask-ai-button:hover { background-color: #4caf50 !important; color: white !important; box-shadow: 0 2px 4px rgba(0,0,0,0.2); } /* Dark mode support */ [data-md-color-scheme="slate"] .ask-ai-button { background-color: white !important; color: #4caf50 !important; } [data-md-color-scheme="slate"] .ask-ai-button:hover { background-color: #4caf50 !important; color: white !important; } /* Responsive adjustments */ @media screen and (max-width: 76.1875em) { .ask-ai-button-container { margin-right: 0.5rem; } .ask-ai-button { padding: 0.4rem 0.8rem !important; font-size: 0.85rem; } } @media screen and (max-width: 60em) { .ask-ai-button-container { display: none; /* Hide on mobile to save space */ } } ================================================ FILE: docs/docs/guides/api-docs.md ================================================ # API Docs ## OpenAPI docs Once you configured your Ninja API and started runserver - go to http://127.0.0.1:8000/api/docs You will see the automatic, interactive API documentation (provided by the OpenAPI / Swagger UI ## CDN vs staticfiles You are not required to put django ninja to `INSTALLED_APPS`. In that case the interactive UI is hosted by CDN. To host docs (Js/css) from your own server - just put "ninja" to INSTALLED_APPS - in that case standard django staticfiles mechanics will host it. ## Switch to Redoc ```python from ninja import Redoc api = NinjaAPI(docs=Redoc()) ``` Then you will see the alternative automatic documentation (provided by Redoc). ## Changing docs display settings To set some custom settings for Swagger or Redocs you can use `settings` param on the docs class ```python from ninja import Redoc, Swagger api = NinjaAPI(docs=Swagger(settings={"persistAuthorization": True})) ... api = NinjaAPI(docs=Redoc(settings={"disableSearch": True})) ``` Settings reference: - [Swagger configuration](https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration/) - [Redoc configuration](https://redocly.com/docs/api-reference-docs/configuration/functionality/) ## Hiding docs ### Hiding the interactive docs viewer To hide only the interactive documentation UI (Swagger or Redoc) while keeping the OpenAPI schema accessible, set `docs_url` to `None`: ```python api = NinjaAPI(docs_url=None) ``` This disables the `/docs` endpoint but the OpenAPI schema remains available at `/openapi.json`. This is useful when you want to: - Disable the interactive UI but keep the schema for API clients or code generators - Use external documentation tools that consume the OpenAPI spec ### Disabling the OpenAPI schema endpoint To disable the OpenAPI schema endpoint, set `openapi_url` to `None`: ```python api = NinjaAPI(openapi_url=None) ``` This disables the `/openapi.json` endpoint. Since the docs viewer depends on the OpenAPI schema, this also disables the docs viewer - no documentation URLs will be registered. ### Summary | Configuration | `/openapi.json` | `/docs` | Use Case | |---------------|-----------------|---------|----------| | Default | Available | Available | Development | | `docs_url=None` | Available | Hidden | Hide UI, keep schema for clients | | `openapi_url=None` | Hidden | Hidden | Completely hide all documentation | ## Protecting docs To protect docs with authentication (or decorate for some other use case) use `docs_decorator` argument: ```python from django.contrib.admin.views.decorators import staff_member_required api = NinjaAPI(docs_decorator=staff_member_required) ``` ## Extending OpenAPI Spec with custom attributes You can extend OpenAPI spec with custom attributes, for example to add `termsOfService` ```python api = NinjaAPI( openapi_extra={ "info": { "termsOfService": "https://example.com/terms/", } }, title="Demo API", description="This is a demo API with dynamic OpenAPI info section" ) ``` ## Resolving the doc's url The url for the api's documentation view can be reversed by referencing the view's name `openapi-view`. In Python code, for example: ```python from django.urls import reverse reverse('api-1.0.0:openapi-view') >>> '/api/docs' ``` In a Django template, for example: ```Html API Docs API Docs ``` ## Creating custom docs viewer To create your own view for OpenAPI - create a class inherited from DocsBase and overwrite `render_page` method: ```python from ninja.openapi.docs import DocsBase class MyDocsViewer(DocsBase): def render_page(self, request, api): ... # return http response ... api = NinjaAPI(docs=MyDocsViewer()) ``` ## Using a custom favicon The django-ninja OpenAPI docs contain a default favicon, the ninja star. To use your own, overwrite the `ninja/favicon.html` django template. ```html {% load static %} {% block favicons %} {% endblock %} ``` for more information, see the [Django documentation on overriding templates](https://docs.djangoproject.com/en/5.2/howto/overriding-templates/). ================================================ FILE: docs/docs/guides/async-support.md ================================================ ## Intro Since **version 3.1**, Django comes with **async views support**. This allows you run efficient concurrent views that are network and/or IO bound. ``` pip install Django>=3.1 django-ninja ``` Async views work more efficiently when it comes to: - calling external APIs over the network - executing/waiting for database queries - reading/writing from/to disk drives **Django Ninja** takes full advantage of async views and makes it very easy to work with them. ## Quick example ### Code Let's take an example. We have an API operation that does some work (currently just sleeps for provided number of seconds) and returns a word: ```python hl_lines="5" import time @api.get("/say-after") def say_after(request, delay: int, word: str): time.sleep(delay) return {"saying": word} ``` To make this code asynchronous, all you have to do is add the **`async`** keyword to a function (and use async aware libraries for work processing - in our case we will replace the stdlib `sleep` with `asyncio.sleep`): ```python hl_lines="1 4 5" import asyncio @api.get("/say-after") async def say_after(request, delay: int, word: str): await asyncio.sleep(delay) return {"saying": word} ``` ### Run To run this code you need an ASGI server like Uvicorn or Daphne. Let's use Uvicorn for, example: To install Uvicorn, use: ``` pip install uvicorn ``` Then start the server: ``` uvicorn your_project.asgi:application --reload ``` > > *Note: replace `your_project` with your project package name*
> *`--reload` flag used to automatically reload server if you do any changes to the code (do not use on production)* >
!!! note You can run async views with `manage.py runserver`, but it does not work well with some libraries, so at this time (July 2020) it is recommended to use ASGI servers like Uvicorn or Daphne. ### Test Go to your browser and open http://127.0.0.1:8000/api/say-after?delay=3&word=hello (**delay=3**) After a 3-second wait you should see the "hello" message. Now let's flood this operation with **100 parallel requests**: ``` ab -c 100 -n 100 "http://127.0.0.1:8000/api/say-after?delay=3&word=hello" ``` which will result in something like this: ``` Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 1.1 1 4 Processing: 3008 3063 16.2 3069 3082 Waiting: 3008 3062 15.7 3068 3079 Total: 3008 3065 16.3 3070 3083 Percentage of the requests served within a certain time (ms) 50% 3070 66% 3072 75% 3075 80% 3076 90% 3081 95% 3082 98% 3083 99% 3083 100% 3083 (longest request) ``` Based on the numbers, our service was able to handle each of the 100 concurrent requests with just a little overhead. To achieve the same concurrency with WSGI and sync operations you would need to spin up about 10 workers with 10 threads each! ## Mixing sync and async operations Keep in mind that you can use **both sync and async operations** in your project, and **Django Ninja** will route it automatically: ```python hl_lines="2 7" @api.get("/say-sync") def say_after_sync(request, delay: int, word: str): time.sleep(delay) return {"saying": word} @api.get("/say-async") async def say_after_async(request, delay: int, word: str): await asyncio.sleep(delay) return {"saying": word} ``` ## Elasticsearch example Let's take a real world use case. For this example, let's use the latest version of Elasticsearch that now comes with async support: ``` pip install elasticsearch>=7.8.0 ``` And now instead of the `Elasticsearch` class, use the `AsyncElasticsearch` class and `await` the results: ```python hl_lines="2 7 11 12" from ninja import NinjaAPI from elasticsearch import AsyncElasticsearch api = NinjaAPI() es = AsyncElasticsearch() @api.get("/search") async def search(request, q: str): resp = await es.search( index="documents", body={"query": {"query_string": {"query": q}}}, size=20, ) return resp["hits"] ``` ## Using ORM Currently, certain key parts of Django are not able to operate safely in an async environment, as they have global state that is not coroutine-aware. These parts of Django are classified as “async-unsafe”, and are protected from execution in an async environment. **The ORM** is the main example, but there are other parts that are also protected in this way. Learn more about async safety here in the official Django docs. So, if you do this: ```python hl_lines="3" @api.get("/blog/{post_id}") async def search(request, post_id: int): blog = Blog.objects.get(pk=post_id) ... ``` it throws an error. Until the async ORM is implemented, you can use the `sync_to_async()` adapter: ```python hl_lines="1 3 9" from asgiref.sync import sync_to_async @sync_to_async def get_blog(post_id): return Blog.objects.get(pk=post_id) @api.get("/blog/{post_id}") async def search(request, post_id: int): blog = await get_blog(post_id) ... ``` or even shorter: ```python hl_lines="3" @api.get("/blog/{post_id}") async def search(request, post_id: int): blog = await sync_to_async(Blog.objects.get)(pk=post_id) ... ``` There is a common **GOTCHA**: Django querysets are lazily evaluated (database query happens only when you start iterating), so this will **not** work: ```python all_blogs = await sync_to_async(Blog.objects.all)() # it will throw an error later when you try to iterate over all_blogs ... ``` Instead, use evaluation (with `list`): ```python all_blogs = await sync_to_async(list)(Blog.objects.all()) ... ``` Since Django **version 4.1**, Django comes with asynchronous versions of ORM operations. These eliminate the need to use `sync_to_async` in most cases. The async operations have the same names as their sync counterparts but are prepended with *a*. So using the example above, you can rewrite it as: ```python hl_lines="3" @api.get("/blog/{post_id}") async def search(request, post_id: int): blog = await Blog.objects.aget(pk=post_id) ... ``` When working with querysets, use `async for` paired with list comprehension: ```python all_blogs = [blog async for blog in Blog.objects.all()] ... ``` Learn more about the async ORM interface in the official Django docs. ================================================ FILE: docs/docs/guides/authentication.md ================================================ # Authentication ## Intro **Django Ninja** provides several tools to help you deal with authentication and authorization easily, rapidly, in a standard way, and without having to study and learn all the security specifications. The core concept is that when you describe an API operation, you can define an authentication object. ```python hl_lines="2 7" {!./src/tutorial/authentication/code001.py!} ``` In this example, the client will only be able to call the `pets` method if it uses Django session authentication (the default is cookie based), otherwise an HTTP-401 error will be returned. If you need to authorize only a superuser, you can use `from ninja.security import django_auth_superuser` instead. ## Automatic OpenAPI schema Here's an example where the client, in order to authenticate, needs to pass a header: `Authorization: Bearer supersecret` ```python hl_lines="4 5 6 7 10" {!./src/tutorial/authentication/bearer01.py!} ``` Now go to the docs at http://localhost:8000/api/docs. ![Swagger UI Auth](../img/auth-swagger-ui.png) Now, when you click the **Authorize** button, you will get a prompt to input your authentication token. ![Swagger UI Auth](../img/auth-swagger-ui-prompt.png) When you do test calls, the Authorization header will be passed for every request. ## Global authentication In case you need to secure **all** methods of your API, you can pass the `auth` argument to the `NinjaAPI` constructor: ```python hl_lines="11 19" from ninja import NinjaAPI, Form from ninja.security import HttpBearer class GlobalAuth(HttpBearer): def authenticate(self, request, token): if token == "supersecret": return token api = NinjaAPI(auth=GlobalAuth()) # @api.get(...) # def ... # @api.post(...) # def ... ``` And, if you need to overrule some of those methods, you can do that on the operation level again by passing the `auth` argument. In this example, authentication will be disabled for the `/token` operation: ```python hl_lines="19" {!./src/tutorial/authentication/global01.py!} ``` ## Available auth options ### Custom function The "`auth=`" argument accepts any Callable object. **NinjaAPI** passes authentication only if the callable object returns a value that can be **converted to boolean `True`**. This return value will be assigned to the `request.auth` attribute. ```python hl_lines="1 2 3 6" {!./src/tutorial/authentication/code002.py!} ``` ### API Key Some API's use API keys for authorization. An API key is a token that a client provides when making API calls to identify itself. The key can be sent in the query string: ``` GET /something?api_key=abcdef12345 ``` or as a request header: ``` GET /something HTTP/1.1 X-API-Key: abcdef12345 ``` or as a cookie: ``` GET /something HTTP/1.1 Cookie: X-API-KEY=abcdef12345 ``` **Django Ninja** comes with built-in classes to help you handle these cases. #### in Query ```python hl_lines="1 2 5 6 7 8 9 10 11 12" {!./src/tutorial/authentication/apikey01.py!} ``` In this example we take a token from `GET['api_key']` and find a `Client` in the database that corresponds to this key. The Client instance will be set to the `request.auth` attribute. Note: **`param_name`** is the name of the GET parameter that will be checked for. If not set, the default of "`key`" will be used. #### in Header ```python hl_lines="1 4" {!./src/tutorial/authentication/apikey02.py!} ``` #### in Cookie ```python hl_lines="1 4" {!./src/tutorial/authentication/apikey03.py!} ``` ### Django Session Authentication **Django Ninja** provides built-in session authentication classes that leverage Django's existing session framework: #### SessionAuth Uses Django's default session authentication - authenticates any logged-in user: ```python from ninja.security import SessionAuth @api.get("/protected", auth=SessionAuth()) def protected_view(request): return {"user": request.auth.username} ``` #### SessionAuthSuperUser Authenticates only users with superuser privileges: ```python from ninja.security import SessionAuthSuperUser @api.get("/admin-only", auth=SessionAuthSuperUser()) def admin_view(request): return {"message": "Hello superuser!"} ``` #### SessionAuthIsStaff Authenticates users who are either superusers or staff members: ```python from ninja.security import SessionAuthIsStaff @api.get("/staff-area", auth=SessionAuthIsStaff()) def staff_view(request): return {"message": "Hello staff member!"} ``` These authentication classes automatically use Django's `SESSION_COOKIE_NAME` setting and check the user's authentication status through the standard Django session framework. ### HTTP Bearer ```python hl_lines="1 4 5 6 7" {!./src/tutorial/authentication/bearer01.py!} ``` ### HTTP Basic Auth ```python hl_lines="1 4 5 6 7" {!./src/tutorial/authentication/basic01.py!} ``` ## Multiple authenticators The **`auth`** argument also allows you to pass multiple authenticators: ```python hl_lines="18" {!./src/tutorial/authentication/multiple01.py!} ``` In this case **Django Ninja** will first check the API key `GET`, and if not set or invalid will check the `header` key. If both are invalid, it will raise an authentication error to the response. ## Router authentication Use `auth` argument on Router to apply authenticator to all operations declared in it: ```python api.add_router("/events/", events_router, auth=BasicAuth()) ``` or using router constructor ```python router = Router(auth=BasicAuth()) ``` This overrides any API level authentication. To allow router operations to not use the API-level authentication by default, you can explicitly set the router's `auth=None`. ## Custom exceptions Raising an exception that has an exception handler will return the response from that handler in the same way an operation would: ```python hl_lines="1 4" {!./src/tutorial/authentication/bearer02.py!} ``` ## Async authentication **Django Ninja** has basic support for asynchronous authentication. While the default authentication classes are not async-compatible, you can still define your custom asynchronous authentication callables and pass them in using `auth`. ```python async def async_auth(request): ... @api.get("/pets", auth=async_auth) def pets(request): ... ``` See [Handling errors](errors.md) for more information. ================================================ FILE: docs/docs/guides/decorators.md ================================================ # Decorators Django Ninja provides flexible decorator support to wrap your API operations with additional functionality like caching, logging, authentication checks, or any custom logic. ## Understanding Decorator Modes Django Ninja supports two modes for applying decorators: ### OPERATION Mode (Default) - Applied **after** Django Ninja's validation - Wraps the operation function with validated data - Has access to parsed and validated parameters - Useful for: business logic, logging with validated data, post-validation checks ### VIEW Mode - Applied **before** Django Ninja's validation - Wraps the entire Django view function - Has access to the raw Django request - Useful for: caching, rate limiting, Django middleware-like functionality - Similar to Django's standard view decorators ## Using `@decorate_view` The `@decorate_view` decorator allows you to apply Django view decorators to individual endpoints. These decorators are always executed in VIEW mode: ```python from django.views.decorators.cache import cache_page from ninja import NinjaAPI from ninja.decorators import decorate_view api = NinjaAPI() @api.get("/cached") @decorate_view(cache_page(60 * 15)) # Cache for 15 minutes def cached_endpoint(request): return {"data": "This response is cached"} ``` You can apply multiple decorators: ```python from django.views.decorators.cache import cache_page from django.views.decorators.vary import vary_on_headers @api.get("/multi") @decorate_view(cache_page(300), vary_on_headers("User-Agent")) def multi_decorated(request): return {"data": "Multiple decorators applied"} ``` ## Using `add_decorator` The `add_decorator` method allows you to apply decorators to multiple endpoints at once. By default, they are executed in OPERATION mode; however, you can switch them to VIEW mode. ### Router-Level Decorators Apply decorators to all endpoints in a router: ```python from ninja import Router router = Router() # Add logging to all operations in this router def log_operation(func): def wrapper(request, *args, **kwargs): print(f"Calling {func.__name__}") result = func(request, *args, **kwargs) print(f"Result: {result}") return result return wrapper router.add_decorator(log_operation) # OPERATION mode by default @router.get("/users") def list_users(request): return {"users": ["Alice", "Bob"]} @router.get("/users/{user_id}") def get_user(request, user_id: int): return {"user_id": user_id} ``` ### API-Level Decorators Apply decorators to all endpoints in your entire API: ```python from ninja import NinjaAPI api = NinjaAPI() # Add CORS headers to all responses (VIEW mode) def cors_headers(func): def wrapper(request, *args, **kwargs): response = func(request, *args, **kwargs) response["Access-Control-Allow-Origin"] = "*" return response return wrapper api.add_decorator(cors_headers, mode="view") # Now all endpoints will have CORS headers @api.get("/data") def get_data(request): return {"data": "example"} ``` ## Practical Examples ### Example 1: Request Timing ```python import time from functools import wraps def timing_decorator(func): @wraps(func) def wrapper(request, *args, **kwargs): start = time.time() result = func(request, *args, **kwargs) duration = time.time() - start if isinstance(result, dict): result["_timing"] = f"{duration:.3f}s" return result return wrapper router = Router() router.add_decorator(timing_decorator) @router.get("/slow") def slow_endpoint(request): time.sleep(1) return {"message": "done"} # Returns: {"message": "done", "_timing": "1.001s"} ``` ### Example 2: Authentication Check (OPERATION mode) ```python from functools import wraps def require_feature_flag(flag_name): def decorator(func): @wraps(func) def wrapper(request, *args, **kwargs): if not request.user.has_feature(flag_name): return {"error": f"Feature {flag_name} not enabled"} return func(request, *args, **kwargs) return wrapper return decorator router = Router() router.add_decorator(require_feature_flag("new_api")) @router.get("/new-feature") def new_feature(request): return {"feature": "enabled"} ``` ### Example 3: Response Caching (VIEW mode) ```python from django.core.cache import cache from functools import wraps import hashlib def cache_response(timeout=300): def decorator(func): @wraps(func) def wrapper(request, *args, **kwargs): # Create cache key from request cache_key = hashlib.md5( f"{request.path}{request.GET.urlencode()}".encode() ).hexdigest() # Try to get from cache cached = cache.get(cache_key) if cached: return cached # Call the view response = func(request, *args, **kwargs) # Cache the response cache.set(cache_key, response, timeout) return response return wrapper return decorator router = Router() router.add_decorator(cache_response(600), mode="view") ``` ## Decorator Execution Order When multiple decorators are applied, they execute in this order: 1. API-level decorators (outermost) 2. Parent router decorators 3. Child router decorators 4. Individual endpoint decorators (innermost) Be aware that VIEW mode decorators are executed before OPERATION mode decorators. ```python api = NinjaAPI() parent_router = Router() child_router = Router() api.add_decorator(api_decorator) parent_router.add_decorator(parent_decorator) child_router.add_decorator(child_decorator) @child_router.get("/test") @decorate_view(endpoint_decorator) def endpoint(request): return {"result": "ok"} parent_router.add_router("/child", child_router) api.add_router("/parent", parent_router) # Execution order: # 1. endpoint_decorator (view) # 1. api_decorator (operational) # 2. parent_decorator (operational) # 3. child_decorator (operational) # 5. endpoint function ``` ## Async Support Decorators work with both sync and async views. When you have mixed sync/async endpoints in the same router, you need to create universal decorators that handle both cases. ### Universal Decorators for Mixed Sync/Async Routers When you have a router with both sync and async endpoints, use `asyncio.iscoroutinefunction()` to detect the function type: ```python import asyncio from functools import wraps def universal_decorator(func): if asyncio.iscoroutinefunction(func): # Handle async functions @wraps(func) async def async_wrapper(request, *args, **kwargs): # Your async logic here result = await func(request, *args, **kwargs) if isinstance(result, dict): result["decorated"] = True result["type"] = "async" return result return async_wrapper else: # Handle sync functions @wraps(func) def sync_wrapper(request, *args, **kwargs): # Your sync logic here result = func(request, *args, **kwargs) if isinstance(result, dict): result["decorated"] = True result["type"] = "sync" return result return sync_wrapper router = Router() router.add_decorator(universal_decorator) @router.get("/async") async def async_endpoint(request): await asyncio.sleep(0.1) return {"endpoint": "async"} @router.get("/sync") def sync_endpoint(request): return {"endpoint": "sync"} ``` ### Async-Only Decorators For routers with only async endpoints, you can use async decorators directly: ```python def async_timing_decorator(func): @wraps(func) async def wrapper(request, *args, **kwargs): start = time.time() result = await func(request, *args, **kwargs) duration = time.time() - start if isinstance(result, dict): result["_timing"] = f"{duration:.3f}s" return result return wrapper router = Router() router.add_decorator(async_timing_decorator) @router.get("/async") async def async_endpoint(request): await asyncio.sleep(1) return {"message": "async done"} ``` ### Sync Decorators on Async Views You can also use sync decorators on async views by handling coroutines: ```python def sync_decorator(func): @wraps(func) def wrapper(request, *args, **kwargs): result = func(request, *args, **kwargs) if asyncio.iscoroutine(result): # Handle async functions async def async_wrapper(): actual_result = await result if isinstance(actual_result, dict): actual_result["sync_decorated"] = True return actual_result return async_wrapper() else: # Handle sync functions if isinstance(result, dict): result["sync_decorated"] = True return result return wrapper ``` ## When to Use Each Mode ### Use VIEW Mode When: - You need access to the raw Django request - Implementing caching at the HTTP level - Adding/modifying HTTP headers - Implementing rate limiting - Working with Django middleware patterns ### Use OPERATION Mode When: - You need access to validated/parsed data - Implementing business logic decorators - Adding data to responses - Logging with type-safe parameters - Post-validation security checks ## Best Practices 1. **Use `functools.wraps`**: Always use `@wraps(func)` to preserve function metadata 2. **Handle mixed sync/async routers**: When your router has both sync and async endpoints, use `asyncio.iscoroutinefunction(func)` to create universal decorators 3. **Choose the right approach for async**: - **Universal decorators**: Best for mixed routers (detect with `iscoroutinefunction`) - **Async-only decorators**: Best for async-only routers (simpler, cleaner) - **Sync decorators with coroutine handling**: Useful for legacy decorators 4. **Be mindful of performance**: Decorators add overhead, especially in VIEW mode 5. **Document side effects**: Clearly document what your decorators modify 6. **Keep decorators focused**: Each decorator should have a single responsibility 7. **Test both sync and async**: When using universal decorators, test both sync and async endpoints ================================================ FILE: docs/docs/guides/errors.md ================================================ # Handling errors **Django Ninja** allows you to install custom exception handlers to deal with how you return responses when errors or handled exceptions occur. ## Custom exception handlers Let's say you are making API that depends on some external service that is designed to be unavailable at some moments. Instead of throwing default 500 error upon exception - you can handle the error and give some friendly response back to the client (to come back later) To achieve that you need: 1. create some exception (or use existing one) 2. use api.exception_handler decorator Example: ```python hl_lines="9 10" api = NinjaAPI() class ServiceUnavailableError(Exception): pass # initializing handler @api.exception_handler(ServiceUnavailableError) def service_unavailable(request, exc): return api.create_response( request, {"message": "Please retry later"}, status=503, ) # some logic that throws exception @api.get("/service") def some_operation(request): if random.choice([True, False]): raise ServiceUnavailableError() return {"message": "Hello"} ``` Exception handler function takes 2 arguments: - **request** - Django http request - **exc** - actual exception function must return http response ## Override the default exception handlers **Django Ninja** registers default exception handlers for the types shown below. You can register your own handlers with `@api.exception_handler` to override the default handlers. #### `ninja.errors.AuthenticationError` Raised when authentication data is not valid #### `ninja.errors.AuthorizationError` Raised when authentication data is valid, but doesn't allow you to access the resource #### `ninja.errors.ValidationError` Raised when request data does not validate #### `ninja.errors.HttpError` Used to throw http error with status code from any place of the code #### `django.http.Http404` Django's default 404 exception (can be returned f.e. with `get_object_or_404`) #### `Exception` Any other unhandled exception by application. Default behavior - **if `settings.DEBUG` is `True`** - returns a traceback in plain text (useful when debugging in console or swagger UI) - **else** - default django exception handler mechanism is used (error logging, email to ADMINS) ## Customizing request validation errors Requests that fail validation raise `ninja.errors.ValidationError` (not to be confused with `pydantic.ValidationError`). `ValidationError`s have a default exception handler that returns a 422 (Unprocessable Content) JSON response of the form: ```json { "detail": [ ... ] } ``` You can change this behavior by overriding the default handler for `ValidationError`s: ```python hl_lines="1 4" from ninja.errors import ValidationError ... @api.exception_handler(ValidationError) def validation_errors(request, exc): return HttpResponse("Invalid input", status=422) ``` If you need even more control over validation errors (for example, if you need to reference the schema associated with the model that failed validation), you can supply your own `validation_error_from_error_contexts` in a `NinjaAPI` subclass: ```python hl_lines="4" from ninja.errors import ValidationError, ValidationErrorContext from typing import Any, Dict, List class CustomNinjaAPI(NinjaAPI): def validation_error_from_error_contexts( self, error_contexts: List[ValidationErrorContext], ) -> ValidationError: custom_error_infos: List[Dict[str, Any]] = [] for context in error_contexts: model = context.model pydantic_schema = model.__pydantic_core_schema__ param_source = model.__ninja_param_source__ for e in context.pydantic_validation_error.errors( include_url=False, include_context=False, include_input=False ): custom_error_info = { # TODO: use `e`, `param_source`, and `pydantic_schema` as desired } custom_error_infos.append(custom_error_info) return ValidationError(custom_error_infos) api = CustomNinjaAPI() ``` Now each `ValidationError` raised during request validation will contain data from your `validation_error_from_error_contexts`. ## Throwing HTTP responses with exceptions As an alternative to custom exceptions and writing handlers for it - you can as well throw http exception that will lead to returning a http response with desired code ```python from ninja.errors import HttpError @api.get("/some/resource") def some_operation(request): if True: raise HttpError(503, "Service Unavailable. Please retry later.") ``` ================================================ FILE: docs/docs/guides/input/body.md ================================================ # Request Body Request bodies are typically used with “create” and “update” operations (POST, PUT, PATCH). For example, when creating a resource using POST or PUT, the request body usually contains the representation of the resource to be created. To declare a **request body**, you need to use **Django Ninja `Schema`**. !!! info Under the hood **Django Ninja** uses Pydantic models with all their power and benefits. The alias `Schema` was chosen to avoid confusion in code when using Django models, as Pydantic's model class is called Model by default, and conflicts with Django's Model class. ## Import Schema First, you need to import `Schema` from `ninja`: ```python hl_lines="2" {!./src/tutorial/body/code01.py!} ``` ## Create your data model Then you declare your data model as a class that inherits from `Schema`. Use standard Python types for all the attributes: ```python hl_lines="5 6 7 8 9" {!./src/tutorial/body/code01.py!} ``` Note: if you use **`None`** as the default value for an attribute, it will become optional in the request body. For example, this model above declares a JSON "`object`" (or Python `dict`) like: ```JSON { "name": "Katana", "description": "An optional description", "price": 299.00, "quantity": 10 } ``` ...as `description` is optional (with a default value of `None`), this JSON "`object`" would also be valid: ```JSON { "name": "Katana", "price": 299.00, "quantity": 10 } ``` ## Declare it as a parameter To add it to your *path operation*, declare it the same way you declared the path and query parameters: ```python hl_lines="13" {!./src/tutorial/body/code01.py!} ``` ... and declare its type as the model you created, `Item`. ## Results With just that Python type declaration, **Django Ninja** will: * Read the body of the request as JSON. * Convert the corresponding types (if needed). * Validate the data. * If the data is invalid, it will return a nice and meaningful error, indicating exactly where and what the incorrect data was. * Give you the received data in the parameter `item`. * Because you declared it in the function to be of type `Item`, you will also have all the editor support (completion, etc.) for all the attributes and their types. * Generate JSON Schema definitions for your models, and you can also use them anywhere else you like if it makes sense for your project. * Those schemas will be part of the generated OpenAPI schema, and used by the automatic documentation UI's. ## Automatic docs The JSON Schemas of your models will be part of your OpenAPI generated schema, and will be shown in the interactive API docs: ![Openapi schema](../../img/body-schema-doc.png) ... and they will be also used in the API docs inside each *path operation* that needs them: ![Openapi schema](../../img/body-schema-doc2.png) ## Editor support In your editor, inside your function you will get type hints and completion everywhere (this wouldn't happen if you received a `dict` instead of a Schema object): ![Type hints](../../img/body-editor.gif) The previous screenshots were taken with Visual Studio Code. You would get the same editor support with PyCharm and most of the other Python editors. ## Request body + path parameters You can declare path parameters **and** body requests at the same time. **Django Ninja** will recognize that the function parameters that match path parameters should be **taken from the path**, and that function parameters that are declared with `Schema` should be **taken from the request body**. ```python hl_lines="11 12" {!./src/tutorial/body/code02.py!} ``` ## Request body + path + query parameters You can also declare **body**, **path** and **query** parameters, all at the same time. **Django Ninja** will recognize each of them and take the data from the correct place. ```python hl_lines="11 12" {!./src/tutorial/body/code03.py!} ``` The function parameters will be recognized as follows: * If the parameter is also declared in the **path**, it will be used as a path parameter. * If the parameter is of a **singular type** (like `int`, `float`, `str`, `bool`, etc.), it will be interpreted as a **query** parameter. * If the parameter is declared to be of the type of **Schema** (or Pydantic `BaseModel`), it will be interpreted as a request **body**. ================================================ FILE: docs/docs/guides/input/file-params.md ================================================ # File uploads Handling files are no different from other parameters. ```python hl_lines="1 2 5" from ninja import NinjaAPI, File from ninja.files import UploadedFile @api.post("/upload") def upload(request, file: File[UploadedFile]): data = file.read() return {'name': file.name, 'len': len(data)} ``` `UploadedFile` is an alias to [Django's UploadFile](https://docs.djangoproject.com/en/stable/ref/files/uploads/#django.core.files.uploadedfile.UploadedFile) and has all the methods and attributes to access the uploaded file: - read() - multiple_chunks(chunk_size=None) - chunks(chunk_size=None) - name - size - content_type - content_type_extra - charset - etc. ## Uploading array of files To **upload several files** at the same time, just declare a `List` of `UploadedFile`: ```python hl_lines="1 6" from typing import List from ninja import NinjaAPI, File from ninja.files import UploadedFile @api.post("/upload-many") def upload_many(request, files: File[List[UploadedFile]]): return [f.name for f in files] ``` ## Uploading files with extra fields Note: The HTTP protocol does not allow you to send files in `application/json` format by default (unless you encode it somehow to JSON on client side) To send files along with some extra attributes, you need to send bodies with `multipart/form-data` encoding. You can do it by simply marking fields with `Form`: ```python hl_lines="14" from ninja import NinjaAPI, Schema, UploadedFile, Form, File from datetime import date api = NinjaAPI() class UserDetails(Schema): first_name: str last_name: str birthdate: date @api.post('/users') def create_user(request, details: Form[UserDetails], file: File[UploadedFile]): return [details.dict(), file.name] ``` Note: in this case all fields should be send as form fields You can as well send payload in single field as JSON - just remove the Form mark from: ```python @api.post('/users') def create_user(request, details: UserDetails, file: File[UploadedFile]): return [details.dict(), file.name] ``` this will expect from the client side to send data as `multipart/form-data with 2 fields: - details: JSON as string - file: file ### List of files with extra info ```python @api.post('/users') def create_user(request, details: Form[UserDetails], files: File[list[UploadedFile]]): return [details.dict(), [f.name for f in files]] ``` ### Optional file input If you would like the file input to be optional, all that you have to do is to pass `None` to the `File` type, like so: ```python @api.post('/users') def create_user(request, details: Form[UserDetails], avatar: File[UploadedFile] = None): user = add_user_to_database(details) if avatar is not None: set_user_avatar(user) ``` ## Handling request.FILES in PUT/PATCH Requests **Problem** ```python @api.put("/upload") # !!!! def upload(request, file: File[UploadedFile]): ... ``` For some [historical reasons Django’s](https://groups.google.com/g/django-users/c/BeBKj_6qNsc) `request.FILES` is populated only for POST requests by default. When using HTTP PUT or PATCH methods with file uploads (e.g., multipart/form-data), request.FILES will not contain uploaded files. This is a known Django behavior, not specific to Django Ninja. As a result, views expecting files in PUT or PATCH requests may not behave correctly, since request.FILES will be empty. **Solution** Django Ninja provides a built-in middleware to automatically fix this behavior: `ninja.compatibility.files.fix_request_files_middleware` This middleware will manually parse multipart/form-data for PUT and PATCH requests and populate request.FILES, making file uploads work as expected across all HTTP methods. **Usage** To enable the middleware, add the following to your Django settings: ```python MIDDLEWARE = [ # ... your existing middleware ... "ninja.compatibility.files.fix_request_files_middleware", ] ``` **Auto-detection** When Django Ninja detects a PUT or PATCH etc methods with multipart/form-data and expected FILES - it will throw an error message suggesting you install the compatibility middleware: Note: This middleware does not interfere with normal POST behavior or any other methods. ================================================ FILE: docs/docs/guides/input/filtering.md ================================================ # Filtering If you want to allow the user to filter your querysets by a number of different attributes, it makes sense to encapsulate your filters into a `FilterSchema` class. `FilterSchema` is a regular `Schema`, so it's using all the necessary features of Pydantic, but it also adds some bells and whistles that ease the translation of the user-facing filtering parameters into database queries. Start off with defining a subclass of `FilterSchema`: ```python hl_lines="6 7 8 9" from ninja import FilterSchema from typing import Optional from datetime import datetime class BookFilterSchema(FilterSchema): name: Optional[str] = None author: Optional[str] = None created_after: Optional[datetime] = None ``` Next, use this schema in conjunction with `Query` in your API handler: ```python hl_lines="2" @api.get("/books") def list_books(request, filters: BookFilterSchema = Query(...)): books = Book.objects.all() books = filters.filter(books) return books ``` Just like described in [defining query params using schema](./query-params.md#using-schema), Django Ninja converts the fields defined in `BookFilterSchema` into query parameters. You can use a shorthand one-liner `.filter()` to apply those filters to your queryset: ```python hl_lines="4" @api.get("/books") def list_books(request, filters: Query[BookFilterSchema]): books = Book.objects.all() books = filters.filter(books) return books ``` Under the hood, `FilterSchema` converts its fields into [Q expressions](https://docs.djangoproject.com/en/3.1/topics/db/queries/#complex-lookups-with-q-objects) which it then combines and uses to filter your queryset. Alternatively to using the `.filter` method, you can get the prepared `Q`-expression and perform the filtering yourself. That can be useful, when you have some additional queryset filtering on top of what you expose to the user through the API: ```python hl_lines="5 8" @api.get("/books") def list_books(request, filters: Query[BookFilterSchema]): # Never serve books from inactive publishers and authors q = Q(author__is_active=True) | Q(publisher__is_active=True) # But allow filtering the rest of the books q &= filters.get_filter_expression() return Book.objects.filter(q) ``` By default, the filters will behave the following way: * `None` values will be ignored and not filtered against; * Every non-`None` field will be converted into a `Q`-expression based on the `Field` definition of each field; * All `Q`-expressions will be merged into one using `AND` logical operator; * The resulting `Q`-expression is used to filter the queryset and return you a queryset with a `.filter` clause applied. ## Customizing Fields By default, `FilterSet` will use the field names to generate Q expressions: ```python class BookFilterSchema(FilterSchema): name: Optional[str] = None ``` The `name` field will be converted into `Q(name=...)` expression. When your database lookups are more complicated than that, you can annotate your fields with an instance of `FilterLookup` where you specify how you wish your field to be looked up for filtering: ```python hl_lines="5" from ninja import FilterSchema, FilterLookup from typing import Annotated class BookFilterSchema(FilterSchema): name: Annotated[Optional[str], FilterLookup("name__icontains")] = None ``` You can even specify multiple lookups as a list: ```python hl_lines="3 4 5" class BookFilterSchema(FilterSchema): search: Annotated[Optional[str], FilterLookup( ["name__icontains", "author__name__icontains", "publisher__name__icontains"] )] ``` By default, field-level expressions are combined using `"OR"` connector, so with the above setup, a query parameter `?search=foobar` will search for books that have "foobar" in either of their name, author or publisher. And to make generic fields, you can make the field name implicit by skipping it: ```python hl_lines="1 4" IContainsField = Annotated[Optional[str], FilterLookup('__icontains')] class BookFilterSchema(FilterSchema): name: IContainsField = None ``` ??? note "Deprecated syntax" In previous versions, database lookups were specified using `Field(q=...)` syntax: ```python from ninja import FilterSchema, Field class BookFilterSchema(FilterSchema): name: Optional[str] = Field(None, q="name__icontains") ``` This approach is still supported, but it is considered **deprecated** and **not recommended** for new code because: - Poor IDE support (IDEs don't recognize custom `Field` arguments) - Uses deprecated Pydantic features (`**extra`) - Less type-safe and harder to maintain The new `FilterLookup` annotation provides better developer experience with full IDE support and type safety. Prefer using `FilterLookup` for new projects. ## Combining expressions By default, * Field-level expressions are joined together using `OR` operator. * The fields themselves are joined together using `AND` operator. So, with the following `FilterSchema`... ```python class BookFilterSchema(FilterSchema): search: Annotated[ Optional[str], FilterLookup(["name__icontains", "author__name__icontains"])] = None popular: Optional[bool] = None ``` ...and the following query parameters from the user ``` http://localhost:8000/api/books?search=harry&popular=true ``` the `FilterSchema` instance will look for popular books that have `harry` in the book's _or_ author's name. You can customize this behavior using an `expression_connector` argument in field-level and class-level definition: ```python hl_lines="12" from ninja import FilterConfigDict, FilterLookup, FilterSchema class BookFilterSchema(FilterSchema): active: Annotated[ Optional[bool], FilterLookup( ["is_active", "publisher__is_active"], expression_connector="AND" )] = None name: Annotated[Optional[str], FilterLookup("name__icontains")] = None model_config = FilterConfigDict(expression_connector="OR") ``` An expression connector can take the values of `"OR"`, `"AND"` and `"XOR"`, but the latter is only [supported](https://docs.djangoproject.com/en/4.1/ref/models/querysets/#xor) in Django starting with 4.1. Now, a request with these query parameters ``` http://localhost:8000/api/books?name=harry&active=true ``` ...shall search for books that have `harry` in their name _or_ are active themselves _and_ are published by active publishers. ## Filtering by Nones You can make the `FilterSchema` treat `None` as a valid value that should be filtered against. This can be done on a field level with a `ignore_none` kwarg: ```python hl_lines="3" class BookFilterSchema(FilterSchema): name: Annotated[Optional[str], FilterLookup("name__icontains")] = None tag: Annotated[Optional[str], FilterLookup("tag", ignore_none=False)] = None ``` This way when no other value for `"tag"` is provided by the user, the filtering will always include a condition `tag=None`. You can also specify this setting for all fields at the same time in `model_config`: ```python hl_lines="5" class BookFilterSchema(FilterSchema): name: Annotated[Optional[str], FilterLookup("name__icontains")] = None tag: Optional[str] = None model_config = FilterConfigDict(ignore_none=False) ``` ## Custom expressions Sometimes you might want to have complex filtering scenarios that cannot be handled by individual Field annotations. For such cases you can implement your field filtering logic as a custom method. Simply define a method called `filter_` which takes a filter value and returns a Q expression: ```python hl_lines="5" class BookFilterSchema(FilterSchema): tag: Optional[str] = None popular: Optional[bool] = None def filter_popular(self, value: bool) -> Q: return Q(view_count__gt=1000) | Q(download_count__gt=100) if value else Q() ``` Such field methods take precedence over what is specified in the `Field()` definition of the corresponding fields. If that is not enough, you can implement your own custom filtering logic for the entire `FilterSet` class in a `custom_expression` method: ```python hl_lines="5" class BookFilterSchema(FilterSchema): name: Optional[str] = None popular: Optional[bool] = None def custom_expression(self) -> Q: q = Q() if self.name: q &= Q(name__icontains=self.name) if self.popular: q &= ( Q(view_count__gt=1000) | Q(downloads__gt=100) | Q(tag='popular') ) return q ``` The `custom_expression` method takes precedence over any other definitions described earlier, including `filter_` methods. ================================================ FILE: docs/docs/guides/input/form-params.md ================================================ # Form data **Django Ninja** also allows you to parse and validate `request.POST` data (aka `application/x-www-form-urlencoded` or `multipart/form-data`). ## Form Data as params ```python hl_lines="1 4" from ninja import NinjaAPI, Form @api.post("/login") def login(request, username: Form[str], password: Form[str]): return {'username': username, 'password': '*****'} ``` Note the following: 1) You need to import the `Form` class from `ninja` ```python from ninja import Form ``` 2) Use `Form` as default value for your parameter: ```python username: Form[str] ``` ## Using a Schema In a similar manner to [Body](body.md#declare-it-as-a-parameter), you can use a Schema to organize your parameters. ```python hl_lines="12" {!./src/tutorial/form/code01.py!} ``` ## Request form + path + query parameters In a similar manner to [Body](body.md#request-body-path-query-parameters), you can use Form data in combination with other parameter sources. You can declare query **and** path **and** form field, **and** etc... parameters at the same time. **Django Ninja** will recognize that the function parameters that match path parameters should be **taken from the path**, and that function parameters that are declared with `Form(...)` should be **taken from the request form fields**, etc. ```python hl_lines="12" {!./src/tutorial/form/code02.py!} ``` ## Mapping Empty Form Field to Default Form fields that are optional, are often sent with an empty value. This value is interpreted as an empty string, and thus may fail validation for fields such as `int` or `bool`. This can be fixed, as described in the Pydantic docs, by using [Generic Classes as Types](https://pydantic-docs.helpmanual.io/usage/types/#generic-classes-as-types). ```python hl_lines="15 16 23-25" {!./src/tutorial/form/code03.py!} ``` ================================================ FILE: docs/docs/guides/input/operations.md ================================================ # HTTP Methods ## Defining operations An `operation` can be one of the following [HTTP methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods): - GET - POST - PUT - DELETE - PATCH **Django Ninja** comes with a decorator for each operation: ```python hl_lines="1 5 9 13 17" @api.get("/path") def get_operation(request): ... @api.post("/path") def post_operation(request): ... @api.put("/path") def put_operation(request): ... @api.delete("/path") def delete_operation(request): ... @api.patch("/path") def patch_operation(request): ... ``` See the [operations parameters](../../reference/operations-parameters.md) reference docs for information on what you can pass to any of these decorators. ## Handling multiple methods If you need to handle multiple methods with a single function for a given path, you can use the `api_operation` decorator: ```python hl_lines="1" @api.api_operation(["POST", "PATCH"], "/path") def mixed_operation(request): ... ``` This feature can also be used to implement other HTTP methods that don't have corresponding **Django Ninja** methods, such as `HEAD` or `OPTIONS`. ```python hl_lines="1" @api.api_operation(["HEAD", "OPTIONS"], "/path") def mixed_operation(request): ... ``` ================================================ FILE: docs/docs/guides/input/path-params.md ================================================ # Path parameters You can declare path "parameters" with the same syntax used by Python format-strings (which luckily also matches the OpenAPI path parameters): ```python hl_lines="1 2" {!./src/tutorial/path/code01.py!} ``` The value of the path parameter `item_id` will be passed to your function as the argument `item_id`. So, if you run this example and go to http://localhost:8000/api/items/foo, you will see this response: ```JSON {"item_id":"foo"} ``` ### Path parameters with types You can declare the type of path parameter in the function using standard Python type annotations: ```python hl_lines="2" {!./src/tutorial/path/code02.py!} ``` In this case,`item_id` is declared to be an **`int`**. This will give you editor and linter support for error checks, completion, etc. If you run this in your browser with http://localhost:8000/api/items/3, you will see this response: ```JSON {"item_id":3} ``` !!! tip Notice that the value your function received (and returned) is **3**, as a Python `int` - not a string `"3"`. So, with just that type declaration, **Django Ninja** gives you automatic request "parsing" and validation. ### Data validation On the other hand, if you go to the browser at http://localhost:8000/api/items/foo *(`"foo"` is not int)*, you will see an HTTP error like this: ```JSON hl_lines="8" { "detail": [ { "loc": [ "path", "item_id" ], "msg": "value is not a valid integer", "type": "type_error.integer" } ] } ``` ### Django Path Converters You can use [Django Path Converters](https://docs.djangoproject.com/en/stable/topics/http/urls/#path-converters) to help parse the path: ```python hl_lines="1" @api.get("/items/{int:item_id}") def read_item(request, item_id): return {"item_id": item_id} ``` In this case,`item_id` will be parsed as an **`int`**. If `item_id` is not a valid `int`, the url will not match. (e.g. if no other path matches, a *404 Not Found* will be returned) !!! tip Notice that, since **Django Ninja** uses a default type of `str` for unannotated parameters, the value the function above received (and returned) is `"3"`, as a Python `str` - not an integer **3**. To receive an `int`, simply declare `item_id` as an `int` type annotation in the function definition as normal: ```python hl_lines="2" @api.get("/items/{int:item_id}") def read_item(request, item_id:int): return {"item_id": item_id} ``` #### Path params with slashes Django's `path` converter allows you to handle path-like parameters: ```python hl_lines="1" @api.get('/dir/{path:value}') def someview(request, value: str): return value ``` you can query this operation with `/dir/some/path/with-slashes` and your `value` will be equal to `some/path/with-slashes` ### Multiple parameters You can pass as many variables as you want into `path`, just remember to have unique names and don't forget to use the same names in the function arguments. ```python @api.get("/events/{year}/{month}/{day}") def events(request, year: int, month: int, day: int): return {"date": [year, month, day]} ``` ### Using Schema You can also use Schema to encapsulate path parameters that depend on each other (and validate them as a group): ```python hl_lines="1 2 5 6 7 8 9 10 11 15" {!./src/tutorial/path/code010.py!} ``` !!! note Notice that here we used a `Path` source hint to let **Django Ninja** know that this schema will be applied to path parameters. ### Documentation Now, when you open your browser at http://localhost:8000/api/docs, you will see the automatic, interactive, API documentation. ![Django Ninja Swagger](../../img/tutorial-path-swagger.png) ================================================ FILE: docs/docs/guides/input/query-params.md ================================================ # Query parameters When you declare other function parameters that are not part of the path parameters, they are automatically interpreted as "query" parameters. ```python hl_lines="5" {!./src/tutorial/query/code01.py!} ``` To query this operation, you use a URL like: ``` http://localhost:8000/api/weapons?offset=0&limit=10 ``` By default, all GET parameters are strings, and when you annotate your function arguments with types, they are converted to that type and validated against it. The same benefits that apply to path parameters also apply to query parameters: - Editor support (obviously) - Data "parsing" - Data validation - Automatic documentation !!! Note if you do not annotate your arguments, they will be treated as `str` types ```python hl_lines="2" @api.get("/weapons") def list_weapons(request, limit, offset): # type(limit) == str # type(offset) == str ``` ### Defaults As query parameters are not a fixed part of a path, they are optional and can have default values: ```python hl_lines="2" @api.get("/weapons") def list_weapons(request, limit: int = 10, offset: int = 0): return weapons[offset : offset + limit] ``` In the example above we set default values of `offset=0` and `limit=10`. So, going to the URL: ``` http://localhost:8000/api/weapons ``` would be the same as going to: ``` http://localhost:8000/api/weapons?offset=0&limit=10 ``` If you go to, for example: ``` http://localhost:8000/api/weapons?offset=20 ``` the parameter values in your function will be: - `offset=20` (because you set it in the URL) - `limit=10` (because that was the default value) ### Required and optional parameters You can declare required or optional GET parameters in the same way as declaring Python function arguments: ```python hl_lines="5" {!./src/tutorial/query/code02.py!} ``` In this case, **Django Ninja** will always validate that you pass the `q` param in the GET, and the `offset` param is an optional integer. ### GET parameters type conversion Let's declare multiple type arguments: ```python hl_lines="5" {!./src/tutorial/query/code03.py!} ``` The `str` type is passed as is. For the `bool` type, all the following: ``` http://localhost:8000/api/example?b=1 http://localhost:8000/api/example?b=True http://localhost:8000/api/example?b=true http://localhost:8000/api/example?b=on http://localhost:8000/api/example?b=yes ``` or any other case variation (uppercase, first letter in uppercase, etc.), your function will see the parameter `b` with a `bool` value of `True`, otherwise as `False`. Date can be both date string and integer (unix timestamp):
http://localhost:8000/api/example?d=1577836800  # same as 2020-01-01
http://localhost:8000/api/example?d=2020-01-01
### Using Schema You can also use Schema to encapsulate GET parameters: ```python hl_lines="1 2 5 6 7 8" {!./src/tutorial/query/code010.py!} ``` For more complex filtering scenarios please refer to [filtering](./filtering.md). ================================================ FILE: docs/docs/guides/input/request-parsers.md ================================================ # Request parsers In most cases, the default content type for REST API's is JSON, but in case you need to work with other content types (like YAML, XML, CSV) or use faster JSON parsers, **Django Ninja** provides a `parser` configuration. ```python api = NinjaAPI(parser=MyYamlParser()) ``` To create your own parser, you need to extend the `ninja.parser.Parser` class, and override the `parse_body` method. ## Example YAML Parser Let's create our custom YAML parser: ```python hl_lines="4 8 9" import yaml from typing import List from ninja import NinjaAPI from ninja.parser import Parser class MyYamlParser(Parser): def parse_body(self, request): return yaml.safe_load(request.body) api = NinjaAPI(parser=MyYamlParser()) class Payload(Schema): ints: List[int] string: str f: float @api.post('/yaml') def operation(request, payload: Payload): return payload.dict() ``` If you now send YAML like this as the request body: ```YAML ints: - 0 - 1 string: hello f: 3.14 ``` it will be correctly parsed, and you should have JSON output like this: ```JSON { "ints": [ 0, 1 ], "string": "hello", "f": 3.14 } ``` ## Example ORJSON Parser [orjson](https://github.com/ijl/orjson#orjson) is a fast, accurate JSON library for Python. It benchmarks as the fastest Python library for JSON and is more accurate than the standard `json` library or other third-party libraries. ``` pip install orjson ``` Parser code: ```python hl_lines="1 8 9" import orjson from ninja import NinjaAPI from ninja.parser import Parser class ORJSONParser(Parser): def parse_body(self, request): return orjson.loads(request.body) api = NinjaAPI(parser=ORJSONParser()) ``` ================================================ FILE: docs/docs/guides/response/config-pydantic.md ================================================ # Overriding Pydantic Config There are many customizations available for a **Django Ninja `Schema`**, via the schema's [Pydantic `model_config`](https://docs.pydantic.dev/latest/api/config/). !!! info Under the hood **Django Ninja** uses [Pydantic Models](https://pydantic-docs.helpmanual.io/usage/models/) with all their power and benefits. The alias `Schema` was chosen to avoid confusion in code when using Django models, as Pydantic's model class is called Model by default, and conflicts with Django's Model class. ## Example Camel Case mode One interesting config attribute is [`alias_generator`](https://docs.pydantic.dev/latest/api/config/?query=alias_generator#pydantic.config.ConfigDict.alias_generator). Using Pydantic's example in **Django Ninja** can look something like: ```python hl_lines="10" from pydantic import ConfigDict from ninja import Schema def to_camel(string: str) -> str: words = string.split('_') return words[0].lower() + ''.join(word.capitalize() for word in words[1:]) class CamelModelSchema(Schema): model_config = ConfigDict(alias_generator=to_camel) str_field_name: str float_field_name: float ``` Keep in mind that when you want modify output for field names (like camel case) - you need to set as well `populate_by_name` and `by_alias` ```python hl_lines="6 14" from pydantic import ConfigDict class UserSchema(ModelSchema): model_config = ConfigDict( alias_generator = to_camel populate_by_name = True, # !!!!!! <-------- ) class Meta: model = User fields = ["id", "email", "is_staff"] @api.get("/users", response=list[UserSchema], by_alias=True) # !!!!!! <-------- by_alias def get_users(request): return User.objects.all() ``` results: ```JSON [ { "id": 1, "email": "tim@apple.com", "isStaff": true }, { "id": 2, "email": "sarah@smith.com", "isStaff": false } ... ] ``` ================================================ FILE: docs/docs/guides/response/django-pydantic-create-schema.md ================================================ # Using create_schema Under the hood, [`ModelSchema`](django-pydantic.md#modelschema) uses the `create_schema` function. This is a more advanced (and less safe) method - please use it carefully. ## `create_schema` **Django Ninja** comes with a helper function `create_schema`: ```python def create_schema( model, # django model name = "", # name for the generated class, if empty model names is used depth = 0, # if > 0 schema will also be created for the nested ForeignKeys and Many2Many (with the provided depth of lookup) fields: list[str] = None, # if passed - ONLY these fields will added to schema exclude: list[str] = None, # if passed - these fields will be excluded from schema optional_fields: list[str] | str = None, # if passed - these fields will not be required on schema (use '__all__' to mark ALL fields required) custom_fields: list[tuple(str, Any, Any)] = None, # if passed - this will override default field types (or add new fields) ) ``` Take this example: ```python hl_lines="2 4" from django.contrib.auth.models import User from ninja.orm import create_schema UserSchema = create_schema(User) # Will create schema like this: # # class UserSchema(Schema): # id: int # username: str # first_name: str # last_name: str # password: str # last_login: datetime # is_superuser: bool # email: str # ... and the rest ``` !!! Warning By default `create_schema` builds a schema with ALL model fields. This can lead to accidental unwanted data exposure (like hashed password, in the above example).
**Always** use `fields` or `exclude` arguments to explicitly define list of attributes. ### Using `fields` ```python hl_lines="1" UserSchema = create_schema(User, fields=['id', 'username']) # Will create schema like this: # # class UserSchema(Schema): # id: int # username: str ``` ### Using `exclude` ```python hl_lines="1 2" UserSchema = create_schema(User, exclude=[ 'password', 'last_login', 'is_superuser', 'is_staff', 'groups', 'user_permissions'] ) # Will create schema without excluded fields: # # class UserSchema(Schema): # id: int # username: str # first_name: str # last_name: str # email: str # is_active: bool # date_joined: datetime ``` ### Using `depth` The `depth` argument allows you to introspect the Django model into the Related fields(ForeignKey, OneToOne, ManyToMany). ```python hl_lines="1 7" UserSchema = create_schema(User, depth=1, fields=['username', 'groups']) # Will create the following schema: # # class UserSchema(Schema): # username: str # groups: List[Group] ``` Note here that groups became a `List[Group]` - many2many field introspected 1 level deeper and created schema as well for group: ```python class Group(Schema): id: int name: str permissions: List[int] ``` ================================================ FILE: docs/docs/guides/response/django-pydantic.md ================================================ # Schemas from Django models Schemas are very useful to define your validation rules and responses, but sometimes you need to reflect your database models into schemas and keep changes in sync. ## ModelSchema `ModelSchema` is a special base class that can automatically generate schemas from your models. All you need is to set `model` and `fields` attributes on your schema `Meta`: ```python hl_lines="2 5 6 7" from django.contrib.auth.models import User from ninja import ModelSchema class UserSchema(ModelSchema): class Meta: model = User fields = ['id', 'username', 'first_name', 'last_name'] # Will create schema like this: # # class UserSchema(Schema): # id: int # username: str # first_name: str # last_name: str ``` !!! note You can also specify the model using a [Django Absolute Lazy relationship](https://docs.djangoproject.com/en/stable/ref/models/fields/#absolute). In the above example, it would be `model = 'auth.User'`. ### Using ALL model fields To use all fields from a model - you can pass `__all__` to `fields`: ```python hl_lines="4" class UserSchema(ModelSchema): class Meta: model = User fields = "__all__" ``` !!! Warning Using __all__ is not recommended.
This can lead to accidental unwanted data exposure (like hashed password, in the above example).
General advice - use `fields` to explicitly define list of fields that you want to be visible in API. ### Excluding model fields To use all fields **except** a few, you can use `exclude` configuration: ```python hl_lines="4" class UserSchema(ModelSchema): class Meta: model = User exclude = ['password', 'last_login', 'user_permissions'] # Will create schema like this: # # class UserSchema(Schema): # id: int # username: str # first_name: str # last_name: str # email: str # is_superuser: bool # ... and the rest ``` ### Overriding fields To change default annotation for some field, or to add a new field, just use annotated attributes as usual. ```python hl_lines="1 2 3 4 8" class GroupSchema(ModelSchema): class Meta: model = Group fields = ['id', 'name'] class UserSchema(ModelSchema): groups: List[GroupSchema] = [] class Meta: model = User fields = ['id', 'username', 'first_name', 'last_name'] ``` ### Making fields optional Pretty often for PATCH API operations you need to make all fields of your schema optional. To do that, you can use config fields_optional ```python hl_lines="5" class PatchGroupSchema(ModelSchema): class Meta: model = Group fields = ['id', 'name', 'description'] # Note: all these fields are required on model level fields_optional = '__all__' ``` Also, you can define a subset of optional fields instead of `__all__`: ```python fields_optional = ['description'] ``` When you process input data, you need to tell Pydantic to avoid setting undefined fields to `None`: ```python @api.patch("/patch/{pk}") def patch(request, pk: int, payload: PatchGroupSchema): # Notice that we set exclude_unset=True updated_fields = payload.dict(exclude_unset=True) obj = MyModel.objects.get(pk=pk) for attr, value in updated_fields.items(): setattr(obj, attr, value) obj.save() ``` ### Custom fields types For each Django field it encounters, `ModelSchema` uses the default `Field.get_internal_type` method to find the correct representation in Pydantic schema (python type). This process works fine for the built-in field types, but there are cases where the user wants to create or use a custom field, with its own mapping to python type. In this case you should use `register_field` method to tell django-ninja which type should this django field represent: ```python hl_lines="4 7 8 9" # models.py class MyModel(models.Model): embedding = pgvector.VectorField() # schemas.py from ninja.orm import register_field register_field('VectorField', list[float]) ``` #### PatchDict Another way to work with patch request data is a `PatchDict` container which allows you to make a schema with all optional fields and get a dict with **only** fields that was provide ```Python hl_lines="1 11" from ninja import PatchDict class GroupSchema(Schema): # You do not have to make fields optional it will be converted by PatchDict name: str description: str due_date: date @api.patch("/patch/{pk}") def modify_data(request, pk: int, payload: PatchDict[GroupSchema]): obj = MyModel.objects.get(pk=pk) for attr, value in payload.items(): setattr(obj, attr, value) obj.save() ``` in this example the `payload` argument will be of type `dict` and only contain fields that were passed in the request and validated using `GroupSchema` ================================================ FILE: docs/docs/guides/response/index.md ================================================ # Response Schema **Django Ninja** allows you to define the schema of your responses both for validation and documentation purposes. Imagine you need to create an API operation that creates a user. The **input** parameter would be **username+password**, but **output** of this operation should be **id+username** (**without** the password). Let's create the input schema: ```python hl_lines="3 5" from ninja import Schema class UserIn(Schema): username: str password: str @api.post("/users/") def create_user(request, data: UserIn): user = User(username=data.username) # User is django auth.User user.set_password(data.password) user.save() # ... return ? ``` Now let's define the output schema, and pass it as a `response` argument to the `@api.post` decorator: ```python hl_lines="8 9 10 13 18" from ninja import Schema class UserIn(Schema): username: str password: str class UserOut(Schema): id: int username: str @api.post("/users/", response=UserOut) def create_user(request, data: UserIn): user = User(username=data.username) user.set_password(data.password) user.save() return user ``` **Django Ninja** will use this `response` schema to: - convert the output data to declared schema - validate the data - add an OpenAPI schema definition - it will be used by the automatic documentation systems - and, most importantly, it **will limit the output data** only to the fields only defined in the schema. ## Nested objects There is also often a need to return responses with some nested/child objects. Imagine we have a `Task` Django model with a `User` ForeignKey: ```python hl_lines="6" from django.db import models class Task(models.Model): title = models.CharField(max_length=200) is_completed = models.BooleanField(default=False) owner = models.ForeignKey("auth.User", null=True, blank=True) ``` Now let's output all tasks, and for each task, output some fields about the user. ```python hl_lines="13 16" from typing import List from ninja import Schema class UserSchema(Schema): id: int first_name: str last_name: str class TaskSchema(Schema): id: int title: str is_completed: bool owner: UserSchema = None # ! None - to mark it as optional @api.get("/tasks", response=List[TaskSchema]) def tasks(request): queryset = Task.objects.select_related("owner") return list(queryset) ``` If you execute this operation, you should get a response like this: ```JSON hl_lines="6 7 8 9 16" [ { "id": 1, "title": "Task 1", "is_completed": false, "owner": { "id": 1, "first_name": "John", "last_name": "Doe", } }, { "id": 2, "title": "Task 2", "is_completed": false, "owner": null }, ] ``` ## Aliases Instead of a nested response, you may want to just flatten the response output. The Ninja `Schema` object extends Pydantic's `Field(..., alias="")` format to work with dotted responses. Using the models from above, let's make a schema that just includes the task owner's first name inline, and also uses `completed` rather than `is_completed`: ```python hl_lines="1 7-9" from ninja import Field, Schema class TaskSchema(Schema): id: int title: str # The first Field param is the default, use ... for required fields. completed: bool = Field(..., alias="is_completed") owner_first_name: str = Field(None, alias="owner.first_name") ``` Aliases also support django template syntax variables access: ```python hl_lines="2" class TaskSchema(Schema): last_message: str = Field(None, alias="message_set.0.text") ``` ```python hl_lines="3" class TaskSchema(Schema): type: str = Field(None) type_display: str = Field(None, alias="get_type_display") # callable will be executed ``` ## Resolvers You can also create calculated fields via resolve methods based on the field name. The method must accept a single argument, which will be the object the schema is resolving against. When creating a resolver as a standard method, `self` gives you access to other validated and formatted attributes in the schema. ```python hl_lines="5 7-11" class TaskSchema(Schema): id: int title: str is_completed: bool owner: Optional[str] = None lower_title: str @staticmethod def resolve_owner(obj): if not obj.owner: return return f"{obj.owner.first_name} {obj.owner.last_name}" def resolve_lower_title(self, obj): return self.title.lower() ``` ### Accessing extra context Pydantic v2 allows you to process an extra context that is passed to the serializer. In the following example you can have resolver that gets request object from passed `context` argument: ```python hl_lines="6" class Data(Schema): a: int path: str = "" @staticmethod def resolve_path(obj, context): request = context["request"] return request.path ``` if you use this schema for incoming requests - the `request` object will be automatically passed to context. You can as well pass your own context: ```python data = Data.model_validate({'some': 1}, context={'request': MyRequest()}) ``` ## Returning querysets In the previous example we specifically converted a queryset into a list (and executed the SQL query during evaluation). You can avoid that and return a queryset as a result, and it will be automatically evaluated to List: ```python hl_lines="3" @api.get("/tasks", response=List[TaskSchema]) def tasks(request): return Task.objects.all() ``` !!! warning If your operation is async, this example will not work because the ORM query needs to be called safely. ```python hl_lines="2" @api.get("/tasks", response=List[TaskSchema]) async def tasks(request): return Task.objects.all() ``` See the [async support](../async-support.md#using-orm) guide for more information. ## FileField and ImageField **Django Ninja** by default converts files and images (declared with `FileField` or `ImageField`) to `string` URL's. An example: ```python hl_lines="3" class Picture(models.Model): title = models.CharField(max_length=100) image = models.ImageField(upload_to='images') ``` If you need to output to response image field, declare a schema for it as follows: ```python hl_lines="3" class PictureSchema(Schema): title: str image: str ``` Once you output this to a response, the URL will be automatically generated for each object: ```JSON { "title": "Zebra", "image": "/static/images/zebra.jpg" } ``` ## Multiple Response Schemas Sometimes you need to define more than response schemas. In case of authentication, for example, you can return: - **200** successful -> token - **401** -> Unauthorized - **402** -> Payment required - **403** -> Forbidden - etc.. In fact, the [OpenAPI specification](https://swagger.io/docs/specification/describing-responses/) allows you to pass multiple response schemas. You can pass to a `response` argument a dictionary where: - key is a response code - value is a schema for that code Also, when you return the result - you have to also pass a status code to tell **Django Ninja** which schema should be used for validation and serialization. An example: ```python hl_lines="1 9 12 14 16" from ninja import Status class Token(Schema): token: str expires: date class Message(Schema): message: str @api.post('/login', response={200: Token, 401: Message, 402: Message}) def login(request, payload: Auth): if auth_not_valid: return Status(401, {'message': 'Unauthorized'}) if negative_balance: return Status(402, {'message': 'Insufficient balance amount. Please proceed to a payment page.'}) return Status(200, {'token': xxx, ...}) ``` !!! warning "Deprecated: tuple syntax" Returning `(status_code, body)` tuples is deprecated and will be removed in a future version. Use `Status(status_code, body)` instead. ## Multiple response codes In the previous example you saw that we basically repeated the `Message` schema twice: ``` ...401: Message, 402: Message} ``` To avoid this duplication you can use multiple response codes for a schema: ```python hl_lines="2 3 6 9 11" ... from ninja import Status from ninja.responses import codes_4xx @api.post('/login', response={200: Token, codes_4xx: Message}) def login(request, payload: Auth): if auth_not_valid: return Status(401, {'message': 'Unauthorized'}) if negative_balance: return Status(402, {'message': 'Insufficient balance amount. Please proceed to a payment page.'}) return Status(200, {'token': xxx, ...}) ``` **Django Ninja** comes with the following HTTP codes: ```python from ninja.responses import codes_1xx from ninja.responses import codes_2xx from ninja.responses import codes_3xx from ninja.responses import codes_4xx from ninja.responses import codes_5xx ``` You can also create your own range using a `frozenset`: ```python my_codes = frozenset({416, 418, 425, 429, 451}) @api.post('/login', response={200: Token, my_codes: Message}) def login(request, payload: Auth): ... ``` ## Empty responses Some responses, such as [204 No Content](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204), have no body. To indicate the response body is empty mark `response` argument with `None` instead of Schema: ```python hl_lines="1 3" @api.post("/no_content", response={204: None}) def no_content(request): return Status(204, None) ``` ## Error responses Check [Handling errors](../errors.md) for more information. ## Self-referencing schemes Sometimes you need to create a schema that has reference to itself, or tree-structure objects. To do that you need: - set a type of your schema in quotes - use `model_rebuild` method to apply self referencing types ```python hl_lines="3 6" class Organization(Schema): title: str part_of: 'Organization' = None #!! note the type in quotes here !! Organization.model_rebuild() # !!! this is important @api.get('/organizations', response=List[Organization]) def list_organizations(request): ... ``` ## Self-referencing schemes from `create_schema()` To be able to use the method `model_rebuild()` from a schema generated via `create_schema()`, the "name" of the class needs to be in our namespace. In this case it is very important to pass the `name` parameter to `create_schema()` ```python hl_lines="3" UserSchema = create_schema( User, name='UserSchema', # !!! this is important for model_rebuild() fields=['id', 'username'] custom_fields=[ ('manager', 'UserSchema', None), ] ) UserSchema.model_rebuild() ``` ## Serializing Outside of Views Serialization of your objects can be done directly in code through the use of the `.from_orm()` method on the schema object. Consider the following model: ```python class Person(models.Model): name = models.CharField(max_length=50) ``` Which can be accessed using this schema: ```python class PersonSchema(Schema): name: str ``` Direct serialization can be performed using the `.from_orm()` method on the schema. Once you have an instance of the schema object, the `.dict()` and `.json()` methods allow you to get at both dictionary output and string JSON versions. ```python >>> person = Person.objects.get(id=1) >>> data = PersonSchema.from_orm(person) >>> data PersonSchema(id=1, name='Mr. Smith') >>> data.dict() {'id':1, 'name':'Mr. Smith'} >>> data.json() '{"id":1, "name":"Mr. Smith"}' ``` Multiple Items: or a queryset (or list) ```python >>> persons = Person.objects.all() >>> data = [PersonSchema.from_orm(i).dict() for i in persons] [{'id':1, 'name':'Mr. Smith'},{'id': 2, 'name': 'Mrs. Smith'}...] ``` ## Django HTTP responses It is also possible to return regular django http responses: ```python from django.http import HttpResponse from django.shortcuts import redirect @api.get("/http") def result_django(request): return HttpResponse('some data') # !!!! @api.get("/something") def some_redirect(request): return redirect("/some-path") # !!!! ``` ================================================ FILE: docs/docs/guides/response/pagination.md ================================================ # Pagination **Django Ninja** comes with a pagination support. This allows you to split large result sets into individual pages. To apply pagination to a function - just apply `paginate` decorator: ```python hl_lines="1 4" from ninja.pagination import paginate @api.get('/users', response=List[UserSchema]) @paginate def list_users(request): return User.objects.all() ``` That's it! Now you can query users with `limit` and `offset` GET parameters ``` /api/users?limit=10&offset=0 ``` by default limit is set to `100` (you can change it in your settings.py using `NINJA_PAGINATION_PER_PAGE`) ## Built in Pagination Classes ### LimitOffsetPagination (default) This is the default pagination class (You can change it in your settings.py using `NINJA_PAGINATION_CLASS` path to a class) ```python hl_lines="1 4" from ninja.pagination import paginate, LimitOffsetPagination @api.get('/users', response=List[UserSchema]) @paginate(LimitOffsetPagination) def list_users(request): return User.objects.all() ``` Example query: ``` /api/users?limit=10&offset=0 ``` this class has two input parameters: - `limit` - defines a number of queryset on the page (default = 100, change in NINJA_PAGINATION_PER_PAGE) - `offset` - set's the page window offset (default: 0, indexing starts with 0) ### PageNumberPagination ```python hl_lines="1 4" from ninja.pagination import paginate, PageNumberPagination @api.get('/users', response=List[UserSchema]) @paginate(PageNumberPagination) def list_users(request): return User.objects.all() ``` Example query: ``` /api/users?page=2 ``` this class has one parameter `page` and outputs 100 queryset per page by default (can be changed with settings.py) Page numbering start with 1 you can also set custom page_size value individually per view: ```python hl_lines="2" @api.get("/users") @paginate(PageNumberPagination, page_size=50) def list_users(... ``` In addition to the `page` parameter, you can also use the `page_size` parameter to dynamically adjust the number of records displayed per page: Example query: ``` /api/users?page=2&page_size=20 ``` This allows you to temporarily override the page size setting in your request. The request will use the specified `page_size` value if provided. Otherwise, it will use either the value specified in the decorator or the value from `PAGINATION_MAX_PER_PAGE_SIZE` in settings.py if no decorator value is set. ### CursorPagination Cursor-based pagination provides stable pagination for datasets that may change frequently. Cursor pagination uses base64 encoded tokens to mark positions in the dataset, ensuring consistent results even when items are added or removed. ```python hl_lines="1 4" from ninja.pagination import paginate, CursorPagination @api.get('/events', response=List[EventSchema]) @paginate(CursorPagination) def list_events(request): return Event.objects.all() ``` Example query: ``` /api/events?cursor=eyJwIjoiMjAyNC0wMS0wMSIsInIiOmZhbHNlLCJvIjowfQ== ``` this class has two input parameters: - `cursor` - base64 token representing the current position (optional, starts from beginning if not provided) - `page_size` - number of items per page (optional) You can specify the `page_size` value to temporarily override in the request: ``` /api/events?cursor=eyJwIjoiMjAyNC0wMS0wMSIsInIiOmZhbHNlLCJvIjowfQ==&page_size=5 ``` This class has a few parameters, which determine how the cursor position is ascertained and the parameter encoded: - `ordering` - tuple of field names to order the queryset. Use `-` prefix for descending order. The first one of which will be used to encode the position. The ordering field should be unique if possible. A string representation of this field will be used to point to the current position of the cursor. Timestamps work well if each item in the collection is created independently. The paginator can handle some non-uniqueness by adding an offset. Defaults to `("-pk",)`, change in `NINJA_PAGINATION_DEFAULT_ORDERING` - `page_size` - default page size for endpoint. Defaults to `100`, change in `NINJA_PAGINATION_PER_PAGE` - `max_page_size` - maximum allowed page size for endpoint. Defaults to `100`, change in `NINJA_PAGINATION_MAX_PER_PAGE_SIZE` Finally, there is a `NINJA_PAGINATION_MAX_OFFSET` setting to limit malicious cursor requests. It defaults to `100`. The class parameters can be set globally via settings as well as per view: ```python hl_lines="2" @api.get("/events") @paginate(CursorPagination, ordering=("start_date", "end_date"), page_size=20, max_page_size=100) def list_events(request): return Event.objects.all() ``` The response includes navigation links and results: ```json { "next": "http://api.example.com/events?cursor=eyJwIjoiMjAyNC0wMS0wMiIsInIiOmZhbHNlLCJvIjowfQ==", "previous": "http://api.example.com/events?cursor=eyJwIjoiMjAyNC0wMS0wMSIsInIiOnRydWUsIm8iOjB9", "results": [ { "id": 1, "title": "Event 1", "start_date": "2024-01-01" }, { "id": 2, "title": "Event 2", "start_date": "2024-01-02" } ] } ``` ## Accessing paginator parameters in view function If you need access to `Input` parameters used for pagination in your view function - use `pass_parameter` argument In that case input data will be available in `**kwargs`: ```python hl_lines="2 4" @api.get("/someview") @paginate(pass_parameter="pagination_info") def someview(request, **kwargs): page = kwargs["pagination_info"].page return ... ``` ## Creating Custom Pagination Class To create a custom pagination class you should subclass `ninja.pagination.PaginationBase` and override the `Input` and `Output` schema classes and `paginate_queryset(self, queryset, request, **params)` method: - The `Input` schema is a Schema class that describes parameters that should be passed to your paginator (f.e. page-number or limit/offset values). - The `Output` schema describes schema for page output (f.e. count/next-page/items/etc). - The `paginate_queryset` method is passed the initial queryset and should return an iterable object that contains only the data in the requested page. This method accepts the following arguments: - `queryset`: a queryset (or iterable) returned by the api function - `pagination` - the paginator.Input parameters (parsed and validated) - `**params`: kwargs that will contain all the arguments that decorated function received Example: ```python hl_lines="7 11 16 26" from ninja.pagination import paginate, PaginationBase from ninja import Schema class CustomPagination(PaginationBase): # only `skip` param, defaults to 5 per page class Input(Schema): skip: int class Output(Schema): items: List[Any] # `items` is a default attribute total: int per_page: int def paginate_queryset(self, queryset, pagination: Input, **params): skip = pagination.skip return { 'items': queryset[skip : skip + 5], 'total': queryset.count(), 'per_page': 5, } @api.get('/users', response=List[UserSchema]) @paginate(CustomPagination) def list_users(request): return User.objects.all() ``` Tip: You can access request object from params: ```python def paginate_queryset(self, queryset, pagination: Input, **params): request = params["request"] ``` #### Async Pagination Standard **Django Ninja** pagination classes support async. If you wish to handle async requests with a custom pagination class, you should subclass `ninja.pagination.AsyncPaginationBase` and override the `apaginate_queryset(self, queryset, request, **params)` method. ### Output attribute By default page items are placed to `'items'` attribute. To override this behaviour use `items_attribute`: ```python hl_lines="4 8" class CustomPagination(PaginationBase): ... class Output(Schema): results: List[Any] total: int per_page: int items_attribute: str = "results" ``` ## Apply pagination to multiple operations at once There is often a case when you need to add pagination to all views that returns querysets or list You can use a builtin router class (`RouterPaginated`) that automatically injects pagination to all operations that defined `response=List[SomeSchema]`: ```python hl_lines="1 3 6 10" from ninja.pagination import RouterPaginated router = RouterPaginated() @router.get("/items", response=List[MySchema]) def items(request): return MyModel.objects.all() @router.get("/other-items", response=List[OtherSchema]) def other_items(request): return OtherModel.objects.all() ``` In this example both operations will have pagination enabled to apply pagination to main `api` instance use `default_router` argument: ```python api = NinjaAPI(default_router=RouterPaginated()) @api.get(... ``` ================================================ FILE: docs/docs/guides/response/response-renderers.md ================================================ # Response renderers The most common response type for a REST API is usually JSON. **Django Ninja** also has support for defining your own custom renderers, which gives you the flexibility to design your own media types. ## Create a renderer To create your own renderer, you need to inherit `ninja.renderers.BaseRenderer` and override the `render` method. Then you can pass an instance of your class to `NinjaAPI` as the `renderer` argument: ```python hl_lines="5 8 9" from ninja import NinjaAPI from ninja.renderers import BaseRenderer class MyRenderer(BaseRenderer): media_type = "text/plain" def render(self, request, data, *, response_status): return ... # your serialization here api = NinjaAPI(renderer=MyRenderer()) ``` The `render` method takes the following arguments: - request -> HttpRequest object - data -> object that needs to be serialized - response_status as an `int` -> the HTTP status code that will be returned to the client You need also define the `media_type` attribute on the class to set the content-type header for the response. ## ORJSON renderer example: [orjson](https://github.com/ijl/orjson#orjson) is a fast, accurate JSON library for Python. It benchmarks as the fastest Python library for JSON and is more accurate than the standard `json` library or other third-party libraries. It also serializes dataclass, datetime, numpy, and UUID instances natively. Here's an example renderer class that uses `orjson`: ```python hl_lines="9 10" import orjson from ninja import NinjaAPI from ninja.renderers import BaseRenderer class ORJSONRenderer(BaseRenderer): media_type = "application/json" def render(self, request, data, *, response_status): return orjson.dumps(data) api = NinjaAPI(renderer=ORJSONRenderer()) ``` ## XML renderer example: This is how you create a renderer that outputs all responses as XML: ```python hl_lines="8 11" from io import StringIO from django.utils.encoding import force_str from django.utils.xmlutils import SimplerXMLGenerator from ninja import NinjaAPI from ninja.renderers import BaseRenderer class XMLRenderer(BaseRenderer): media_type = "text/xml" def render(self, request, data, *, response_status): stream = StringIO() xml = SimplerXMLGenerator(stream, "utf-8") xml.startDocument() xml.startElement("data", {}) self._to_xml(xml, data) xml.endElement("data") xml.endDocument() return stream.getvalue() def _to_xml(self, xml, data): if isinstance(data, (list, tuple)): for item in data: xml.startElement("item", {}) self._to_xml(xml, item) xml.endElement("item") elif isinstance(data, dict): for key, value in data.items(): xml.startElement(key, {}) self._to_xml(xml, value) xml.endElement(key) elif data is None: # Don't output any value pass else: xml.characters(force_str(data)) api = NinjaAPI(renderer=XMLRenderer()) ``` *(Copyright note: this code is basically copied from [DRF-xml](https://jpadilla.github.io/django-rest-framework-xml/))* ================================================ FILE: docs/docs/guides/response/temporal_response.md ================================================ # Altering the Response Sometimes you'll want to change the response just before it gets served, for example, to add a header or alter a cookie. To do this, simply declare a function parameter with a type of `HttpResponse`: ```python from django.http import HttpRequest, HttpResponse @api.get("/cookie/") def feed_cookiemonster(request: HttpRequest, response: HttpResponse): # Set a cookie. response.set_cookie("cookie", "delicious") # Set a header. response["X-Cookiemonster"] = "blue" return {"cookiemonster_happy": True} ``` ## Temporal response object This response object is used for the base of all responses built by Django Ninja, including error responses. This object is *not* used if a Django `HttpResponse` object is returned directly by an operation. Obviously this response object won't contain the content yet, but it does have the `content_type` set (but you probably don't want to be changing it). The `status_code` will get overridden depending on the return value (200 by default, or the status code if a two-part tuple is returned). ## Changing the base response object You can alter this temporal response object by overriding the `NinjaAPI.create_temporal_response` method. ```python def create_temporal_response(self, request: HttpRequest) -> HttpResponse: response = super().create_temporal_response(request) # Do your magic here... return response ``` ================================================ FILE: docs/docs/guides/routers.md ================================================ # Routers Real world applications can almost never fit all logic into a single file. **Django Ninja** comes with an easy way to split your API into multiple modules using Routers. Let's say you have a Django project with a structure like this: ``` ├── myproject │ └── settings.py ├── events/ │ ├── __init__.py │ └── models.py ├── news/ │ ├── __init__.py │ └── models.py ├── blogs/ │ ├── __init__.py │ └── models.py └── manage.py ``` To add API's to each of the Django applications, create an `api.py` module in each app: ``` hl_lines="5 9 13" ├── myproject │ └── settings.py ├── events/ │ ├── __init__.py │ ├── api.py │ └── models.py ├── news/ │ ├── __init__.py │ ├── api.py │ └── models.py ├── blogs/ │ ├── __init__.py │ ├── api.py │ └── models.py └── manage.py ``` Now let's add a few operations to `events/api.py`. The trick is that instead of using the `NinjaAPI` class, you use the **Router** class: ```python hl_lines="1 4 6 13" from ninja import Router from .models import Event router = Router() @router.get('/') def list_events(request): return [ {"id": e.id, "title": e.title} for e in Event.objects.all() ] @router.get('/{event_id}') def event_details(request, event_id: int): event = Event.objects.get(id=event_id) return {"title": event.title, "details": event.details} ``` Then do the same for the `news` app with `news/api.py`: ```python hl_lines="1 4" from ninja import Router from .models import News router = Router() @router.get('/') def list_news(request): ... @router.get('/{news_id}') def news_details(request, news_id: int): ... ``` and then also `blogs/api.py`. Finally, let's group them together. In your top level project folder (next to `urls.py`), create another `api.py` file with the main `NinjaAPI` instance: ``` hl_lines="2" ├── myproject │ ├── api.py │ └── settings.py ├── events/ │ ... ├── news/ │ ... ├── blogs/ │ ... ``` It should look like this: ```python from ninja import NinjaAPI api = NinjaAPI() ``` Now we import all the routers from the various apps, and include them into the main API instance: ```python hl_lines="2 6 7 8" from ninja import NinjaAPI from events.api import router as events_router api = NinjaAPI() api.add_router("/events/", events_router) # You can add a router as an object api.add_router("/news/", "news.api.router") # or by Python path api.add_router("/blogs/", "blogs.api.router") ``` Now, include `api` to your urls as usual and open your browser at `/api/docs`, and you should see all your routers combined into a single API: ![Swagger UI Simple Routers](../img/simple-routers-swagger.png) ## Router authentication Use `auth` argument to apply authenticator to all operations declared by router: ```python api.add_router("/events/", events_router, auth=BasicAuth()) ``` or using router constructor ```python router = Router(auth=BasicAuth()) ``` ## Router tags You can use `tags` argument to apply tags to all operations declared by router: ```python api.add_router("/events/", events_router, tags=["events"]) ``` or using router constructor ```python router = Router(tags=["events"]) ``` ## Nested routers There are also times when you need to split your logic up even more. **Django Ninja** makes it possible to include a router into another router as many times as you like, and finally include the top level router into the main `api` instance. Basically, what that means is that you have `add_router` both on the `api` instance and on the `router` instance: ```python hl_lines="7 8 9 32 33 34" from django.contrib import admin from django.urls import path from ninja import NinjaAPI, Router api = NinjaAPI() first_router = Router() second_router = Router() third_router = Router() @api.get("/add") def add(request, a: int, b: int): return {"result": a + b} @first_router.get("/add") def add(request, a: int, b: int): return {"result": a + b} @second_router.get("/add") def add(request, a: int, b: int): return {"result": a + b} @third_router.get("/add") def add(request, a: int, b: int): return {"result": a + b} second_router.add_router("l3", third_router) first_router.add_router("l2", second_router) api.add_router("l1", first_router) urlpatterns = [ path("admin/", admin.site.urls), path("api/", api.urls), ] ``` Now you have the following endpoints: ``` /api/add /api/l1/add /api/l1/l2/add /api/l1/l2/l3/add ``` Great! Now go have a look at the automatically generated docs: ![Swagger UI Nested Routers](../img/nested-routers-swagger.png) ### Nested url parameters You can also use url parameters in nested routers by adding `= Path(...)` to the function parameters: ```python hl_lines="13 16" from django.contrib import admin from django.urls import path from ninja import NinjaAPI, Path, Router api = NinjaAPI() router = Router() @api.get("/add/{a}/{b}") def add(request, a: int, b: int): return {"result": a + b} @router.get("/multiply/{c}") def multiply(request, c: int, a: int = Path(...), b: int = Path(...)): return {"result": (a + b) * c} api.add_router("add/{a}/{b}", router) urlpatterns = [ path("admin/", admin.site.urls), path("api/", api.urls), ] ``` This will generate the following endpoints: ``` /api/add/{a}/{b} /api/add/{a}/{b}/multiply/{c} ``` ================================================ FILE: docs/docs/guides/testing.md ================================================ # Testing **Django Ninja** is fully compatible with standard [django test client](https://docs.djangoproject.com/en/dev/topics/testing/tools/) , but also provides a test client to make it easy to test just APIs without middleware/url-resolver layer making tests run faster. To test the following API: ```python from ninja import NinjaAPI, Schema api = NinjaAPI() router = Router() class HelloResponse(Schema): msg: str @router.get("/hello", response=HelloResponse) def hello(request): return {"msg": "Hello World"} api.add_router("", router) ``` You can use the Django test class: ```python from django.test import TestCase from ninja.testing import TestClient class HelloTest(TestCase): def test_hello(self): # don't forget to import router from code above client = TestClient(router) response = client.get("/hello") self.assertEqual(response.status_code, 200) self.assertEqual(response.json(), {"msg": "Hello World"}) ``` It is also possible to access the deserialized data using the `data` property: ```python self.assertEqual(response.data, {"msg": "Hello World"}) ``` ## Attributes Arbitrary attributes can be added to the request object by passing keyword arguments to the client request methods: ```python class HelloTest(TestCase): def test_hello(self): client = TestClient(router) # request.company_id will now be set within the view response = client.get("/hello", company_id=1) ``` ### Headers It is also possible to specify headers, both from the TestCase instantiation and the actual request: ```python client = TestClient(router, headers={"A": "a", "B": "b"}) # The request will be made with {"A": "na", "B": "b", "C": "nc"} headers response = client.get("/test-headers", headers={"A": "na", "C": "nc"}) ``` ### Cookies It is also possible to specify cookies, both from the TestCase instantiation and the actual request: ```python client = TestClient(router, COOKIES={"A": "a", "B": "b"}) # The request will be made with {"A": "na", "B": "b", "C": "nc"} cookies response = client.get("/test-cookies", COOKIES={"A": "na", "C": "nc"}) ``` ### Users It is also possible to specify a User for the request: ```python user = User.objects.create(...) client = TestClient(router) # The request will be made with user logged in response = client.get("/test-with-user", user=user) ``` ## Testing async operations To test operations in async context use `TestAsyncClient`: ```python from ninja.testing import TestAsyncClient client = TestAsyncClient(router) response = await client.post("/test/") ``` ================================================ FILE: docs/docs/guides/throttling.md ================================================ # Throttling Throttles allows to control the rate of requests that clients can make to an API. Django Ninja allows to set custom throttlers globally (across all operations in NinjaAPI instance), on router level and each operation individually. !!! note The application-level throttling that Django Ninja provides should not be considered a security measure or protection against brute forcing or denial-of-service attacks. Deliberately malicious actors will always be able to spoof IP origins. The built-in throttling implementations are implemented using Django's cache framework, and use non-atomic operations to determine the request rate, which may sometimes result in some fuzziness. Django Ninja’s throttling feature is pretty much based on what Django Rest Framework (DRF) uses, which you can check out [here](https://www.django-rest-framework.org/api-guide/throttling/). So, if you’ve already got custom throttling set up for DRF, there’s a good chance it’ll work with Django Ninja right out of the box. The key difference is that you need to pass initialized Throttle objects instead of classes (which should give a better performance). You can specify a rate using the format requests/time-unit, where time-unit represents a number of units followed by an optional unit of time. If the unit is omitted, it defaults to seconds. For example, the following are equivalent and all represent "100 requests per 5 minutes": * 100/5m * 100/300s * 100/300 The following units are supported: * `s` or `sec` * `m` or `min` * `h` or `hour` * `d` or `day` ## Usage ### Global The following example will limit unauthenticated users to only 10 requests per second, while authenticated can make 100/s ```Python from ninja.throttling import AnonRateThrottle, AuthRateThrottle api = NinjaAPI( throttle=[ AnonRateThrottle('10/s'), AuthRateThrottle('100/s'), ], ) ``` !!! tip `throttle` argument accepts single object and list of throttle objects ### Router level Pass `throttle` argument either to `add_router` function ```Python api = NinjaAPI() ... api.add_router('/sensitive', 'myapp.api.router', throttle=AnonRateThrottle('100/m')) ``` or directly to init of the Router class: ```Python router = Router(..., throttle=[AnonRateThrottle('1000/h')]) ``` ### Operation level If `throttle` argument is passed to operation - it will overrule all global and router throttles: ```Python from ninja.throttling import UserRateThrottle @api.get('/some', throttle=[UserRateThrottle('10000/d')]) def some(request): ... ``` ## Builtin throttlers ### AnonRateThrottle Will only throttle unauthenticated users. The IP address of the incoming request is used to generate a unique key to throttle against. ### UserRateThrottle Will throttle users (**if you use django build-in user authentication**) to a given rate of requests across the API. The user id is used to generate a unique key to throttle against. Unauthenticated requests will fall back to using the IP address of the incoming request to generate a unique key to throttle against. ### AuthRateThrottle Will throttle by Django ninja [authentication](authentication.md) to a given rate of requests across the API. Unauthenticated requests will fall back to using the IP address of the incoming request to generate a unique key to throttle against. Note: the cache key in case of `request.auth` will be generated by `sha256(str(request.auth))` - so if you returning some custom objects inside authentication make sure to implement `__str__` method that will return a unique value for the user. ## Custom throttles To create a custom throttle, override `BaseThrottle` (or any of builtin throttles) and implement `.allow_request(self, request)`. The method should return `True` if the request should be allowed, and `False` otherwise. Example ```Python from ninja.throttling import AnonRateThrottle class NoReadsThrottle(AnonRateThrottle): """Do not throttle GET requests""" def allow_request(self, request): if request.method == "GET": return True return super().allow_request(request) ``` ================================================ FILE: docs/docs/guides/urls.md ================================================ # Reverse Resolution of URLS A reverse URL name is generated for each method in a Django Ninja Schema (or `Router`). ## How URLs are generated The URLs are all contained within a namespace, which defaults to `"api-1.0.0"`, and each URL name matches the function it is decorated. For example: ```python api = NinjaAPI() @api.get("/") def index(request): ... index_url = reverse_lazy("api-1.0.0:index") ``` This implicit URL name will only be set for the first operation for each API path. If you *don't* want any implicit reverse URL name generated, just explicitly specify `url_name=""` (an empty string) on the method decorator. ### Changing the URL name Rather than using the default URL name, you can specify it explicitly as a property on the method decorator. ```python @api.get("/users", url_name="user_list") def users(request): ... users_url = reverse_lazy("api-1.0.0:user_list") ``` This will override any implicit URL name to this API path. #### Overriding default url names You can also override implicit url naming by overwriting the `get_operation_url_name` method: ```python class MyAPI(NinjaAPI): def get_operation_url_name(self, operation, router): return operation.view_func.__name__ + '_my_extra_suffix' api = MyAPI() ``` ### Customizing the namespace The default URL namespace is built by prepending the Schema's version with `"api-"`, however you can explicitly specify the namespace by overriding the `urls_namespace` attribute of the `NinjaAPI` Schema class. ```python api = NinjaAPI(auth=token_auth, version='2') api_private = NinjaAPI(auth=session_auth, urls_namespace='private_api') api_users_url = reverse_lazy("api-2:users") private_api_admins_url = reverse_lazy("private_api:admins") ``` ================================================ FILE: docs/docs/guides/versioning.md ================================================ # Versioning ## Different API version numbers With **Django Ninja** it's easy to run multiple API versions from a single Django project. All you have to do is create two or more NinjaAPI instances with different `version` arguments: **api_v1.py**: ```python hl_lines="4" from ninja import NinjaAPI api = NinjaAPI(version='1.0.0') @api.get('/hello') def hello(request): return {'message': 'Hello from V1'} ``` api_**v2**.py: ```python hl_lines="4" from ninja import NinjaAPI api = NinjaAPI(version='2.0.0') @api.get('/hello') def hello(request): return {'message': 'Hello from V2'} ``` and then in **urls.py**: ```python hl_lines="8 9" ... from api_v1 import api as api_v1 from api_v2 import api as api_v2 urlpatterns = [ ... path('api/v1/', api_v1.urls), path('api/v2/', api_v2.urls), ] ``` Now you can go to different OpenAPI docs pages for each version: - http://127.0.0.1/api/**v1**/docs - http://127.0.0.1/api/**v2**/docs ## Different business logic In the same way, you can define a different API for different components or areas: ```python hl_lines="4 7" ... api = NinjaAPI(auth=token_auth, urls_namespace='public_api') ... api_private = NinjaAPI(auth=session_auth, urls_namespace='private_api') ... urlpatterns = [ ... path('api/', api.urls), path('internal-api/', api_private.urls), ] ``` !!! note If you use different **NinjaAPI** instances, you need to define different `version`s or different `urls_namespace`s. ================================================ FILE: docs/docs/help.md ================================================ # Help / Get Help ## Do you like Django Ninja? If you like this project, there is a tiny thing you can do to let us know that we're moving in the right direction. Simply give django-ninja a star on github ![github star](img/github-star.png) or share this URL on social media: ``` https://django-ninja.dev ``` Follow updates on twitter @django_ninja ## Do you want to help us? Pull requests are always welcome. You can inspect our docs for typos and spelling mistakes, and create pull requests or open an issue. If you have any suggestions to improve **Django Ninja**, please create them as issues on GitHub. ## Do you need help? Do not hesitate. Go to GitHub issues and describe your question or problem. We'll attempt to address them quickly. Join the chat at our Discord server. [Code-on the webdesign and web development company](https://code-on.be/) gives commercial consulting for Django-Ninja. If you are looking for support please contact Code-on and we will be in touch with you soon. ================================================ FILE: docs/docs/index.md ================================================ # Django Ninja - Fast Django REST Framework
RUSSIA INVADED UKRAINE - Please read
![Django Ninja](img/hero.png) Django Ninja is a web framework for building APIs with Django and Python 3.6+ type hints. Key features: - **Easy**: Designed to be easy to use and intuitive. - **FAST execution**: Very high performance thanks to **Pydantic** and **async support**. - **Fast to code**: Type hints and automatic docs lets you focus only on business logic. - **Standards-based**: Based on the open standards for APIs: **OpenAPI** (previously known as Swagger) and **JSON Schema**. - **Django friendly**: (obviously) has good integration with the Django core and ORM. - **Production ready**: Used by multiple companies on live projects (If you use Django Ninja and would like to publish your feedback, please email ppr.vitaly@gmail.com). Benchmarks: ![Django Ninja REST Framework](img/benchmark.png) ## Installation ``` pip install django-ninja ``` ## Quick Example Start a new Django project (or use an existing one) ``` django-admin startproject apidemo ``` in `urls.py` ```python hl_lines="3 5 8 9 10 15" {!./src/index001.py!} ``` Now, run it as usual: ``` ./manage.py runserver ``` Note: You don't have to add Django Ninja to your installed apps for it to work. ## Check it Open your browser at http://127.0.0.1:8000/api/add?a=1&b=2 You will see the JSON response as: ```JSON {"result": 3} ``` Now you've just created an API that: - receives an HTTP GET request at `/api/add` - takes, validates and type-casts GET parameters `a` and `b` - decodes the result to JSON - generates an OpenAPI schema for defined operation ## Interactive API docs Now go to http://127.0.0.1:8000/api/docs You will see the automatic, interactive API documentation (provided by the OpenAPI / Swagger UI or Redoc): ![Swagger UI](img/index-swagger-ui.png) ## Recap In summary, you declare the types of parameters, body, etc. **once only**, as function parameters. You do that with standard modern Python types. You don't have to learn a new syntax, the methods or classes of a specific library, etc. Just standard **Python 3.6+**. For example, for an `int`: ```python a: int ``` or, for a more complex `Item` model: ```python class Item(Schema): foo: str bar: float def operation(a: Item): ... ``` ... and with that single declaration you get: * Editor support, including: * Completion * Type checks * Validation of data: * Automatic and clear errors when the data is invalid * Validation, even for deeply nested JSON objects * Conversion of input data coming from the network, to Python data and types, and reading from: * JSON * Path parameters * Query parameters * Cookies * Headers * Forms * Files * Automatic, interactive API documentation This project was heavily inspired by FastAPI (developed by Sebastián Ramírez) ================================================ FILE: docs/docs/javascripts/ask-ai-button.js ================================================ // Add "Ask AI" button to the Material for MkDocs navbar document.addEventListener('DOMContentLoaded', function() { // Find the header navigation actions (right side of navbar) const headerActions = document.querySelector('.md-header__topic + .md-header__option'); const headerTitle = document.querySelector('.md-header__title'); if (headerTitle) { // Create the Ask AI button const askAiButton = document.createElement('a'); askAiButton.href = '/chat'; // Update this URL to your desired destination askAiButton.className = 'md-button ask-ai-button'; askAiButton.textContent = 'Ask AI'; askAiButton.title = 'Ask AI about Django Ninja'; // Create a container for the button const buttonContainer = document.createElement('div'); buttonContainer.className = 'ask-ai-button-container'; buttonContainer.appendChild(askAiButton); // Insert the button after the header title const header = document.querySelector('.md-header__inner'); if (header) { // Find the right spot - after title, before search/repo buttons const source = document.querySelector('.md-header__source'); if (source) { header.insertBefore(buttonContainer, source); } else { header.appendChild(buttonContainer); } } } }); ================================================ FILE: docs/docs/motivation.md ================================================ # Motivation !!! quote **Django Ninja** looks basically the same as **FastAPI**, so why not just use FastAPI? Indeed, **Django Ninja** is heavily inspired by FastAPI (developed by Sebastián Ramírez) That said, there are few issues when it comes to getting FastAPI and Django to work together properly: 1) **FastAPI** declares to be ORM agnostic (meaning you can use it with SQLAlchemy or the Django ORM), but in reality the Django ORM is not yet ready for async use (it may be in version 4.0 or 4.1), and if you use it in sync mode, you can have a [closed connection issue](https://github.com/tiangolo/fastapi/issues/716) which you will have to overcome with a **lot** of effort. 2) The dependency injection with arguments makes your code too verbose when you rely on authentication and database sessions in your operations (which for some projects is about 99% of all operations). ```python hl_lines="25 26" ... app = FastAPI() # Dependency def get_db(): db = SessionLocal() try: yield db finally: db.close() async def get_current_user(token: str = Depends(oauth2_scheme)): user = decode(token) if not user: raise HTTPException(...) return user @app.get("/task/{task_id}", response_model=Task) def read_user( task_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): ... use db with current_user .... ``` 3) Since the word `model` in Django is "reserved" for use by the ORM, it becomes very confusing when you mix the Django ORM with Pydantic/FastAPI model naming conventions. ### Django Ninja Django Ninja addresses all those issues, and integrates very well with Django (ORM, urls, views, auth and more) Working at [Code-on a Django webdesign webedevelopment studio](https://code-on.be/) I get all sorts of challenges and to solve these I started Django-Ninja in 2020. Note: **Django Ninja is a production ready project** - my estimation is at this time already 100+ companies using it in production and 500 new developers joining every month. Some companies are already looking for developers with django ninja experience. #### Main Features 1) Since you can have multiple Django Ninja API instances - you can run [multiple API versions](guides/versioning.md) inside one Django project. ```python api_v1 = NinjaAPI(version='1.0', auth=token_auth) ... api_v2 = NinjaAPI(version='2.0', auth=token_auth) ... api_private = NinjaAPI(auth=session_auth, urls_namespace='private_api') ... urlpatterns = [ ... path('api/v1/', api_v1.urls), path('api/v2/', api_v2.urls), path('internal-api/', api_private.urls), ] ``` 2) The Django Ninja 'Schema' class is integrated with the ORM, so you can [serialize querysets](guides/response/index.md#returning-querysets) or ORM objects: ```python @api.get("/tasks", response=List[TaskSchema]) def tasks(request): return Task.objects.all() @api.get("/tasks", response=TaskSchema) def tasks_details(request): task = Task.objects.first() return task ``` 3) [Create Schema's from Django Models](guides/response/django-pydantic.md). 4) Instead of dependency arguments, **Django Ninja** uses `request` instance attributes (in the same way as regular Django views) - more detail at [Authentication](guides/authentication.md). ================================================ FILE: docs/docs/proposals/cbv.md ================================================ # Class Based Operations !!! warning "" This is just a proposal and it is **not present in library code**, but eventually this can be a part of Django Ninja. Please consider adding likes/dislikes or comments in [github issue](https://github.com/vitalik/django-ninja/issues/15) to express your feeling about this proposal ## Problem An API operation is a callable which takes a request and parameters and returns a response, but it is often a case in real world when you need to reuse the same pieces of code in multiple operations. Let's take the following example: - we have a Todo application with Projects and Tasks - each project has multiple tasks - each project may also have an owner (user) - users should not be able to access projects they do not own Model structure is something like this: ```python class Project(models.Model): title = models.CharField(max_length=100) owner = models.ForeignKey('auth.User', on_delete=models.CASCADE) class Task(models.Model): project = models.ForeignKey(Project, on_delete=models.CASCADE) title = models.CharField(max_length=100) completed = models.BooleanField() ``` Now, let's create a few API operations for it: - a list of tasks for the project - some task details - a 'complete task' action The code should validate that a user can only access his/her own project's tasks (otherwise, return 404) It can be something like this: ```python router = Router() @router.get('/project/{project_id}/tasks/', response=List[TaskOut]) def task_list(request): user_projects = request.user.project_set project = get_object_or_404(user_projects, id=project_id)) return project.task_set.all() @router.get('/project/{project_id}/tasks/{task_id}/', response=TaskOut) def details(request, task_id: int): user_projects = request.user.project_set project = get_object_or_404(user_projects, id=project_id)) user_tasks = project.task_set.all() return get_object_or_404(user_tasks, id=task_id) @router.post('/project/{project_id}/tasks/{task_id}/complete', response=TaskOut) def complete(request, task_id: int): user_projects = request.user.project_set project = get_object_or_404(user_projects, id=project_id)) user_tasks = project.task_set.all() task = get_object_or_404(user_tasks, id=task_id) task.completed = True task.save() return task ``` As you can see, these lines are getting repeated pretty often to check permission: ```python hl_lines="1 2" user_projects = request.user.project_set project = get_object_or_404(user_projects, id=project_id)) ``` You can extract it to a function, but it will just make it 3 lines smaller, and it will still be pretty polluted ... ## Solution The proposal is to have alternative called "Class Based Operation" where you can decorate the entire class with a `path` decorator: ```python hl_lines="7 8" from ninja import Router router = Router() @router.path('/project/{project_id}/tasks') class Tasks: def __init__(self, request, project_id=int): user_projects = request.user.project_set self.project = get_object_or_404(user_projects, id=project_id)) self.tasks = self.project.task_set.all() @router.get('/', response=List[TaskOut]) def task_list(self, request): return self.tasks @router.get('/{task_id}/', response=TaskOut) def details(self, request, task_id: int): return get_object_or_404(self.tasks, id=task_id) @router.post('/{task_id}/complete', response=TaskOut) def complete(self, request, task_id: int): task = get_object_or_404(self.tasks, id=task_id) task.completed = True task.save() return task ``` All common initiation and permission checks are placed in the constructor: ```python hl_lines="4 5 6" @router.path('/project/{project_id}/tasks') class Tasks: def __init__(self, request, project_id=int): user_projects = request.user.project_set self.project = get_object_or_404(user_projects, id=project_id)) self.tasks = self.project.task_set.all() ``` This makes the main business operation focus only on tasks (exposed as the `self.tasks` attribute) You can use both `api` and `router` instances to support class paths. ## Issue The `__init__` method: ```def __init__(self, request, project_id=int):``` Python doesn't support the `async` keyword for `__init__`, so to support async operations we need some other method for initialization, but `__init__` sounds the most logical. ## Your thoughts/proposals Please give you thoughts/likes/dislikes about this proposal in the [github issue](https://github.com/vitalik/django-ninja/issues/15) ================================================ FILE: docs/docs/proposals/index.md ================================================ # Enhancement Proposals Enhancement Proposals are a formal way of proposing large feature additions to the **Django Ninja Framework**. You can create a proposal by making a pull request with a new page under [`docs/proposals`](https://github.com/vitalik/django-ninja/tree/master/docs/docs/proposals), or by creating an [issue on github](https://github.com/vitalik/django-ninja/issues). Please see the current proposals: - [Class Based Operations](cbv.md) ================================================ FILE: docs/docs/proposals/v1.md ================================================ # Potential v1 changes Django Ninja is already used by tens of companies and by the visitors and downloads stats it's growing. At this point introducing changes that will force current users to change their code (or break it) is not acceptable. On the other hand some decisions that where initially made does not work well. These breaking changes will be introduced in version 1.0.0 ## Changes that most likely be in v1 - **auth** will be class interface instead of callable (to support async authenticators) - **responses** to support **codes/headers/content** (like general Response class) - **routers paths** currently automatically **joined with "/"** - which might not needed on some cases where router prefix will act like a prefix and not subfolder ## Your thoughts/proposals Please give you thoughts/likes/dislikes in the [github issue](https://github.com/vitalik/django-ninja/issues/146). ================================================ FILE: docs/docs/reference/api.md ================================================ # NinjaAPI ::: ninja.main.NinjaAPI rendering: show_signature: False group_by_category: False ================================================ FILE: docs/docs/reference/csrf.md ================================================ # CSRF ## What is CSRF? > [Cross Site Request Forgery](https://en.wikipedia.org/wiki/Cross-site_request_forgery) occurs when a malicious website contains a link, a form button or some JavaScript that is intended to perform some action on your website, using the credentials (or location on the network, not covered by this documentation) of a logged-in user who visits the malicious site in their browser. ## How to protect against CSRF with Django Ninja ### Use an authentication method not automatically embedded in the request CSRF attacks rely on authentication methods that are automatically included in requests started from another site, like [cookies](https://en.wikipedia.org/wiki/HTTP_cookie) or [Basic access authentication](https://en.wikipedia.org/wiki/Basic_access_authentication). Using an authentication method that does not automatically gets embedded, such as the `Authorization: Bearer` header for exemple, mitigates this attack. ### Use Django's built-in CSRF protection In case you are using the default Django authentication, which uses cookies, you must also use the default [Django CSRF protection](https://docs.djangoproject.com/en/4.2/ref/csrf/). By default, **Django Ninja** has CSRF protection turned **OFF** for all operations, but will automatically enable csrf **for Cookie based** authentication: ```python hl_lines="8" from ninja import NinjaAPI from ninja.security import APIKeyCookie class CookieAuth(APIKeyCookie): def authenticate(self, request, key): return key == "test" api = NinjaAPI(auth=CookieAuth()) ``` or django-auth based (which is inherited from cookie based auth): ```python hl_lines="4" from ninja import NinjaAPI from ninja.security import django_auth api = NinjaAPI(auth=django_auth) ``` #### Django `ensure_csrf_cookie` decorator You can use the Django [ensure_csrf_cookie](https://docs.djangoproject.com/en/4.2/ref/csrf/#django.views.decorators.csrf.ensure_csrf_cookie) decorator on an unprotected route to make it include a `Set-Cookie` header for the CSRF token. Note that: - The route decorator must be executed before (i.e. above) the [ensure_csrf_cookie](https://docs.djangoproject.com/en/4.2/ref/csrf/#django.views.decorators.csrf.ensure_csrf_cookie) decorator). - You must `csrf_exempt` that route. - The `ensure_csrf_cookie` decorator works only on a Django `HttpResponse` (and subclasses like `JsonResponse`) and not on a dict like most Django Ninja decorators. - If you [set a Cookie based authentication (which includes `django_auth`) globally to your API](../guides/authentication.md), you'll have to specifically disable auth on that route (with `auth=None` in the route decorator) as Cookie based authentication would raise an Exception when applied to an unprotected route (for security reasons). ```python hl_lines="4" from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie @api.post("/csrf") @ensure_csrf_cookie @csrf_exempt def get_csrf_token(request): return HttpResponse() ``` A request to that route triggers a response with the adequate `Set-Cookie` header from Django. #### Frontend code You may use the [Using CSRF protection with AJAX](https://docs.djangoproject.com/en/4.2/howto/csrf/#using-csrf-protection-with-ajax) and [Setting the token on the AJAX request](https://docs.djangoproject.com/en/4.2/howto/csrf/#setting-the-token-on-the-ajax-request) part of the [How to use Django’s CSRF protection](https://docs.djangoproject.com/en/4.2/howto/csrf/) to know how to handle that CSRF protection token in your frontend code. ## A word about CORS You may want to set-up your frontend and API on different sites (in that case, you may check [django-cors-headers](https://github.com/adamchainz/django-cors-headers)). While not directly related to CSRF, CORS (Cross-Origin Resource Sharing) may help in case you are defining the CSRF cookie on another site than the frontend consuming it, as this is not allowed by default by the [Same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy). You may check the [django-cors-headers README](https://github.com/adamchainz/django-cors-headers#readme) then. ================================================ FILE: docs/docs/reference/management-commands.md ================================================ # Management Commands Management commands require **Django Ninja** to be installed in Django's `INSTALLED_APPS` setting: ```python INSTALLED_APPS = [ ... 'ninja', ] ``` ::: ninja.management.commands selection: filters: - "![A-Z]" rendering: show_root_toc_entry: False ================================================ FILE: docs/docs/reference/operations-parameters.md ================================================ # Operations parameters ## OpenAPI Schema related The following parameters interact with how the OpenAPI schema (and docs) are generated. ### `tags` You can group your API operations using the `tags` argument (`list[str]`). ```python hl_lines="6" @api.get("/hello/") def hello(request, name: str): return {"hello": name} @api.post("/orders/", tags=["orders"]) def create_order(request, order: Order): return {"success": True} ``` Tagged operations may be handled differently by various tools and libraries. For example, the Swagger UI uses tags to group the displayed operations. ![Summary`](../img/operation_tags.png) #### Router tags You can use `tags` argument to apply tags to all operations declared by router: ```python api.add_router("/events/", events_router, tags=["events"]) # or using constructor: router = Router(tags=["events"]) ``` ### `summary` A human-readable name for your operation. By default, it's generated by capitalizing your operation function name: ```python hl_lines="2" @api.get("/hello/") def hello(request, name: str): return {"hello": name} ``` ![Summary`](../img/operation_summary_default.png) If you want to override it or translate it to other language, use the `summary` argument in the `api` decorator. ```python hl_lines="1" @api.get("/hello/", summary="Say Hello") def hello(request, name: str): return {"hello": name} ``` ![Summary`](../img/operation_summary.png) ### `description` To provide more information about your operation, use either the `description` argument or normal Python docstrings: ```python hl_lines="1" @api.post("/orders/", description="Creates an order and updates stock") def create_order(request, order: Order): return {"success": True} ``` ![Summary`](../img/operation_description.png) When you need to provide a long multi line description, you can use Python `docstrings` for the function definition: ```python hl_lines="4 5 6 7" @api.post("/orders/") def create_order(request, order: Order): """ To create an order please provide: - **first_name** - **last_name** - and **list of Items** *(product + amount)* """ return {"success": True} ``` ![Summary`](../img/operation_description_docstring.png) ### `operation_id` The OpenAPI `operationId` is an optional unique string used to identify an operation. If provided, these IDs must be unique among all operations described in your API. By default, **Django Ninja** sets it to `module name` + `function name`. If you want to set it individually for each operation, use the `operation_id` argument: ```python hl_lines="2" ... @api.post("/tasks", operation_id="create_task") def new_task(request): ... ``` If you want to override global behavior, you can inherit the NinjaAPI instance and override the `get_openapi_operation_id` method. It will be called for each operation that you defined, so you can set your custom naming logic like this: ```python hl_lines="5 6 7 9" from ninja import NinjaAPI class MySuperApi(NinjaAPI): def get_openapi_operation_id(self, operation): # here you can access operation ( .path , .view_func, etc) return ... api = MySuperApi() @api.get(...) ... ``` ### `deprecated` Mark an operation as deprecated without removing it by using the `deprecated` argument: ```python hl_lines="1" @api.post("/make-order/", deprecated=True) def some_old_method(request, order: str): return {"success": True} ``` It will be marked as deprecated in the JSON Schema and also in the interactive OpenAPI docs: ![Deprecated](../img/deprecated.png) ### `include_in_schema` If you need to include/exclude some operation from OpenAPI schema use `include_in_schema` argument: ```python hl_lines="1" @api.post("/hidden", include_in_schema=False) def some_hidden_operation(request): pass ``` ## openapi_extra You can customize your OpenAPI schema for specific endpoint (detail [OpenAPI Customize Options](https://swagger.io/docs/specification/about/)) ```python hl_lines="1 26" # You can set requestBody from openapi_extra @api.get( "/tasks", openapi_extra={ "requestBody": { "content": { "application/json": { "schema": { "required": ["email"], "type": "object", "properties": { "name": {"type": "string"}, "phone": {"type": "number"}, "email": {"type": "string"}, }, } } }, "required": True, } }, ) def some_operation(request): pass # You can add additional responses to the automatically generated schema @api.post( "/tasks", openapi_extra={ "responses": { 400: { "description": "Error Response", }, 404: { "description": "Not Found Response", }, }, }, ) def some_operation_2(request): pass ``` ## Response output options There are a few arguments that lets you tune response's output: ### `by_alias` Whether field aliases should be used as keys in the response (defaults to `False`). ### `exclude_unset` Whether fields that were not set when creating the schema, and have their default values, should be excluded from the response (defaults to `False`). ### `exclude_defaults` Whether fields which are equal to their default values (whether set or otherwise) should be excluded from the response (defaults to `False`). ### `exclude_none` Whether fields which are equal to `None` should be excluded from the response (defaults to `False`). ## url_name Allows you to set api endpoint url name (using [django path's naming](https://docs.djangoproject.com/en/stable/topics/http/urls/#reversing-namespaced-urls)) ```python hl_lines="1 7" @api.post("/tasks", url_name='tasks') def some_operation(request): pass # then you can get the url with reverse('api-1.0.0:tasks') ``` See the [Reverse Resolution of URLs](../guides/urls.md) guide for more details. ## Specifying servers If you want to specify single or multiple servers for OpenAPI specification `servers` can be used when initializing NinjaAPI instance: ```python hl_lines="4 5 6 7" from ninja import NinjaAPI api = NinjaAPI( servers=[ {"url": "https://stag.example.com", "description": "Staging env"}, {"url": "https://prod.example.com", "description": "Production env"}, ] ) ``` This will allow switching between environments when using interactive OpenAPI docs: ![Servers](../img/servers.png) ================================================ FILE: docs/docs/reference/settings.md ================================================ # Django Settings ::: ninja.conf.Settings rendering: show_source: False show_root_toc_entry: False ================================================ FILE: docs/docs/releases.md ================================================ # Release Notes Follow and subscribe for new releases on GitHub: ================================================ FILE: docs/docs/tutorial/index.md ================================================ # Tutorial - First Steps This tutorial shows you how to use **Django Ninja** with most of its features. This tutorial assumes that you know at least some basics of the Django Framework, like how to create a project and run it. ## Installation ```console pip install django-ninja ``` !!! note It is not required, but you can also put `ninja` to `INSTALLED_APPS`. In that case the OpenAPI/Swagger UI (or Redoc) will be loaded (faster) from the included JavaScript bundle (otherwise the JavaScript bundle comes from a CDN). ## Create a Django project Start a new Django project (or if you already have an existing Django project, skip to the next step). ``` django-admin startproject myproject ``` ## Create the API Let's create a module for our API. Create an `api.py` file in the same directory location as your Django project's root `urls.py`: ```python from ninja import NinjaAPI api = NinjaAPI() ``` Now go to `urls.py` and add the following: ```python hl_lines="3 7" from django.contrib import admin from django.urls import path from .api import api urlpatterns = [ path("admin/", admin.site.urls), path("api/", api.urls), ] ``` ## Our first operation **Django Ninja** comes with a decorator for each HTTP method (`GET`, `POST`, `PUT`, etc). In our `api.py` file, let's add in a simple "hello world" operation. ```python hl_lines="5-7" from ninja import NinjaAPI api = NinjaAPI() @api.get("/hello") def hello(request): return "Hello world" ``` Now browsing to localhost:8000/api/hello will return a simple JSON response: ```json "Hello world" ``` !!! success Continue on to **[Parsing input](step2.md)**. ================================================ FILE: docs/docs/tutorial/other/crud.md ================================================ # CRUD example **CRUD** - **C**reate, **R**etrieve, **U**pdate, **D**elete are the four basic functions of persistent storage. This example will show you how to implement these functions with **Django Ninja**. Let's say you have the following Django models that you need to perform these operations on: ```python class Department(models.Model): title = models.CharField(max_length=100) class Employee(models.Model): first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) department = models.ForeignKey(Department, on_delete=models.CASCADE) birthdate = models.DateField(null=True, blank=True) cv = models.FileField(null=True, blank=True) ``` Now let's create CRUD operations for the Employee model. ## Create To create an employee lets define an INPUT schema: ```python from datetime import date from ninja import Schema class EmployeeIn(Schema): first_name: str last_name: str department_id: int = None birthdate: date = None ``` This schema will be our input payload: ```python hl_lines="2" @api.post("/employees") def create_employee(request, payload: EmployeeIn): employee = Employee.objects.create(**payload.dict()) return {"id": employee.id} ``` !!! tip `Schema` objects have `.dict()` method with all the schema attributes represented as a dict. You can pass it as `**kwargs` to the Django model's `create` method (or model `__init__`). See the recipe below for handling the file upload (when using Django models): ```python hl_lines="2" from ninja import UploadedFile, File @api.post("/employees") def create_employee(request, payload: EmployeeIn, cv: File[UploadedFile]): payload_dict = payload.dict() employee = Employee(**payload_dict) employee.cv.save(cv.name, cv) # will save model instance as well return {"id": employee.id} ``` If you just need to handle a file upload: ```python hl_lines="2" from django.core.files.storage import FileSystemStorage from ninja import UploadedFile, File STORAGE = FileSystemStorage() @api.post("/upload") def create_upload(request, cv: File[UploadedFile]): filename = STORAGE.save(cv.name, cv) # Handle things further ``` ## Retrieve ### Single object Now to get employee we will define a schema that will describe what our responses will look like. Here we will basically use the same schema as `EmployeeIn`, but will add an extra attribute `id`: ```python hl_lines="2" class EmployeeOut(Schema): id: int first_name: str last_name: str department_id: int = None birthdate: date = None ``` !!! note Defining response schemas are not really required, but when you do define it you will get results validation, documentation and automatic ORM objects to JSON conversions. We will use this schema as the `response` type for our `GET` employee view: ```python hl_lines="1" @api.get("/employees/{employee_id}", response=EmployeeOut) def get_employee(request, employee_id: int): employee = get_object_or_404(Employee, id=employee_id) return employee ``` Notice that we simply returned an employee ORM object, without a need to convert it to a dict. The `response` schema does automatic result validation and conversion to JSON: ```python hl_lines="4" @api.get("/employees/{employee_id}", response=EmployeeOut) def get_employee(request, employee_id: int): employee = get_object_or_404(Employee, id=employee_id) return employee ``` ### List of objects To output a list of employees, we can reuse the same `EmployeeOut` schema. We will just set the `response` schema to a *List* of `EmployeeOut`. ```python hl_lines="3" from typing import List @api.get("/employees", response=List[EmployeeOut]) def list_employees(request): qs = Employee.objects.all() return qs ``` Another cool trick - notice we just returned a Django ORM queryset: ```python hl_lines="4" @api.get("/employees", response=List[EmployeeOut]) def list_employees(request): qs = Employee.objects.all() return qs ``` It automatically gets evaluated, validated and converted to a JSON list! ## Update Update is pretty trivial. We just use the `PUT` method and also pass `employee_id`: ```python hl_lines="1" @api.put("/employees/{employee_id}") def update_employee(request, employee_id: int, payload: EmployeeIn): employee = get_object_or_404(Employee, id=employee_id) for attr, value in payload.dict().items(): setattr(employee, attr, value) employee.save() return {"success": True} ``` **Note** Here we used the `payload.dict` method to set all object attributes: `for attr, value in payload.dict().items()` You can also do this more explicit: ```python employee.first_name = payload.first_name employee.last_name = payload.last_name employee.department_id = payload.department_id employee.birthdate = payload.birthdate ``` **Partial updates** To allow the user to make partial updates, use `payload.dict(exclude_unset=True).items()`. This ensures that only the specified fields get updated. **Enforcing strict field validation** By default, any provided fields that don't exist in the schema will be silently ignored. To raise an error for these invalid fields, you can set `extra = "forbid"` in the model_config. For example: ```python hl_lines="5" from pydantic import ConfigDict class EmployeeIn(Schema): # your fields here... model_config = ConfigDict(extra="forbid") ``` ## Delete Delete is also pretty simple. We just get employee by `id` and delete it from the DB: ```python hl_lines="1 2 4" @api.delete("/employees/{employee_id}") def delete_employee(request, employee_id: int): employee = get_object_or_404(Employee, id=employee_id) employee.delete() return {"success": True} ``` ## Final code Here's a full CRUD example: ```python from datetime import date from typing import List from ninja import NinjaAPI, Schema from django.shortcuts import get_object_or_404 from employees.models import Employee api = NinjaAPI() class EmployeeIn(Schema): first_name: str last_name: str department_id: int = None birthdate: date = None class EmployeeOut(Schema): id: int first_name: str last_name: str department_id: int = None birthdate: date = None @api.post("/employees") def create_employee(request, payload: EmployeeIn): employee = Employee.objects.create(**payload.dict()) return {"id": employee.id} @api.get("/employees/{employee_id}", response=EmployeeOut) def get_employee(request, employee_id: int): employee = get_object_or_404(Employee, id=employee_id) return employee @api.get("/employees", response=List[EmployeeOut]) def list_employees(request): qs = Employee.objects.all() return qs @api.put("/employees/{employee_id}") def update_employee(request, employee_id: int, payload: EmployeeIn): employee = get_object_or_404(Employee, id=employee_id) for attr, value in payload.dict().items(): setattr(employee, attr, value) employee.save() return {"success": True} @api.delete("/employees/{employee_id}") def delete_employee(request, employee_id: int): employee = get_object_or_404(Employee, id=employee_id) employee.delete() return {"success": True} ``` ================================================ FILE: docs/docs/tutorial/other/video.md ================================================ # Video Tutorials ## Sneaky REST APIs With Django Ninja [realpython.com/lessons/sneaky-rest-apis-with-django-ninja-overview/](https://realpython.com/lessons/sneaky-rest-apis-with-django-ninja-overview/) ## Creating a CRUD API with Django-Ninja by BugBytes (English) ================================================ FILE: docs/docs/tutorial/step2.md ================================================ # Tutorial - Parsing Input ## Input from the query string Let's change our operation to accept a name from the URL's query string. To do that, just add a `name` argument to our function. ```python @api.get("/hello") def hello(request, name): return f"Hello {name}" ``` When we provide a name argument, we get the expected (HTTP 200) response. localhost:8000/api/hello?name=you: ```json "Hello you" ``` ### Defaults Not providing the argument will return an HTTP 422 error response. *[HTTP 422]: Unprocessable Entity localhost:8000/api/hello: ```json { "detail": [ { "loc": ["query", "name"], "msg": "field required", "type": "value_error.missing" } ] } ``` We can specify a default for the `name` argument in case it isn't provided: ```python hl_lines="2" @api.get("/hello") def hello(request, name="world"): return f"Hello {name}" ``` ## Input types **Django Ninja** uses standard [Python type hints](https://docs.python.org/3/library/typing.html) to format the input types. If no type is provided then a string is assumed (but it is good practice to provide type hints for all your arguments). Let's add a second operation that does some basic math with integers. ```python hl_lines="5-7" @api.get("/hello") def hello(request, name: str = "world"): return f"Hello {name}" @api.get("/math") def math(request, a: int, b: int): return {"add": a + b, "multiply": a * b} ``` localhost:8000/api/math?a=2&b=3: ```json { "add": 5, "multiply": 6 } ``` ## Input from the path You can declare path "parameters" with the same syntax used by Python format-strings. Any parameters found in the path string will be passed to your function as arguments, rather than expecting them from the query string. ```python hl_lines="1" @api.get("/math/{a}and{b}") def math(request, a: int, b: int): return {"add": a + b, "multiply": a * b} ``` Now we access the math operation from localhost:8000/api/math/2and3. ## Input from the request body We are going to change our `hello` operation to use HTTP `POST` instead, and take arguments from the request body. To specify that arguments come from the body, we need to declare a Schema. *[Schema]: An extension of a Pydantic "Model" ```python hl_lines="1 5-6 8-10" from ninja import NinjaAPI, Schema api = NinjaAPI() class HelloSchema(Schema): name: str = "world" @api.post("/hello") def hello(request, data: HelloSchema): return f"Hello {data.name}" ``` ### Self-documenting API Accessing localhost:8000/api/hello now results in a HTTP 405 error response, since we need to POST to this URL instead. *[HTTP 405]: Method Not Allowed An easy way to do this is to use the Swagger documentation that is automatically created for us, at default URL of "/docs" (appended to our API url root). 1. Visit localhost:8000/api/docs to see the operations we have created 1. Open the `/api/hello` operation 2. Click "Try it out" 3. Change the request body 4. Click "Execute" !!! success Continue on to **[Handling responses](step3.md)** ================================================ FILE: docs/docs/tutorial/step3.md ================================================ # Tutorial - Handling Responses ## Define a response Schema **Django Ninja** allows you to define the schema of your responses both for validation and documentation purposes. We'll create a third operation that will return information about the current Django user. ```python from ninja import Schema class UserSchema(Schema): username: str is_authenticated: bool # Unauthenticated users don't have the following fields, so provide defaults. email: str = None first_name: str = None last_name: str = None @api.get("/me", response=UserSchema) def me(request): return request.user ``` This will convert the Django `User` object into a dictionary of only the defined fields. ### Multiple response types Let's return a different response if the current user is not authenticated. ```python hl_lines="2-5 7-8 10 12-13" class UserSchema(Schema): username: str email: str first_name: str last_name: str class Error(Schema): message: str @api.get("/me", response={200: UserSchema, 403: Error}) def me(request): if not request.user.is_authenticated: return 403, {"message": "Please sign in first"} return request.user ``` As you see, you can return a 2-part tuple which will be interpreted as the HTTP response code and the data. !!! success That concludes the tutorial! Check out the **Other Tutorials** or the **How-to Guides** for more information. ================================================ FILE: docs/docs/whatsnew_v1.md ================================================ # Welcome to Django Ninja 1.0 To get started install latest version with ``` pip install -U django-ninja ``` django-ninja v1 is compatible with Python 3.7 and above. Django ninja series 0.x is still supported but will receive only security updates and critical bug fixes # What's new in Django Ninja 1.0 ## Support for Pydantic2 Pydantic version 2 is re-written in Rust and includes a lot of improvements and features like: - Safer types. - Better extensibility. - Better performance By our tests average project can gain some 10% performance increase on average, while some edge parsing/serializing cases can give you 4x boost. On the other hand it introduces breaking changes and pydantic 1 and 2 are not very compatible - but we tried or best to make this transition easy as possible. So if you used 'Schema' class migration to ninja v1 should be easy. Otherwise follow [pydantic migration guide](https://docs.pydantic.dev/latest/migration/) Some features that are made possible with pydantic2 ### pydantic context Pydantic now supports context during validation and serialization and Django ninja passes "request" object during request and response work ```Python hl_lines="6 7" class Payload(Schema): id: int name: str request_path: str @staticmethod def resolve_request_path(data, context): request = context["request"] return request.get_full_path() ``` During response a "response_code" is also passed to context ## Schema.Meta Pydantic now deprecates BaseModel.Config class. But to keep things consistent with all other django parts we introduce "Meta" class for ModelSchema - which works in a similar way as django's ModelForms: ```Python hl_lines="2 4" class TxItem(ModelSchema): class Meta: model = Transaction fields = ["id", "account", "amount", "timestamp"] ``` (The "Config" class is still supported, but deprecated) ## Shorter / cleaner parameters syntax ```python @api.post('/some') def some_form(request, username: Form[str], password: Form[str]): return True ``` instead of ```python @api.post('/some') def some_form(request, username: str = Form(...), password: str = Form(...)): return True ``` or ```python @api.post('/some') def some_form(request, data: Form[AuthSchema]): return True ``` instead of ```python @api.post('/some') def some_form(request, data: AuthSchema = Form(...)): return True ``` with all the autocompletion in editors On the other hand the **old syntax is still supported** so you can easily port your project to a newer django-ninja version without much haste #### + Annotated typing.Annotated is also supported: ```Python @api.get("/annotated") def annotated(request, data: Annotated[SomeData, Form()]): return {"data": data.dict()} ``` ## Async auth support The async authenticators are finally supported. All you have to do is just add `async` to your `authenticate` method: ```Python class Auth(HttpBearer): async def authenticate(self, request, token): await asyncio.sleep(1) if token == "secret": return token ``` ## Changed CSRF Behavior `csrf=True` requirement is no longer required if you use cookie based authentication. Instead CSRF protection is enabled automatically. This also allow you to mix csrf-protected authenticators and other methods that does not require cookies: ```Python api = NinjaAPI(auth=[django_auth, Auth()]) ``` ## Docs Doc viewer are now configurable and plugable. By default django ninja comes with Swagger and Redoc: ```Python from ninja import NinjaAPI, Redoc, Swagger # use redoc api = NinjaAPI(docs=Redoc())) # use swagger: api = NinjaAPI(docs=Swagger()) # set configuration for swagger: api = NinjaAPI(docs=Swagger({"persistAuthorization": True})) ``` Users now able to create custom docs viewer by inheriting `DocsBase` class ## Router add_router supports string paths: ```Python api = NinjaAPI() api.add_router('/app1', 'myproject.app1.router') api.add_router('/app2', 'myproject.app2.router') api.add_router('/app3', 'myproject.app3.router') api.add_router('/app4', 'myproject.app4.router') api.add_router('/app5', 'myproject.app5.router') ``` ## Decorators When django ninja decorates a view with .get/.post etc. - it wraps the result of the function (which in most cases are not HttpResponse - but some serializable object) so it's not really possible to use some built-in or 3rd-party decorators like: ```python hl_lines="4" from django.views.decorators.cache import cache_page @api.get("/test") @cache_page(5) # <----- will not work def test_view(request): return {"some": "Complex data"} ``` This example does not work. Now django ninja introduces a decorator decorate_view that allows inject decorators that work with http response: ```python hl_lines="1 4" from ninja.decorators import decorate_view @api.get("/test") @decorate_view(cache_page(5)) def test_view(request): return str(datetime.now()) ``` ## Paginations `paginate_queryset` method now takes `request` object #### Backwards incompatible stuff - resolve_xxx(self, ...) - support resolve with (self) is dropped in favor of pydantic build-in functionality - pydantic v1 is no longer supported - python 3.6 is no longer supported BTW - if you like this project and still did not give it a github start - please do so ![github star](img/github-star.png) ================================================ FILE: docs/mkdocs.yml ================================================ site_name: Django Ninja site_description: Django Ninja - Django REST framework with high performance, easy to learn, fast to code. site_url: https://django-ninja.dev repo_name: vitalik/django-ninja repo_url: https://github.com/vitalik/django-ninja edit_uri: "" extra: analytics: provider: google property: G-0E3XZ663ZR extra_css: - extra.css extra_javascript: - javascripts/ask-ai-button.js theme: name: material palette: - media: "(prefers-color-scheme)" primary: green toggle: icon: material/brightness-auto name: Switch to light mode - media: "(prefers-color-scheme: light)" scheme: default primary: green toggle: icon: material/weather-night name: Switch to dark mode - media: "(prefers-color-scheme: dark)" scheme: slate primary: green toggle: icon: material/weather-sunny name: Switch to light mode logo: img/docs-logo.png favicon: img/favicon.svg language: en features: - navigation.expand - search.highlight - search.suggest icon: repo: fontawesome/brands/github-alt nav: - Intro: index.md - motivation.md - Tutorial: - "First Steps": tutorial/index.md - "Parsing Input": tutorial/step2.md - "Handling Responses": tutorial/step3.md - Other Tutorials: - tutorial/other/video.md - tutorial/other/crud.md - How-to Guides: - Parsing input: - guides/input/operations.md - guides/input/path-params.md - guides/input/query-params.md - guides/input/body.md - guides/input/form-params.md - guides/input/file-params.md - guides/input/request-parsers.md - guides/input/filtering.md - Handling responses: - Defining a Schema: guides/response/index.md - guides/response/temporal_response.md - Generating a Schema from Django models: guides/response/django-pydantic.md - Generating a Schema dynamically: guides/response/django-pydantic-create-schema.md - guides/response/config-pydantic.md - guides/response/pagination.md - guides/response/response-renderers.md - Splitting your API with Routers: guides/routers.md - guides/decorators.md - guides/authentication.md - guides/throttling.md - guides/testing.md - guides/api-docs.md - guides/errors.md - guides/urls.md - guides/async-support.md - guides/versioning.md - Reference: - NinjaAPI class: reference/api.md - reference/csrf.md - reference/operations-parameters.md - reference/management-commands.md - reference/settings.md - releases.md - help.md - Enhancement Proposals: - Intro: proposals/index.md - proposals/cbv.md - proposals/v1.md - What's new in V1: whatsnew_v1.md markdown_extensions: - markdown_include.include - markdown.extensions.codehilite: guess_lang: false # Uncomment these 2 lines during development to more easily add highlights #- pymdownx.highlight: # linenums: true - abbr - codehilite - admonition - pymdownx.details - pymdownx.superfences plugins: - search - mkdocstrings: handlers: python: setup_commands: - from django.conf import settings - settings.configure() ================================================ FILE: docs/requirements.txt ================================================ mkdocs==1.5.3 mkdocs-autorefs==1.0.1 mkdocs-material==9.5.4 mkdocs-material-extensions==1.3.1 markdown-include==0.8.1 mkdocstrings[python]==0.24.0 griffe==0.47.0 ================================================ FILE: docs/src/index001.py ================================================ from django.contrib import admin from django.urls import path from ninja import NinjaAPI api = NinjaAPI() @api.get("/add") def add(request, a: int, b: int): return {"result": a + b} urlpatterns = [ path("admin/", admin.site.urls), path("api/", api.urls), ] ================================================ FILE: docs/src/tutorial/authentication/apikey01.py ================================================ from ninja.security import APIKeyQuery from someapp.models import Client class ApiKey(APIKeyQuery): param_name = "api_key" def authenticate(self, request, key): try: return Client.objects.get(key=key) except Client.DoesNotExist: pass api_key = ApiKey() @api.get("/apikey", auth=api_key) def apikey(request): assert isinstance(request.auth, Client) return f"Hello {request.auth}" ================================================ FILE: docs/src/tutorial/authentication/apikey02.py ================================================ from ninja.security import APIKeyHeader class ApiKey(APIKeyHeader): param_name = "X-API-Key" def authenticate(self, request, key): if key == "supersecret": return key header_key = ApiKey() @api.get("/headerkey", auth=header_key) def apikey(request): return f"Token = {request.auth}" ================================================ FILE: docs/src/tutorial/authentication/apikey03.py ================================================ from ninja.security import APIKeyCookie class CookieKey(APIKeyCookie): def authenticate(self, request, key): if key == "supersecret": return key cookie_key = CookieKey() @api.get("/cookiekey", auth=cookie_key) def apikey(request): return f"Token = {request.auth}" ================================================ FILE: docs/src/tutorial/authentication/basic01.py ================================================ from ninja.security import HttpBasicAuth class BasicAuth(HttpBasicAuth): def authenticate(self, request, username, password): if username == "admin" and password == "secret": return username @api.get("/basic", auth=BasicAuth()) def basic(request): return {"httpuser": request.auth} ================================================ FILE: docs/src/tutorial/authentication/bearer01.py ================================================ from ninja.security import HttpBearer class AuthBearer(HttpBearer): def authenticate(self, request, token): if token == "supersecret": return token @api.get("/bearer", auth=AuthBearer()) def bearer(request): return {"token": request.auth} ================================================ FILE: docs/src/tutorial/authentication/bearer02.py ================================================ from ninja import NinjaAPI from ninja.security import HttpBearer api = NinjaAPI() class InvalidToken(Exception): pass @api.exception_handler(InvalidToken) def on_invalid_token(request, exc): return api.create_response(request, {"detail": "Invalid token supplied"}, status=401) class AuthBearer(HttpBearer): def authenticate(self, request, token): if token == "supersecret": return token raise InvalidToken @api.get("/bearer", auth=AuthBearer()) def bearer(request): return {"token": request.auth} ================================================ FILE: docs/src/tutorial/authentication/code001.py ================================================ from ninja import NinjaAPI from ninja.security import django_auth api = NinjaAPI() @api.get("/pets", auth=django_auth) def pets(request): return f"Authenticated user {request.auth}" ================================================ FILE: docs/src/tutorial/authentication/code002.py ================================================ def ip_whitelist(request): if request.META["REMOTE_ADDR"] == "8.8.8.8": return "8.8.8.8" @api.get("/ipwhitelist", auth=ip_whitelist) def ipwhitelist(request): return f"Authenticated client, IP = {request.auth}" ================================================ FILE: docs/src/tutorial/authentication/global01.py ================================================ from ninja import NinjaAPI, Form from ninja.security import HttpBearer class GlobalAuth(HttpBearer): def authenticate(self, request, token): if token == "supersecret": return token api = NinjaAPI(auth=GlobalAuth()) # @api.get(...) # def ... # @api.post(...) # def ... @api.post("/token", auth=None) # < overriding global auth def get_token(request, username: str = Form(...), password: str = Form(...)): if username == "admin" and password == "giraffethinnknslong": return {"token": "supersecret"} ================================================ FILE: docs/src/tutorial/authentication/multiple01.py ================================================ from ninja.security import APIKeyQuery, APIKeyHeader class AuthCheck: def authenticate(self, request, key): if key == "supersecret": return key class QueryKey(AuthCheck, APIKeyQuery): pass class HeaderKey(AuthCheck, APIKeyHeader): pass @api.get("/multiple", auth=[QueryKey(), HeaderKey()]) def multiple(request): return f"Token = {request.auth}" ================================================ FILE: docs/src/tutorial/authentication/schema01.py ================================================ ================================================ FILE: docs/src/tutorial/body/code01.py ================================================ from typing import Optional from ninja import Schema class Item(Schema): name: str description: Optional[str] = None price: float quantity: int @api.post("/items") def create(request, item: Item): return item ================================================ FILE: docs/src/tutorial/body/code02.py ================================================ from ninja import Schema class Item(Schema): name: str description: str = None price: float quantity: int @api.put("/items/{item_id}") def update(request, item_id: int, item: Item): return {"item_id": item_id, "item": item.dict()} ================================================ FILE: docs/src/tutorial/body/code03.py ================================================ from ninja import Schema class Item(Schema): name: str description: str = None price: float quantity: int @api.post("/items/{item_id}") def update(request, item_id: int, item: Item, q: str): return {"item_id": item_id, "item": item.dict(), "q": q} ================================================ FILE: docs/src/tutorial/form/code01.py ================================================ from ninja import Form, Schema class Item(Schema): name: str description: str = None price: float quantity: int @api.post("/items") def create(request, item: Form[Item]): return item ================================================ FILE: docs/src/tutorial/form/code02.py ================================================ from ninja import Form, Schema class Item(Schema): name: str description: str = None price: float quantity: int @api.post("/items/{item_id}") def update(request, item_id: int, q: str, item: Form[Item]): return {"item_id": item_id, "item": item.dict(), "q": q} ================================================ FILE: docs/src/tutorial/form/code03.py ================================================ from ninja import Form, Schema from typing import Annotated, TypeVar from pydantic import WrapValidator from pydantic_core import PydanticUseDefault def _empty_str_to_default(v, handler, info): if isinstance(v, str) and v == '': raise PydanticUseDefault return handler(v) T = TypeVar('T') EmptyStrToDefault = Annotated[T, WrapValidator(_empty_str_to_default)] class Item(Schema): name: str description: str = None price: EmptyStrToDefault[float] = 0.0 quantity: EmptyStrToDefault[int] = 0 in_stock: EmptyStrToDefault[bool] = True @api.post("/items-blank-default") def update(request, item: Form[Item]): return item.dict() ================================================ FILE: docs/src/tutorial/path/code01.py ================================================ @api.get("/items/{item_id}") def read_item(request, item_id): return {"item_id": item_id} ================================================ FILE: docs/src/tutorial/path/code010.py ================================================ import datetime from ninja import Schema, Path class PathDate(Schema): year: int month: int day: int def value(self): return datetime.date(self.year, self.month, self.day) @api.get("/events/{year}/{month}/{day}") def events(request, date: Path[PathDate]): return {"date": date.value()} ================================================ FILE: docs/src/tutorial/path/code02.py ================================================ @api.get("/items/{item_id}") def read_item(request, item_id: int): return {"item_id": item_id} ================================================ FILE: docs/src/tutorial/query/code01.py ================================================ weapons = ["Ninjato", "Shuriken", "Katana", "Kama", "Kunai", "Naginata", "Yari"] @api.get("/weapons") def list_weapons(request, limit: int = 10, offset: int = 0): return weapons[offset: offset + limit] ================================================ FILE: docs/src/tutorial/query/code010.py ================================================ import datetime from typing import List from pydantic import Field from ninja import Query, Schema class Filters(Schema): limit: int = 100 offset: int = None query: str = None category__in: List[str] = Field(None, alias="categories") @api.get("/filter") def events(request, filters: Query[Filters]): return {"filters": filters.dict()} ================================================ FILE: docs/src/tutorial/query/code02.py ================================================ weapons = ["Ninjato", "Shuriken", "Katana", "Kama", "Kunai", "Naginata", "Yari"] @api.get("/weapons/search") def search_weapons(request, q: str, offset: int = 0): results = [w for w in weapons if q in w.lower()] return results[offset : offset + 10] ================================================ FILE: docs/src/tutorial/query/code03.py ================================================ from datetime import date @api.get("/example") def example(request, s: str = None, b: bool = None, d: date = None, i: int = None): return [s, b, d, i] ================================================ FILE: mypy.ini ================================================ [mypy] python_version = 3.12 show_column_numbers = True show_error_codes = True follow_imports = normal ignore_missing_imports = True # Exclude directories exclude = ^(\.venv|venv|env)/ # be strict disallow_untyped_calls = True warn_return_any = True strict_optional = True warn_no_return = True warn_redundant_casts = True warn_unused_ignores = True disallow_untyped_defs = True check_untyped_defs = True no_implicit_reexport = True [mypy-ninja.compatibility.*] ignore_errors = True ================================================ FILE: ninja/__init__.py ================================================ """Django Ninja - Fast Django REST framework""" __version__ = "1.6.2" from pydantic import Field from ninja.files import UploadedFile from ninja.filter_schema import FilterConfigDict, FilterLookup, FilterSchema from ninja.main import NinjaAPI from ninja.openapi.docs import Redoc, Swagger from ninja.orm import ModelSchema from ninja.params import ( Body, BodyEx, Cookie, CookieEx, File, FileEx, Form, FormEx, Header, HeaderEx, P, Path, PathEx, Query, QueryEx, ) from ninja.patch_dict import PatchDict from ninja.responses import Status from ninja.router import Router from ninja.schema import Schema from ninja.streaming import JSONL, SSE __all__ = [ "Field", "UploadedFile", "NinjaAPI", "Body", "Cookie", "File", "Form", "Header", "Path", "Query", "BodyEx", "CookieEx", "FileEx", "FormEx", "HeaderEx", "PathEx", "QueryEx", "Router", "P", "Schema", "ModelSchema", "FilterSchema", "FilterLookup", "FilterConfigDict", "Swagger", "Redoc", "PatchDict", "SSE", "JSONL", "Status", ] ================================================ FILE: ninja/compatibility/__init__.py ================================================ ================================================ FILE: ninja/compatibility/files.py ================================================ from typing import Any, List from asgiref.sync import iscoroutinefunction, sync_to_async from django.conf import settings from django.http import HttpRequest from django.utils.decorators import sync_and_async_middleware from ninja.conf import settings as ninja_settings from ninja.params.models import FileModel FIX_MIDDLEWARE_PATH: str = "ninja.compatibility.files.fix_request_files_middleware" FIX_METHODS = ninja_settings.FIX_REQUEST_FILES_METHODS def need_to_fix_request_files(methods: List[str], params_models: List[Any]) -> bool: has_files_params = any( issubclass(model_class, FileModel) for model_class in params_models ) method_needs_fix = bool(set(methods) & FIX_METHODS) middleware_installed = FIX_MIDDLEWARE_PATH in settings.MIDDLEWARE return has_files_params and method_needs_fix and not middleware_installed @sync_and_async_middleware def fix_request_files_middleware(get_response: Any) -> Any: """ This middleware fixes long historical Django behavior where request.FILES is only populated for POST requests. https://code.djangoproject.com/ticket/12635 """ if iscoroutinefunction(get_response): async def async_middleware(request: HttpRequest) -> Any: if ( request.method in FIX_METHODS and request.content_type != "application/json" ): initial_method = request.method request.method = "POST" request.META["REQUEST_METHOD"] = "POST" await sync_to_async(request._load_post_and_files)() request.META["REQUEST_METHOD"] = initial_method request.method = initial_method return await get_response(request) return async_middleware else: def sync_middleware(request: HttpRequest) -> Any: if ( request.method in FIX_METHODS and request.content_type != "application/json" ): initial_method = request.method request.method = "POST" request.META["REQUEST_METHOD"] = "POST" request._load_post_and_files() request.META["REQUEST_METHOD"] = initial_method request.method = initial_method return get_response(request) return sync_middleware ================================================ FILE: ninja/compatibility/streaming.py ================================================ """Compatibility layer for async streaming responses. Django 4.2+ supports passing async iterators to StreamingHttpResponse. On older versions, async generators must be eagerly consumed into a list. TODO: When dropping Django < 4.2 support: 1. Remove this module entirely. 2. In AsyncOperation._async_stream_response (ninja/operation.py), pass the async content generator directly to StreamingHttpResponse and copy temporal_response headers lazily inside the generator: async def content_iter(): async for chunk in content_gen: yield chunk for key, value in temporal_response.items(): if key.lower() != "content-type": response[key] = value for cookie_name, cookie in temporal_response.cookies.items(): response.cookies[cookie_name] = cookie response = StreamingHttpResponse( content_iter(), content_type=..., status=..., ) """ from typing import Any, Dict import django from django.http import HttpResponse, StreamingHttpResponse ASYNC_STREAMING = django.VERSION >= (4, 2) def _copy_temporal_headers( temporal_response: HttpResponse, response: StreamingHttpResponse ) -> None: """Copy headers and cookies from temporal response, skipping Content-Type.""" for key, value in temporal_response.items(): if key.lower() != "content-type": response[key] = value for cookie_name, cookie in temporal_response.cookies.items(): response.cookies[cookie_name] = cookie if ASYNC_STREAMING: async def create_streaming_response( content_gen: Any, *, content_type: str, status: int, temporal_response: HttpResponse, extra_headers: Dict[str, str], ) -> StreamingHttpResponse: """Create a StreamingHttpResponse from an async content generator. Django 4.2+: passes the async generator directly and copies temporal response headers/cookies lazily after the generator is exhausted. """ async def with_lazy_headers() -> Any: async for chunk in content_gen: yield chunk _copy_temporal_headers(temporal_response, response) response = StreamingHttpResponse( with_lazy_headers(), content_type=content_type, status=status, ) for key, value in extra_headers.items(): response[key] = value return response else: async def create_streaming_response( content_gen: Any, *, content_type: str, status: int, temporal_response: HttpResponse, extra_headers: Dict[str, str], ) -> StreamingHttpResponse: """Create a StreamingHttpResponse from an async content generator. Django < 4.2: eagerly consumes the async generator into a list since StreamingHttpResponse does not support async iterators. """ chunks = [] async for chunk in content_gen: chunks.append(chunk) response = StreamingHttpResponse( iter(chunks), content_type=content_type, status=status, ) _copy_temporal_headers(temporal_response, response) for key, value in extra_headers.items(): response[key] = value return response ================================================ FILE: ninja/compatibility/util.py ================================================ import sys from typing import Union __all__ = ["UNION_TYPES", "get_annotations_from_namespace"] # python3.10+ syntax of creating a union or optional type (with str | int) # UNION_TYPES allows to check both universes if types are a union try: from types import UnionType UNION_TYPES = (Union, UnionType) except ImportError: UNION_TYPES = (Union,) # python3.14+ no longer puts __annotations__ in the class namespace dict # during metaclass __new__; instead an __annotate__ function is used (PEP 749) if sys.version_info >= (3, 14): import annotationlib def get_annotations_from_namespace(namespace: dict) -> dict: ann = annotationlib.get_annotate_from_class_namespace(namespace) if ann is not None: return annotationlib.call_annotate_function( ann, format=annotationlib.Format.VALUE ) return namespace.get("__annotations__", {}) else: def get_annotations_from_namespace(namespace: dict) -> dict: return namespace.get("__annotations__", {}) ================================================ FILE: ninja/conf.py ================================================ from math import inf from typing import Dict, Optional, Set, Tuple from django.conf import settings as django_settings from pydantic import BaseModel, ConfigDict, Field class Settings(BaseModel): model_config = ConfigDict(from_attributes=True) # Pagination PAGINATION_CLASS: str = Field( "ninja.pagination.LimitOffsetPagination", alias="NINJA_PAGINATION_CLASS" ) PAGINATION_DEFAULT_ORDERING: Tuple[str, ...] = Field( ("-pk",), alias="NINJA_PAGINATION_DEFAULT_ORDERING" ) PAGINATION_MAX_OFFSET: int = Field(100, alias="NINJA_PAGINATION_MAX_OFFSET") PAGINATION_PER_PAGE: int = Field(100, alias="NINJA_PAGINATION_PER_PAGE") PAGINATION_MAX_PER_PAGE_SIZE: int = Field(100, alias="NINJA_MAX_PER_PAGE_SIZE") PAGINATION_MAX_LIMIT: int = Field(inf, alias="NINJA_PAGINATION_MAX_LIMIT") # type: ignore # Throttling NUM_PROXIES: Optional[int] = Field(None, alias="NINJA_NUM_PROXIES") DEFAULT_THROTTLE_RATES: Dict[str, Optional[str]] = Field( { "auth": "10000/day", "user": "10000/day", "anon": "1000/day", }, alias="NINJA_DEFAULT_THROTTLE_RATES", ) FIX_REQUEST_FILES_METHODS: Set[str] = Field( {"PUT", "PATCH", "DELETE"}, alias="NINJA_FIX_REQUEST_FILES_METHODS" ) settings = Settings.model_validate(django_settings) if hasattr(django_settings, "NINJA_DOCS_VIEW"): raise Exception( "NINJA_DOCS_VIEW is removed. Use NinjaAPI(docs=...) instead" ) # pragma: no cover ================================================ FILE: ninja/constants.py ================================================ from typing import Any, Dict, Optional __all__ = ["NOT_SET"] class NOT_SET_TYPE: def __repr__(self) -> str: # pragma: no cover return f"{__name__}.{self.__class__.__name__}" def __copy__(self) -> Any: return NOT_SET def __deepcopy__(self, memodict: Optional[Dict] = None) -> Any: return NOT_SET NOT_SET = NOT_SET_TYPE() ================================================ FILE: ninja/decorators.py ================================================ from functools import partial from typing import Any, Callable, Tuple from typing_extensions import Literal from ninja.operation import Operation from ninja.types import TCallable from ninja.utils import contribute_operation_callback # Type for decorator modes DecoratorMode = Literal["operation", "view"] # Since @api.method decorator is applied to function # that is not always returns a HttpResponse object # there is no way to apply some standard decorators form # django stdlib or public plugins # # @decorate_view allows to apply any view decorator to Ninja api operation # # @api.get("/some") # @decorate_view(cache_page(60 * 15)) # <------- # def some(request): # ... # def decorate_view(*decorators: Callable[..., Any]) -> Callable[[TCallable], TCallable]: def outer_wrapper(op_func: TCallable) -> TCallable: if hasattr(op_func, "_ninja_operation"): # Means user used decorate_view on top of @api.method _apply_decorators(decorators, op_func._ninja_operation) # type: ignore else: # Means user used decorate_view after(bottom) of @api.method contribute_operation_callback( op_func, partial(_apply_decorators, decorators) ) return op_func return outer_wrapper def _apply_decorators( decorators: Tuple[Callable[..., Any]], operation: Operation ) -> None: # Track decorators for cloning support if not hasattr(operation, "_run_decorators"): operation._run_decorators = [] # type: ignore for deco in decorators: operation.run = deco(operation.run) # type: ignore operation._run_decorators.append(deco) # type: ignore ================================================ FILE: ninja/errors.py ================================================ import logging import traceback from functools import partial from typing import TYPE_CHECKING, Generic, List, Optional, TypeVar import pydantic from django.conf import settings from django.http import Http404, HttpRequest, HttpResponse from ninja.types import DictStrAny if TYPE_CHECKING: from ninja import NinjaAPI # pragma: no cover from ninja.params.models import ParamModel # pragma: no cover __all__ = [ "ConfigError", "AuthenticationError", "AuthorizationError", "ValidationError", "HttpError", "set_default_exc_handlers", ] logger = logging.getLogger("django") class ConfigError(Exception): pass TModel = TypeVar("TModel", bound="ParamModel") class ValidationErrorContext(Generic[TModel]): """ The full context of a `pydantic.ValidationError`, including all information needed to produce a `ninja.errors.ValidationError`. """ def __init__( self, pydantic_validation_error: pydantic.ValidationError, model: TModel ): self.pydantic_validation_error = pydantic_validation_error self.model = model class ValidationError(Exception): """ This exception raised when operation params do not validate Note: this is not the same as pydantic.ValidationError the errors attribute as well holds the location of the error(body, form, query, etc.) """ def __init__(self, errors: List[DictStrAny]) -> None: self.errors = errors super().__init__(errors) class HttpError(Exception): def __init__(self, status_code: int, message: str) -> None: self.status_code = status_code self.message = message super().__init__(status_code, message) def __str__(self) -> str: return self.message class AuthenticationError(HttpError): def __init__(self, status_code: int = 401, message: str = "Unauthorized") -> None: super().__init__(status_code=status_code, message=message) class AuthorizationError(HttpError): def __init__(self, status_code: int = 403, message: str = "Forbidden") -> None: super().__init__(status_code=status_code, message=message) class Throttled(HttpError): def __init__(self, wait: Optional[int]) -> None: self.wait = wait super().__init__(status_code=429, message="Too many requests.") def set_default_exc_handlers(api: "NinjaAPI") -> None: api.add_exception_handler( Exception, partial(_default_exception, api=api), ) api.add_exception_handler( Http404, partial(_default_404, api=api), ) api.add_exception_handler( HttpError, partial(_default_http_error, api=api), ) api.add_exception_handler( ValidationError, partial(_default_validation_error, api=api), ) def _default_404(request: HttpRequest, exc: Exception, api: "NinjaAPI") -> HttpResponse: msg = "Not Found" if settings.DEBUG: msg += f": {exc}" return api.create_response(request, {"detail": msg}, status=404) def _default_http_error( request: HttpRequest, exc: HttpError, api: "NinjaAPI" ) -> HttpResponse: return api.create_response(request, {"detail": str(exc)}, status=exc.status_code) def _default_validation_error( request: HttpRequest, exc: ValidationError, api: "NinjaAPI" ) -> HttpResponse: return api.create_response(request, {"detail": exc.errors}, status=422) def _default_exception( request: HttpRequest, exc: Exception, api: "NinjaAPI" ) -> HttpResponse: if not settings.DEBUG: raise exc # let django deal with it logger.exception(exc) tb = traceback.format_exc() return HttpResponse(tb, status=500, content_type="text/plain") ================================================ FILE: ninja/files.py ================================================ from typing import Any, Callable, Dict from django.core.files.uploadedfile import UploadedFile as DjangoUploadedFile from pydantic_core import core_schema __all__ = ["UploadedFile"] class UploadedFile(DjangoUploadedFile): @classmethod def __get_pydantic_json_schema__( cls, core_schema: Any, handler: Callable[..., Any] ) -> Dict: # calling handler(core_schema) here raises an exception json_schema: Dict[str, str] = {} json_schema.update(type="string", format="binary") return json_schema @classmethod def _validate(cls, v: Any, _: Any) -> Any: if not isinstance(v, DjangoUploadedFile): raise ValueError(f"Expected UploadFile, received: {type(v)}") return v @classmethod def __get_pydantic_core_schema__( cls, source: Any, handler: Callable[..., Any] ) -> Any: return core_schema.with_info_plain_validator_function(cls._validate) ================================================ FILE: ninja/filter_schema.py ================================================ import warnings from typing import Any, List, Optional, TypeVar, Union, cast from django.core.exceptions import ImproperlyConfigured from django.db.models import Q, QuerySet from pydantic import ConfigDict from pydantic.fields import FieldInfo from typing_extensions import Literal from .constants import NOT_SET from .schema import Schema # XOR is available only in Django 4.1+: https://docs.djangoproject.com/en/4.1/ref/models/querysets/#xor ExpressionConnector = Literal["AND", "OR", "XOR"] DEFAULT_IGNORE_NONE: bool = True DEFAULT_CLASS_LEVEL_EXPRESSION_CONNECTOR: ExpressionConnector = "AND" DEFAULT_FIELD_LEVEL_EXPRESSION_CONNECTOR: ExpressionConnector = "OR" class FilterLookup: """ Annotation class for specifying database query lookups in FilterSchema fields. Example usage: class MyFilterSchema(FilterSchema): name: Annotated[Union[str, None], FilterLookup("name__icontains")] = None search: Annotated[Union[str, None], FilterLookup(["name__icontains", "email__icontains"])] = None """ def __init__( self, q: Union[str, List[str], None], *, expression_connector: ExpressionConnector = DEFAULT_FIELD_LEVEL_EXPRESSION_CONNECTOR, ignore_none: bool = DEFAULT_IGNORE_NONE, ): """ Args: q: Database lookup expression(s). Can be: - A string like "name__icontains" - A list of strings like ["name__icontains", "email__icontains"] - Use "__" prefix for implicit field name: "__icontains" becomes "fieldname__icontains" expression_connector: How to combine multiple field-level expressions ("OR", "AND", "XOR"). Default is "OR". ignore_none: Whether to ignore None values for this field specifically. Default is True. """ self.q = q self.expression_connector = expression_connector self.ignore_none = ignore_none T = TypeVar("T", bound=QuerySet) class FilterConfigDict(ConfigDict, total=False): ignore_none: bool expression_connector: ExpressionConnector class FilterSchema(Schema): model_config = FilterConfigDict( ignore_none=DEFAULT_IGNORE_NONE, expression_connector=DEFAULT_CLASS_LEVEL_EXPRESSION_CONNECTOR, ) def custom_expression(self) -> Q: """ Implement this method to return a combination of filters that will be used """ raise NotImplementedError def get_filter_expression(self) -> Q: """ Returns a Q expression based on the current filters """ try: return self.custom_expression() except NotImplementedError: return self._connect_fields() def filter(self, queryset: T) -> T: return queryset.filter(self.get_filter_expression()) def _get_filter_lookup( self, field_name: str, field_info: FieldInfo ) -> Optional[FilterLookup]: if not hasattr(field_info, "metadata") or not field_info.metadata: return None filter_lookups = [ metadata_item for metadata_item in field_info.metadata if isinstance(metadata_item, FilterLookup) ] if len(filter_lookups) == 0: return None elif len(filter_lookups) == 1: return filter_lookups[0] else: raise ImproperlyConfigured( f"Multiple FilterLookup instances found in metadata of {self.__class__.__name__}.{field_name}.\n" f"Use at most one FilterLookup instance per field.\n" f"If you need multiple lookups, specify them as a list in a single FilterLookup:\n" f"{field_name}: Annotated[{field_info.annotation}, FilterLookup(['lookup1', 'lookup2', ...])]" ) def _get_field_q_expression( self, field_name: str, field_info: FieldInfo, ) -> Union[str, List[str], None]: filter_lookup = self._get_filter_lookup(field_name, field_info) if filter_lookup: return filter_lookup.q # Legacy approach, consider removing in future versions return cast( Union[str, List[str], None], self._get_from_deprecated_field_extra(field_name, field_info, "q"), ) def _get_field_expression_connector( self, field_name: str, field_info: FieldInfo, ) -> Union[ExpressionConnector, None]: filter_lookup = self._get_filter_lookup(field_name, field_info) if filter_lookup: return filter_lookup.expression_connector # Legacy approach, consider removing in future versions return cast( Union[ExpressionConnector, None], self._get_from_deprecated_field_extra( field_name, field_info, "expression_connector" ), ) def _get_field_ignore_none( self, field_name: str, field_info: FieldInfo ) -> Union[bool, None]: filter_lookup = self._get_filter_lookup(field_name, field_info) if filter_lookup: return filter_lookup.ignore_none # Legacy approach, consider removing in future versions return cast( Union[bool, None], self._get_from_deprecated_field_extra( field_name, field_info, "ignore_none" ), ) def _resolve_field_expression( self, field_name: str, field_value: Any, field_info: FieldInfo ) -> Q: func = getattr(self, f"filter_{field_name}", None) if callable(func): return cast(Q, func(field_value)) q_expression = self._get_field_q_expression(field_name, field_info) expression_connector = ( self._get_field_expression_connector(field_name, field_info) or DEFAULT_FIELD_LEVEL_EXPRESSION_CONNECTOR ) if not q_expression: return Q(**{field_name: field_value}) elif isinstance(q_expression, str): if q_expression.startswith("__"): q_expression = f"{field_name}{q_expression}" return Q(**{q_expression: field_value}) elif isinstance(q_expression, list) and all( isinstance(item, str) for item in q_expression ): q = Q() for q_expression_part in q_expression: if q_expression_part.startswith("__"): q_expression_part = f"{field_name}{q_expression_part}" q = q._combine( # type: ignore[attr-defined] Q(**{q_expression_part: field_value}), expression_connector, ) return q else: raise ImproperlyConfigured( f"Field {field_name} of {self.__class__.__name__} defines an invalid value for 'q'.\n" f"Use FilterLookup annotation: {field_name}: Annotated[{field_info.annotation}, FilterLookup('lookup')]\n" f"Alternatively, you can implement {self.__class__.__name__}.filter_{field_name} that must return a Q expression for that field" ) def _connect_fields(self) -> Q: q = Q() class_ignore_none = self.model_config.get("ignore_none", DEFAULT_IGNORE_NONE) for field_name, field_info in self.__class__.model_fields.items(): filter_value = getattr(self, field_name) # class-level ignore_none set to False (non-default) takes precedence over field-level ignore_none if class_ignore_none is False: ignore_none = False else: field_ignore_none = self._get_field_ignore_none(field_name, field_info) if field_ignore_none is not None: ignore_none = field_ignore_none else: ignore_none = DEFAULT_IGNORE_NONE # Resolve Q expression for a field even if we skip it due to None value # So that improperly configured fields are easier to detect field_q = self._resolve_field_expression( field_name, filter_value, field_info ) if filter_value is None and ignore_none: continue q = q._combine( # type: ignore[attr-defined] field_q, self.model_config.get( "expression_connector", DEFAULT_CLASS_LEVEL_EXPRESSION_CONNECTOR ), ) return q def _get_from_deprecated_field_extra( self, field_name: str, field_info: FieldInfo, attr: str ) -> Union[Any, None]: """ Backward-compatible shim which looks up filtering parameters in the Field's **extra kwargs. Consider removing this method in favor of FilterLookup annotation class. """ field_extra = cast(dict, field_info.json_schema_extra) or {} value = field_extra.get(attr, NOT_SET) if value is not NOT_SET: warnings.warn( f"Using Pydantic Field with extra keyword arguments ('{attr}') " f"in field {self.__class__.__name__}.{field_name} is deprecated. Please use ninja.FilterLookup instead:\n" f" from typing import Annotated\n" f" from ninja import FilterLookup, FilterSchema\n\n" f" class {self.__class__.__name__}(FilterSchema):\n" f" {field_name}: Annotated[Optional[...], FilterLookup(q='...', ...)] = None", DeprecationWarning, stacklevel=4, ) return value return None ================================================ FILE: ninja/main.py ================================================ from typing import ( TYPE_CHECKING, Any, Callable, Dict, List, Optional, Sequence, Tuple, Type, TypeVar, Union, ) from django.http import HttpRequest, HttpResponse from django.urls import URLPattern, URLResolver, reverse from django.utils.module_loading import import_string from ninja.constants import NOT_SET, NOT_SET_TYPE from ninja.decorators import DecoratorMode from ninja.errors import ( ConfigError, ValidationError, ValidationErrorContext, set_default_exc_handlers, ) from ninja.openapi import get_schema from ninja.openapi.docs import DocsBase, Swagger from ninja.openapi.schema import OpenAPISchema from ninja.openapi.urls import get_openapi_urls, get_root_url from ninja.parser import Parser from ninja.renderers import BaseRenderer, JSONRenderer from ninja.router import BoundRouter, Router, RouterMount from ninja.throttling import BaseThrottle from ninja.types import DictStrAny, TCallable if TYPE_CHECKING: from .operation import Operation # pragma: no cover __all__ = ["NinjaAPI"] _E = TypeVar("_E", bound=Exception) Exc = Union[_E, Type[_E]] ExcHandler = Callable[[HttpRequest, Exc[_E]], HttpResponse] class NinjaAPI: """ Ninja API """ def __init__( self, *, title: str = "NinjaAPI", version: str = "1.0.0", description: str = "", openapi_url: Optional[str] = "/openapi.json", docs: DocsBase = Swagger(), docs_url: Optional[str] = "/docs", docs_decorator: Optional[Callable[[TCallable], TCallable]] = None, servers: Optional[List[DictStrAny]] = None, urls_namespace: Optional[str] = None, auth: Optional[Union[Sequence[Callable], Callable, NOT_SET_TYPE]] = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, renderer: Optional[BaseRenderer] = None, parser: Optional[Parser] = None, default_router: Optional[Router] = None, openapi_extra: Optional[Dict[str, Any]] = None, ): """ Args: title: A title for the api. description: A description for the api. version: The API version. urls_namespace: The Django URL namespace for the API. If not provided, the namespace will be ``"api-" + self.version``. openapi_url: The relative URL to serve the openAPI spec. openapi_extra: Additional attributes for the openAPI spec. docs_url: The relative URL to serve the API docs. servers: List of target hosts used in openAPI spec. auth (Callable | Sequence[Callable] | NOT_SET | None): Authentication class renderer: Default response renderer parser: Default request parser """ self.title = title self.version = version self.description = description self.openapi_url = openapi_url self.docs = docs self.docs_url = docs_url self.docs_decorator = docs_decorator self.servers = servers or [] self.urls_namespace = urls_namespace or f"api-{self.version}" self.renderer = renderer or JSONRenderer() self.parser = parser or Parser() self.openapi_extra = openapi_extra or {} self._exception_handlers: Dict[Exc, ExcHandler] = {} self.set_default_exception_handlers() self.auth: Optional[Union[Sequence[Callable], NOT_SET_TYPE]] if callable(auth): self.auth = [auth] else: self.auth = auth self.throttle = throttle # Top-level router registrations (new architecture) # Stores (prefix, router, auth, throttle, tags, url_name_prefix) for each add_router call self._router_registrations: List[ Tuple[str, Router, Any, Any, Optional[List[str]], Optional[str]] ] = [] self._bound_routers_cache: Optional[List[BoundRouter]] = None # Backward compat: keep _routers list populated self._routers: List[Tuple[str, Router]] = [] self.default_router = default_router or Router() self.add_router("", self.default_router) def get( self, path: str, *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, ) -> Callable[[TCallable], TCallable]: """ `GET` operation. See operations parameters reference. """ return self.default_router.get( path, auth=auth is NOT_SET and self.auth or auth, throttle=throttle is NOT_SET and self.throttle or throttle, response=response, operation_id=operation_id, summary=summary, description=description, tags=tags, deprecated=deprecated, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, url_name=url_name, include_in_schema=include_in_schema, openapi_extra=openapi_extra, ) def post( self, path: str, *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, ) -> Callable[[TCallable], TCallable]: """ `POST` operation. See operations parameters reference. """ return self.default_router.post( path, auth=auth is NOT_SET and self.auth or auth, throttle=throttle is NOT_SET and self.throttle or throttle, response=response, operation_id=operation_id, summary=summary, description=description, tags=tags, deprecated=deprecated, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, url_name=url_name, include_in_schema=include_in_schema, openapi_extra=openapi_extra, ) def delete( self, path: str, *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, ) -> Callable[[TCallable], TCallable]: """ `DELETE` operation. See operations parameters reference. """ return self.default_router.delete( path, auth=auth is NOT_SET and self.auth or auth, throttle=throttle is NOT_SET and self.throttle or throttle, response=response, operation_id=operation_id, summary=summary, description=description, tags=tags, deprecated=deprecated, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, url_name=url_name, include_in_schema=include_in_schema, openapi_extra=openapi_extra, ) def patch( self, path: str, *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, ) -> Callable[[TCallable], TCallable]: """ `PATCH` operation. See operations parameters reference. """ return self.default_router.patch( path, auth=auth is NOT_SET and self.auth or auth, throttle=throttle is NOT_SET and self.throttle or throttle, response=response, operation_id=operation_id, summary=summary, description=description, tags=tags, deprecated=deprecated, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, url_name=url_name, include_in_schema=include_in_schema, openapi_extra=openapi_extra, ) def put( self, path: str, *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, ) -> Callable[[TCallable], TCallable]: """ `PUT` operation. See operations parameters reference. """ return self.default_router.put( path, auth=auth is NOT_SET and self.auth or auth, throttle=throttle is NOT_SET and self.throttle or throttle, response=response, operation_id=operation_id, summary=summary, description=description, tags=tags, deprecated=deprecated, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, url_name=url_name, include_in_schema=include_in_schema, openapi_extra=openapi_extra, ) def api_operation( self, methods: List[str], path: str, *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, ) -> Callable[[TCallable], TCallable]: return self.default_router.api_operation( methods, path, auth=auth is NOT_SET and self.auth or auth, throttle=throttle is NOT_SET and self.throttle or throttle, response=response, operation_id=operation_id, summary=summary, description=description, tags=tags, deprecated=deprecated, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, url_name=url_name, include_in_schema=include_in_schema, openapi_extra=openapi_extra, ) def add_decorator( self, decorator: Callable, mode: DecoratorMode = "operation", ) -> None: """ Add a decorator to be applied to all operations in the entire API. Args: decorator: The decorator function to apply mode: "operation" (default) applies after validation, "view" applies before validation """ # Store decorator on default router - will be inherited by all routers during build self.default_router.add_decorator(decorator, mode) def add_router( self, prefix: str, router: Union[Router, str], *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, tags: Optional[List[str]] = None, url_name_prefix: Optional[str] = None, parent_router: Optional[Router] = None, ) -> None: """ Add a router to this API. Args: prefix: URL prefix for all routes in the router router: Router instance or import path string auth: Authentication override for this router throttle: Throttle override for this router tags: Tags override for this router url_name_prefix: Prefix for URL names (required when mounting same router multiple times) parent_router: Internal use - parent router for nested routers """ # Prevent adding routers after URLs have been generated if self._bound_routers_cache is not None: raise ConfigError( "Cannot add routers after URLs have been generated. " "Add all routers before accessing api.urls" ) if isinstance(router, str): router = import_string(router) assert isinstance(router, Router) # Check for duplicate router template - require url_name_prefix existing_templates = {reg[1] for reg in self._router_registrations} if router in existing_templates and url_name_prefix is None: raise ConfigError( "Router is already mounted to this API. When mounting the same router " "multiple times, you must provide unique url_name_prefix for each mount." ) # Store registration for later processing during URL generation # This allows child routers to be added after add_router() is called self._router_registrations.append(( prefix, router, auth, throttle, tags, url_name_prefix, )) # Backward compat: keep _routers list updated (just the top-level router) self._routers.append((prefix, router)) @property def urls(self) -> Tuple[List[Union[URLResolver, URLPattern]], str, str]: """ str: URL configuration Returns: Django URL configuration """ self._validate() return ( self._get_urls(), "ninja", self.urls_namespace.split(":")[-1], # ^ if api included into nested urls, we only care about last bit here ) def _get_bound_routers(self) -> List[BoundRouter]: """Get or create bound router instances.""" if self._bound_routers_cache is None: # Build mounts from registrations (delayed to capture all child routers) all_mounts: List[RouterMount] = [] for ( prefix, router, auth, throttle, tags, url_name_prefix, ) in self._router_registrations: # Get API-level decorators from default router api_decorators = ( self.default_router._decorators if router is not self.default_router else [] ) # Build mount configurations (non-mutating) # Pass auth/throttle/tags so they can be inherited by children mounts = router.build_routers( prefix, api_decorators, inherited_auth=auth, inherited_throttle=throttle, inherited_tags=tags, ) # Apply mount-level overrides to the first (parent) mount # build_routers() always returns at least one mount (the router itself) first_mount = mounts[0] if auth is not NOT_SET: first_mount.auth = auth if throttle is not NOT_SET: first_mount.throttle = throttle if tags is not None: first_mount.tags = tags # Apply url_name_prefix to all mounts if url_name_prefix is not None: for mount in mounts: mount.url_name_prefix = url_name_prefix all_mounts.extend(mounts) # Create bound routers from mounts self._bound_routers_cache = [ BoundRouter(mount, self) for mount in all_mounts ] # Freeze all templates after binding for mount in all_mounts: mount.template._freeze() # Update _routers for backward compat (include all nested routers) self._routers = [(m.prefix, m.template) for m in all_mounts] return self._bound_routers_cache def _get_urls(self) -> List[Union[URLResolver, URLPattern]]: result = get_openapi_urls(self) for bound_router in self._get_bound_routers(): result.extend(bound_router.urls_paths(bound_router.prefix)) result.append(get_root_url(self)) return result def get_root_path(self, path_params: DictStrAny) -> str: name = f"{self.urls_namespace}:api-root" return reverse(name, kwargs=path_params) def create_response( self, request: HttpRequest, data: Any, *, status: Optional[int] = None, temporal_response: Optional[HttpResponse] = None, ) -> HttpResponse: if temporal_response: status = temporal_response.status_code assert status content = self.renderer.render(request, data, response_status=status) if temporal_response: response = temporal_response response.content = content else: response = HttpResponse( content, status=status, content_type=self.get_content_type() ) return response def create_temporal_response(self, request: HttpRequest) -> HttpResponse: return HttpResponse("", content_type=self.get_content_type()) def get_content_type(self) -> str: return f"{self.renderer.media_type}; charset={self.renderer.charset}" def get_openapi_schema( self, *, path_prefix: Optional[str] = None, path_params: Optional[DictStrAny] = None, ) -> OpenAPISchema: if path_prefix is None: path_prefix = self.get_root_path(path_params or {}) return get_schema(api=self, path_prefix=path_prefix) def get_openapi_operation_id(self, operation: "Operation") -> str: name = operation.view_func.__name__ module = operation.view_func.__module__ return (module + "_" + name).replace(".", "_") def get_operation_url_name(self, operation: "Operation", router: Router) -> str: """ Get the default URL name to use for an operation if it wasn't explicitly provided. """ return operation.view_func.__name__ def add_exception_handler( self, exc_class: Type[_E], handler: ExcHandler[_E] ) -> None: assert issubclass(exc_class, Exception) self._exception_handlers[exc_class] = handler def exception_handler( self, exc_class: Type[Exception] ) -> Callable[[TCallable], TCallable]: def decorator(func: TCallable) -> TCallable: self.add_exception_handler(exc_class, func) return func return decorator def set_default_exception_handlers(self) -> None: set_default_exc_handlers(self) def on_exception(self, request: HttpRequest, exc: Exc[_E]) -> HttpResponse: handler = self._lookup_exception_handler(exc) if handler is None: raise exc return handler(request, exc) def validation_error_from_error_contexts( self, error_contexts: List[ValidationErrorContext] ) -> ValidationError: errors: List[Dict[str, Any]] = [] for context in error_contexts: model = context.model e = context.pydantic_validation_error for i in e.errors(include_url=False): i["loc"] = ( model.__ninja_param_source__, ) + model.__ninja_flatten_map_reverse__.get(i["loc"], i["loc"]) # removing pydantic hints del i["input"] # type: ignore if ( "ctx" in i and "error" in i["ctx"] and isinstance(i["ctx"]["error"], Exception) ): i["ctx"]["error"] = str(i["ctx"]["error"]) errors.append(dict(i)) return ValidationError(errors) def _lookup_exception_handler(self, exc: Exc[_E]) -> Optional[ExcHandler[_E]]: for cls in type(exc).__mro__: if cls in self._exception_handlers: return self._exception_handlers[cls] return None def _validate(self) -> None: # Registry check no longer needed - routers are independent templates # and can be reused across multiple APIs without conflicts pass ================================================ FILE: ninja/management/__init__.py ================================================ ================================================ FILE: ninja/management/commands/__init__.py ================================================ ================================================ FILE: ninja/management/commands/export_openapi_schema.py ================================================ import json from pathlib import Path from typing import Any, Optional from django.core.management.base import BaseCommand, CommandError, CommandParser from django.urls.base import resolve from django.utils.module_loading import import_string from ninja.main import NinjaAPI from ninja.management.utils import command_docstring from ninja.responses import NinjaJSONEncoder class Command(BaseCommand): """ Example: ```terminal python manage.py export_openapi_schema ``` ```terminal python manage.py export_openapi_schema --api project.urls.api ``` """ help = "Exports Open API schema" def _get_api_instance(self, api_path: Optional[str] = None) -> NinjaAPI: if not api_path: try: return resolve("/api/").func.keywords["api"] # type: ignore except AttributeError: raise CommandError( "No NinjaAPI instance found; please specify one with --api" ) from None try: api = import_string(api_path) except ImportError: raise CommandError( f"Module or attribute for {api_path} not found!" ) from None if not isinstance(api, NinjaAPI): raise CommandError(f"{api_path} is not instance of NinjaAPI!") return api def add_arguments(self, parser: CommandParser) -> None: parser.add_argument( "--api", dest="api", default=None, type=str, help="Specify api instance module", ) parser.add_argument( "--output", dest="output", default=None, type=str, help="Output schema to a file (outputs to stdout if omitted).", ) parser.add_argument( "--indent", dest="indent", default=None, type=int, help="JSON indent" ) parser.add_argument( "--sorted", dest="sort_keys", default=False, action="store_true", help="Sort Json keys", ) parser.add_argument( "--ensure-ascii", dest="ensure_ascii", default=False, action="store_true", help="ensure_ascii for JSON output", ) def handle(self, *args: Any, **options: Any) -> None: api = self._get_api_instance(options["api"]) schema = api.get_openapi_schema() result = json.dumps( schema, cls=NinjaJSONEncoder, indent=options["indent"], sort_keys=options["sort_keys"], ensure_ascii=options["ensure_ascii"], ) if options["output"]: with Path(options["output"]).open("wb") as f: f.write(result.encode()) else: self.stdout.write(result) __doc__ = command_docstring(Command) ================================================ FILE: ninja/management/utils.py ================================================ import textwrap from typing import Type from django.core.management.base import BaseCommand def command_docstring(cmd: Type[BaseCommand]) -> str: base_args = [] if cmd is not BaseCommand: # pragma: no branch base_parser = cmd().create_parser("base", "") for group in base_parser._action_groups: for action in group._group_actions: base_args.append(",".join(action.option_strings)) parser = cmd().create_parser("command", "") doc = parser.description or "" if cmd.__doc__: # pragma: no branch if doc: # pragma: no branch doc += "\n\n" doc += textwrap.dedent(cmd.__doc__) args = [] for group in parser._action_groups: for action in group._group_actions: if "--help" in action.option_strings: continue name = ",".join(action.option_strings) action_type = action.type if not action_type and action.nargs != 0: action_type = str if action_type: if isinstance(action_type, type): # pragma: no branch action_type = action_type.__name__ name += f" ({action_type})" help = action.help or "" if help and not action.required and action.nargs != 0: if not help.endswith("."): help += "." if action.default is not None: help += f" Defaults to {action.default}." else: help += " Optional." args.append((name, help)) # Sort args from this class first, then base args. args.sort(key=lambda o: (o[0] in base_args, o[0])) if args: # pragma: no branch doc += "\n\nAttributes:" for name, description in args: doc += f"\n {name}: {description}" return doc ================================================ FILE: ninja/openapi/__init__.py ================================================ from ninja.openapi.schema import get_schema __all__ = ["get_schema"] ================================================ FILE: ninja/openapi/docs.py ================================================ import json from abc import ABC, abstractmethod from pathlib import Path from typing import TYPE_CHECKING, Any, Optional from django.conf import settings from django.http import HttpRequest, HttpResponse from django.shortcuts import render from django.urls import reverse from ninja.constants import NOT_SET from ninja.types import DictStrAny if TYPE_CHECKING: # if anyone knows a cleaner way to make mypy happy - welcome from ninja import NinjaAPI # pragma: no cover ABS_TPL_PATH = Path(__file__).parent.parent / "templates/ninja/" class DocsBase(ABC): @abstractmethod def render_page( self, request: HttpRequest, api: "NinjaAPI", **kwargs: Any ) -> HttpResponse: pass # pragma: no cover def get_openapi_url(self, api: "NinjaAPI", path_params: DictStrAny) -> str: return reverse(f"{api.urls_namespace}:openapi-json", kwargs=path_params) class Swagger(DocsBase): template = "ninja/swagger.html" template_cdn = str(ABS_TPL_PATH / "swagger_cdn.html") default_settings = { "layout": "BaseLayout", "deepLinking": True, } def __init__(self, settings: Optional[DictStrAny] = None): self.settings = {} self.settings.update(self.default_settings) if settings: self.settings.update(settings) def render_page( self, request: HttpRequest, api: "NinjaAPI", **kwargs: Any ) -> HttpResponse: self.settings["url"] = self.get_openapi_url(api, kwargs) context = { "swagger_settings": json.dumps(self.settings, indent=1), "api": api, "add_csrf": _csrf_needed(api), } return render_template(request, self.template, self.template_cdn, context) class Redoc(DocsBase): template = "ninja/redoc.html" template_cdn = str(ABS_TPL_PATH / "redoc_cdn.html") default_settings: DictStrAny = {} def __init__(self, settings: Optional[DictStrAny] = None): self.settings = {} self.settings.update(self.default_settings) if settings: self.settings.update(settings) def render_page( self, request: HttpRequest, api: "NinjaAPI", **kwargs: Any ) -> HttpResponse: context = { "redoc_settings": json.dumps(self.settings, indent=1), "openapi_json_url": self.get_openapi_url(api, kwargs), "api": api, } return render_template(request, self.template, self.template_cdn, context) def render_template( request: HttpRequest, template: str, template_cdn: str, context: DictStrAny ) -> HttpResponse: """ I do not really want ninja to be required in INSTALLED_APPS to ease installation so it automatically detects - if ninja is in INSTALLED_APPS - then we render with django.shortcuts.render otherwise - rendering custom html with swagger js from cdn """ if "ninja" in settings.INSTALLED_APPS: return render(request, template, context) else: return _render_cdn_template(request, template_cdn, context) def _render_cdn_template( request: HttpRequest, template_path: str, context: Optional[DictStrAny] = None ) -> HttpResponse: "this is helper to find and render html template when ninja is not in INSTALLED_APPS" from django.template import RequestContext, Template tpl = Template(Path(template_path).read_text()) html = tpl.render(RequestContext(request, context)) return HttpResponse(html) def _csrf_needed(api: "NinjaAPI") -> bool: """ Check if any of the API's auth handlers require CSRF protection. """ if not api.auth or api.auth == NOT_SET: return False return any(getattr(a, "csrf", False) for a in api.auth) # type: ignore ================================================ FILE: ninja/openapi/schema.py ================================================ import itertools import re from http.client import responses from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Set, Tuple from django.utils.termcolors import make_style from pydantic.json_schema import JsonSchemaMode from ninja.constants import NOT_SET from ninja.operation import Operation from ninja.params.models import TModel, TModels from ninja.schema import NinjaGenerateJsonSchema from ninja.types import DictStrAny from ninja.utils import normalize_path if TYPE_CHECKING: from ninja import NinjaAPI # pragma: no cover REF_TEMPLATE: str = "#/components/schemas/{model}" BODY_CONTENT_TYPES: Dict[str, str] = { "body": "application/json", "form": "application/x-www-form-urlencoded", "file": "multipart/form-data", } def get_schema(api: "NinjaAPI", path_prefix: str = "") -> "OpenAPISchema": openapi = OpenAPISchema(api, path_prefix) return openapi bold_red_style = make_style(opts=("bold",), fg="red") class OpenAPISchema(dict): def __init__(self, api: "NinjaAPI", path_prefix: str) -> None: self.api = api self.path_prefix = path_prefix self.schemas: DictStrAny = {} self.securitySchemes: DictStrAny = {} self.all_operation_ids: Set = set() extra_info = api.openapi_extra.get("info", {}) super().__init__([ ("openapi", "3.1.0"), ( "info", { "title": api.title, "version": api.version, "description": api.description, **extra_info, }, ), ("paths", self.get_paths()), ("components", self.get_components()), ("servers", api.servers), ]) for k, v in api.openapi_extra.items(): if k not in self: self[k] = v def get_paths(self) -> DictStrAny: result: DictStrAny = {} # Use bound routers to ensure operations have correct auth/throttle/tags for bound_router in self.api._get_bound_routers(): for path, path_view in bound_router.path_operations.items(): full_path = "/".join([i for i in (bound_router.prefix, path) if i]) full_path = "/" + self.path_prefix + full_path full_path = normalize_path(full_path) full_path = re.sub( r"{[^}:]+:", "{", full_path ) # remove path converters path_methods = self.methods(path_view.operations) if path_methods: try: result[full_path].update(path_methods) except KeyError: result[full_path] = path_methods return result def methods(self, operations: list) -> DictStrAny: result = {} for op in operations: if op.include_in_schema: operation_details = self.operation_details(op) for method in op.methods: result[method.lower()] = operation_details return result def deep_dict_update( self, main_dict: Dict[Any, Any], update_dict: Dict[Any, Any] ) -> None: for key in update_dict: if ( key in main_dict and isinstance(main_dict[key], dict) and isinstance(update_dict[key], dict) ): self.deep_dict_update( main_dict[key], update_dict[key] ) # pragma: no cover elif ( key in main_dict and isinstance(main_dict[key], list) and isinstance(update_dict[key], list) ): main_dict[key].extend(update_dict[key]) else: main_dict[key] = update_dict[key] def operation_details(self, operation: Operation) -> DictStrAny: op_id = operation.operation_id or self.api.get_openapi_operation_id(operation) if op_id in self.all_operation_ids: print( bold_red_style( f'Warning: operation_id "{op_id}" is already used (Try giving a different name to: {operation.view_func.__module__}.{operation.view_func.__name__})' ) ) self.all_operation_ids.add(op_id) result = { "operationId": op_id, "summary": operation.summary, "parameters": self.operation_parameters(operation), "responses": self.responses(operation), } if operation.description: result["description"] = operation.description if operation.tags: result["tags"] = operation.tags if operation.deprecated: result["deprecated"] = operation.deprecated # type: ignore body = self.request_body(operation) if body: result["requestBody"] = body security = self.operation_security(operation) if security: result["security"] = security if operation.openapi_extra: self.deep_dict_update(result, operation.openapi_extra) return result def operation_parameters(self, operation: Operation) -> List[DictStrAny]: result = [] for model in operation.models: if model.__ninja_param_source__ not in BODY_CONTENT_TYPES: result.extend(self._extract_parameters(model)) return result def _extract_parameters(self, model: TModel) -> List[DictStrAny]: result = [] schema = model.model_json_schema( ref_template=REF_TEMPLATE, schema_generator=NinjaGenerateJsonSchema, ) required = set(schema.get("required", [])) properties = schema["properties"] if "$defs" in schema: self.add_schema_definitions(schema["$defs"]) for name, details in properties.items(): is_required = name in required p_name: str p_schema: DictStrAny p_required: bool for p_name, p_schema, p_required in flatten_properties( name, details, is_required, schema.get("$defs", {}) ): if not p_schema.get("include_in_schema", True): continue param = { "in": model.__ninja_param_source__, "name": p_name, "schema": p_schema, "required": p_required, } # copy description from schema description to param description if "description" in p_schema: param["description"] = p_schema["description"] if "examples" in p_schema: param["examples"] = p_schema["examples"] elif "example" in p_schema: param["example"] = p_schema["example"] if "deprecated" in p_schema: param["deprecated"] = p_schema["deprecated"] result.append(param) return result def _flatten_schema(self, model: TModel) -> DictStrAny: params = self._extract_parameters(model) flattened = { "title": model.__name__, # type: ignore "type": "object", "properties": {p["name"]: p["schema"] for p in params}, } required = [p["name"] for p in params if p["required"]] if required: flattened["required"] = required return flattened def _create_schema_from_model( self, model: TModel, by_alias: bool = True, remove_level: bool = True, mode: JsonSchemaMode = "validation", ) -> Tuple[DictStrAny, bool]: if hasattr(model, "__ninja_flatten_map__"): schema = self._flatten_schema(model) else: schema = model.model_json_schema( ref_template=REF_TEMPLATE, by_alias=by_alias, schema_generator=NinjaGenerateJsonSchema, mode=mode, ).copy() # move Schemas from definitions if schema.get("$defs"): self.add_schema_definitions(schema.pop("$defs")) if remove_level and len(schema["properties"]) == 1: name, details = list(schema["properties"].items())[0] # ref = details["$ref"] required = name in schema.get("required", {}) return details, required else: return schema, True def _create_multipart_schema_from_models( self, models: TModels, mode: JsonSchemaMode = "validation", ) -> Tuple[DictStrAny, str]: # We have File and Form or Body, so we need to use multipart (File) content_type = BODY_CONTENT_TYPES["file"] # get the various schemas result = merge_schemas([ self._create_schema_from_model(model, remove_level=False)[0] for model in models ]) result["title"] = "MultiPartBodyParams" return result, content_type def request_body(self, operation: Operation) -> DictStrAny: models = [ m for m in operation.models if m.__ninja_param_source__ in BODY_CONTENT_TYPES ] if not models: return {} if len(models) == 1: model = models[0] content_type = BODY_CONTENT_TYPES[model.__ninja_param_source__] schema, required = self._create_schema_from_model( model, remove_level=model.__ninja_param_source__ == "body", mode="validation", ) else: schema, content_type = self._create_multipart_schema_from_models( models, mode="validation" ) required = True return { "content": {content_type: {"schema": schema}}, "required": required, } def responses(self, operation: Operation) -> Dict[int, DictStrAny]: assert bool(operation.response_models), f"{operation.response_models} empty" result = {} for status, model in operation.response_models.items(): if status == Ellipsis: continue # it's not yet clear what it means if user wants to output any other code description = responses.get(status, "Unknown Status Code") details: Dict[int, Any] = {status: {"description": description}} if model not in [None, NOT_SET]: # ::TODO:: test this: by_alias == True schema = self._create_schema_from_model( model, by_alias=operation.by_alias, mode="serialization" )[0] if operation.stream_format is not None: details[status]["content"] = ( operation.stream_format.openapi_content_schema(schema) ) else: details[status]["content"] = { self.api.renderer.media_type: {"schema": schema} } result.update(details) return result def operation_security(self, operation: Operation) -> Optional[List[DictStrAny]]: if not operation.auth_callbacks: return None result = [] for auth in operation.auth_callbacks: if hasattr(auth, "openapi_security_schema"): scopes: List[DictStrAny] = [] # TODO: scopes name = auth.__class__.__name__ result.append({name: scopes}) # TODO: check if unique self.securitySchemes[name] = auth.openapi_security_schema return result def get_components(self) -> DictStrAny: result = {"schemas": self.schemas} if self.securitySchemes: result["securitySchemes"] = self.securitySchemes return result def add_schema_definitions(self, definitions: dict) -> None: # TODO: check if schema["definitions"] are unique # if not - workaround (maybe use pydantic.schema.schema(models)) to process list of models # assert set(definitions.keys()) - set(self.schemas.keys()) == set() # ::TODO:: this is broken in interesting ways for by_alias, # because same schema (name) can have different values self.schemas.update(definitions) def flatten_properties( prop_name: str, prop_details: DictStrAny, prop_required: bool, definitions: DictStrAny, ) -> Generator[Tuple[str, DictStrAny, bool], None, None]: """ extracts all nested model's properties into flat properties (used f.e. in GET params with multiple arguments and models) """ if "allOf" in prop_details: resolve_allOf(prop_details, definitions) if len(prop_details["allOf"]) == 1 and "enum" in prop_details["allOf"][0]: # is_required = "default" not in prop_details yield prop_name, prop_details, prop_required else: # pragma: no cover # TODO: this code was for pydanitc 1.7+ ... <2.9 - check if this is still needed for item in prop_details["allOf"]: yield from flatten_properties("", item, True, definitions) elif "items" in prop_details and "$ref" in prop_details["items"]: def_name = prop_details["items"]["$ref"].rsplit("/", 1)[-1] prop_details["items"].update(definitions[def_name]) del prop_details["items"]["$ref"] # seems num data is there so ref not needed yield prop_name, prop_details, prop_required elif "$ref" in prop_details: def_name = prop_details["$ref"].split("/")[-1] definition = definitions[def_name] yield from flatten_properties(prop_name, definition, prop_required, definitions) elif "properties" in prop_details: required = set(prop_details.get("required", [])) for k, v in prop_details["properties"].items(): is_required = k in required yield from flatten_properties(k, v, is_required, definitions) else: yield prop_name, prop_details, prop_required def resolve_allOf(details: DictStrAny, definitions: DictStrAny) -> None: """ resolves all $ref's in 'allOf' section """ for item in details["allOf"]: if "$ref" in item: def_name = item["$ref"].rsplit("/", 1)[-1] item.update(definitions[def_name]) del item["$ref"] def merge_schemas(schemas: List[DictStrAny]) -> DictStrAny: result = schemas[0] for scm in schemas[1:]: result["properties"].update(scm["properties"]) required_list = result.get("required", []) required_list.extend( itertools.chain.from_iterable( schema.get("required", ()) for schema in schemas[1:] ) ) if required_list: result["required"] = required_list return result ================================================ FILE: ninja/openapi/urls.py ================================================ from functools import partial from typing import TYPE_CHECKING, Any, List from django.urls import path from .views import default_home, openapi_json, openapi_view if TYPE_CHECKING: from ninja import NinjaAPI # pragma: no cover __all__ = ["get_openapi_urls", "get_root_url"] def get_openapi_urls(api: "NinjaAPI") -> List[Any]: result = [] if api.openapi_url: view = partial(openapi_json, api=api) if api.docs_decorator: view = api.docs_decorator(view) # type: ignore result.append( path(api.openapi_url.lstrip("/"), view, name="openapi-json"), ) assert ( api.openapi_url != api.docs_url ), "Please use different urls for openapi_url and docs_url" if api.docs_url: view = partial(openapi_view, api=api) if api.docs_decorator: view = api.docs_decorator(view) # type: ignore result.append( path(api.docs_url.lstrip("/"), view, name="openapi-view"), ) return result def get_root_url(api: "NinjaAPI") -> Any: return path("", partial(default_home, api=api), name="api-root") ================================================ FILE: ninja/openapi/views.py ================================================ from typing import TYPE_CHECKING, Any, NoReturn from django.http import Http404, HttpRequest, HttpResponse from ninja.openapi.docs import DocsBase from ninja.responses import Response if TYPE_CHECKING: # if anyone knows a cleaner way to make mypy happy - welcome from ninja import NinjaAPI # pragma: no cover def default_home(request: HttpRequest, api: "NinjaAPI", **kwargs: Any) -> NoReturn: "This view is mainly needed to determine the full path for API operations" docs_url = f"{request.path}{api.docs_url}".replace("//", "/") raise Http404(f"docs_url = {docs_url}") def openapi_json(request: HttpRequest, api: "NinjaAPI", **kwargs: Any) -> HttpResponse: schema = api.get_openapi_schema(path_params=kwargs) return Response(schema) def openapi_view(request: HttpRequest, api: "NinjaAPI", **kwargs: Any) -> HttpResponse: docs: DocsBase = api.docs return docs.render_page(request, api, **kwargs) ================================================ FILE: ninja/operation.py ================================================ import inspect import warnings from typing import ( TYPE_CHECKING, Any, Callable, Coroutine, Dict, Iterable, List, Optional, Sequence, Type, Union, cast, ) import pydantic from asgiref.sync import async_to_sync, sync_to_async from django.http import ( HttpRequest, HttpResponse, HttpResponseNotAllowed, StreamingHttpResponse, ) from django.http.response import HttpResponseBase from pydantic import BaseModel from ninja.compatibility.files import FIX_MIDDLEWARE_PATH, need_to_fix_request_files from ninja.compatibility.streaming import create_streaming_response from ninja.constants import NOT_SET, NOT_SET_TYPE from ninja.errors import ( AuthenticationError, ConfigError, Throttled, ValidationErrorContext, ) from ninja.params.models import TModels from ninja.responses import Status from ninja.schema import Schema, pydantic_version from ninja.signature import ViewSignature, is_async from ninja.streaming import StreamFormat, _serialize_item, _StreamAlias from ninja.throttling import BaseThrottle from ninja.types import DictStrAny from ninja.utils import is_async_callable if TYPE_CHECKING: from ninja import NinjaAPI # pragma: no cover __all__ = ["Operation", "PathView", "ResponseObject"] class Operation: def __init__( self, path: str, methods: List[str], view_func: Callable, *, auth: Optional[Union[Sequence[Callable], Callable, NOT_SET_TYPE]] = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, include_in_schema: bool = True, url_name: Optional[str] = None, openapi_extra: Optional[Dict[str, Any]] = None, ) -> None: self.is_async = False self.path: str = path self.methods: List[str] = methods self.view_func: Callable = view_func self.api: NinjaAPI = cast("NinjaAPI", None) self.csrf_exempt: bool = getattr(view_func, "csrf_exempt", False) if url_name is not None: self.url_name = url_name self.auth_param: Optional[Union[Sequence[Callable], Callable, object]] = auth self.auth_callbacks: Sequence[Callable] = [] self._set_auth(auth) if isinstance(throttle, BaseThrottle): throttle = [throttle] self.throttle_param = throttle self.throttle_objects: List[BaseThrottle] = [] if throttle is not NOT_SET: for th in throttle: # type: ignore assert isinstance( th, BaseThrottle ), "Throttle should be an instance of BaseThrottle" self.throttle_objects.append(th) self.signature = ViewSignature(self.path, self.view_func) self.models: TModels = self.signature.models self.stream_format: Optional[Type[StreamFormat]] = None self.stream_item_model: Optional[Type[Schema]] = None self.response_models: Dict[Any, Any] if isinstance(response, _StreamAlias): self.stream_format = response.format_cls self.stream_item_model = self._create_response_model(response.item_type) self.response_models = {200: self.stream_item_model} elif response is NOT_SET: self.response_models = {200: NOT_SET} elif isinstance(response, dict): self.response_models = self._create_response_model_multiple(response) else: self.response_models = {200: self._create_response_model(response)} if need_to_fix_request_files(methods, self.models): raise ConfigError( f"Router '{path}' has method(s) {methods} that require fixing request.FILES. " f"Please add '{FIX_MIDDLEWARE_PATH}' to settings.MIDDLEWARE" ) self.operation_id = operation_id self.summary = summary or self.view_func.__name__.title().replace("_", " ") self.description = description or self.signature.docstring self.tags = tags self.deprecated = deprecated self.include_in_schema = include_in_schema self.openapi_extra = openapi_extra # Exporting models params self.by_alias = by_alias or False self.exclude_unset = exclude_unset or False self.exclude_defaults = exclude_defaults or False self.exclude_none = exclude_none or False if hasattr(view_func, "_ninja_contribute_to_operation"): # Allow 3rd party code to contribute to the operation behavior callbacks: List[Callable] = view_func._ninja_contribute_to_operation for callback in callbacks: callback(self) def clone(self) -> "Operation": """ Create a fresh copy of this operation for binding to an API. This method is used when mounting the same router multiple times to ensure each mount has independent operation instances. """ # Create instance without calling __init__ to avoid expensive processing cloned = object.__new__(self.__class__) # Copy all essential attributes cloned.is_async = self.is_async cloned.path = self.path cloned.methods = list(self.methods) cloned.view_func = self.view_func cloned.api = cast("NinjaAPI", None) # Will be set during binding cloned.csrf_exempt = self.csrf_exempt # Copy url_name if it exists if hasattr(self, "url_name"): cloned.url_name = self.url_name # Copy auth settings cloned.auth_param = self.auth_param cloned.auth_callbacks = list(self.auth_callbacks) # Copy throttle settings cloned.throttle_param = self.throttle_param cloned.throttle_objects = list(self.throttle_objects) # Copy signature and models (immutable after creation, safe to share) cloned.signature = self.signature cloned.models = self.models # Copy streaming attributes cloned.stream_format = self.stream_format cloned.stream_item_model = self.stream_item_model # Copy response models (dict copy for isolation) cloned.response_models = dict(self.response_models) # Copy metadata cloned.operation_id = self.operation_id cloned.summary = self.summary cloned.description = self.description cloned.tags = list(self.tags) if self.tags else None cloned.deprecated = self.deprecated cloned.include_in_schema = self.include_in_schema cloned.openapi_extra = dict(self.openapi_extra) if self.openapi_extra else None # Copy export model params cloned.by_alias = self.by_alias cloned.exclude_unset = self.exclude_unset cloned.exclude_defaults = self.exclude_defaults cloned.exclude_none = self.exclude_none # Re-apply run decorators (from decorate_view) to the clone's run method # We can't just copy the decorated run because it's bound to the original instance if hasattr(self, "_run_decorators") and self._run_decorators: cloned._run_decorators = [] # type: ignore[attr-defined] for deco in self._run_decorators: cloned.run = deco(cloned.run) # type: ignore cloned._run_decorators.append(deco) # type: ignore[attr-defined] return cloned def run(self, request: HttpRequest, **kw: Any) -> HttpResponseBase: error = self._run_checks(request) if error: return error try: temporal_response = self.api.create_temporal_response(request) values = self._get_values(request, kw, temporal_response) result = self.view_func(request, **values) if self.stream_format: return self._stream_response(request, result, temporal_response) return self._result_to_response(request, result, temporal_response) except Exception as e: if isinstance(e, TypeError) and "required positional argument" in str(e): msg = "Did you fail to use functools.wraps() in a decorator?" msg = f"{e.args[0]}: {msg}" if e.args else msg e.args = (msg,) + e.args[1:] return self.api.on_exception(request, e) def _validate_stream_item(self, item: Any, request: HttpRequest) -> str: """Validate a single stream item and return serialized JSON string.""" assert self.stream_item_model is not None resp_object = ResponseObject(item) validated = self.stream_item_model.model_validate( resp_object, context={"request": request, "response_status": 200} ) model_dump_kwargs: Dict[str, Any] = {} if pydantic_version >= [2, 7]: # pragma: no branch # pydantic added support for serialization context at 2.7 model_dump_kwargs.update( context={"request": request, "response_status": 200} ) result = validated.model_dump( by_alias=self.by_alias, exclude_unset=self.exclude_unset, exclude_defaults=self.exclude_defaults, exclude_none=self.exclude_none, **model_dump_kwargs, )["response"] return _serialize_item(result) def _stream_response( self, request: HttpRequest, generator: Any, temporal_response: HttpResponse, ) -> StreamingHttpResponse: """Create a StreamingHttpResponse from a sync generator.""" assert self.stream_format is not None fmt = self.stream_format def content_iter() -> Any: for item in generator: data = self._validate_stream_item(item, request) yield fmt.format_chunk(data) # Copy headers/cookies after generator completes (user may set them inside) for key, value in temporal_response.items(): if key.lower() != "content-type": response[key] = value for cookie_name, cookie in temporal_response.cookies.items(): response.cookies[cookie_name] = cookie response = StreamingHttpResponse( content_iter(), content_type=fmt.media_type, status=temporal_response.status_code, ) # Add format-specific headers for key, value in fmt.response_headers().items(): response[key] = value return response def _set_auth( self, auth: Optional[Union[Sequence[Callable], Callable, object]] ) -> None: if auth is not None and auth is not NOT_SET: self.auth_callbacks = isinstance(auth, Sequence) and auth or [auth] def _run_checks(self, request: HttpRequest) -> Optional[HttpResponse]: "Runs security/throttle checks for each operation" # NOTE: if you change anything in this function - do this also in AsyncOperation # Set CSRF exempt status on request so auth handlers can check it if self.csrf_exempt: # _ninja_csrf_exempt is a special flag that tells auth handler to skip CSRF checks request._ninja_csrf_exempt = True # type: ignore # auth: if self.auth_callbacks: error = self._run_authentication(request) if error: return error # Throttling: if self.throttle_objects: error = self._check_throttles(request) if error: return error return None def _run_authentication(self, request: HttpRequest) -> Optional[HttpResponse]: for callback in self.auth_callbacks: try: if is_async_callable(callback) or getattr(callback, "is_async", False): result = callback(request) if inspect.iscoroutine(result): result = async_to_sync(callback)(request) else: result = callback(request) except Exception as exc: return self.api.on_exception(request, exc) if result: request.auth = result # type: ignore return None return self.api.on_exception(request, AuthenticationError()) def _check_throttles(self, request: HttpRequest) -> Optional[HttpResponse]: throttle_durations = [] for throttle in self.throttle_objects: if not throttle.allow_request(request): throttle_durations.append(throttle.wait()) if throttle_durations: # Filter out `None` values which may happen in case of config / rate durations = [ duration for duration in throttle_durations if duration is not None ] duration = max(durations, default=None) return self.api.on_exception(request, Throttled(wait=duration)) # type: ignore return None def _model_dump_kwargs(self, request: HttpRequest, status: int) -> Dict[str, Any]: kwargs: Dict[str, Any] = {} if pydantic_version >= [2, 7]: kwargs["context"] = {"request": request, "response_status": status} return kwargs def _result_to_response( self, request: HttpRequest, result: Any, temporal_response: HttpResponse ) -> HttpResponseBase: """ The protocol for results - if HttpResponse - returns as is - if Status object - uses status code + body - if tuple with 2 elements - means http_code + body (deprecated) - otherwise it's a body """ if isinstance(result, HttpResponseBase): return result status: int = 200 if len(self.response_models) == 1: status = next(iter(self.response_models)) if isinstance(result, Status): status = result.status_code result = result.value elif isinstance(result, tuple) and len(result) == 2: warnings.warn( "Returning tuple (status_code, response) is deprecated. " "Use Status(status_code, response) instead.", DeprecationWarning, stacklevel=2, ) status, result = result if status in self.response_models: response_model = self.response_models[status] elif Ellipsis in self.response_models: response_model = self.response_models[Ellipsis] else: raise ConfigError( f"Schema for status {status} is not set in response" f" {self.response_models.keys()}" ) temporal_response.status_code = status if response_model is NOT_SET: return self.api.create_response( request, result, temporal_response=temporal_response ) if response_model is None: # Empty response. return temporal_response model_dump_kwargs = self._model_dump_kwargs(request, status) # Skip re-validation for pydantic model instances matching the response type resp_annotation = response_model.model_fields["response"].annotation if ( isinstance(resp_annotation, type) and isinstance(result, BaseModel) and isinstance(result, resp_annotation) ): result = cast(BaseModel, result).model_dump( by_alias=self.by_alias, exclude_unset=self.exclude_unset, exclude_defaults=self.exclude_defaults, exclude_none=self.exclude_none, **model_dump_kwargs, ) return self.api.create_response( request, result, temporal_response=temporal_response ) resp_object = ResponseObject(result) # ^ we need object because getter_dict seems work only with model_validate validated_object = response_model.model_validate( resp_object, context={"request": request, "response_status": status} ) result = validated_object.model_dump( by_alias=self.by_alias, exclude_unset=self.exclude_unset, exclude_defaults=self.exclude_defaults, exclude_none=self.exclude_none, **model_dump_kwargs, )["response"] return self.api.create_response( request, result, temporal_response=temporal_response ) def _get_values( self, request: HttpRequest, path_params: Any, temporal_response: HttpResponse ) -> DictStrAny: values = {} error_contexts: List[ValidationErrorContext] = [] for model in self.models: try: data = model.resolve(request, self.api, path_params) values.update(data) except pydantic.ValidationError as e: error_contexts.append( ValidationErrorContext(pydantic_validation_error=e, model=model) ) if error_contexts: validation_error = self.api.validation_error_from_error_contexts( error_contexts ) raise validation_error if self.signature.response_arg: values[self.signature.response_arg] = temporal_response return values def _create_response_model_multiple( self, response_param: DictStrAny ) -> Dict[str, Optional[Type[Schema]]]: result = {} for key, model in response_param.items(): status_codes = isinstance(key, Iterable) and key or [key] for code in status_codes: result[code] = self._create_response_model(model) return result def _create_response_model(self, response_param: Any) -> Optional[Type[Schema]]: if response_param is None: return None attrs = {"__annotations__": {"response": response_param}} return type("NinjaResponseSchema", (Schema,), attrs) class AsyncOperation(Operation): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self.is_async = True async def run(self, request: HttpRequest, **kw: Any) -> HttpResponseBase: # type: ignore error = await self._run_checks(request) if error: return error try: temporal_response = self.api.create_temporal_response(request) values = self._get_values(request, kw, temporal_response) if self.stream_format: result = self.view_func(request, **values) return await self._async_stream_response( request, result, temporal_response ) result = await self.view_func(request, **values) return self._result_to_response(request, result, temporal_response) except Exception as e: return self.api.on_exception(request, e) async def _async_stream_response( self, request: HttpRequest, generator: Any, temporal_response: HttpResponse, ) -> StreamingHttpResponse: """Create a StreamingHttpResponse from an async generator.""" assert self.stream_format is not None fmt = self.stream_format async def content_gen() -> Any: async for item in generator: data = self._validate_stream_item(item, request) yield fmt.format_chunk(data) return await create_streaming_response( content_gen(), content_type=fmt.media_type, status=temporal_response.status_code, temporal_response=temporal_response, extra_headers=fmt.response_headers(), ) async def _run_checks(self, request: HttpRequest) -> Optional[HttpResponse]: # type: ignore "Runs security/throttle checks for each operation" # NOTE: if you change anything in this function - do this also in Sync Operation # Set CSRF exempt status on request so auth handlers can check it if self.csrf_exempt: request._ninja_csrf_exempt = True # type: ignore # auth: if self.auth_callbacks: error = await self._run_authentication(request) if error: return error # Throttling: if self.throttle_objects: error = self._check_throttles(request) if error: return error return None async def _run_authentication(self, request: HttpRequest) -> Optional[HttpResponse]: # type: ignore for callback in self.auth_callbacks: try: if is_async_callable(callback) or getattr(callback, "is_async", False): cor: Optional[Coroutine] = callback(request) if cor is None: result = None else: result = await cor else: result = await sync_to_async(callback)(request) except Exception as exc: return self.api.on_exception(request, exc) if result: request.auth = result # type: ignore return None return self.api.on_exception(request, AuthenticationError()) class PathView: def __init__(self) -> None: self.operations: List[Operation] = [] self.is_async = False # if at least one operation is async - will become True self.url_name: Optional[str] = None def add_operation( self, path: str, methods: List[str], view_func: Callable, *, auth: Optional[Union[Sequence[Callable], Callable, NOT_SET_TYPE]] = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, ) -> Operation: if url_name: self.url_name = url_name OperationClass = Operation is_streaming = isinstance(response, _StreamAlias) if is_async(view_func) or ( is_streaming and inspect.isasyncgenfunction(view_func) ): self.is_async = True OperationClass = AsyncOperation operation = OperationClass( path, methods, view_func, auth=auth, throttle=throttle, response=response, operation_id=operation_id, summary=summary, description=description, tags=tags, deprecated=deprecated, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, include_in_schema=include_in_schema, url_name=url_name, openapi_extra=openapi_extra, ) self.operations.append(operation) view_func._ninja_operation = operation # type: ignore return operation def clone(self) -> "PathView": """ Create a fresh copy of this PathView with cloned operations. This method is used when mounting the same router multiple times to ensure each mount has independent PathView and Operation instances. """ cloned = PathView() cloned.is_async = self.is_async cloned.url_name = self.url_name cloned.operations = [op.clone() for op in self.operations] return cloned def get_view(self) -> Callable: # Create a unique view function for this PathView if self.is_async: # Create a wrapper for async view async def async_view_wrapper( request: HttpRequest, *args: Any, **kwargs: Any ) -> HttpResponseBase: return await self._async_view(request, *args, **kwargs) # All django-ninja views are CSRF exempt at Django middleware level # Cookie-based auth (APIKeyCookie) handles CSRF checking separately async_view_wrapper.csrf_exempt = True # type: ignore return async_view_wrapper else: # Create a wrapper for sync view def sync_view_wrapper( request: HttpRequest, *args: Any, **kwargs: Any ) -> HttpResponseBase: return self._sync_view(request, *args, **kwargs) # All django-ninja views are CSRF exempt at Django middleware level # Cookie-based auth (APIKeyCookie) handles CSRF checking separately sync_view_wrapper.csrf_exempt = True # type: ignore return sync_view_wrapper def _sync_view(self, request: HttpRequest, *a: Any, **kw: Any) -> HttpResponseBase: operation = self._find_operation(request) if operation is None: return self._not_allowed() return operation.run(request, *a, **kw) async def _async_view( self, request: HttpRequest, *a: Any, **kw: Any ) -> HttpResponseBase: operation = self._find_operation(request) if operation is None: return self._not_allowed() if operation.is_async: return await cast(AsyncOperation, operation).run(request, *a, **kw) return await sync_to_async(operation.run)(request, *a, **kw) def _find_operation(self, request: HttpRequest) -> Optional[Operation]: for op in self.operations: if request.method in op.methods: return op return None def _not_allowed(self) -> HttpResponse: allowed_methods = set() for op in self.operations: allowed_methods.update(op.methods) return HttpResponseNotAllowed(allowed_methods, content=b"Method not allowed") class ResponseObject: "Basically this is just a helper to be able to pass response to pydantic's model_validate" def __init__(self, response: HttpResponse) -> None: self.response = response ================================================ FILE: ninja/orm/__init__.py ================================================ from ninja.orm.factory import create_schema from ninja.orm.fields import register_field from ninja.orm.metaclass import ModelSchema __all__ = ["create_schema", "register_field", "ModelSchema"] ================================================ FILE: ninja/orm/factory.py ================================================ import itertools from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Type, Union, cast from django.db.models import Field as DjangoField from django.db.models import ManyToManyRel, ManyToOneRel, Model from pydantic import create_model as create_pydantic_model from ninja.errors import ConfigError from ninja.orm.fields import get_schema_field from ninja.schema import Schema # MAYBE: # Schema = create_schema(Model, exclude=['id']) # # @api.post # def operation_create(request, payload: Schema): # orm_instance = payload.orm.apply(Model()) # orm_instance.save() # # @api.post("/{id}") # def operation_edit(request, id: int, payload: Schema): # orm_instance = payload.orm.apply(Model.objects.get(id=id)) # orm_instance.save() __all__ = ["SchemaFactory", "factory", "create_schema"] SchemaKey = Tuple[Type[Model], str, int, str, str, str, str] class SchemaFactory: def __init__(self) -> None: self.schemas: Dict[SchemaKey, Type[Schema]] = {} self.schema_names: Set[str] = set() def create_schema( self, model: Type[Model], *, name: str = "", depth: int = 0, fields: Optional[List[str]] = None, exclude: Optional[List[str]] = None, optional_fields: Optional[List[str]] = None, custom_fields: Optional[List[Tuple[str, Any, Any]]] = None, base_class: Type[Schema] = Schema, ) -> Type[Schema]: name = name or model.__name__ if fields and exclude: raise ConfigError("Only one of 'fields' or 'exclude' should be set.") key = self.get_key( model, name, depth, fields, exclude, optional_fields, custom_fields ) if key in self.schemas: return self.schemas[key] model_fields_list = list(self._selected_model_fields(model, fields, exclude)) if optional_fields: if optional_fields == "__all__": optional_fields = [f.name for f in model_fields_list] definitions = {} for fld in model_fields_list: python_type, field_info = get_schema_field( fld, depth=depth, optional=optional_fields and (fld.name in optional_fields), ) definitions[fld.name] = (python_type, field_info) if custom_fields: for fld_name, python_type, field_info in custom_fields: # if not isinstance(field_info, FieldInfo): # field_info = Field(field_info) definitions[fld_name] = (python_type, field_info) if name in self.schema_names: name = self._get_unique_name(name) schema: Type[Schema] = create_pydantic_model( name, __config__=None, __base__=base_class, __module__=base_class.__module__, __validators__={}, **definitions, ) # type: ignore # __model_name: str, # *, # __config__: ConfigDict | None = None, # __base__: None = None, # __module__: str = __name__, # __validators__: dict[str, AnyClassMethod] | None = None, # __cls_kwargs__: dict[str, Any] | None = None, # **field_definitions: Any, self.schemas[key] = schema self.schema_names.add(name) return schema def get_key( self, model: Type[Model], name: str, depth: int, fields: Union[str, List[str], None], exclude: Optional[List[str]], optional_fields: Optional[Union[List[str], str]], custom_fields: Optional[List[Tuple[str, str, Any]]], ) -> SchemaKey: "returns a hashable value for all given parameters" # TODO: must be a test that compares all kwargs from init to get_key return ( model, name, depth, str(fields), str(exclude), str(optional_fields), str(custom_fields), ) def _get_unique_name(self, name: str) -> str: "Returns a unique name by adding counter suffix" for num in itertools.count(start=2): # pragma: no branch result = f"{name}{num}" if result not in self.schema_names: break return result def _selected_model_fields( self, model: Type[Model], fields: Optional[List[str]] = None, exclude: Optional[List[str]] = None, ) -> Iterator[DjangoField]: "Returns iterator for model fields based on `exclude` or `fields` arguments" all_fields = {f.name: f for f in self._model_fields(model)} if not fields and not exclude: for f in all_fields.values(): yield f invalid_fields = (set(fields or []) | set(exclude or [])) - all_fields.keys() if invalid_fields: raise ConfigError( f"DjangoField(s) {invalid_fields} are not in model {model}" ) if fields: for name in fields: yield all_fields[name] if exclude: for f in all_fields.values(): if f.name not in exclude: yield f def _model_fields(self, model: Type[Model]) -> Iterator[DjangoField]: "returns iterator with all the fields that can be part of schema" for fld in model._meta.get_fields(): if isinstance(fld, (ManyToOneRel, ManyToManyRel)): # skipping relations continue yield cast(DjangoField, fld) factory = SchemaFactory() create_schema = factory.create_schema ================================================ FILE: ninja/orm/fields.py ================================================ import datetime from decimal import Decimal from typing import Any, Callable, Dict, List, Tuple, Type, TypeVar, Union, no_type_check from uuid import UUID from django.db.models import ManyToManyField from django.db.models.fields import Field as DjangoField from pydantic import IPvAnyAddress from pydantic.fields import FieldInfo from pydantic_core import PydanticUndefined, core_schema from ninja.errors import ConfigError from ninja.openapi.schema import OpenAPISchema from ninja.types import DictStrAny __all__ = ["create_m2m_link_type", "get_schema_field", "get_related_field_schema"] # keep_lazy seems not needed as .title forces translation anyway # https://github.com/vitalik/django-ninja/issues/774 # @keep_lazy_text def title_if_lower(s: str) -> str: if s == s.lower(): return s.title() return s class AnyObject: @classmethod def __get_pydantic_core_schema__( cls, source: Any, handler: Callable[..., Any] ) -> Any: return core_schema.with_info_plain_validator_function(cls.validate) @classmethod def __get_pydantic_json_schema__( cls, schema: Any, handler: Callable[..., Any] ) -> DictStrAny: return {"type": "object"} @classmethod def validate(cls, value: Any, _: Any) -> Any: return value TYPES = { "AutoField": int, "BigAutoField": int, "BigIntegerField": int, "BinaryField": bytes, "BooleanField": bool, "CharField": str, "DateField": datetime.date, "DateTimeField": datetime.datetime, "DecimalField": Decimal, "DurationField": datetime.timedelta, "FileField": str, "FilePathField": str, "FloatField": float, "GenericIPAddressField": IPvAnyAddress, "IPAddressField": IPvAnyAddress, "IntegerField": int, "JSONField": AnyObject, "NullBooleanField": bool, "PositiveBigIntegerField": int, "PositiveIntegerField": int, "PositiveSmallIntegerField": int, "SlugField": str, "SmallAutoField": int, "SmallIntegerField": int, "TextField": str, "TimeField": datetime.time, "UUIDField": UUID, # postgres fields: "ArrayField": List, "CICharField": str, "CIEmailField": str, "CITextField": str, "HStoreField": Dict, } TModel = TypeVar("TModel") def register_field(django_field: str, python_type: Any) -> None: TYPES[django_field] = python_type @no_type_check def create_m2m_link_type(type_: Type[TModel]) -> Type[TModel]: class M2MLink(type_): # type: ignore @classmethod def __get_pydantic_core_schema__(cls, source, handler): return core_schema.with_info_plain_validator_function(cls._validate) @classmethod def __get_pydantic_json_schema__(cls, schema, handler): json_type = { int: "integer", str: "string", float: "number", UUID: "string", }[type_] return {"type": json_type} @classmethod def _validate(cls, v: Any, _): try: return v.pk # when we output queryset - we have db instances except AttributeError: return type_(v) # when we read payloads we have primakey keys return M2MLink @no_type_check def get_schema_field( field: DjangoField, *, depth: int = 0, optional: bool = False ) -> Tuple: "Returns pydantic field from django's model field" alias = None default = ... default_factory = None description = None title = None max_length = None nullable = False python_type = None if field.is_relation: if depth > 0: return get_related_field_schema(field, depth=depth) internal_type = field.related_model._meta.pk.get_internal_type() if not field.concrete and field.auto_created or field.null or optional: default = None nullable = True alias = getattr(field, "get_attname", None) and field.get_attname() pk_type = TYPES.get(internal_type, int) if field.one_to_many or field.many_to_many: m2m_type = create_m2m_link_type(pk_type) python_type = List[m2m_type] # type: ignore else: python_type = pk_type else: _f_name, _f_path, _f_pos, field_options = field.deconstruct() blank = field_options.get("blank", False) null = field_options.get("null", False) max_length = field_options.get("max_length") internal_type = field.get_internal_type() try: python_type = TYPES[internal_type] except KeyError as e: msg = [ f"Do not know how to convert django field '{internal_type}'.", "Try from ninja.orm import register_field", f"register_field('{internal_type}', )", ] raise ConfigError("\n".join(msg)) from e if field.primary_key or blank or null or optional: default = None nullable = True if field.has_default(): if callable(field.default): default_factory = field.default else: default = field.default if default_factory: default = PydanticUndefined if nullable: python_type = Union[python_type, None] # aka Optional in 3.7+ description = field.help_text or None title = title_if_lower(field.verbose_name) return ( python_type, FieldInfo( default=default, alias=alias, validation_alias=alias, serialization_alias=alias, default_factory=default_factory, title=title, description=description, max_length=max_length, ), ) @no_type_check def get_related_field_schema(field: DjangoField, *, depth: int) -> Tuple[OpenAPISchema]: from ninja.orm import create_schema model = field.related_model schema = create_schema(model, depth=depth - 1) default = ... if not field.concrete and field.auto_created or field.null: default = None if isinstance(field, ManyToManyField): schema = List[schema] # type: ignore return ( schema, FieldInfo( default=default, description=field.help_text, title=title_if_lower(field.verbose_name), ), ) ================================================ FILE: ninja/orm/metaclass.py ================================================ from typing import Any, List, Optional, Union, no_type_check from django.apps import apps from django.db.models import Model as DjangoModel from pydantic.dataclasses import dataclass from ninja.compatibility.util import get_annotations_from_namespace from ninja.errors import ConfigError from ninja.orm.factory import create_schema from ninja.schema import ResolverMetaclass, Schema _is_modelschema_class_defined = False @dataclass class MetaConf: model: Any fields: Optional[List[str]] = None exclude: Union[List[str], str, None] = None fields_optional: Union[List[str], str, None] = None @staticmethod def from_schema_class(name: str, namespace: dict) -> "MetaConf": if "Config" in namespace: raise ConfigError( # pragma: no cover "The use of `Config` class is removed for ModelSchema, use 'Meta' instead", ) if "Meta" in namespace: meta = namespace["Meta"] model = meta.model if isinstance(model, str): try: app_label, model_name = model.split(".") except ValueError as e: raise ValueError( f"Model string must be in format 'app_label.ModelName', got: {model}" ) from e model = apps.get_model(app_label, model_name) fields = getattr(meta, "fields", None) exclude = getattr(meta, "exclude", None) optional_fields = getattr(meta, "fields_optional", None) else: raise ConfigError(f"ModelSchema class '{name}' requires a 'Meta' subclass") assert issubclass(model, DjangoModel) if not fields and not exclude: raise ConfigError( "Creating a ModelSchema without either the 'fields' attribute" " or the 'exclude' attribute is prohibited" ) if fields == "__all__": fields = None # ^ when None is passed to create_schema - all fields are selected return MetaConf( model=model, fields=fields, exclude=exclude, fields_optional=optional_fields, ) class ModelSchemaMetaclass(ResolverMetaclass): @no_type_check def __new__( mcs, name: str, bases: tuple, namespace: dict, **kwargs, ): cls = super().__new__( mcs, name, bases, namespace, **kwargs, ) for base in reversed(bases): if ( _is_modelschema_class_defined and issubclass(base, ModelSchema) and base == ModelSchema ): meta_conf = MetaConf.from_schema_class(name, namespace) custom_fields = [] annotations = get_annotations_from_namespace(namespace) for attr_name, type in annotations.items(): if attr_name.startswith("_"): continue default = namespace.get(attr_name, ...) custom_fields.append((attr_name, type, default)) # # cls.__doc__ = namespace.get("__doc__", config.model.__doc__) # cls.__fields__ = {} # forcing pydantic recreate # # assert False, "!! cls.model_fields" # print(config.model, name, fields, exclude, "!!") model_schema = create_schema( meta_conf.model, name=name, fields=meta_conf.fields, exclude=meta_conf.exclude, optional_fields=meta_conf.fields_optional, custom_fields=custom_fields, base_class=cls, ) model_schema.__doc__ = cls.__doc__ return model_schema return cls class ModelSchema(Schema, metaclass=ModelSchemaMetaclass): pass _is_modelschema_class_defined = True ================================================ FILE: ninja/orm/shortcuts.py ================================================ from typing import Any, List, Type from ninja import Schema from ninja.orm.factory import create_schema __all__ = ["S", "L"] # GOAL: # from ninja.orm import S, L # S(Job) -> JobSchema? Job? # S(Job) -> should reuse already created schema # S(Job, fields='xxx') -> new schema ? how to name Job1 , 2, 3 and so on ? # L(Job) -> List[Job] def S(model: Any, **kwargs: Any) -> Type[Schema]: return create_schema(model, **kwargs) def L(model: Any, **kwargs: Any) -> List[Any]: schema = S(model, **kwargs) return List[schema] # type: ignore ================================================ FILE: ninja/pagination.py ================================================ import binascii import inspect from abc import ABC, abstractmethod from base64 import b64decode, b64encode from functools import partial, wraps from math import inf from typing import ( Any, AsyncGenerator, Callable, List, Optional, Tuple, Type, Union, ) from urllib import parse from django.db.models import QuerySet from django.http import HttpRequest from django.utils.module_loading import import_string from pydantic import BaseModel, field_validator from typing_extensions import get_args as get_collection_args from ninja import Field, Query, Router, Schema from ninja.conf import settings from ninja.constants import NOT_SET from ninja.errors import ConfigError, ValidationError from ninja.operation import Operation from ninja.responses import Status from ninja.signature.details import is_collection_type from ninja.utils import ( contribute_operation_args, contribute_operation_callback, is_async_callable, ) class PaginationBase(ABC): class Input(Schema): pass InputSource = Query(...) class Output(Schema): items: List[Any] count: int items_attribute: str = "items" def __init__(self, *, pass_parameter: Optional[str] = None, **kwargs: Any) -> None: self.pass_parameter = pass_parameter @abstractmethod def paginate_queryset( self, queryset: QuerySet, pagination: Any, request: HttpRequest, **params: Any, ) -> Any: pass # pragma: no cover def _items_count(self, queryset: QuerySet) -> int: """ Since lists are mainly compatible with QuerySets and can be passed to paginator. We will first to try to use .count - and if not there will use a len """ try: # forcing to find queryset.count instead of list.count: return queryset.all().count() except AttributeError: return len(queryset) class AsyncPaginationBase(PaginationBase): @abstractmethod async def apaginate_queryset( self, queryset: QuerySet, pagination: Any, request: HttpRequest, **params: Any, ) -> Any: pass # pragma: no cover async def _aitems_count(self, queryset: QuerySet) -> int: try: return await queryset.all().acount() except AttributeError: return len(queryset) class LimitOffsetPagination(AsyncPaginationBase): class Input(Schema): limit: int = Field( settings.PAGINATION_PER_PAGE, ge=1, le=( settings.PAGINATION_MAX_LIMIT if settings.PAGINATION_MAX_LIMIT != inf else None ), ) offset: int = Field(0, ge=0) def paginate_queryset( self, queryset: QuerySet, pagination: Input, request: HttpRequest, **params: Any, ) -> Any: offset = pagination.offset limit: int = min(pagination.limit, settings.PAGINATION_MAX_LIMIT) return { self.items_attribute: queryset[offset : offset + limit], "count": self._items_count(queryset), } # noqa: E203 async def apaginate_queryset( self, queryset: QuerySet, pagination: Input, request: HttpRequest, **params: Any, ) -> Any: offset = pagination.offset limit: int = min(pagination.limit, settings.PAGINATION_MAX_LIMIT) if isinstance(queryset, QuerySet): items = [obj async for obj in queryset[offset : offset + limit]] else: items = queryset[offset : offset + limit] return { self.items_attribute: items, "count": await self._aitems_count(queryset), } # noqa: E203 class PageNumberPagination(AsyncPaginationBase): class Input(Schema): page: int = Field(1, ge=1) page_size: Optional[int] = Field(None, ge=1) def __init__( self, page_size: int = settings.PAGINATION_PER_PAGE, max_page_size: int = settings.PAGINATION_MAX_PER_PAGE_SIZE, **kwargs: Any, ) -> None: self.page_size = page_size self.max_page_size = max_page_size super().__init__(**kwargs) def _get_page_size(self, requested_page_size: Optional[int]) -> int: if requested_page_size is None: return self.page_size return min(requested_page_size, self.max_page_size) def paginate_queryset( self, queryset: QuerySet, pagination: Input, request: HttpRequest, **params: Any, ) -> Any: page_size = self._get_page_size(pagination.page_size) offset = (pagination.page - 1) * page_size return { self.items_attribute: queryset[offset : offset + page_size], "count": self._items_count(queryset), } # noqa: E203 async def apaginate_queryset( self, queryset: QuerySet, pagination: Input, request: HttpRequest, **params: Any, ) -> Any: page_size = self._get_page_size(pagination.page_size) offset = (pagination.page - 1) * page_size if isinstance(queryset, QuerySet): items = [obj async for obj in queryset[offset : offset + page_size]] else: items = queryset[offset : offset + page_size] return { self.items_attribute: items, "count": await self._aitems_count(queryset), } # noqa: E203 class CursorPagination(AsyncPaginationBase): max_page_size: int page_size: int items_attribute: str = "results" def __init__( self, *, ordering: Tuple[str, ...] = settings.PAGINATION_DEFAULT_ORDERING, page_size: int = settings.PAGINATION_PER_PAGE, max_page_size: int = settings.PAGINATION_MAX_PER_PAGE_SIZE, **kwargs: Any, ) -> None: self.ordering = ordering # take the first ordering parameter as the attribute for establishing # position self._order_attribute = ( ordering[0][1:] if ordering[0].startswith("-") else ordering[0] ) self._order_attribute_reversed = ordering[0].startswith("-") self.page_size = page_size self.max_page_size = max_page_size super().__init__(**kwargs) class Input(Schema): page_size: Optional[int] = None cursor: Optional[str] = None class Output(Schema): previous: Optional[str] next: Optional[str] results: List[Any] class Cursor(BaseModel): """ Represents pagination state. This is encoded in a base64 query parameter. """ p: Optional[str] = Field( default=None, title="position", description="String identifier for the current position in the dataset", ) r: bool = Field( default=False, title="reverse", description="Whether to reverse the ordering direction", ) # offset enables the use of a non-unique ordering field # e.g. if created time of two items is exactly the same, we can use the offset # to figure out the position exactly o: int = Field( default=0, ge=0, lt=settings.PAGINATION_MAX_OFFSET, title="offset", description="Number of items to skip from the current position", ) @field_validator("*", mode="before") @classmethod def validate_individual_queryparam(cls, value: Any) -> Any: """ Handle query string parsing quirks where single values become lists. URL parsing libraries wrap single query parameters in lists, we only care about a single value """ if isinstance(value, list): return value[0] return value @classmethod def from_encoded_param( cls, encoded_param: Optional[str], context: Any = None ) -> "CursorPagination.Cursor": """ Deserialize cursor from URL-safe base64 token. """ if not encoded_param: return cls() try: decoded = b64decode( encoded_param.encode("ascii"), validate=True ).decode("ascii") except (ValueError, binascii.Error) as e: raise ValidationError([{"cursor": "Invalid Cursor"}]) from e parsed_querystring = parse.parse_qs(decoded, keep_blank_values=True) return cls.model_validate(parsed_querystring, context=context) def encode_as_param(self) -> str: """ Serialize cursor to URL-safe base64 token. """ data = self.model_dump( exclude_defaults=True, exclude_none=True, exclude_unset=True ) query_string = parse.urlencode(data, doseq=True) return b64encode(query_string.encode("ascii")).decode("ascii") @staticmethod def _reverse_order(order: Tuple[str, ...]) -> Tuple[str, ...]: """ Flip ordering direction for backward pagination. Example: ("-created", "pk") becomes ("created", "-pk") ("name", "-updated") becomes ("-name", "updated") """ return tuple( marker[1:] if marker.startswith("-") else f"-{marker}" for marker in order ) def _get_position(self, item: Any) -> str: """ Extract the string representation of the attribute value used for ordering, which serves as the position identifier. """ return str(getattr(item, self._order_attribute)) def _get_page_size(self, requested_page_size: Optional[int]) -> int: """ Determine the actual page size to use, respecting configured limits. Uses the default page size when no specific size is requested, otherwise clamps the requested size within the allowed range to prevent resource exhaustion attacks. """ if requested_page_size is None: return self.page_size return min(self.max_page_size, max(1, requested_page_size)) def _build_next_cursor( self, current_cursor: Cursor, results: List[Any], additional_position: Optional[str] = None, ) -> Optional[Cursor]: """ Build cursor for next page """ if (additional_position is None and not current_cursor.r) or not results: return None if not current_cursor.r: # next position is provided by the additional position in a forward cursor next_position = additional_position else: # default to the last item # this will result in this item being included in the next set of results # when flipping from a reversed cursor query to a forward cursor query next_position = self._get_position(results[-1]) offset = 0 if current_cursor.p == next_position and not current_cursor.r: offset += current_cursor.o + len(results) else: # Count duplicates at page end to find the offset for item in reversed(results): item_position_value = self._get_position(item) if item_position_value != next_position: break offset += 1 return self.Cursor(o=offset, r=False, p=next_position) def _build_previous_cursor( self, current_cursor: Cursor, results: List[Any], additional_position: Optional[str] = None, ) -> Optional[Cursor]: """ Build cursor for previous page """ if ( current_cursor.r and additional_position is None ) or current_cursor.p is None: return None if not results: # End of dataset - create reverse cursor to go backward return self.Cursor(o=0, r=True, p=current_cursor.p) if current_cursor.r: # previous position is provided by the additional position in a # reversed cursor previous_position = additional_position else: # default to the first item # this will result in this item being included in the previous set of # results when flipping from a forward cursor query to a reversed # cursor query previous_position = self._get_position(results[0]) offset = 0 if current_cursor.p == previous_position and current_cursor.r: offset += current_cursor.o + len(results) else: # Count duplicates at page end to find the offset for item in results: item_position_value = self._get_position(item) if item_position_value != previous_position: break offset += 1 return self.Cursor(o=offset, r=True, p=previous_position) @staticmethod def _add_cursor_to_URL(url: str, cursor: Optional[Cursor]) -> Optional[str]: """ Build pagination URLs with an encoded cursor. Ignore any previous cursors but preserve any other query parameters Example: Given URL "https://api.example.com/pages?tag=hiring" and a cursor with position "2024-01-01T10:00:00Z", returns: "https://api.example.com/pages?cursor=cD0yMDI0LTAxLTAxVDEwJTNBMDA%3D&tag=hiring" """ if cursor is None: return None (scheme, netloc, path, query, fragment) = parse.urlsplit(url) query_dict = parse.parse_qs(query, keep_blank_values=True) query_dict["cursor"] = [cursor.encode_as_param()] query = parse.urlencode(sorted(query_dict.items()), doseq=True) return parse.urlunsplit((scheme, netloc, path, query, fragment)) def _order_queryset(self, queryset: QuerySet, cursor: Cursor) -> QuerySet: """ Apply ordering to queryset based on cursor direction. For backward pagination (cursor.r=True), flips the ordering direction to traverse the dataset in reverse. """ if cursor.r: return queryset.order_by(*self._reverse_order(self.ordering)) return queryset.order_by(*self.ordering) def _find_position(self, queryset: QuerySet, cursor: Cursor) -> QuerySet: """ Filter queryset to start from the cursor position. """ if cursor.p is None: return queryset cmp = "gte" if cursor.r == self._order_attribute_reversed else "lte" filters = {f"{self._order_attribute}__{cmp}": cursor.p} return queryset.filter(**filters) def paginate_queryset( self, queryset: QuerySet, pagination: Input, request: HttpRequest, **params: Any ) -> Any: """ Execute cursor-based pagination with stable positioning. We fetch page_size + 1 items to detect whether more pages exist without requiring a separate count query. The extra item is discarded from results but used for next/previous cursor generation. """ page_size = self._get_page_size(pagination.page_size) cursor = self.Cursor.from_encoded_param(pagination.cursor) queryset = self._order_queryset(queryset, cursor) queryset = self._find_position(queryset, cursor) # fetch results here and turn into a list results_plus_one = list(queryset[cursor.o : cursor.o + page_size + 1]) additional_position = ( self._get_position(results_plus_one[-1]) if len(results_plus_one) > page_size else None ) if cursor.r: results = list(reversed(results_plus_one[:page_size])) else: results = results_plus_one[:page_size] next_cursor = self._build_next_cursor( current_cursor=cursor, results=results, additional_position=additional_position, ) previous_cursor = self._build_previous_cursor( current_cursor=cursor, results=results, additional_position=additional_position, ) base_url = request.build_absolute_uri() return { "next": self._add_cursor_to_URL(base_url, next_cursor), "previous": self._add_cursor_to_URL(base_url, previous_cursor), self.items_attribute: results, } async def apaginate_queryset( self, queryset: QuerySet, pagination: Input, request: HttpRequest, **params: Any, ) -> Any: """ Execute async cursor-based pagination with stable positioning. We fetch page_size + 1 items to detect whether more pages exist without requiring a separate count query. The extra item is discarded from results but used for next/previous cursor generation. """ page_size = self._get_page_size(pagination.page_size) cursor = self.Cursor.from_encoded_param(pagination.cursor) queryset = self._order_queryset(queryset, cursor) queryset = self._find_position(queryset, cursor) # fetch results here and turn into a list results_plus_one = [ obj async for obj in queryset[cursor.o : cursor.o + page_size + 1] ] additional_position = ( self._get_position(results_plus_one[-1]) if len(results_plus_one) > page_size else None ) if cursor.r: results = list(reversed(results_plus_one[:page_size])) else: results = results_plus_one[:page_size] next_cursor = self._build_next_cursor( current_cursor=cursor, results=results, additional_position=additional_position, ) previous_cursor = self._build_previous_cursor( current_cursor=cursor, results=results, additional_position=additional_position, ) base_url = request.build_absolute_uri() return { "next": self._add_cursor_to_URL(base_url, next_cursor), "previous": self._add_cursor_to_URL(base_url, previous_cursor), self.items_attribute: results, } def paginate( func_or_pgn_class: Any = NOT_SET, **paginator_params: Any ) -> Callable[..., Any]: """ @api.get(... @paginate def my_view(request): or @api.get(... @paginate(PageNumberPagination) def my_view(request): """ isfunction = inspect.isfunction(func_or_pgn_class) isnotset = func_or_pgn_class == NOT_SET pagination_class: Type[Union[PaginationBase, AsyncPaginationBase]] = import_string( settings.PAGINATION_CLASS ) if isfunction: return _inject_pagination(func_or_pgn_class, pagination_class) if not isnotset: pagination_class = func_or_pgn_class def wrapper(func: Callable[..., Any]) -> Any: return _inject_pagination(func, pagination_class, **paginator_params) return wrapper def _inject_pagination( func: Callable[..., Any], paginator_class: Type[Union[PaginationBase, AsyncPaginationBase]], **paginator_params: Any, ) -> Callable[..., Any]: if getattr(func, "_ninja_is_paginated", False): return func # ^ user changed pagination manually on function already paginator = paginator_class(**paginator_params) # Check if Input schema has any fields # If it has no fields, we should make it optional to support Pydantic 2.12+ has_input_fields = bool(paginator.Input.model_fields) if is_async_callable(func): if not hasattr(paginator, "apaginate_queryset"): raise ConfigError("Pagination class not configured for async requests") @wraps(func) async def view_with_pagination(request: HttpRequest, **kwargs: Any) -> Any: pagination_params = kwargs.pop("ninja_pagination", None) if pagination_params is None: pagination_params = paginator.Input() if paginator.pass_parameter: kwargs[paginator.pass_parameter] = pagination_params items = await func(request, **kwargs) status_code = None if isinstance(items, Status): status_code = items.status_code items = items.value result = await paginator.apaginate_queryset( items, pagination=pagination_params, request=request, **kwargs ) async def evaluate(results: Union[List, QuerySet]) -> AsyncGenerator: for result in results: yield result if paginator.Output: # type: ignore result[paginator.items_attribute] = [ result async for result in evaluate(result[paginator.items_attribute]) ] if status_code is not None: return Status(status_code, result) return result else: @wraps(func) def view_with_pagination(request: HttpRequest, **kwargs: Any) -> Any: pagination_params = kwargs.pop("ninja_pagination", None) if pagination_params is None: pagination_params = paginator.Input() if paginator.pass_parameter: kwargs[paginator.pass_parameter] = pagination_params items = func(request, **kwargs) status_code = None if isinstance(items, Status): status_code = items.status_code items = items.value result = paginator.paginate_queryset( items, pagination=pagination_params, request=request, **kwargs ) if paginator.Output: # type: ignore result[paginator.items_attribute] = list( result[paginator.items_attribute] ) # ^ forcing queryset evaluation #TODO: check why pydantic did not do it here if status_code is not None: return Status(status_code, result) return result # Only contribute args if Input has fields # For empty Input schemas, don't add the parameter at all to support Pydantic 2.12+ if has_input_fields: contribute_operation_args( view_with_pagination, "ninja_pagination", paginator.Input, paginator.InputSource, ) if paginator.Output: # type: ignore contribute_operation_callback( view_with_pagination, partial(make_response_paginated, paginator), ) view_with_pagination._ninja_is_paginated = True # type: ignore return view_with_pagination class RouterPaginated(Router): def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self.pagination_class = import_string(settings.PAGINATION_CLASS) def add_api_operation( self, path: str, methods: List[str], view_func: Callable[..., Any], **kwargs: Any, ) -> None: response = kwargs["response"] if is_collection_type(response): view_func = _inject_pagination(view_func, self.pagination_class) return super().add_api_operation(path, methods, view_func, **kwargs) def make_response_paginated(paginator: PaginationBase, op: Operation) -> None: """ Takes operation response and changes it to the paginated response for example: response=List[Some] will be changed to: response=PagedSome where Paged some will be a subclass of paginator.Output: class PagedSome: items: List[Some] count: int """ status_code, item_schema = _find_collection_response(op) # Switching schema to Output schema try: new_name = f"Paged{item_schema.__name__}" except AttributeError: # pragma: no cover # special case for `typing.Any`, only raised for Python < 3.10 new_name = f"Paged{str(item_schema).replace('.', '_')}" # pragma: no cover new_schema = type( new_name, (paginator.Output,), { "__annotations__": {paginator.items_attribute: List[item_schema]}, # type: ignore }, ) # typing: ignore response = op._create_response_model(new_schema) # Changing response model to newly created one op.response_models[status_code] = response def _find_collection_response(op: Operation) -> Tuple[int, Any]: """ Walks through defined operation responses and finds the first that is of a collection type (e.g. List[SomeSchema]) """ for code, resp_model in op.response_models.items(): if resp_model is None or resp_model is NOT_SET: continue model = resp_model.__annotations__["response"] if is_collection_type(model): item_schema = get_collection_args(model)[0] return code, item_schema raise ConfigError( f'"{op.view_func}" has no collection response (e.g. response=List[SomeSchema])' ) ================================================ FILE: ninja/params/__init__.py ================================================ from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Pattern, TypeVar, Union from typing_extensions import Annotated from ninja.params import functions as param_functions __all__ = [ "Body", "Cookie", "File", "Form", "Header", "Path", "Query", "BodyEx", "CookieEx", "FileEx", "FormEx", "HeaderEx", "PathEx", "QueryEx", "P", ] class ParamShortcut: def __init__(self, base_func: Callable[..., Any]) -> None: self._base_func = base_func def __call__(self, *args: Any, **kwargs: Any) -> Any: return self._base_func(*args, **kwargs) def __getitem__(self, args: Any) -> Any: if isinstance(args, tuple): return Annotated[args[0], self._base_func(**args[1])] return Annotated[args, self._base_func()] if TYPE_CHECKING: # pragma: nocover # mypy cheats T = TypeVar("T") Body = Annotated[T, param_functions.Body()] Cookie = Annotated[T, param_functions.Cookie()] File = Annotated[T, param_functions.File()] Form = Annotated[T, param_functions.Form()] Header = Annotated[T, param_functions.Header()] Path = Annotated[T, param_functions.Path()] Query = Annotated[T, param_functions.Query()] # mypy does not like to extend already annotated params # with extra annotation (so need to cheat with these XXX-Ex types): from typing_extensions import Annotated as BodyEx from typing_extensions import Annotated as CookieEx from typing_extensions import Annotated as FileEx from typing_extensions import Annotated as FormEx from typing_extensions import Annotated as HeaderEx from typing_extensions import Annotated as PathEx from typing_extensions import Annotated as QueryEx else: Body = ParamShortcut(param_functions.Body) Cookie = ParamShortcut(param_functions.Cookie) File = ParamShortcut(param_functions.File) Form = ParamShortcut(param_functions.Form) Header = ParamShortcut(param_functions.Header) Path = ParamShortcut(param_functions.Path) Query = ParamShortcut(param_functions.Query) # mypy does not like to extend already annotated params # with extra annotation (so need to cheat with these XXX-Ex types): BodyEx = Body CookieEx = Cookie FileEx = File FormEx = Form HeaderEx = Header PathEx = Path QueryEx = Query def P( *, alias: Optional[str] = None, title: Optional[str] = None, description: Optional[str] = None, gt: Optional[float] = None, ge: Optional[float] = None, lt: Optional[float] = None, le: Optional[float] = None, min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Union[str, Pattern[str], None] = None, example: Any = None, examples: Optional[Dict[str, Any]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, ) -> Dict[str, Any]: "Arguments for BodyEx, QueryEx, etc." return dict( alias=alias, title=title, description=description, gt=gt, ge=ge, lt=lt, le=le, min_length=min_length, max_length=max_length, pattern=pattern, example=example, examples=examples, deprecated=deprecated, include_in_schema=include_in_schema, **extra, ) ================================================ FILE: ninja/params/functions.py ================================================ # Yeah, this is a bit strange # but the whole point of this module is to make mypy and typehints happy # what it basically does makes function XXX that create instance of models.XXX # and annotates function with result = Any # idea from https://github.com/tiangolo/fastapi/blob/master/fastapi/param_functions.py from typing import Any, Dict, Optional, Pattern, Union from ninja.params import models def Path( # noqa: N802 default: Any = ..., *, alias: Optional[str] = None, title: Optional[str] = None, description: Optional[str] = None, gt: Optional[float] = None, ge: Optional[float] = None, lt: Optional[float] = None, le: Optional[float] = None, min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Union[str, Pattern[str], None] = None, example: Any = None, examples: Optional[Dict[str, Any]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, ) -> Any: return models.Path( default, alias=alias, title=title, description=description, gt=gt, ge=ge, lt=lt, le=le, min_length=min_length, max_length=max_length, pattern=pattern, example=example, examples=examples, deprecated=deprecated, include_in_schema=include_in_schema, **extra, ) def Query( # noqa: N802 default: Any = ..., *, alias: Optional[str] = None, title: Optional[str] = None, description: Optional[str] = None, gt: Optional[float] = None, ge: Optional[float] = None, lt: Optional[float] = None, le: Optional[float] = None, min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Union[str, Pattern[str], None] = None, example: Any = None, examples: Optional[Dict[str, Any]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, ) -> Any: return models.Query( default, alias=alias, title=title, description=description, gt=gt, ge=ge, lt=lt, le=le, min_length=min_length, max_length=max_length, pattern=pattern, example=example, examples=examples, deprecated=deprecated, include_in_schema=include_in_schema, **extra, ) def Header( # noqa: N802 default: Any = ..., *, alias: Optional[str] = None, title: Optional[str] = None, description: Optional[str] = None, gt: Optional[float] = None, ge: Optional[float] = None, lt: Optional[float] = None, le: Optional[float] = None, min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Union[str, Pattern[str], None] = None, example: Any = None, examples: Optional[Dict[str, Any]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, ) -> Any: return models.Header( default, alias=alias, title=title, description=description, gt=gt, ge=ge, lt=lt, le=le, min_length=min_length, max_length=max_length, pattern=pattern, example=example, examples=examples, deprecated=deprecated, include_in_schema=include_in_schema, **extra, ) def Cookie( # noqa: N802 default: Any = ..., *, alias: Optional[str] = None, title: Optional[str] = None, description: Optional[str] = None, gt: Optional[float] = None, ge: Optional[float] = None, lt: Optional[float] = None, le: Optional[float] = None, min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Union[str, Pattern[str], None] = None, example: Any = None, examples: Optional[Dict[str, Any]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, ) -> Any: return models.Cookie( default, alias=alias, title=title, description=description, gt=gt, ge=ge, lt=lt, le=le, min_length=min_length, max_length=max_length, pattern=pattern, example=example, examples=examples, deprecated=deprecated, include_in_schema=include_in_schema, **extra, ) def Body( # noqa: N802 default: Any = ..., *, alias: Optional[str] = None, title: Optional[str] = None, description: Optional[str] = None, gt: Optional[float] = None, ge: Optional[float] = None, lt: Optional[float] = None, le: Optional[float] = None, min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Union[str, Pattern[str], None] = None, example: Any = None, examples: Optional[Dict[str, Any]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, ) -> Any: return models.Body( default, alias=alias, title=title, description=description, gt=gt, ge=ge, lt=lt, le=le, min_length=min_length, max_length=max_length, pattern=pattern, example=example, examples=examples, deprecated=deprecated, include_in_schema=include_in_schema, **extra, ) def Form( # noqa: N802 default: Any = ..., *, alias: Optional[str] = None, title: Optional[str] = None, description: Optional[str] = None, gt: Optional[float] = None, ge: Optional[float] = None, lt: Optional[float] = None, le: Optional[float] = None, min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Union[str, Pattern[str], None] = None, example: Any = None, examples: Optional[Dict[str, Any]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, ) -> Any: return models.Form( default, alias=alias, title=title, description=description, gt=gt, ge=ge, lt=lt, le=le, min_length=min_length, max_length=max_length, pattern=pattern, example=example, examples=examples, deprecated=deprecated, include_in_schema=include_in_schema, **extra, ) def File( # noqa: N802 default: Any = ..., *, alias: Optional[str] = None, title: Optional[str] = None, description: Optional[str] = None, gt: Optional[float] = None, ge: Optional[float] = None, lt: Optional[float] = None, le: Optional[float] = None, min_length: Optional[int] = None, max_length: Optional[int] = None, pattern: Union[str, Pattern[str], None] = None, example: Any = None, examples: Optional[Dict[str, Any]] = None, deprecated: Optional[bool] = None, include_in_schema: bool = True, **extra: Any, ) -> Any: return models.File( default, alias=alias, title=title, description=description, gt=gt, ge=ge, lt=lt, le=le, min_length=min_length, max_length=max_length, pattern=pattern, example=example, examples=examples, deprecated=deprecated, include_in_schema=include_in_schema, **extra, ) ================================================ FILE: ninja/params/models.py ================================================ from abc import ABC, abstractmethod from typing import ( TYPE_CHECKING, Any, Dict, List, Optional, Pattern, Tuple, Type, TypeVar, Union, ) from django.conf import settings from django.http import HttpRequest from pydantic import BaseModel from pydantic.fields import FieldInfo from ninja.errors import HttpError from ninja.types import DictStrAny if TYPE_CHECKING: from ninja import NinjaAPI # pragma: no cover __all__ = [ "ParamModel", "QueryModel", "PathModel", "HeaderModel", "CookieModel", "BodyModel", "FormModel", "FileModel", ] TModel = TypeVar("TModel", bound="ParamModel") TModels = List[TModel] class ParamModel(BaseModel, ABC): __ninja_param_source__ = None @classmethod @abstractmethod def get_request_data( cls, request: HttpRequest, api: "NinjaAPI", path_params: DictStrAny ) -> Optional[DictStrAny]: pass # pragma: no cover @classmethod def resolve( cls: Type[TModel], request: HttpRequest, api: "NinjaAPI", path_params: DictStrAny, ) -> TModel: data = cls.get_request_data(request, api, path_params) if data is None: return cls() data = cls._map_data_paths(data) return cls.model_validate(data, context={"request": request}) @classmethod def _map_data_paths(cls, data: DictStrAny) -> DictStrAny: flatten_map = getattr(cls, "__ninja_flatten_map__", None) if not flatten_map: return data mapped_data: DictStrAny = {} for key, path in flatten_map.items(): cls._map_data_path(mapped_data, data.get(key), path) return mapped_data @classmethod def _map_data_path( cls, data: DictStrAny, value: Any, path: Tuple[str, ...] ) -> None: current = data for key in path[:-1]: current = current.setdefault(key, {}) if value is not None: current[path[-1]] = value class QueryModel(ParamModel): @classmethod def get_request_data( cls, request: HttpRequest, api: "NinjaAPI", path_params: DictStrAny ) -> Optional[DictStrAny]: list_fields = getattr(cls, "__ninja_collection_fields__", []) return api.parser.parse_querydict(request.GET, list_fields, request) class PathModel(ParamModel): @classmethod def get_request_data( cls, request: HttpRequest, api: "NinjaAPI", path_params: DictStrAny ) -> Optional[DictStrAny]: return path_params class HeaderModel(ParamModel): __ninja_flatten_map__: DictStrAny @classmethod def get_request_data( cls, request: HttpRequest, api: "NinjaAPI", path_params: DictStrAny ) -> Optional[DictStrAny]: data = {} headers = request.headers for name in cls.__ninja_flatten_map__: if name in headers: data[name] = headers[name] return data class CookieModel(ParamModel): @classmethod def get_request_data( cls, request: HttpRequest, api: "NinjaAPI", path_params: DictStrAny ) -> Optional[DictStrAny]: return request.COOKIES class BodyModel(ParamModel): __read_from_single_attr__: str @classmethod def get_request_data( cls, request: HttpRequest, api: "NinjaAPI", path_params: DictStrAny ) -> Optional[DictStrAny]: if request.body: try: data = api.parser.parse_body(request) except Exception as e: msg = "Cannot parse request body" if settings.DEBUG: msg += f" ({e})" raise HttpError(400, msg) from e varname = getattr(cls, "__read_from_single_attr__", None) if varname: data = {varname: data} return data return None class FormModel(ParamModel): @classmethod def get_request_data( cls, request: HttpRequest, api: "NinjaAPI", path_params: DictStrAny ) -> Optional[DictStrAny]: list_fields = getattr(cls, "__ninja_collection_fields__", []) return api.parser.parse_querydict(request.POST, list_fields, request) class FileModel(ParamModel): @classmethod def get_request_data( cls, request: HttpRequest, api: "NinjaAPI", path_params: DictStrAny ) -> Optional[DictStrAny]: list_fields = getattr(cls, "__ninja_collection_fields__", []) return api.parser.parse_querydict(request.FILES, list_fields, request) class _HttpRequest(HttpRequest): body: bytes = b"" class _MultiPartBodyModel(BodyModel): __ninja_body_params__: DictStrAny @classmethod def get_request_data( cls, request: HttpRequest, api: "NinjaAPI", path_params: DictStrAny ) -> Optional[DictStrAny]: req = _HttpRequest() get_request_data = super().get_request_data results: DictStrAny = {} for name, annotation in cls.__ninja_body_params__.items(): if name in request.POST: data = request.POST[name] if annotation is str and data[0] != '"' and data[-1] != '"': data = f'"{data}"' req.body = data.encode() results[name] = get_request_data(req, api, path_params) return results class Param(FieldInfo): # type: ignore[misc] def __init__( self, default: Any, *, alias: Optional[str] = None, title: Optional[str] = None, description: Optional[str] = None, gt: Optional[float] = None, ge: Optional[float] = None, lt: Optional[float] = None, le: Optional[float] = None, min_length: Optional[int] = None, max_length: Optional[int] = None, example: Optional[Any] = None, examples: Optional[Dict[str, Any]] = None, deprecated: Optional[bool] = None, include_in_schema: Optional[bool] = True, pattern: Union[str, Pattern[str], None] = None, # param_name: str = None, # param_type: Any = None, **extra: Any, ): self.deprecated = deprecated # self.param_name: str = None # self.param_type: Any = None self.model_field: Optional[FieldInfo] = None json_schema_extra = {} if example: json_schema_extra["example"] = example if examples: json_schema_extra["examples"] = examples if deprecated: json_schema_extra["deprecated"] = deprecated if not include_in_schema: json_schema_extra["include_in_schema"] = include_in_schema if alias and not extra.get("validation_alias"): extra["validation_alias"] = alias if alias and not extra.get("serialization_alias"): extra["serialization_alias"] = alias super().__init__( default=default, alias=alias, title=title, description=description, gt=gt, ge=ge, lt=lt, le=le, min_length=min_length, max_length=max_length, pattern=pattern, json_schema_extra=json_schema_extra, **extra, ) @classmethod def _param_source(cls) -> str: "Openapi param.in value or body type" return cls.__name__.lower() class Path(Param): # type: ignore[misc] _model = PathModel class Query(Param): # type: ignore[misc] _model = QueryModel class Header(Param): # type: ignore[misc] _model = HeaderModel class Cookie(Param): # type: ignore[misc] _model = CookieModel class Body(Param): # type: ignore[misc] _model = BodyModel class Form(Param): # type: ignore[misc] _model = FormModel class File(Param): # type: ignore[misc] _model = FileModel class _MultiPartBody(Param): # type: ignore[misc] _model = _MultiPartBodyModel @classmethod def _param_source(cls) -> str: return "body" ================================================ FILE: ninja/parser.py ================================================ import json from typing import List, cast from django.http import HttpRequest from django.utils.datastructures import MultiValueDict from ninja.types import DictStrAny __all__ = ["Parser"] class Parser: "Default json parser" def parse_body(self, request: HttpRequest) -> DictStrAny: return cast(DictStrAny, json.loads(request.body)) def parse_querydict( self, data: MultiValueDict, list_fields: List[str], request: HttpRequest ) -> DictStrAny: result: DictStrAny = {} for key in data.keys(): if key in list_fields: result[key] = data.getlist(key) else: result[key] = data[key] return result ================================================ FILE: ninja/patch_dict.py ================================================ from typing import ( TYPE_CHECKING, Any, Dict, Generic, Optional, Type, TypeVar, ) from pydantic import BaseModel from pydantic_core import core_schema from ninja import Body from ninja.orm import ModelSchema from ninja.schema import Schema from ninja.utils import is_optional_type class ModelToDict(dict): _wrapped_model: Any = None _wrapped_model_dump_params: Dict[str, Any] = {} @classmethod def __get_pydantic_core_schema__(cls, _source: Any, _handler: Any) -> Any: return core_schema.no_info_after_validator_function( cls._validate, cls._wrapped_model.__pydantic_core_schema__, ) @classmethod def _validate(cls, input_value: Any) -> Any: return input_value.model_dump(**cls._wrapped_model_dump_params) def get_schema_annotations(schema_cls: Type[Any]) -> Dict[str, Any]: annotations: Dict[str, Any] = {} excluded_bases = {Schema, ModelSchema, BaseModel} bases = schema_cls.mro()[:-1] final_bases = reversed([b for b in bases if b not in excluded_bases]) for base in final_bases: annotations.update(getattr(base, "__annotations__", {})) return annotations def create_patch_schema(schema_cls: Type[Any]) -> Type[ModelToDict]: schema_annotations = get_schema_annotations(schema_cls) values, annotations = {}, {} # assert False, f"{schema_cls} - {schema_cls.model_fields}" for f in schema_cls.model_fields.keys(): t = schema_annotations[f] if not is_optional_type(t): values[f] = getattr(schema_cls, f, None) annotations[f] = Optional[t] values["__annotations__"] = annotations OptionalSchema = type(f"{schema_cls.__name__}Patch", (schema_cls,), values) class OptionalDictSchema(ModelToDict): _wrapped_model = OptionalSchema _wrapped_model_dump_params = {"exclude_unset": True} return OptionalDictSchema class PatchDictUtil: def __getitem__(self, schema_cls: Any) -> Any: new_cls = create_patch_schema(schema_cls) return Body[new_cls] # type: ignore if TYPE_CHECKING: # pragma: nocover T = TypeVar("T") class PatchDict(Dict[Any, Any], Generic[T]): pass else: PatchDict = PatchDictUtil() ================================================ FILE: ninja/py.typed ================================================ ================================================ FILE: ninja/renderers.py ================================================ import json from typing import Any, Mapping, Optional, Type from django.http import HttpRequest from ninja.responses import NinjaJSONEncoder __all__ = ["BaseRenderer", "JSONRenderer"] class BaseRenderer: media_type: Optional[str] = None charset: str = "utf-8" def render(self, request: HttpRequest, data: Any, *, response_status: int) -> Any: raise NotImplementedError("Please implement .render() method") class JSONRenderer(BaseRenderer): media_type = "application/json" encoder_class: Type[json.JSONEncoder] = NinjaJSONEncoder json_dumps_params: Mapping[str, Any] = {} def render(self, request: HttpRequest, data: Any, *, response_status: int) -> Any: return json.dumps(data, cls=self.encoder_class, **self.json_dumps_params) ================================================ FILE: ninja/responses.py ================================================ from enum import Enum from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network from typing import Any, FrozenSet, Generic, TypeVar from django.core.serializers.json import DjangoJSONEncoder from django.http import JsonResponse from pydantic import AnyUrl, BaseModel from pydantic_core import Url __all__ = [ "NinjaJSONEncoder", "Response", "Status", "codes_1xx", "codes_2xx", "codes_3xx", "codes_4xx", "codes_5xx", ] T = TypeVar("T") class Status(Generic[T]): """Return a response with an explicit HTTP status code. Usage: return Status(200, {"key": "value"}) return Status(204, None) """ __slots__ = ("status_code", "value") def __init__(self, status_code: int, value: T): self.status_code = status_code self.value = value class NinjaJSONEncoder(DjangoJSONEncoder): def default(self, o: Any) -> Any: if isinstance(o, BaseModel): return o.model_dump() if isinstance(o, (Url, AnyUrl)): return str(o) if isinstance(o, (IPv4Address, IPv4Network, IPv6Address, IPv6Network)): return str(o) if isinstance(o, Enum): return str(o) return super().default(o) class Response(JsonResponse): def __init__(self, data: Any, **kwargs: Any) -> None: super().__init__(data, encoder=NinjaJSONEncoder, safe=False, **kwargs) def resp_codes(from_code: int, to_code: int) -> FrozenSet[int]: return frozenset(range(from_code, to_code + 1)) # most common http status codes codes_1xx = resp_codes(100, 101) codes_2xx = resp_codes(200, 206) codes_3xx = resp_codes(300, 308) codes_4xx = resp_codes(400, 412) | frozenset({416, 418, 425, 429, 451}) codes_5xx = resp_codes(500, 504) ================================================ FILE: ninja/router.py ================================================ import re from dataclasses import dataclass, field from typing import ( TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Tuple, Union, ) from django.urls import URLPattern from django.urls import path as django_path from django.utils.module_loading import import_string from ninja.constants import NOT_SET, NOT_SET_TYPE from ninja.decorators import DecoratorMode from ninja.errors import ConfigError from ninja.operation import PathView from ninja.throttling import BaseThrottle from ninja.types import TCallable from ninja.utils import normalize_path, replace_path_param_notation if TYPE_CHECKING: from ninja import NinjaAPI # pragma: no cover __all__ = ["Router", "RouterMount", "BoundRouter"] @dataclass class RouterMount: """ Configuration for how a Router template is mounted to an API. This class stores the mount-time configuration without mutating the original Router template, enabling router reuse across multiple APIs or multiple mount points within the same API. """ template: "Router" prefix: str url_name_prefix: Optional[str] = None auth: Any = NOT_SET throttle: Any = NOT_SET tags: Optional[List[str]] = None inherited_decorators: List[Tuple[Callable, DecoratorMode]] = field( default_factory=list ) # Inherited auth/throttle/tags from parent routers (for nested router inheritance) inherited_auth: Any = NOT_SET inherited_throttle: Any = NOT_SET inherited_tags: Optional[List[str]] = None class BoundRouter: """ A Router template bound to a specific API instance. Contains cloned operations with decorators applied. Each mount of a router creates a new BoundRouter instance, ensuring complete isolation between mounts. """ def __init__(self, mount: RouterMount, api: "NinjaAPI") -> None: self.mount = mount self.template = mount.template self.api = api self.prefix = mount.prefix self.url_name_prefix = mount.url_name_prefix # Effective settings priority: # 1. mount override (from api.add_router auth/throttle/tags params on this specific mount) # 2. template's own settings (set on the Router itself) # 3. inherited from parent (for nested routers where parent has auth) if mount.auth is not NOT_SET: self.auth = mount.auth elif mount.template.auth is not NOT_SET: self.auth = mount.template.auth elif mount.inherited_auth is not NOT_SET: self.auth = mount.inherited_auth else: self.auth = NOT_SET if mount.throttle is not NOT_SET: self.throttle = mount.throttle elif mount.template.throttle is not NOT_SET: self.throttle = mount.template.throttle elif mount.inherited_throttle is not NOT_SET: self.throttle = mount.inherited_throttle else: self.throttle = NOT_SET # Tags handling (issue #794): # - mount.tags (from add_router call) = explicit override, use as-is # - Otherwise, accumulate: inherited tags + template's own tags self.tags: Optional[List[str]] if mount.tags is not None: # Explicit tags from add_router() call - use as override self.tags = mount.tags else: # Accumulate inherited tags with template's own tags accumulated_tags: List[str] = [] if mount.inherited_tags is not None: accumulated_tags.extend(mount.inherited_tags) if mount.template.tags is not None: accumulated_tags.extend(mount.template.tags) self.tags = accumulated_tags or None # Clone operations and apply decorators self.path_operations: Dict[str, PathView] = {} self._bind_operations() def _bind_operations(self) -> None: """Clone operations from template and apply effective settings.""" effective_decorators = ( self.mount.inherited_decorators + self.template._decorators ) for path, path_view in self.template.path_operations.items(): cloned_view = path_view.clone() for operation in cloned_view.operations: # Bind to API operation.api = self.api # Apply auth inheritance if operation.auth_param == NOT_SET: if self.auth != NOT_SET: operation._set_auth(self.auth) elif self.api.auth != NOT_SET: operation._set_auth(self.api.auth) # Apply throttle inheritance if operation.throttle_param == NOT_SET: if self.api.throttle != NOT_SET: throttle = self.api.throttle operation.throttle_objects = ( isinstance(throttle, BaseThrottle) and [throttle] or throttle # type: ignore ) if self.throttle != NOT_SET: throttle = self.throttle operation.throttle_objects = ( isinstance(throttle, BaseThrottle) and [throttle] or throttle # type: ignore ) # Apply tags inheritance if operation.tags is None and self.tags is not None: # type: ignore[has-type] operation.tags = self.tags # type: ignore[has-type] # Apply decorators (fresh application - no tracking needed) for decorator, mode in effective_decorators: if mode == "view": operation.run = decorator(operation.run) # type: ignore elif mode == "operation": operation.view_func = decorator(operation.view_func) else: raise ValueError( f"Invalid decorator mode: {mode}" ) # pragma: no cover self.path_operations[path] = cloned_view def urls_paths(self, prefix: str) -> Iterator[URLPattern]: """Generate URL patterns for this bound router.""" prefix = replace_path_param_notation(prefix) for path, path_view in self.path_operations.items(): path = replace_path_param_notation(path) route = "/".join([i for i in (prefix, path) if i]) route = normalize_path(route) route = route.lstrip("/") for operation in path_view.operations: url_name = getattr(operation, "url_name", "") if not url_name: url_name = self.api.get_operation_url_name( operation, router=self.template ) # Apply url_name_prefix if specified if self.url_name_prefix and url_name: url_name = f"{self.url_name_prefix}_{url_name}" yield django_path(route, path_view.get_view(), name=url_name) class Router: def __init__( self, *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, tags: Optional[List[str]] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, ) -> None: self._frozen = False self.auth = auth self.throttle = throttle self.tags = tags self.by_alias = by_alias self.exclude_unset = exclude_unset self.exclude_defaults = exclude_defaults self.exclude_none = exclude_none self.path_operations: Dict[str, PathView] = {} self._routers: List[Tuple[str, Router, Optional[List[str]]]] = [] self._decorators: List[Tuple[Callable, DecoratorMode]] = [] def _freeze(self) -> None: """Mark router as frozen - no more modifications allowed.""" self._frozen = True for _, child_router, _ in self._routers: child_router._freeze() def _check_not_frozen(self) -> None: """Raise error if attempting to modify a frozen router.""" if self._frozen: raise ConfigError( "Cannot modify router after URLs have been generated. " "Routers become frozen when api.urls is accessed." ) def get( self, path: str, *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, ) -> Callable[[TCallable], TCallable]: return self.api_operation( ["GET"], path, auth=auth, throttle=throttle, response=response, operation_id=operation_id, summary=summary, description=description, tags=tags, deprecated=deprecated, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, url_name=url_name, include_in_schema=include_in_schema, openapi_extra=openapi_extra, ) def post( self, path: str, *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, ) -> Callable[[TCallable], TCallable]: return self.api_operation( ["POST"], path, auth=auth, throttle=throttle, response=response, operation_id=operation_id, summary=summary, description=description, tags=tags, deprecated=deprecated, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, url_name=url_name, include_in_schema=include_in_schema, openapi_extra=openapi_extra, ) def delete( self, path: str, *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, ) -> Callable[[TCallable], TCallable]: return self.api_operation( ["DELETE"], path, auth=auth, throttle=throttle, response=response, operation_id=operation_id, summary=summary, description=description, tags=tags, deprecated=deprecated, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, url_name=url_name, include_in_schema=include_in_schema, openapi_extra=openapi_extra, ) def patch( self, path: str, *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, ) -> Callable[[TCallable], TCallable]: return self.api_operation( ["PATCH"], path, auth=auth, throttle=throttle, response=response, operation_id=operation_id, summary=summary, description=description, tags=tags, deprecated=deprecated, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, url_name=url_name, include_in_schema=include_in_schema, openapi_extra=openapi_extra, ) def put( self, path: str, *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, ) -> Callable[[TCallable], TCallable]: return self.api_operation( ["PUT"], path, auth=auth, throttle=throttle, response=response, operation_id=operation_id, summary=summary, description=description, tags=tags, deprecated=deprecated, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, url_name=url_name, include_in_schema=include_in_schema, openapi_extra=openapi_extra, ) def api_operation( self, methods: List[str], path: str, *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, ) -> Callable[[TCallable], TCallable]: def decorator(view_func: TCallable) -> TCallable: self.add_api_operation( path, methods, view_func, auth=auth, throttle=throttle, response=response, operation_id=operation_id, summary=summary, description=description, tags=tags, deprecated=deprecated, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, url_name=url_name, include_in_schema=include_in_schema, openapi_extra=openapi_extra, ) return view_func return decorator def add_api_operation( self, path: str, methods: List[str], view_func: Callable, *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, response: Any = NOT_SET, operation_id: Optional[str] = None, summary: Optional[str] = None, description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, by_alias: Optional[bool] = None, exclude_unset: Optional[bool] = None, exclude_defaults: Optional[bool] = None, exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, ) -> None: self._check_not_frozen() path = re.sub(r"\{uuid:(\w+)\}", r"{uuidstr:\1}", path, flags=re.IGNORECASE) # django by default convert strings to UUIDs # but we want to keep them as strings to let pydantic handle conversion/validation # if user whants UUID object # uuidstr is custom registered converter # No decoration here - will be done in build_routers if path not in self.path_operations: path_view = PathView() self.path_operations[path] = path_view else: path_view = self.path_operations[path] by_alias = by_alias is None and self.by_alias or by_alias exclude_unset = exclude_unset is None and self.exclude_unset or exclude_unset exclude_defaults = ( exclude_defaults is None and self.exclude_defaults or exclude_defaults ) exclude_none = exclude_none is None and self.exclude_none or exclude_none path_view.add_operation( path=path, methods=methods, view_func=view_func, auth=auth, throttle=throttle, response=response, operation_id=operation_id, summary=summary, description=description, tags=tags, deprecated=deprecated, by_alias=by_alias, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, exclude_none=exclude_none, url_name=url_name, include_in_schema=include_in_schema, openapi_extra=openapi_extra, ) # Note: API binding is now done via BoundRouter when urls are generated return None def urls_paths( self, prefix: str, api: Optional["NinjaAPI"] = None ) -> Iterator[URLPattern]: """ Generate URL patterns for this router. Note: This method is primarily for internal use. For mounting routers to APIs, use NinjaAPI.add_router() which handles proper binding via BoundRouter. Args: prefix: URL prefix for all paths api: Optional API instance for generating URL names (for backward compat) """ # Ensure decorators are applied before generating URLs self._apply_decorators_to_operations() prefix = replace_path_param_notation(prefix) for path, path_view in self.path_operations.items(): for operation in path_view.operations: path = replace_path_param_notation(path) route = "/".join([i for i in (prefix, path) if i]) # to skip lot of checks we simply treat double slash as a mistake: route = normalize_path(route) route = route.lstrip("/") url_name = getattr(operation, "url_name", "") if not url_name and api: url_name = api.get_operation_url_name(operation, router=self) yield django_path(route, path_view.get_view(), name=url_name) def add_router( self, prefix: str, router: Union["Router", str], *, auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, tags: Optional[List[str]] = None, ) -> None: self._check_not_frozen() if isinstance(router, str): router = import_string(router) assert isinstance(router, Router) # Store child router with its mount-time configuration # Auth/throttle are stored on the child router template, # but tags from add_router are stored separately to distinguish from Router(tags=...) if auth != NOT_SET: router.auth = auth if throttle != NOT_SET: router.throttle = throttle # Store as (prefix, router, tags) - tags here are mount-level overrides self._routers.append((prefix, router, tags)) def add_decorator( self, decorator: Callable, mode: DecoratorMode = "operation", ) -> None: """ Add a decorator to be applied to all operations in this router. Args: decorator: The decorator function to apply mode: "operation" (default) applies after validation, "view" applies before validation """ self._check_not_frozen() if mode not in ("view", "operation"): raise ValueError(f"Invalid decorator mode: {mode}") self._decorators.append((decorator, mode)) def build_routers( self, prefix: str, inherited_decorators: Optional[List[Tuple[Callable, DecoratorMode]]] = None, inherited_auth: Any = NOT_SET, inherited_throttle: Any = NOT_SET, inherited_tags: Optional[List[str]] = None, ) -> List[RouterMount]: """ Build mount configurations for this router and all child routers. This method does NOT mutate any router state - it returns a list of RouterMount objects that describe how to bind routers to an API. Args: prefix: The URL prefix for this router inherited_decorators: Decorators inherited from parent routers/API inherited_auth: Auth inherited from parent routers inherited_throttle: Throttle inherited from parent routers inherited_tags: Tags inherited from parent routers Returns: List of RouterMount configurations for this router and all descendants """ if inherited_decorators is None: inherited_decorators = [] # Create mount configuration for this router mount = RouterMount( template=self, prefix=prefix, inherited_decorators=list(inherited_decorators), inherited_auth=inherited_auth, inherited_throttle=inherited_throttle, inherited_tags=inherited_tags, ) # Calculate values to pass to children child_decorators = inherited_decorators + self._decorators # For auth/throttle/tags, effective value is used for children: # priority: this router's own setting > inherited child_auth = self.auth if self.auth is not NOT_SET else inherited_auth child_throttle = ( self.throttle if self.throttle is not NOT_SET else inherited_throttle ) child_tags = self.tags if self.tags is not None else inherited_tags # Build mounts for child routers child_mounts: List[RouterMount] = [] for child_prefix, child_router, child_mount_tags in self._routers: child_path = normalize_path("/".join((prefix, child_prefix))).lstrip("/") mounts = child_router.build_routers( child_path, child_decorators, child_auth, child_throttle, child_tags, ) # Apply mount-level tags override to the first mount (the child router itself) if mounts and child_mount_tags is not None: mounts[0].tags = child_mount_tags child_mounts.extend(mounts) return [mount, *child_mounts] def _apply_decorators_to_operations(self) -> None: """Apply all stored decorators to operations in this router""" for path_view in self.path_operations.values(): for operation in path_view.operations: # Track what decorators have already been applied to avoid duplicates applied_decorators = getattr(operation, "_applied_decorators", []) # Apply decorators that haven't been applied yet for decorator, mode in self._decorators: if (decorator, mode) not in applied_decorators: if mode == "view": operation.run = decorator(operation.run) # type: ignore elif mode == "operation": operation.view_func = decorator(operation.view_func) else: raise ValueError( f"Invalid decorator mode: {mode}" ) # pragma: no cover applied_decorators.append((decorator, mode)) # Store what decorators have been applied operation._applied_decorators = applied_decorators # type: ignore[attr-defined] ================================================ FILE: ninja/schema.py ================================================ """ Since "Model" word would be very confusing when used in django context, this module basically makes an alias for it named "Schema" and adds extra whistles to be able to work with django querysets and managers. The schema is a bit smarter than a standard pydantic Model because it can handle dotted attributes and resolver methods. For example:: class UserSchema(User): name: str initials: str boss: str = Field(None, alias="boss.first_name") @staticmethod def resolve_name(obj): return f"{obj.first_name} {obj.last_name}" """ import warnings from typing import ( Any, Callable, Dict, Type, TypeVar, Union, no_type_check, ) import pydantic from django.db.models import Manager, QuerySet from django.db.models.fields.files import FieldFile from django.template import Variable, VariableDoesNotExist from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, model_validator from pydantic._internal._model_construction import ModelMetaclass from pydantic.functional_validators import ModelWrapValidatorHandler from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue from typing_extensions import dataclass_transform from ninja.signature.utils import get_args_names, has_kwargs from ninja.types import DictStrAny pydantic_version = list(map(int, pydantic.VERSION.split(".")[:2])) assert pydantic_version >= [2, 0], "Pydantic 2.0+ required" __all__ = ["BaseModel", "Field", "DjangoGetter", "Schema"] S = TypeVar("S", bound="Schema") class DjangoGetter: __slots__ = ("_obj", "_schema_cls", "_context", "__dict__") def __init__(self, obj: Any, schema_cls: Type[S], context: Any = None): self._obj = obj self._schema_cls = schema_cls self._context = context def __getattr__(self, key: str) -> Any: # if key.startswith("__pydantic"): # return getattr(self._obj, key) resolver = self._schema_cls._ninja_resolvers.get(key) if resolver: value = resolver(getter=self) else: if isinstance(self._obj, dict): if key not in self._obj: raise AttributeError(key) value = self._obj[key] else: try: value = getattr(self._obj, key) except AttributeError: try: # value = attrgetter(key)(self._obj) value = Variable(key).resolve(self._obj) # TODO: Variable(key) __init__ is actually slower than # Variable.resolve - so it better be cached except VariableDoesNotExist as e: raise AttributeError(key) from e return self._convert_result(value) # def get(self, key: Any, default: Any = None) -> Any: # try: # return self[key] # except KeyError: # return default def _convert_result(self, result: Any) -> Any: if isinstance(result, Manager): return list(result.all()) elif isinstance(result, getattr(QuerySet, "__origin__", QuerySet)): return list(result) if callable(result): return result() elif isinstance(result, FieldFile): if not result: return None return result.url return result def __repr__(self) -> str: return f"" class Resolver: __slots__ = ("_func", "_static", "_takes_context") _static: bool _func: Any _takes_context: bool def __init__(self, func: Union[Callable, staticmethod]): if isinstance(func, staticmethod): self._static = True self._func = func.__func__ else: self._static = False self._func = func arg_names = get_args_names(self._func) self._takes_context = has_kwargs(self._func) or "context" in arg_names def __call__(self, getter: DjangoGetter) -> Any: kwargs = {} if self._takes_context: kwargs["context"] = getter._context if self._static: return self._func(getter._obj, **kwargs) raise NotImplementedError( "Non static resolves are not supported yet" ) # pragma: no cover # return self._func(self._fake_instance(getter), getter._obj) # def _fake_instance(self, getter: DjangoGetter) -> "Schema": # """ # Generate a partial schema instance that can be used as the ``self`` # attribute of resolver functions. # """ # class PartialSchema(Schema): # def __getattr__(self, key: str) -> Any: # value = getattr(getter, key) # field = getter._schema_cls.model_fields[key] # value = field.validate(value, values={}, loc=key, cls=None)[0] # return value # return PartialSchema() @dataclass_transform(kw_only_default=True, field_specifiers=(Field,)) class ResolverMetaclass(ModelMetaclass): _ninja_resolvers: Dict[str, Resolver] @no_type_check def __new__(cls, name, bases, namespace, **kwargs): resolvers = {} for base in reversed(bases): base_resolvers = getattr(base, "_ninja_resolvers", None) if base_resolvers: resolvers.update(base_resolvers) for attr, resolve_func in namespace.items(): if not attr.startswith("resolve_"): continue if ( not callable(resolve_func) # A staticmethod isn't directly callable in Python <=3.9. and not isinstance(resolve_func, staticmethod) ): continue # pragma: no cover resolvers[attr[8:]] = Resolver(resolve_func) result = super().__new__(cls, name, bases, namespace, **kwargs) result._ninja_resolvers = resolvers return result class NinjaGenerateJsonSchema(GenerateJsonSchema): def default_schema(self, schema: Any) -> JsonSchemaValue: # Pydantic default actually renders null's and default_factory's # which really breaks swagger and django model callable defaults # so here we completely override behavior json_schema = self.generate_inner(schema["schema"]) default = None if "default" in schema and schema["default"] is not None: default = self.encode_default(schema["default"]) if "$ref" in json_schema: # Since reference schemas do not support child keys, we wrap the reference schema in a single-case allOf: result = {"allOf": [json_schema]} else: result = json_schema if default is not None: result["default"] = default return result class Schema(BaseModel, metaclass=ResolverMetaclass): model_config = ConfigDict(from_attributes=True) @model_validator(mode="wrap") @classmethod def _run_root_validator( cls, values: Any, handler: ModelWrapValidatorHandler[S], info: ValidationInfo ) -> Any: # If Pydantic intends to validate against the __dict__ of the immediate Schema # object, then we need to call `handler` directly on `values` before the conversion # to DjangoGetter, since any checks or modifications on DjangoGetter's __dict__ # will not persist to the original object. forbids_extra = cls.model_config.get("extra") == "forbid" should_validate_assignment = cls.model_config.get("validate_assignment", False) if forbids_extra or should_validate_assignment: handler(values) values = DjangoGetter(values, cls, info.context) return handler(values) @classmethod def from_orm(cls: Type[S], obj: Any, **kw: Any) -> S: return cls.model_validate(obj, **kw) def dict(self, *a: Any, **kw: Any) -> DictStrAny: "Backward compatibility with pydantic 1.x" return self.model_dump(*a, **kw) @classmethod def json_schema(cls) -> DictStrAny: return cls.model_json_schema(schema_generator=NinjaGenerateJsonSchema) @classmethod def schema(cls) -> DictStrAny: # type: ignore warnings.warn( ".schema() is deprecated, use .json_schema() instead", DeprecationWarning, stacklevel=2, ) return cls.json_schema() ================================================ FILE: ninja/security/__init__.py ================================================ from ninja.security.apikey import APIKeyCookie, APIKeyHeader, APIKeyQuery from ninja.security.http import HttpBasicAuth, HttpBearer from ninja.security.session import SessionAuth, SessionAuthIsStaff, SessionAuthSuperUser __all__ = [ "APIKeyCookie", "APIKeyHeader", "APIKeyQuery", "HttpBasicAuth", "HttpBearer", "SessionAuth", "SessionAuthSuperUser", "django_auth", "django_auth_superuser", "django_auth_is_staff", ] django_auth = SessionAuth() django_auth_superuser = SessionAuthSuperUser() django_auth_is_staff = SessionAuthIsStaff() ================================================ FILE: ninja/security/apikey.py ================================================ from abc import ABC, abstractmethod from typing import Any, Optional from django.http import HttpRequest from ninja.errors import HttpError from ninja.security.base import AuthBase from ninja.utils import check_csrf __all__ = ["APIKeyBase", "APIKeyQuery", "APIKeyCookie", "APIKeyHeader"] class APIKeyBase(AuthBase, ABC): openapi_type: str = "apiKey" param_name: str = "key" def __init__(self) -> None: self.openapi_name = self.param_name # this sets the name of the security schema super().__init__() def __call__(self, request: HttpRequest) -> Optional[Any]: key = self._get_key(request) return self.authenticate(request, key) @abstractmethod def _get_key(self, request: HttpRequest) -> Optional[str]: pass # pragma: no cover @abstractmethod def authenticate(self, request: HttpRequest, key: Optional[str]) -> Optional[Any]: pass # pragma: no cover class APIKeyQuery(APIKeyBase, ABC): openapi_in: str = "query" def _get_key(self, request: HttpRequest) -> Optional[str]: return request.GET.get(self.param_name) class APIKeyCookie(APIKeyBase, ABC): openapi_in: str = "cookie" def __init__(self, csrf: bool = True) -> None: self.csrf = csrf super().__init__() def _get_key(self, request: HttpRequest) -> Optional[str]: # Skip CSRF check if the operation is marked as csrf_exempt if self.csrf and not getattr(request, "_ninja_csrf_exempt", False): error_response = check_csrf(request) if error_response: raise HttpError(403, "CSRF check Failed") return request.COOKIES.get(self.param_name) class APIKeyHeader(APIKeyBase, ABC): openapi_in: str = "header" def _get_key(self, request: HttpRequest) -> Optional[str]: headers = request.headers return headers.get(self.param_name) ================================================ FILE: ninja/security/base.py ================================================ from abc import ABC, abstractmethod from typing import Any, Optional from django.http import HttpRequest from ninja.errors import ConfigError from ninja.utils import is_async_callable __all__ = ["SecuritySchema", "AuthBase"] class SecuritySchema(dict): def __init__(self, type: str, **kwargs: Any) -> None: super().__init__(type=type, **kwargs) class AuthBase(ABC): def __init__(self) -> None: if not hasattr(self, "openapi_type"): raise ConfigError("If you extend AuthBase you need to define openapi_type") kwargs = {} for attr in dir(self): if attr.startswith("openapi_"): name = attr.replace("openapi_", "", 1) kwargs[name] = getattr(self, attr) self.openapi_security_schema = SecuritySchema(**kwargs) self.is_async = False if hasattr(self, "authenticate"): # pragma: no branch self.is_async = is_async_callable(self.authenticate) @abstractmethod def __call__(self, request: HttpRequest) -> Optional[Any]: pass # pragma: no cover ================================================ FILE: ninja/security/http.py ================================================ import logging from abc import ABC, abstractmethod from base64 import b64decode from typing import Any, Optional, Tuple from urllib.parse import unquote from django.conf import settings from django.http import HttpRequest from ninja.security.base import AuthBase __all__ = ["HttpAuthBase", "HttpBearer", "DecodeError", "HttpBasicAuth"] logger = logging.getLogger("django") class HttpAuthBase(AuthBase, ABC): openapi_type: str = "http" class HttpBearer(HttpAuthBase, ABC): openapi_scheme: str = "bearer" header: str = "Authorization" def __call__(self, request: HttpRequest) -> Optional[Any]: headers = request.headers auth_value = headers.get(self.header) if not auth_value: return None parts = auth_value.split(" ") if parts[0].lower() != self.openapi_scheme: if settings.DEBUG: logger.error(f"Unexpected auth - '{auth_value}'") return None token = " ".join(parts[1:]) return self.authenticate(request, token) @abstractmethod def authenticate(self, request: HttpRequest, token: str) -> Optional[Any]: pass # pragma: no cover class DecodeError(Exception): pass class HttpBasicAuth(HttpAuthBase, ABC): # TODO: maybe HttpBasicAuthBase openapi_scheme = "basic" header = "Authorization" def __call__(self, request: HttpRequest) -> Optional[Any]: headers = request.headers auth_value = headers.get(self.header) if not auth_value: return None try: username, password = self.decode_authorization(auth_value) except DecodeError as e: if settings.DEBUG: logger.exception(e) return None return self.authenticate(request, username, password) @abstractmethod def authenticate( self, request: HttpRequest, username: str, password: str ) -> Optional[Any]: pass # pragma: no cover def decode_authorization(self, value: str) -> Tuple[str, str]: parts = value.split(" ") if len(parts) == 1: user_pass_encoded = parts[0] elif len(parts) == 2 and parts[0].lower() == "basic": user_pass_encoded = parts[1] else: raise DecodeError("Invalid Authorization header") try: username, password = b64decode(user_pass_encoded).decode().split(":", 1) return unquote(username), unquote(password) except Exception as e: # dear contributors please do not change to valueerror - here can be multiple exceptions raise DecodeError("Invalid Authorization header") from e ================================================ FILE: ninja/security/session.py ================================================ from typing import Any, Optional from django.conf import settings from django.http import HttpRequest from ninja.security.apikey import APIKeyCookie __all__ = ["SessionAuth", "SessionAuthSuperUser", "SessionAuthIsStaff"] class SessionAuth(APIKeyCookie): "Reusing Django session authentication" param_name: str = settings.SESSION_COOKIE_NAME def authenticate(self, request: HttpRequest, key: Optional[str]) -> Optional[Any]: if request.user.is_authenticated: return request.user return None class SessionAuthSuperUser(APIKeyCookie): "Reusing Django session authentication & verify that the user is a super user" param_name: str = settings.SESSION_COOKIE_NAME def authenticate(self, request: HttpRequest, key: Optional[str]) -> Optional[Any]: is_superuser = getattr(request.user, "is_superuser", None) if request.user.is_authenticated and is_superuser: return request.user return None class SessionAuthIsStaff(SessionAuthSuperUser): def authenticate(self, request: HttpRequest, key: Optional[str]) -> Optional[Any]: result = super().authenticate(request, key) if result is not None: return result if request.user.is_authenticated and getattr(request.user, "is_staff", None): return request.user return None ================================================ FILE: ninja/signature/__init__.py ================================================ from ninja.signature.details import ViewSignature from ninja.signature.utils import is_async __all__ = ["ViewSignature", "is_async"] ================================================ FILE: ninja/signature/details.py ================================================ import inspect import warnings from collections import defaultdict, namedtuple from sys import version_info from typing import Any, Callable, Dict, Generator, List, Optional, Tuple import pydantic from django.http import HttpResponse from pydantic.fields import FieldInfo from pydantic_core import PydanticUndefined from typing_extensions import Annotated, get_args, get_origin from ninja import UploadedFile from ninja.compatibility.util import UNION_TYPES from ninja.errors import ConfigError from ninja.params.models import ( Body, File, Form, Param, Path, Query, TModel, TModels, _MultiPartBody, ) from ninja.signature.utils import get_path_param_names, get_typed_signature from ninja.utils import is_optional_type __all__ = [ "ViewSignature", "is_pydantic_model", "is_collection_type", "detect_collection_fields", ] FuncParam = namedtuple( "FuncParam", ["name", "alias", "source", "annotation", "is_collection"] ) class ViewSignature: FLATTEN_PATH_SEP = ( "\x1e" # ASCII Record Separator. IE: not generally used in query names ) response_arg: Optional[str] = None def __init__(self, path: str, view_func: Callable[..., Any]) -> None: self.view_func = view_func self.signature = get_typed_signature(self.view_func) self.path = path self.path_params_names = get_path_param_names(path) self.docstring = inspect.cleandoc(view_func.__doc__ or "") self.has_kwargs = False self.params = [] for name, arg in self.signature.parameters.items(): if name == "request": # TODO: maybe better assert that 1st param is request or check by type? # maybe even have attribute like `has_request` # so that users can ignore passing request if not needed continue if arg.kind == arg.VAR_KEYWORD: # Skipping **kwargs self.has_kwargs = True continue if arg.kind == arg.VAR_POSITIONAL: # Skipping *args continue if arg.annotation is HttpResponse: self.response_arg = name continue if ( arg.annotation is inspect.Parameter.empty and isinstance(arg.default, type) and issubclass(arg.default, pydantic.BaseModel) ): raise ConfigError( f"Looks like you are using `{name}={arg.default.__name__}` instead of `{name}: {arg.default.__name__}` (annotation)" ) func_param = self._get_param_type(name, arg) self.params.append(func_param) if hasattr(view_func, "_ninja_contribute_args"): # _ninja_contribute_args is a special attribute # which allows developers to create custom function params # inside decorators or other functions for p_name, p_type, p_source in view_func._ninja_contribute_args: self.params.append( FuncParam(p_name, p_source.alias or p_name, p_source, p_type, False) ) self.models: TModels = self._create_models() self._validate_view_path_params() def _validate_view_path_params(self) -> None: """verify all path params are present in the path model fields""" if self.path_params_names: path_model = next( (m for m in self.models if m.__ninja_param_source__ == "path"), None ) missing = tuple( sorted( name for name in self.path_params_names if not (path_model and name in path_model.__ninja_flatten_map__) ) ) if missing: warnings.warn_explicit( UserWarning( f"Field(s) {missing} are in the view path, but were not found in the view signature." ), category=None, filename=inspect.getfile(self.view_func), lineno=inspect.getsourcelines(self.view_func)[1], source=None, ) def _create_models(self) -> TModels: params_by_source_cls: Dict[Any, List[FuncParam]] = defaultdict(list) for param in self.params: param_source_cls = type(param.source) params_by_source_cls[param_source_cls].append(param) is_multipart_response_with_body = Body in params_by_source_cls and ( File in params_by_source_cls or Form in params_by_source_cls ) if is_multipart_response_with_body: params_by_source_cls[_MultiPartBody] = params_by_source_cls.pop(Body) result = [] for param_cls, args in params_by_source_cls.items(): cls_name: str = param_cls.__name__ + "Params" attrs = {i.name: i.source for i in args} attrs["__ninja_param_source__"] = param_cls._param_source() attrs["__ninja_flatten_map_reverse__"] = {} if attrs["__ninja_param_source__"] == "file": pass elif attrs["__ninja_param_source__"] in { "form", "query", "header", "cookie", "path", }: flatten_map = self._args_flatten_map(args) attrs["__ninja_flatten_map__"] = flatten_map attrs["__ninja_flatten_map_reverse__"] = { v: (k,) for k, v in flatten_map.items() } else: assert attrs["__ninja_param_source__"] == "body" if is_multipart_response_with_body: attrs["__ninja_body_params__"] = { i.alias: i.annotation for i in args } else: # ::TODO:: this is still sus. build some test cases attrs["__read_from_single_attr__"] = ( args[0].name if len(args) == 1 else None ) # adding annotations attrs["__annotations__"] = {i.name: i.annotation for i in args} # collection fields: attrs["__ninja_collection_fields__"] = detect_collection_fields( args, attrs.get("__ninja_flatten_map__", {}) ) base_cls = param_cls._model model_cls = type(cls_name, (base_cls,), attrs) # TODO: https://pydantic-docs.helpmanual.io/usage/models/#dynamic-model-creation - check if anything special in create_model method that I did not use result.append(model_cls) return result def _args_flatten_map(self, args: List[FuncParam]) -> Dict[str, Tuple[str, ...]]: flatten_map = {} arg_names: Any = {} for arg in args: if is_pydantic_model(arg.annotation): for name, path in self._model_flatten_map(arg.annotation, arg.alias): if name in flatten_map: raise ConfigError( f"Duplicated name: '{name}' in params: '{arg_names[name]}' & '{arg.name}'" ) flatten_map[name] = tuple(path.split(self.FLATTEN_PATH_SEP)) arg_names[name] = arg.name else: name = arg.alias if name in flatten_map: raise ConfigError( f"Duplicated name: '{name}' also in '{arg_names[name]}'" ) flatten_map[name] = (name,) arg_names[name] = name return flatten_map def _model_flatten_map(self, model: TModel, prefix: str) -> Generator: model = _unwrap_union_model(model) field: FieldInfo for attr, field in model.model_fields.items(): field_name = field.alias or attr name = f"{prefix}{self.FLATTEN_PATH_SEP}{field_name}" if is_pydantic_model(field.annotation): yield from self._model_flatten_map(field.annotation, name) # type: ignore else: yield field_name, name def _get_param_type(self, name: str, arg: inspect.Parameter) -> FuncParam: # _EMPTY = self.signature.empty annotation = arg.annotation default = arg.default if get_origin(annotation) is Annotated: args = get_args(annotation) if isinstance(args[-1], Param): prev_default = default if len(args) == 2: annotation, default = args else: # TODO: Remove version check once support for <=3.8 is dropped. # Annotated[] is only available at runtime in 3.9+ per # https://docs.python.org/3/library/typing.html#typing.Annotated if version_info >= (3, 9): # NOTE: Annotated[args[:-1]] seems to have the same runtime # behavior as Annotated[*args[:-1]], but the latter is # invalid in Python < 3.11 because star expressions # were not allowed in index expressions. annotation, default = Annotated[args[:-1]], args[-1] else: # pragma: no cover -- requires specific Python versions raise NotImplementedError( "This definition requires Python version 3.9+" ) if prev_default != self.signature.empty: default.default = prev_default if annotation == self.signature.empty: if default == self.signature.empty: annotation = str else: if isinstance(default, Param): annotation = type(default.default) else: annotation = type(default) if annotation == PydanticUndefined.__class__: # TODO: ^ check why is that so annotation = str if annotation == type(None) or annotation == type(Ellipsis): # noqa annotation = str is_collection = is_collection_type(annotation) if annotation == UploadedFile or ( is_collection and annotation.__args__[0] == UploadedFile ): # People often forgot to mark UploadedFile as a File, so we better assign it automatically if default == self.signature.empty or default is None: default = default == self.signature.empty and ... or default return FuncParam(name, name, File(default), annotation, is_collection) # 1) if type of the param is defined as one of the Param's subclasses - we just use that definition if isinstance(default, Param): param_source = default # 2) if param name is a part of the path parameter elif name in self.path_params_names: assert ( default == self.signature.empty ), f"'{name}' is a path param, default not allowed" param_source = Path(...) # 3) if param is a collection, or annotation is part of pydantic model: elif is_collection or is_pydantic_model(annotation): if default == self.signature.empty: param_source = Body(...) else: param_source = Body(default) # 4) the last case is query param else: if default == self.signature.empty: param_source = Query(...) else: param_source = Query(default) # If default is None but annotation is not Optional, # wrap it in Optional to allow None values in Pydantic v2 if default is None and not is_optional_type(annotation): annotation = Optional[annotation] return FuncParam( name, param_source.alias or name, param_source, annotation, is_collection ) def _unwrap_union_model(annotation: Any) -> Any: """If annotation is a Union containing a pydantic model, return that model class.""" if get_origin(annotation) in UNION_TYPES: for arg in get_args(annotation): if arg is not type(None) and is_pydantic_model(arg): return arg return annotation def is_pydantic_model(cls: Any) -> bool: try: origin = get_origin(cls) # Handle Annotated types - extract the actual type if origin is Annotated: args = get_args(cls) return is_pydantic_model(args[0]) # Handle Union types if origin in UNION_TYPES: return any(issubclass(arg, pydantic.BaseModel) for arg in get_args(cls)) return issubclass(cls, pydantic.BaseModel) except TypeError: # pragma: no cover return False def is_collection_type(annotation: Any) -> bool: origin = get_origin(annotation) if origin in UNION_TYPES: for arg in get_args(annotation): if is_collection_type(arg): return True return False collection_types = (List, list, set, tuple) if origin is None: return ( isinstance(annotation, collection_types) if not isinstance(annotation, type) else issubclass(annotation, collection_types) ) else: return origin in collection_types # TODO: I guess we should handle only list def detect_collection_fields( args: List[FuncParam], flatten_map: Dict[str, Tuple[str, ...]] ) -> List[str]: """ Django QueryDict has values that are always lists, so we need to help django ninja to understand better the input parameters if it's a list or a single value This method detects attributes that should be treated by ninja as lists and returns this list as a result """ result = [i.alias or i.name for i in args if i.is_collection] if flatten_map: args_d = {arg.alias: arg for arg in args} for path in (p for p in flatten_map.values() if len(p) > 1): annotation_or_field: Any = args_d[path[0]].annotation for attr in path[1:]: if hasattr(annotation_or_field, "annotation"): annotation_or_field = annotation_or_field.annotation annotation_or_field = _unwrap_union_model(annotation_or_field) annotation_or_field = next( ( a for a in annotation_or_field.model_fields.values() if a.alias == attr ), annotation_or_field.model_fields.get(attr), ) # pragma: no cover annotation_or_field = getattr( annotation_or_field, "outer_type_", annotation_or_field ) # if hasattr(annotation_or_field, "annotation"): annotation_or_field = annotation_or_field.annotation if is_collection_type(annotation_or_field): result.append(path[-1]) return result ================================================ FILE: ninja/signature/utils.py ================================================ import asyncio import inspect import re from sys import version_info from typing import Any, Callable, ForwardRef, List, Set from django.urls import register_converter from django.urls.converters import UUIDConverter from pydantic._internal._typing_extra import eval_type_lenient as evaluate_forwardref from ninja.types import DictStrAny __all__ = [ "get_typed_signature", "get_typed_annotation", "make_forwardref", "get_path_param_names", "is_async", ] def get_typed_signature(call: Callable[..., Any]) -> inspect.Signature: "Finds call signature and resolves all forwardrefs" signature = inspect.signature(call) globalns = getattr(call, "__globals__", {}) typed_params = [ inspect.Parameter( name=param.name, kind=param.kind, default=param.default, annotation=get_typed_annotation(param, globalns), ) for param in signature.parameters.values() ] typed_signature = inspect.Signature(typed_params) return typed_signature def get_typed_annotation(param: inspect.Parameter, globalns: DictStrAny) -> Any: annotation = param.annotation if isinstance(annotation, str): annotation = make_forwardref(annotation, globalns) return annotation def make_forwardref(annotation: str, globalns: DictStrAny) -> Any: # NOTE: in future versions of pydantic, the import may be changed to: # from pydantic._internal._typing_extra import try_eval_type # usage: # result, _ = try_eval_type(forward_ref, globalns, globalns) forward_ref = ForwardRef(annotation) return evaluate_forwardref(forward_ref, globalns, globalns) def get_path_param_names(path: str) -> Set[str]: """turns path string like /foo/{var}/path/{int:another}/end to set {'var', 'another'}""" return {item.strip("{}").split(":")[-1] for item in re.findall("{[^}]*}", path)} def is_async(callable: Callable[..., Any]) -> bool: # TODO: Drop this condition once support for <= 3.11 is dropped if version_info >= (3, 12): return inspect.iscoroutinefunction(callable) else: return asyncio.iscoroutinefunction(callable) # pragma: no cover def has_kwargs(func: Callable[..., Any]) -> bool: for param in inspect.signature(func).parameters.values(): if param.kind == param.VAR_KEYWORD: return True return False def get_args_names(func: Callable[..., Any]) -> List[str]: "returns list of function argument names" return list(inspect.signature(func).parameters.keys()) class UUIDStrConverter(UUIDConverter): """Return a path converted UUID as a str instead of the standard UUID""" def to_python(self, value: str) -> str: # type: ignore return value # return string value instead of UUID register_converter(UUIDStrConverter, "uuidstr") ================================================ FILE: ninja/static/ninja/redoc.standalone.js ================================================ /*! For license information please see redoc.standalone.js.LICENSE.txt */ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("null")):"function"==typeof define&&define.amd?define(["null"],t):"object"==typeof exports?exports.Redoc=t(require("null")):e.Redoc=t(e.null)}(this,(function(e){return function(){var t={854:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{l(r.next(e))}catch(e){o(e)}}function a(e){try{l(r.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.mapTypeToComponent=t.bundleDocument=t.bundleFromString=t.bundle=t.OasVersion=void 0;const i=n(8142),o=n(2928),s=n(2161),a=n(1990),l=n(5735),c=n(3101),u=n(3873),p=n(2900),d=n(3416),f=n(8209),h=n(4125),m=n(474),g=n(4335);var y;function b(e){return r(this,void 0,void 0,(function*(){const{document:t,config:n,customTypes:r,externalRefResolver:i,dereference:u=!1,skipRedoclyRegistryRefs:d=!1,removeUnusedComponents:f=!1,keepUrlRefs:h=!1}=e,y=(0,c.detectSpec)(t.parsed),b=(0,c.getMajorSpecVersion)(y),v=n.getRulesForOasVersion(b),w=(0,a.normalizeTypes)(n.extendTypes(null!=r?r:(0,c.getTypes)(y),y),n),k=(0,p.initRules)(v,n,"preprocessors",y),S=(0,p.initRules)(v,n,"decorators",y),E={problems:[],oasVersion:y,refTypes:new Map,visitorsData:{}};f&&S.push({severity:"error",ruleId:"remove-unused-components",visitor:b===c.SpecMajorVersion.OAS2?(0,m.RemoveUnusedComponents)({}):(0,g.RemoveUnusedComponents)({})});let O=yield(0,o.resolveDocument)({rootDocument:t,rootType:w.Root,externalRefResolver:i});k.length>0&&((0,l.walkDocument)({document:t,rootType:w.Root,normalizedVisitors:(0,s.normalizeVisitors)(k,w),resolvedRefMap:O,ctx:E}),O=yield(0,o.resolveDocument)({rootDocument:t,rootType:w.Root,externalRefResolver:i}));const _=(0,s.normalizeVisitors)([{severity:"error",ruleId:"bundler",visitor:x(b,u,d,t,O,h)},...S],w);return(0,l.walkDocument)({document:t,rootType:w.Root,normalizedVisitors:_,resolvedRefMap:O,ctx:E}),{bundle:t,problems:E.problems.map((e=>n.addProblemToIgnore(e))),fileDependencies:i.getFiles(),rootType:w.Root,refTypes:E.refTypes,visitorsData:E.visitorsData}}))}function v(e,t){switch(t){case c.SpecMajorVersion.OAS3:switch(e){case"Schema":return"schemas";case"Parameter":return"parameters";case"Response":return"responses";case"Example":return"examples";case"RequestBody":return"requestBodies";case"Header":return"headers";case"SecuritySchema":return"securitySchemes";case"Link":return"links";case"Callback":return"callbacks";default:return null}case c.SpecMajorVersion.OAS2:switch(e){case"Schema":return"definitions";case"Parameter":return"parameters";case"Response":return"responses";default:return null}case c.SpecMajorVersion.Async2:switch(e){case"Schema":return"schemas";case"Parameter":return"parameters";default:return null}}}function x(e,t,n,r,s,a){let l,p;const m={ref:{leave(i,l,c){if(!c.location||void 0===c.node)return void(0,d.reportUnresolvedRef)(c,l.report,l.location);if(c.location.source===r.source&&c.location.source===l.location.source&&"scalar"!==l.type.name&&!t)return;if(n&&(0,h.isRedoclyRegistryURL)(i.$ref))return;if(a&&(0,u.isAbsoluteUrl)(i.$ref))return;const p=v(l.type.name,e);p?t?(y(p,c,l),g(i,c,l)):(i.$ref=y(p,c,l),function(e,t,n){const i=(0,o.makeRefId)(n.location.source.absoluteRef,e.$ref);s.set(i,{document:r,isRemote:!1,node:t.node,nodePointer:e.$ref,resolved:!0})}(i,c,l)):g(i,c,l)}},Root:{enter(t,n){p=n.location,e===c.SpecMajorVersion.OAS3?l=t.components=t.components||{}:e===c.SpecMajorVersion.OAS2&&(l=t)}}};function g(e,t,n){if((0,f.isPlainObject)(t.node)){delete e.$ref;const n=Object.assign({},t.node,e);Object.assign(e,n)}else n.parent[n.key]=t.node}function y(t,n,r){l[t]=l[t]||{};const i=function(e,t,n){const[r,i]=[e.location.source.absoluteRef,e.location.pointer],o=l[t];let s="";const a=i.slice(2).split("/").filter(f.isTruthy);for(;a.length>0;)if(s=a.pop()+(s?`-${s}`:""),!o||!o[s]||b(o[s],e,n))return s;if(s=(0,u.refBaseName)(r)+(s?`_${s}`:""),!o[s]||b(o[s],e,n))return s;const c=s;let p=2;for(;o[s]&&!b(o[s],e,n);)s=`${c}-${p}`,p++;return o[s]||n.report({message:`Two schemas are referenced with the same name but different content. Renamed ${c} to ${s}.`,location:n.location,forceSeverity:"warn"}),s}(n,t,r);return l[t][i]=n.node,e===c.SpecMajorVersion.OAS3?`#/components/${t}/${i}`:`#/${t}/${i}`}function b(e,t,n){var r;return!(!(0,u.isRef)(e)||(null===(r=n.resolve(e,p.absolutePointer).location)||void 0===r?void 0:r.absolutePointer)!==t.location.absolutePointer)||i(e,t.node)}return e===c.SpecMajorVersion.OAS3&&(m.DiscriminatorMapping={leave(n,r){for(const i of Object.keys(n)){const o=n[i],s=r.resolve({$ref:o});if(!s.location||void 0===s.node)return void(0,d.reportUnresolvedRef)(s,r.report,r.location.child(i));const a=v("Schema",e);t?y(a,s,r):n[i]=y(a,s,r)}}}),m}!function(e){e.Version2="oas2",e.Version3_0="oas3_0",e.Version3_1="oas3_1"}(y||(t.OasVersion=y={})),t.bundle=function(e){return r(this,void 0,void 0,(function*(){const{ref:t,doc:n,externalRefResolver:r=new o.BaseResolver(e.config.resolve),base:i=null}=e;if(!t&&!n)throw new Error("Document or reference is required.\n");const s=void 0===n?yield r.resolveDocument(i,t,!0):n;if(s instanceof Error)throw s;return b(Object.assign(Object.assign({document:s},e),{config:e.config.styleguide,externalRefResolver:r}))}))},t.bundleFromString=function(e){return r(this,void 0,void 0,(function*(){const{source:t,absoluteRef:n,externalRefResolver:r=new o.BaseResolver(e.config.resolve)}=e,i=(0,o.makeDocumentFromString)(t,n||"/");return b(Object.assign(Object.assign({document:i},e),{externalRefResolver:r,config:e.config.styleguide}))}))},t.bundleDocument=b,t.mapTypeToComponent=v},8921:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Config=t.StyleguideConfig=t.AVAILABLE_REGIONS=t.DOMAINS=t.DEFAULT_REGION=t.IGNORE_FILE=void 0;const r=n(7992),i=n(7975),o=n(970),s=n(8209),a=n(3101),l=n(1827),c=n(462),u=n(3873);t.IGNORE_FILE=".redocly.lint-ignore.yaml",t.DEFAULT_REGION="us",t.DOMAINS=function(){const e={us:"redocly.com",eu:"eu.redocly.com"},t=l.env.REDOCLY_DOMAIN;return(null==t?void 0:t.endsWith(".redocly.host"))&&(e[t.split(".")[0]]=t),"redoc.online"===t&&(e[t]=t),e}(),t.AVAILABLE_REGIONS=Object.keys(t.DOMAINS);class p{constructor(e,n){this.rawConfig=e,this.configFile=n,this.ignore={},this._usedRules=new Set,this._usedVersions=new Set,this.plugins=e.plugins||[],this.doNotResolveExamples=!!e.doNotResolveExamples,this.recommendedFallback=e.recommendedFallback||!1,this.rules={[a.SpecVersion.OAS2]:Object.assign(Object.assign({},e.rules),e.oas2Rules),[a.SpecVersion.OAS3_0]:Object.assign(Object.assign({},e.rules),e.oas3_0Rules),[a.SpecVersion.OAS3_1]:Object.assign(Object.assign({},e.rules),e.oas3_1Rules),[a.SpecVersion.Async2]:Object.assign(Object.assign({},e.rules),e.async2Rules)},this.preprocessors={[a.SpecVersion.OAS2]:Object.assign(Object.assign({},e.preprocessors),e.oas2Preprocessors),[a.SpecVersion.OAS3_0]:Object.assign(Object.assign({},e.preprocessors),e.oas3_0Preprocessors),[a.SpecVersion.OAS3_1]:Object.assign(Object.assign({},e.preprocessors),e.oas3_1Preprocessors),[a.SpecVersion.Async2]:Object.assign(Object.assign({},e.preprocessors),e.async2Preprocessors)},this.decorators={[a.SpecVersion.OAS2]:Object.assign(Object.assign({},e.decorators),e.oas2Decorators),[a.SpecVersion.OAS3_0]:Object.assign(Object.assign({},e.decorators),e.oas3_0Decorators),[a.SpecVersion.OAS3_1]:Object.assign(Object.assign({},e.decorators),e.oas3_1Decorators),[a.SpecVersion.Async2]:Object.assign(Object.assign({},e.decorators),e.async2Decorators)},this.extendPaths=e.extendPaths||[],this.pluginPaths=e.pluginPaths||[],this.resolveIgnore(function(e){return e?(0,s.doesYamlFileExist)(e)?i.join(i.dirname(e),t.IGNORE_FILE):i.join(e,t.IGNORE_FILE):l.isBrowser?void 0:i.join(process.cwd(),t.IGNORE_FILE)}(n))}resolveIgnore(e){if(e&&(0,s.doesYamlFileExist)(e)){this.ignore=(0,o.parseYaml)(r.readFileSync(e,"utf-8"))||{};for(const t of Object.keys(this.ignore)){this.ignore[(0,u.isAbsoluteUrl)(t)?t:i.resolve(i.dirname(e),t)]=this.ignore[t];for(const e of Object.keys(this.ignore[t]))this.ignore[t][e]=new Set(this.ignore[t][e]);(0,u.isAbsoluteUrl)(t)||delete this.ignore[t]}}}saveIgnore(){const e=this.configFile?i.dirname(this.configFile):process.cwd(),n=i.join(e,t.IGNORE_FILE),a={};for(const t of Object.keys(this.ignore)){const n=a[(0,u.isAbsoluteUrl)(t)?t:(0,s.slash)(i.relative(e,t))]=this.ignore[t];for(const e of Object.keys(n))n[e]=Array.from(n[e])}r.writeFileSync(n,"# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API.\n# See https://redoc.ly/docs/cli/ for more information.\n"+(0,o.stringifyYaml)(a))}addIgnore(e){const t=this.ignore,n=e.location[0];if(void 0===n.pointer)return;const r=t[n.source.absoluteRef]=t[n.source.absoluteRef]||{};(r[e.ruleId]=r[e.ruleId]||new Set).add(n.pointer)}addProblemToIgnore(e){const t=e.location[0];if(void 0===t.pointer)return e;const n=(this.ignore[t.source.absoluteRef]||{})[e.ruleId],r=n&&n.has(t.pointer);return r?Object.assign(Object.assign({},e),{ignored:r}):e}extendTypes(e,t){let n=e;for(const e of this.plugins)if(void 0!==e.typeExtension)switch(t){case a.SpecVersion.OAS3_0:case a.SpecVersion.OAS3_1:if(!e.typeExtension.oas3)continue;n=e.typeExtension.oas3(n,t);break;case a.SpecVersion.OAS2:if(!e.typeExtension.oas2)continue;n=e.typeExtension.oas2(n,t);break;case a.SpecVersion.Async2:if(!e.typeExtension.async2)continue;n=e.typeExtension.async2(n,t);break;default:throw new Error("Not implemented")}return n}getRuleSettings(e,t){this._usedRules.add(e),this._usedVersions.add(t);const n=this.rules[t][e]||"off";return"string"==typeof n?{severity:n}:Object.assign({severity:"error"},n)}getPreprocessorSettings(e,t){this._usedRules.add(e),this._usedVersions.add(t);const n=this.preprocessors[t][e]||"off";return"string"==typeof n?{severity:"on"===n?"error":n}:Object.assign({severity:"error"},n)}getDecoratorSettings(e,t){this._usedRules.add(e),this._usedVersions.add(t);const n=this.decorators[t][e]||"off";return"string"==typeof n?{severity:"on"===n?"error":n}:Object.assign({severity:"error"},n)}getUnusedRules(){const e=[],t=[],n=[];for(const r of Array.from(this._usedVersions))e.push(...Object.keys(this.rules[r]).filter((e=>!this._usedRules.has(e)))),t.push(...Object.keys(this.decorators[r]).filter((e=>!this._usedRules.has(e)))),n.push(...Object.keys(this.preprocessors[r]).filter((e=>!this._usedRules.has(e))));return{rules:e,preprocessors:n,decorators:t}}getRulesForOasVersion(e){switch(e){case a.SpecMajorVersion.OAS3:const e=[];return this.plugins.forEach((t=>{var n;return(null===(n=t.preprocessors)||void 0===n?void 0:n.oas3)&&e.push(t.preprocessors.oas3)})),this.plugins.forEach((t=>{var n;return(null===(n=t.rules)||void 0===n?void 0:n.oas3)&&e.push(t.rules.oas3)})),this.plugins.forEach((t=>{var n;return(null===(n=t.decorators)||void 0===n?void 0:n.oas3)&&e.push(t.decorators.oas3)})),e;case a.SpecMajorVersion.OAS2:const t=[];return this.plugins.forEach((e=>{var n;return(null===(n=e.preprocessors)||void 0===n?void 0:n.oas2)&&t.push(e.preprocessors.oas2)})),this.plugins.forEach((e=>{var n;return(null===(n=e.rules)||void 0===n?void 0:n.oas2)&&t.push(e.rules.oas2)})),this.plugins.forEach((e=>{var n;return(null===(n=e.decorators)||void 0===n?void 0:n.oas2)&&t.push(e.decorators.oas2)})),t;case a.SpecMajorVersion.Async2:const n=[];return this.plugins.forEach((e=>{var t;return(null===(t=e.preprocessors)||void 0===t?void 0:t.async2)&&n.push(e.preprocessors.async2)})),this.plugins.forEach((e=>{var t;return(null===(t=e.rules)||void 0===t?void 0:t.async2)&&n.push(e.rules.async2)})),this.plugins.forEach((e=>{var t;return(null===(t=e.decorators)||void 0===t?void 0:t.async2)&&n.push(e.decorators.async2)})),n}}skipRules(e){for(const t of e||[])for(const e of Object.values(a.SpecVersion))this.rules[e][t]&&(this.rules[e][t]="off")}skipPreprocessors(e){for(const t of e||[])for(const e of Object.values(a.SpecVersion))this.preprocessors[e][t]&&(this.preprocessors[e][t]="off")}skipDecorators(e){for(const t of e||[])for(const e of Object.values(a.SpecVersion))this.decorators[e][t]&&(this.decorators[e][t]="off")}}t.StyleguideConfig=p,t.Config=class{constructor(e,t){this.rawConfig=e,this.configFile=t,this.apis=e.apis||{},this.styleguide=new p(e.styleguide||{},t),this.theme=e.theme||{},this.resolve=(0,c.getResolveConfig)(null==e?void 0:e.resolve),this.region=e.region,this.organization=e.organization,this.files=e.files||[],this.telemetry=e.telemetry}}},2900:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.initRules=void 0;const r=n(8209);t.initRules=function(e,t,n,i){return e.flatMap((e=>Object.keys(e).map((r=>{const o=e[r],s="rules"===n?t.getRuleSettings(r,i):"preprocessors"===n?t.getPreprocessorSettings(r,i):t.getDecoratorSettings(r,i);if("off"===s.severity)return;const a=s.severity,l=o(s);return Array.isArray(l)?l.map((e=>({severity:a,ruleId:r,visitor:e}))):{severity:a,ruleId:r,visitor:l}})))).flatMap((e=>e)).filter(r.isDefined)}},462:function(e,t,n){"use strict";var r=this&&this.__rest||function(e,t){var n={};for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols){var i=0;for(r=Object.getOwnPropertySymbols(e);it[e]));n[e]&&null===t&&(0,i.showWarningForDeprecatedField)(e),n[e]&&t&&n[t]&&(0,i.showErrorForDeprecatedField)(e,t),n[e]&&r&&n[r]&&(0,i.showErrorForDeprecatedField)(e,t,r),(n[e]||o)&&(0,i.showWarningForDeprecatedField)(e,t,r)}t.parsePresetName=function(e){if(e.indexOf("/")>-1){const[t,n]=e.split("/");return{pluginId:t,configName:n}}return{pluginId:"",configName:e}},t.transformApiDefinitionsToApis=a,t.prefixRules=function(e,t){if(!t)return e;const n={};for(const r of Object.keys(e))n[`${t}/${r}`]=e[r];return n},t.mergeExtends=function(e){const t={rules:{},oas2Rules:{},oas3_0Rules:{},oas3_1Rules:{},async2Rules:{},preprocessors:{},oas2Preprocessors:{},oas3_0Preprocessors:{},oas3_1Preprocessors:{},async2Preprocessors:{},decorators:{},oas2Decorators:{},oas3_0Decorators:{},oas3_1Decorators:{},async2Decorators:{},plugins:[],pluginPaths:[],extendPaths:[]};for(const n of e){if(n.extends)throw new Error(`'extends' is not supported in shared configs yet: ${JSON.stringify(n,null,2)}.`);Object.assign(t.rules,n.rules),Object.assign(t.oas2Rules,n.oas2Rules),(0,i.assignExisting)(t.oas2Rules,n.rules||{}),Object.assign(t.oas3_0Rules,n.oas3_0Rules),(0,i.assignExisting)(t.oas3_0Rules,n.rules||{}),Object.assign(t.oas3_1Rules,n.oas3_1Rules),(0,i.assignExisting)(t.oas3_1Rules,n.rules||{}),Object.assign(t.async2Rules,n.async2Rules),(0,i.assignExisting)(t.async2Rules,n.rules||{}),Object.assign(t.preprocessors,n.preprocessors),Object.assign(t.oas2Preprocessors,n.oas2Preprocessors),(0,i.assignExisting)(t.oas2Preprocessors,n.preprocessors||{}),Object.assign(t.oas3_0Preprocessors,n.oas3_0Preprocessors),(0,i.assignExisting)(t.oas3_0Preprocessors,n.preprocessors||{}),Object.assign(t.oas3_1Preprocessors,n.oas3_1Preprocessors),(0,i.assignExisting)(t.oas3_1Preprocessors,n.preprocessors||{}),Object.assign(t.async2Preprocessors,n.async2Preprocessors),(0,i.assignExisting)(t.async2Preprocessors,n.preprocessors||{}),Object.assign(t.decorators,n.decorators),Object.assign(t.oas2Decorators,n.oas2Decorators),(0,i.assignExisting)(t.oas2Decorators,n.decorators||{}),Object.assign(t.oas3_0Decorators,n.oas3_0Decorators),(0,i.assignExisting)(t.oas3_0Decorators,n.decorators||{}),Object.assign(t.oas3_1Decorators,n.oas3_1Decorators),(0,i.assignExisting)(t.oas3_1Decorators,n.decorators||{}),Object.assign(t.async2Decorators,n.async2Decorators),(0,i.assignExisting)(t.async2Decorators,n.decorators||{}),t.plugins.push(...n.plugins||[]),t.pluginPaths.push(...n.pluginPaths||[]),t.extendPaths.push(...new Set(n.extendPaths))}return t},t.getMergedConfig=function(e,t){var n,r,s,a,l,c,u,p;const d=[...Object.values(e.apis).map((e=>{var t;return null===(t=null==e?void 0:e.styleguide)||void 0===t?void 0:t.extendPaths})),null===(r=null===(n=e.rawConfig)||void 0===n?void 0:n.styleguide)||void 0===r?void 0:r.extendPaths].flat().filter(i.isTruthy),f=[...Object.values(e.apis).map((e=>{var t;return null===(t=null==e?void 0:e.styleguide)||void 0===t?void 0:t.pluginPaths})),null===(a=null===(s=e.rawConfig)||void 0===s?void 0:s.styleguide)||void 0===a?void 0:a.pluginPaths].flat().filter(i.isTruthy);return t?new o.Config(Object.assign(Object.assign({},e.rawConfig),{styleguide:Object.assign(Object.assign({},e.apis[t]?e.apis[t].styleguide:e.rawConfig.styleguide),{extendPaths:d,pluginPaths:f}),theme:Object.assign(Object.assign({},e.rawConfig.theme),null===(l=e.apis[t])||void 0===l?void 0:l.theme),files:[...e.files,...null!==(p=null===(u=null===(c=e.apis)||void 0===c?void 0:c[t])||void 0===u?void 0:u.files)&&void 0!==p?p:[]]}),e.configFile):e},t.checkForDeprecatedFields=u,t.transformConfig=function(e){var t,n;const i=[["apiDefinitions","apis",void 0],["referenceDocs","openapi","theme"],["lint",void 0,void 0],["styleguide",void 0,void 0],["features.openapi","openapi","theme"]];for(const[t,n,r]of i)u(t,n,e,r);const{apis:o,apiDefinitions:p,referenceDocs:d,lint:f}=e,h=r(e,["apis","apiDefinitions","referenceDocs","lint"]),{styleguideConfig:m,rawConfigRest:g}=l(h),y=Object.assign({theme:{openapi:Object.assign(Object.assign(Object.assign({},d),e["features.openapi"]),null===(t=e.theme)||void 0===t?void 0:t.openapi),mockServer:Object.assign(Object.assign({},e["features.mockServer"]),null===(n=e.theme)||void 0===n?void 0:n.mockServer)},apis:c(o)||a(p),styleguide:m||f},g);return function(e){var t,n;let r=Object.assign({},null===(t=e.styleguide)||void 0===t?void 0:t.rules);for(const t of Object.values(e.apis||{}))r=Object.assign(Object.assign({},r),null===(n=null==t?void 0:t.styleguide)||void 0===n?void 0:n.rules);for(const e of Object.keys(r))e.startsWith("assert/")&&s.logger.warn(`\nThe 'assert/' syntax in ${e} is deprecated. Update your configuration to use 'rule/' instead. Examples and more information: https://redocly.com/docs/cli/rules/configurable-rules/\n`)}(y),y},t.getResolveConfig=function(e){var t,n;return{http:{headers:null!==(n=null===(t=null==e?void 0:e.http)||void 0===t?void 0:t.headers)&&void 0!==n?n:[],customFetch:void 0}}},t.getUniquePlugins=function(e){const t=new Set,n=[];for(const r of e)t.has(r.id)?r.id&&s.logger.warn(`Duplicate plugin id "${s.colorize.red(r.id)}".\n`):(n.push(r),t.add(r.id));return n};class p extends Error{}t.ConfigValidationError=p},1827:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.env=t.isBrowser=void 0,t.isBrowser="undefined"!=typeof window||"undefined"!=typeof self||"undefined"==typeof process,t.env=t.isBrowser?{}:{}||{}},970:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.stringifyYaml=t.parseYaml=void 0;const r=n(7210),i=r.JSON_SCHEMA.extend({implicit:[r.types.merge],explicit:[r.types.binary,r.types.omap,r.types.pairs,r.types.set]});t.parseYaml=(e,t)=>(0,r.load)(e,Object.assign({schema:i},t)),t.stringifyYaml=(e,t)=>(0,r.dump)(e,t)},2678:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.logger=t.colorize=t.colorOptions=void 0;const r=n(8825);var i=n(8825);Object.defineProperty(t,"colorOptions",{enumerable:!0,get:function(){return i.options}});const o=n(1827),s=n(8209);t.colorize=new Proxy(r,{get(e,t){return o.isBrowser?s.identity:e[t]}}),t.logger=new class{stderr(e){return process.stderr.write(e)}info(e){return o.isBrowser?console.log(e):this.stderr(e)}warn(e){return o.isBrowser?console.warn(e):this.stderr(t.colorize.yellow(e))}error(e){return o.isBrowser?console.error(e):this.stderr(t.colorize.red(e))}}},3101:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getTypes=t.getMajorSpecVersion=t.detectSpec=t.SpecMajorVersion=t.SpecVersion=void 0;const r=n(4409),i=n(4154),o=n(2082),s=n(264);var a,l;!function(e){e.OAS2="oas2",e.OAS3_0="oas3_0",e.OAS3_1="oas3_1",e.Async2="async2"}(a||(t.SpecVersion=a={})),function(e){e.OAS2="oas2",e.OAS3="oas3",e.Async2="async2"}(l||(t.SpecMajorVersion=l={}));const c={[a.OAS2]:r.Oas2Types,[a.OAS3_0]:i.Oas3Types,[a.OAS3_1]:o.Oas3_1Types,[a.Async2]:s.AsyncApi2Types};t.detectSpec=function(e){if("object"!=typeof e)throw new Error("Document must be JSON object, got "+typeof e);if(e.openapi&&"string"!=typeof e.openapi)throw new Error(`Invalid OpenAPI version: should be a string but got "${typeof e.openapi}"`);if(e.openapi&&e.openapi.startsWith("3.0"))return a.OAS3_0;if(e.openapi&&e.openapi.startsWith("3.1"))return a.OAS3_1;if(e.swagger&&"2.0"===e.swagger)return a.OAS2;if(e.openapi||e.swagger)throw new Error(`Unsupported OpenAPI version: ${e.openapi||e.swagger}`);if(e.asyncapi&&e.asyncapi.startsWith("2."))return a.Async2;if(e.asyncapi)throw new Error(`Unsupported AsyncAPI version: ${e.asyncapi}`);throw new Error("Unsupported specification")},t.getMajorSpecVersion=function(e){return e===a.OAS2?l.OAS2:e===a.Async2?l.Async2:l.OAS3},t.getTypes=function(e){return c[e]}},4125:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{l(r.next(e))}catch(e){o(e)}}function a(e){try{l(r.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.isRedoclyRegistryURL=t.RedoclyClient=void 0;const i=n(3986),o=n(7975),s=n(2941),a=n(919),l=n(8921),c=n(1827),u=n(8209),p=n(2678),d=".redocly-config.json";t.RedoclyClient=class{constructor(e){this.accessTokens={},this.region=this.loadRegion(e),this.loadTokens(),this.domain=e?l.DOMAINS[e]:c.env.REDOCLY_DOMAIN||l.DOMAINS[l.DEFAULT_REGION],c.env.REDOCLY_DOMAIN=this.domain,this.registryApi=new a.RegistryApi(this.accessTokens,this.region)}loadRegion(e){if(e&&!l.DOMAINS[e])throw new Error(`Invalid argument: region in config file.\nGiven: ${p.colorize.green(e)}, choices: "us", "eu".`);return c.env.REDOCLY_DOMAIN?l.AVAILABLE_REGIONS.find((e=>l.DOMAINS[e]===c.env.REDOCLY_DOMAIN))||l.DEFAULT_REGION:e||l.DEFAULT_REGION}getRegion(){return this.region}hasTokens(){return(0,u.isNotEmptyObject)(this.accessTokens)}hasToken(){return!!this.accessTokens[this.region]}getAuthorizationHeader(){return r(this,void 0,void 0,(function*(){return this.accessTokens[this.region]}))}setAccessTokens(e){this.accessTokens=e}loadTokens(){const e=(0,o.resolve)((0,s.homedir)(),d),t=this.readCredentialsFile(e);(0,u.isNotEmptyObject)(t)&&this.setAccessTokens(Object.assign(Object.assign({},t),t.token&&!t[this.region]&&{[this.region]:t.token})),c.env.REDOCLY_AUTHORIZATION&&this.setAccessTokens(Object.assign(Object.assign({},this.accessTokens),{[this.region]:c.env.REDOCLY_AUTHORIZATION}))}getAllTokens(){return Object.entries(this.accessTokens).filter((([e])=>l.AVAILABLE_REGIONS.includes(e))).map((([e,t])=>({region:e,token:t})))}getValidTokens(){return r(this,void 0,void 0,(function*(){const e=this.getAllTokens(),t=yield Promise.allSettled(e.map((({token:e,region:t})=>this.verifyToken(e,t))));return e.filter(((e,n)=>"fulfilled"===t[n].status)).map((({token:e,region:t})=>({token:e,region:t,valid:!0})))}))}getTokens(){return r(this,void 0,void 0,(function*(){return this.hasTokens()?yield this.getValidTokens():[]}))}isAuthorizedWithRedoclyByRegion(){return r(this,void 0,void 0,(function*(){if(!this.hasTokens())return!1;const e=this.accessTokens[this.region];if(!e)return!1;try{return yield this.verifyToken(e,this.region),!0}catch(e){return!1}}))}isAuthorizedWithRedocly(){return r(this,void 0,void 0,(function*(){return this.hasTokens()&&(0,u.isNotEmptyObject)(yield this.getValidTokens())}))}readCredentialsFile(e){return(0,i.existsSync)(e)?JSON.parse((0,i.readFileSync)(e,"utf-8")):{}}verifyToken(e,t,n=!1){return r(this,void 0,void 0,(function*(){return this.registryApi.authStatus(e,t,n)}))}login(e,t=!1){return r(this,void 0,void 0,(function*(){const n=(0,o.resolve)((0,s.homedir)(),d);try{yield this.verifyToken(e,this.region,t)}catch(e){throw new Error("Authorization failed. Please check if you entered a valid API key.")}const r=Object.assign(Object.assign({},this.readCredentialsFile(n)),{[this.region]:e,token:e});this.accessTokens=r,this.registryApi.setAccessTokens(r),(0,i.writeFileSync)(n,JSON.stringify(r,null,2))}))}logout(){const e=(0,o.resolve)((0,s.homedir)(),d);(0,i.existsSync)(e)&&(0,i.unlinkSync)(e)}},t.isRedoclyRegistryURL=function(e){const t=c.env.REDOCLY_DOMAIN||l.DOMAINS[l.DEFAULT_REGION],n="redocly.com"===t?"redoc.ly":t;return!(!e.startsWith(`https://api.${t}/registry/`)&&!e.startsWith(`https://api.${n}/registry/`))}},919:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{l(r.next(e))}catch(e){o(e)}}function a(e){try{l(r.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.RegistryApi=void 0;const i=n(8381),o=n(8921),s=n(8209),a=n(2079).rE;t.RegistryApi=class{constructor(e,t){this.accessTokens=e,this.region=t}get accessToken(){return(0,s.isNotEmptyObject)(this.accessTokens)&&this.accessTokens[this.region]}getBaseUrl(e=o.DEFAULT_REGION){return`https://api.${o.DOMAINS[e]}/registry`}setAccessTokens(e){return this.accessTokens=e,this}request(e="",t={},n){var o,s;return r(this,void 0,void 0,(function*(){const r="undefined"!=typeof process&&(null===(o={})||void 0===o?void 0:o.REDOCLY_CLI_COMMAND)||"",l="undefined"!=typeof process&&(null===(s={})||void 0===s?void 0:s.REDOCLY_ENVIRONMENT)||"",c=Object.assign({},t.headers||{},{"x-redocly-cli-version":a,"user-agent":`redocly-cli / ${a} ${r} ${l}`});if(!c.hasOwnProperty("authorization"))throw new Error("Unauthorized");const u=yield(0,i.default)(`${this.getBaseUrl(n)}${e}`,Object.assign({},t,{headers:c}));if(401===u.status)throw new Error("Unauthorized");if(404===u.status){const e=yield u.json();throw new Error(e.code)}return u}))}authStatus(e,t,n=!1){return r(this,void 0,void 0,(function*(){try{const n=yield this.request("",{headers:{authorization:e}},t);return yield n.json()}catch(e){throw n&&console.log(e),e}}))}prepareFileUpload({organizationId:e,name:t,version:n,filesHash:i,filename:o,isUpsert:s}){return r(this,void 0,void 0,(function*(){const r=yield this.request(`/${e}/${t}/${n}/prepare-file-upload`,{method:"POST",headers:{"content-type":"application/json",authorization:this.accessToken},body:JSON.stringify({filesHash:i,filename:o,isUpsert:s})},this.region);if(r.ok)return r.json();throw new Error("Could not prepare file upload")}))}pushApi({organizationId:e,name:t,version:n,rootFilePath:i,filePaths:o,branch:s,isUpsert:a,isPublic:l,batchId:c,batchSize:u}){return r(this,void 0,void 0,(function*(){if(!(yield this.request(`/${e}/${t}/${n}`,{method:"PUT",headers:{"content-type":"application/json",authorization:this.accessToken},body:JSON.stringify({rootFilePath:i,filePaths:o,branch:s,isUpsert:a,isPublic:l,batchId:c,batchSize:u})},this.region)).ok)throw new Error("Could not push api")}))}}},3873:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isAnchor=t.isMappingRef=t.isAbsoluteUrl=t.refBaseName=t.pointerBaseName=t.parsePointer=t.parseRef=t.escapePointer=t.unescapePointer=t.Location=t.isRef=t.joinPointer=void 0;const r=n(8209);function i(e,t){return""===e&&(e="#/"),"/"===e[e.length-1]?e+t:e+"/"+t}t.joinPointer=i,t.isRef=function(e){return e&&"string"==typeof e.$ref};class o{constructor(e,t){this.source=e,this.pointer=t}child(e){return new o(this.source,i(this.pointer,(Array.isArray(e)?e:[e]).map(a).join("/")))}key(){return Object.assign(Object.assign({},this),{reportOnKey:!0})}get absolutePointer(){return this.source.absoluteRef+("#/"===this.pointer?"":this.pointer)}}function s(e){return decodeURIComponent(e.replace(/~1/g,"/").replace(/~0/g,"~"))}function a(e){return"number"==typeof e?e:e.replace(/~/g,"~0").replace(/\//g,"~1")}t.Location=o,t.unescapePointer=s,t.escapePointer=a,t.parseRef=function(e){const[t,n]=e.split("#/");return{uri:t||null,pointer:n?n.split("/").map(s).filter(r.isTruthy):[]}},t.parsePointer=function(e){return e.substr(2).split("/").map(s)},t.pointerBaseName=function(e){const t=e.split("/");return t[t.length-1]},t.refBaseName=function(e){const t=e.split(/[\/\\]/);return t[t.length-1].replace(/\.[^.]+$/,"")},t.isAbsoluteUrl=function(e){return e.startsWith("http://")||e.startsWith("https://")},t.isMappingRef=function(e){return e.startsWith("#")||e.startsWith("https://")||e.startsWith("http://")||e.startsWith("./")||e.startsWith("../")||e.indexOf("/")>-1},t.isAnchor=function(e){return/^#[A-Za-z][A-Za-z0-9\-_:.]*$/.test(e)}},2928:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{l(r.next(e))}catch(e){o(e)}}function a(e){try{l(r.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.resolveDocument=t.BaseResolver=t.makeDocumentFromString=t.makeRefId=t.YamlParseError=t.ResolveError=t.Source=void 0;const i=n(7411),o=n(7975),s=n(3873),a=n(1990),l=n(8209);class c{constructor(e,t,n){this.absoluteRef=e,this.body=t,this.mimeType=n}getAst(e){var t;return void 0===this._ast&&(this._ast=null!==(t=e(this.body,{filename:this.absoluteRef}))&&void 0!==t?t:void 0,this._ast&&0===this._ast.kind&&""===this._ast.value&&1!==this._ast.startPosition&&(this._ast.startPosition=1,this._ast.endPosition=1)),this._ast}getLines(){return void 0===this._lines&&(this._lines=this.body.split(/\r\n|[\n\r]/g)),this._lines}}t.Source=c;class u extends Error{constructor(e){super(e.message),this.originalError=e,Object.setPrototypeOf(this,u.prototype)}}t.ResolveError=u;const p=/\((\d+):(\d+)\)$/;class d extends Error{constructor(e,t){super(e.message.split("\n")[0]),this.originalError=e,this.source=t,Object.setPrototypeOf(this,d.prototype);const[,n,r]=this.message.match(p)||[];this.line=parseInt(n,10),this.col=parseInt(r,10)}}function f(e,t){return e+"::"+t}function h(e,t){return{prev:e,node:t}}t.YamlParseError=d,t.makeRefId=f,t.makeDocumentFromString=function(e,t){const n=new c(t,e);try{return{source:n,parsed:(0,l.parseYaml)(e,{filename:t})}}catch(e){throw new d(e,n)}},t.BaseResolver=class{constructor(e={http:{headers:[]}}){this.config=e,this.cache=new Map}getFiles(){return new Set(Array.from(this.cache.keys()))}resolveExternalRef(e,t){return(0,s.isAbsoluteUrl)(t)?t:e&&(0,s.isAbsoluteUrl)(e)?new URL(t,e).href:o.resolve(e?o.dirname(e):process.cwd(),t)}loadExternalRef(e){return r(this,void 0,void 0,(function*(){try{if((0,s.isAbsoluteUrl)(e)){const{body:t,mimeType:n}=yield(0,l.readFileFromUrl)(e,this.config.http);return new c(e,t,n)}{if(i.lstatSync(e).isDirectory())throw new Error(`Expected a file but received a folder at ${e}`);const t=yield i.promises.readFile(e,"utf-8");return new c(e,t.replace(/\r\n/g,"\n"))}}catch(e){throw e.message=e.message.replace(", lstat",""),new u(e)}}))}parseDocument(e,t=!1){var n;const r=e.absoluteRef.substr(e.absoluteRef.lastIndexOf("."));if(![".json",".json",".yml",".yaml"].includes(r)&&!(null===(n=e.mimeType)||void 0===n?void 0:n.match(/(json|yaml|openapi)/))&&!t)return{source:e,parsed:e.body};try{return{source:e,parsed:(0,l.parseYaml)(e.body,{filename:e.absoluteRef})}}catch(t){throw new d(t,e)}}resolveDocument(e,t,n=!1){return r(this,void 0,void 0,(function*(){const r=this.resolveExternalRef(e,t),i=this.cache.get(r);if(i)return i;const o=this.loadExternalRef(r).then((e=>this.parseDocument(e,n)));return this.cache.set(r,o),o}))}};const m={name:"unknown",properties:{}},g={name:"scalar",properties:{}};t.resolveDocument=function(e){return r(this,void 0,void 0,(function*(){const{rootDocument:t,externalRefResolver:n,rootType:i}=e,o=new Map,c=new Set,u=[];let p;!function e(t,i,p,d){const y=i.source.absoluteRef,b=new Map;function v(e,t,i){return r(this,void 0,void 0,(function*(){if(function(e,t){for(;e;){if(e.node===t)return!0;e=e.prev}return!1}(i.prev,t))throw new Error("Self-referencing circular pointer");if((0,s.isAnchor)(t.$ref)){yield(0,l.nextTick)();const n={resolved:!0,isRemote:!1,node:b.get(t.$ref),document:e,nodePointer:t.$ref},r=f(e.source.absoluteRef,t.$ref);return o.set(r,n),n}const{uri:r,pointer:a}=(0,s.parseRef)(t.$ref),c=null!==r;let u;try{u=c?yield n.resolveDocument(e.source.absoluteRef,r):e}catch(n){const r={resolved:!1,isRemote:c,document:void 0,error:n},i=f(e.source.absoluteRef,t.$ref);return o.set(i,r),r}let p={resolved:!0,document:u,isRemote:c,node:e.parsed,nodePointer:"#/"},d=u.parsed;const m=a;for(const e of m){if("object"!=typeof d){d=void 0;break}if(void 0!==d[e])d=d[e],p.nodePointer=(0,s.joinPointer)(p.nodePointer,(0,s.escapePointer)(e));else{if(!(0,s.isRef)(d)){d=void 0;break}if(p=yield v(u,d,h(i,d)),u=p.document||u,"object"!=typeof p.node){d=void 0;break}d=p.node[e],p.nodePointer=(0,s.joinPointer)(p.nodePointer,(0,s.escapePointer)(e))}}p.node=d,p.document=u;const g=f(e.source.absoluteRef,t.$ref);return p.document&&(0,s.isRef)(d)&&(p=yield v(p.document,d,h(i,d))),o.set(g,p),Object.assign({},p)}))}!function t(n,r,o){if("object"!=typeof n||null===n)return;const l=`${r.name}::${o}`;if(c.has(l))return;c.add(l);const[p,d]=Object.entries(n).find((([e])=>"$anchor"===e))||[];if(d&&b.set(`#${d}`,n),Array.isArray(n)){const e=r.items;if(void 0===e&&r!==m&&r!==a.SpecExtension)return;for(let r=0;r{t.resolved&&e(t.node,t.document,t.nodePointer,r)}));u.push(t)}}}(t,d,y+p)}(t.parsed,t,"#/",i);do{p=yield Promise.all(u)}while(u.length!==p.length);return o}))}},3416:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.reportUnresolvedRef=t.NoUnresolvedRefs=void 0;const r=n(2928);function i(e,t,n){var i;const o=e.error;o instanceof r.YamlParseError&&t({message:"Failed to parse: "+o.message,location:{source:o.source,pointer:void 0,start:{col:o.col,line:o.line}}});const s=null===(i=e.error)||void 0===i?void 0:i.message;t({location:n,message:"Can't resolve $ref"+(s?": "+s:"")})}t.NoUnresolvedRefs=()=>({ref:{leave(e,{report:t,location:n},r){void 0===r.node&&i(r,t,n)}},DiscriminatorMapping(e,{report:t,resolve:n,location:r}){for(const o of Object.keys(e)){const s=n({$ref:e[o]});if(void 0!==s.node)return;i(s,t,r.child(o))}}}),t.reportUnresolvedRef=i},474:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RemoveUnusedComponents=void 0;const r=n(8209);t.RemoveUnusedComponents=()=>{const e=new Map;function t(t,n,r){var i;e.set(t.absolutePointer,{used:(null===(i=e.get(t.absolutePointer))||void 0===i?void 0:i.used)||!1,componentType:n,name:r})}return{ref:{leave(t,{type:n,resolve:r,key:i}){if(["Schema","Parameter","Response","SecurityScheme"].includes(n.name)){const n=r(t);if(!n.location)return;const[o,s]=n.location.absolutePointer.split("#",2),a=`${o}#${s.split("/").slice(0,3).join("/")}`;e.set(a,{used:!0,name:i.toString()})}}},Root:{leave(t,n){const i=n.getVisitorData();i.removedCount=0;const o=new Set;e.forEach((e=>{const{used:n,name:r,componentType:s}=e;!n&&s&&(o.add(s),delete t[s][r],i.removedCount++)}));for(const e of o)(0,r.isEmptyObject)(t[e])&&delete t[e]}},NamedSchemas:{Schema(e,{location:n,key:r}){e.allOf||t(n,"definitions",r.toString())}},NamedParameters:{Parameter(e,{location:n,key:r}){t(n,"parameters",r.toString())}},NamedResponses:{Response(e,{location:n,key:r}){t(n,"responses",r.toString())}},NamedSecuritySchemes:{SecurityScheme(e,{location:n,key:r}){t(n,"securityDefinitions",r.toString())}}}}},4335:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.RemoveUnusedComponents=void 0;const r=n(8209);t.RemoveUnusedComponents=()=>{const e=new Map;function t(t,n,r){var i;e.set(t.absolutePointer,{used:(null===(i=e.get(t.absolutePointer))||void 0===i?void 0:i.used)||!1,componentType:n,name:r})}return{ref:{leave(t,{type:n,resolve:r,key:i}){if(["Schema","Header","Parameter","Response","Example","RequestBody"].includes(n.name)){const n=r(t);if(!n.location)return;const[o,s]=n.location.absolutePointer.split("#",2),a=`${o}#${s.split("/").slice(0,4).join("/")}`;e.set(a,{used:!0,name:i.toString()})}}},Root:{leave(t,n){const i=n.getVisitorData();i.removedCount=0,e.forEach((e=>{const{used:n,componentType:o,name:s}=e;if(!n&&o&&t.components){const e=t.components[o];delete e[s],i.removedCount++,(0,r.isEmptyObject)(e)&&delete t.components[o]}})),(0,r.isEmptyObject)(t.components)&&delete t.components}},NamedSchemas:{Schema(e,{location:n,key:r}){e.allOf||t(n,"schemas",r.toString())}},NamedParameters:{Parameter(e,{location:n,key:r}){t(n,"parameters",r.toString())}},NamedResponses:{Response(e,{location:n,key:r}){t(n,"responses",r.toString())}},NamedExamples:{Example(e,{location:n,key:r}){t(n,"examples",r.toString())}},NamedRequestBodies:{RequestBody(e,{location:n,key:r}){t(n,"requestBodies",r.toString())}},NamedHeaders:{Header(e,{location:n,key:r}){t(n,"headers",r.toString())}}}}},264:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.AsyncApi2Types=void 0;const r=n(1990),i=n(3873),o={properties:{},allowed(){return["http","ws","kafka","anypointmq","amqp","amqp1","mqtt","mqtt5","nats","jms","sns","solace","sqs","stomp","redis","mercure","ibmmq","googlepubsub","pulsar"]},additionalProperties:{type:"object"}},s={properties:{},allowed(){return["http","ws","kafka","anypointmq","amqp","amqp1","mqtt","mqtt5","nats","jms","sns","solace","sqs","stomp","redis","mercure","ibmmq","googlepubsub","pulsar"]},additionalProperties:{type:"object"}},a={properties:{},allowed(){return["http","ws","kafka","anypointmq","amqp","amqp1","mqtt","mqtt5","nats","jms","sns","solace","sqs","stomp","redis","mercure","ibmmq","googlepubsub","pulsar"]},additionalProperties:{type:"object"}},l={properties:{},allowed(){return["http","ws","kafka","anypointmq","amqp","amqp1","mqtt","mqtt5","nats","jms","sns","solace","sqs","stomp","redis","mercure","ibmmq","googlepubsub","pulsar"]},additionalProperties:{type:"object"}},c={properties:{$id:{type:"string"},id:{type:"string"},$schema:{type:"string"},definitions:"NamedSchemas",$defs:"NamedSchemas",$vocabulary:{type:"string"},externalDocs:"ExternalDocs",discriminator:"Discriminator",myArbitraryKeyword:{type:"boolean"},title:{type:"string"},multipleOf:{type:"number",minimum:0},maximum:{type:"number"},minimum:{type:"number"},exclusiveMaximum:{type:"number"},exclusiveMinimum:{type:"number"},maxLength:{type:"integer",minimum:0},minLength:{type:"integer",minimum:0},pattern:{type:"string"},maxItems:{type:"integer",minimum:0},minItems:{type:"integer",minimum:0},uniqueItems:{type:"boolean"},maxProperties:{type:"integer",minimum:0},minProperties:{type:"integer",minimum:0},required:{type:"array",items:{type:"string"}},enum:{type:"array"},type:e=>Array.isArray(e)?{type:"array",items:{enum:["object","array","string","number","integer","boolean","null"]}}:{enum:["object","array","string","number","integer","boolean","null"]},allOf:(0,r.listOf)("Schema"),anyOf:(0,r.listOf)("Schema"),oneOf:(0,r.listOf)("Schema"),not:"Schema",if:"Schema",then:"Schema",else:"Schema",dependentSchemas:(0,r.listOf)("Schema"),prefixItems:(0,r.listOf)("Schema"),contains:"Schema",minContains:{type:"integer",minimum:0},maxContains:{type:"integer",minimum:0},patternProperties:{type:"object"},propertyNames:"Schema",unevaluatedItems:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",unevaluatedProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",summary:{type:"string"},properties:"SchemaProperties",items:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",description:{type:"string"},format:{type:"string"},contentEncoding:{type:"string"},contentMediaType:{type:"string"},default:null,readOnly:{type:"boolean"},writeOnly:{type:"boolean"},examples:{type:"array"},example:{isExample:!0},deprecated:{type:"boolean"},const:null,$comment:{type:"string"},dependencies:{type:"object"}}},u={properties:{},additionalProperties:e=>(0,i.isMappingRef)(e)?{type:"string",directResolveAs:"Schema"}:{type:"string"}},p={properties:{type:{enum:["userPassword","apiKey","X509","symmetricEncryption","asymmetricEncryption","httpApiKey","http","oauth2","openIdConnect","plain","scramSha256","scramSha512","gssapi"]},description:{type:"string"},name:{type:"string"},in:{type:"string",enum:["query","header","cookie","user","password"]},scheme:{type:"string"},bearerFormat:{type:"string"},flows:"SecuritySchemeFlows",openIdConnectUrl:{type:"string"}},required(e){switch(null==e?void 0:e.type){case"apiKey":return["type","in"];case"httpApiKey":return["type","name","in"];case"http":return["type","scheme"];case"oauth2":return["type","flows"];case"openIdConnect":return["type","openIdConnectUrl"];default:return["type"]}},allowed(e){switch(null==e?void 0:e.type){case"apiKey":return["type","in","description"];case"httpApiKey":return["type","name","in","description"];case"http":return["type","scheme","bearerFormat","description"];case"oauth2":return["type","flows","description"];case"openIdConnect":return["type","openIdConnectUrl","description"];default:return["type","description"]}},extensionsPrefix:"x-"},d={properties:{}};o.properties.http=d;const f={properties:{}};s.properties.http=f;const h={properties:{headers:"Schema",bindingVersion:{type:"string"}}};a.properties.http=h;const m={properties:{type:{type:"string"},method:{type:"string",enum:["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS","CONNECT","TRACE"]},headers:"Schema",bindingVersion:{type:"string"}}};l.properties.http=m;const g={properties:{method:{type:"string"},query:"Schema",headers:"Schema",bindingVersion:{type:"string"}}};o.properties.ws=g;const y={properties:{}};s.properties.ws=y;const b={properties:{}};a.properties.ws=b;const v={properties:{}};l.properties.ws=v;const x={properties:{topic:{type:"string"},partitions:{type:"integer"},replicas:{type:"integer"},topicConfiguration:"KafkaTopicConfiguration",bindingVersion:{type:"string"}}};o.properties.kafka=x;const w={properties:{}};s.properties.kafka=w;const k={properties:{key:"Schema",schemaIdLocation:{type:"string"},schemaIdPayloadEncoding:{type:"string"},schemaLookupStrategy:{type:"string"},bindingVersion:{type:"string"}}};a.properties.kafka=k;const S={properties:{groupId:"Schema",clientId:"Schema",bindingVersion:{type:"string"}}};l.properties.kafka=S;const E={properties:{destination:{type:"string"},destinationType:{type:"string"},bindingVersion:{type:"string"}}};o.properties.anypointmq=E;const O={properties:{}};s.properties.anypointmq=O;const _={properties:{headers:"Schema",bindingVersion:{type:"string"}}};a.properties.anypointmq=_;const A={properties:{}};l.properties.anypointmq=A;const C={properties:{}};o.properties.amqp=C;const j={properties:{}};s.properties.amqp=j;const P={properties:{contentEncoding:{type:"string"},messageType:{type:"string"},bindingVersion:{type:"string"}}};a.properties.amqp=P;const T={properties:{expiration:{type:"integer"},userId:{type:"string"},cc:{type:"array",items:{type:"string"}},priority:{type:"integer"},deliveryMode:{type:"integer"},mandatory:{type:"boolean"},bcc:{type:"array",items:{type:"string"}},replyTo:{type:"string"},timestamp:{type:"boolean"},ack:{type:"boolean"},bindingVersion:{type:"string"}}};l.properties.amqp=T;const I={properties:{}};o.properties.amqp1=I;const R={properties:{}};s.properties.amqp1=R;const N={properties:{}};a.properties.amqp1=N;const $={properties:{}};l.properties.amqp1=$;const L={properties:{qos:{type:"integer"},retain:{type:"boolean"},bindingVersion:{type:"string"}}};o.properties.mqtt=L;const D={properties:{clientId:{type:"string"},cleanSession:{type:"boolean"},lastWill:"MqttServerBindingLastWill",keepAlive:{type:"integer"},bindingVersion:{type:"string"}}};s.properties.mqtt=D;const M={properties:{bindingVersion:{type:"string"}}};a.properties.mqtt=M;const F={properties:{qos:{type:"integer"},retain:{type:"boolean"},bindingVersion:{type:"string"}}};l.properties.mqtt=F;const z={properties:{}};o.properties.mqtt5=z;const B={properties:{}};s.properties.mqtt5=B;const U={properties:{}};a.properties.mqtt5=U;const q={properties:{}};l.properties.mqtt5=q;const V={properties:{}};o.properties.nats=V;const W={properties:{}};s.properties.nats=W;const H={properties:{}};a.properties.nats=H;const Y={properties:{queue:{type:"string"},bindingVersion:{type:"string"}}};l.properties.nats=Y;const G={properties:{destination:{type:"string"},destinationType:{type:"string"},bindingVersion:{type:"string"}}};o.properties.jms=G;const Q={properties:{}};s.properties.jms=Q;const X={properties:{headers:"Schema",bindingVersion:{type:"string"}}};a.properties.jms=X;const K={properties:{headers:"Schema",bindingVersion:{type:"string"}}};l.properties.jms=K;const Z={properties:{}};o.properties.solace=Z;const J={properties:{bindingVersion:{type:"string"},msgVpn:{type:"string"}}};s.properties.solace=J;const ee={properties:{}};a.properties.solace=ee;const te={properties:{bindingVersion:{type:"string"},destinations:(0,r.listOf)("SolaceDestination")}};l.properties.solace=te;const ne={properties:{}};o.properties.stomp=ne;const re={properties:{}};s.properties.stomp=re;const ie={properties:{}};a.properties.stomp=ie;const oe={properties:{}};l.properties.stomp=oe;const se={properties:{}};o.properties.redis=se;const ae={properties:{}};s.properties.redis=ae;const le={properties:{}};a.properties.redis=le;const ce={properties:{}};l.properties.redis=ce;const ue={properties:{}};o.properties.mercure=ue;const pe={properties:{}};s.properties.mercure=pe;const de={properties:{}};a.properties.mercure=de;const fe={properties:{}};l.properties.mercure=fe,t.AsyncApi2Types={Root:{properties:{asyncapi:null,info:"Info",id:{type:"string"},servers:"ServerMap",channels:"ChannelMap",components:"Components",tags:"TagList",externalDocs:"ExternalDocs",defaultContentType:{type:"string"}},required:["asyncapi","channels","info"]},Tag:{properties:{name:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs"},required:["name"]},TagList:(0,r.listOf)("Tag"),ServerMap:{properties:{},additionalProperties:(e,t)=>t.match(/^[A-Za-z0-9_\-]+$/)?"Server":void 0},ExternalDocs:{properties:{description:{type:"string"},url:{type:"string"}},required:["url"]},Server:{properties:{url:{type:"string"},protocol:{type:"string"},protocolVersion:{type:"string"},description:{type:"string"},variables:"ServerVariablesMap",security:"SecurityRequirementList",bindings:"ServerBindings",tags:"TagList"},required:["url","protocol"]},ServerVariable:{properties:{enum:{type:"array",items:{type:"string"}},default:{type:"string"},description:{type:"string"},examples:{type:"array",items:{type:"string"}}},required:[]},ServerVariablesMap:(0,r.mapOf)("ServerVariable"),SecurityRequirement:{properties:{},additionalProperties:{type:"array",items:{type:"string"}}},SecurityRequirementList:(0,r.listOf)("SecurityRequirement"),Info:{properties:{title:{type:"string"},version:{type:"string"},description:{type:"string"},termsOfService:{type:"string"},contact:"Contact",license:"License"},required:["title","version"]},Contact:{properties:{name:{type:"string"},url:{type:"string"},email:{type:"string"}}},License:{properties:{name:{type:"string"},url:{type:"string"}},required:["name"]},HttpServerBinding:f,HttpChannelBinding:d,HttpMessageBinding:h,HttpOperationBinding:m,WsServerBinding:y,WsChannelBinding:g,WsMessageBinding:b,WsOperationBinding:v,KafkaServerBinding:w,KafkaTopicConfiguration:{properties:{"cleanup.policy":{type:"array",items:{enum:["delete","compact"]}},"retention.ms":{type:"integer"},"retention.bytes":{type:"integer"},"delete.retention.ms":{type:"integer"},"max.message.bytes":{type:"integer"}}},KafkaChannelBinding:x,KafkaMessageBinding:k,KafkaOperationBinding:S,AnypointmqServerBinding:O,AnypointmqChannelBinding:E,AnypointmqMessageBinding:_,AnypointmqOperationBinding:A,AmqpServerBinding:j,AmqpChannelBinding:C,AmqpMessageBinding:P,AmqpOperationBinding:T,Amqp1ServerBinding:R,Amqp1ChannelBinding:I,Amqp1MessageBinding:N,Amqp1OperationBinding:$,MqttServerBindingLastWill:{properties:{topic:{type:"string"},qos:{type:"integer"},message:{type:"string"},retain:{type:"boolean"}}},MqttServerBinding:D,MqttChannelBinding:L,MqttMessageBinding:M,MqttOperationBinding:F,Mqtt5ServerBinding:B,Mqtt5ChannelBinding:z,Mqtt5MessageBinding:U,Mqtt5OperationBinding:q,NatsServerBinding:W,NatsChannelBinding:V,NatsMessageBinding:H,NatsOperationBinding:Y,JmsServerBinding:Q,JmsChannelBinding:G,JmsMessageBinding:X,JmsOperationBinding:K,SolaceServerBinding:J,SolaceChannelBinding:Z,SolaceMessageBinding:ee,SolaceDestination:{properties:{destinationType:{type:"string",enum:["queue","topic"]},deliveryMode:{type:"string",enum:["direct","persistent"]},"queue.name":{type:"string"},"queue.topicSubscriptions":{type:"array",items:{type:"string"}},"queue.accessType":{type:"string",enum:["exclusive","nonexclusive"]},"queue.maxMsgSpoolSize":{type:"string"},"queue.maxTtl":{type:"string"},"topic.topicSubscriptions":{type:"array",items:{type:"string"}}}},SolaceOperationBinding:te,StompServerBinding:re,StompChannelBinding:ne,StompMessageBinding:ie,StompOperationBinding:oe,RedisServerBinding:ae,RedisChannelBinding:se,RedisMessageBinding:le,RedisOperationBinding:ce,MercureServerBinding:pe,MercureChannelBinding:ue,MercureMessageBinding:de,MercureOperationBinding:fe,ServerBindings:s,ChannelBindings:o,ChannelMap:{properties:{},additionalProperties:"Channel"},Channel:{properties:{description:{type:"string"},subscribe:"Operation",publish:"Operation",parameters:"ParametersMap",bindings:"ChannelBindings",servers:{type:"array",items:{type:"string"}}}},Parameter:{properties:{description:{type:"string"},schema:"Schema",location:{type:"string"}}},ParametersMap:(0,r.mapOf)("Parameter"),Operation:{properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},security:"SecurityRequirementList",bindings:"OperationBindings",traits:"OperationTraitList",message:"Message"},required:[]},Schema:c,MessageExample:{properties:{payload:{isExample:!0},summary:{type:"string"},name:{type:"string"},headers:{type:"object"}}},SchemaProperties:{properties:{},additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema"},DiscriminatorMapping:u,Discriminator:{properties:{propertyName:{type:"string"},mapping:"DiscriminatorMapping"},required:["propertyName"]},Components:{properties:{messages:"NamedMessages",parameters:"NamedParameters",schemas:"NamedSchemas",correlationIds:"NamedCorrelationIds",messageTraits:"NamedMessageTraits",operationTraits:"NamedOperationTraits",streamHeaders:"NamedStreamHeaders",securitySchemes:"NamedSecuritySchemes",servers:"ServerMap",serverVariables:"ServerVariablesMap",channels:"ChannelMap",serverBindings:"ServerBindings",channelBindings:"ChannelBindings",operationBindings:"OperationBindings",messageBindings:"MessageBindings"}},NamedSchemas:(0,r.mapOf)("Schema"),NamedMessages:(0,r.mapOf)("Message"),NamedMessageTraits:(0,r.mapOf)("MessageTrait"),NamedOperationTraits:(0,r.mapOf)("OperationTrait"),NamedParameters:(0,r.mapOf)("Parameter"),NamedSecuritySchemes:(0,r.mapOf)("SecurityScheme"),NamedCorrelationIds:(0,r.mapOf)("CorrelationId"),NamedStreamHeaders:(0,r.mapOf)("StreamHeader"),ImplicitFlow:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},authorizationUrl:{type:"string"}},required:["authorizationUrl","scopes"]},PasswordFlow:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["tokenUrl","scopes"]},ClientCredentials:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["tokenUrl","scopes"]},AuthorizationCode:{properties:{refreshUrl:{type:"string"},authorizationUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["authorizationUrl","tokenUrl","scopes"]},SecuritySchemeFlows:{properties:{implicit:"ImplicitFlow",password:"PasswordFlow",clientCredentials:"ClientCredentials",authorizationCode:"AuthorizationCode"}},SecurityScheme:p,Message:{properties:{messageId:{type:"string"},headers:"Schema",payload:"Schema",correlationId:"CorrelationId",schemaFormat:{type:"string"},contentType:{type:"string"},name:{type:"string"},title:{type:"string"},summary:{type:"string"},description:{type:"string"},tags:"TagList",externalDocs:"ExternalDocs",bindings:"MessageBindings",traits:"MessageTraitList"},additionalProperties:{}},MessageBindings:a,OperationBindings:l,OperationTrait:{properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},security:"SecurityRequirementList",bindings:"OperationBindings"},required:[]},OperationTraitList:(0,r.listOf)("OperationTrait"),MessageTrait:{properties:{messageId:{type:"string"},headers:"Schema",correlationId:"CorrelationId",schemaFormat:{type:"string"},contentType:{type:"string"},name:{type:"string"},title:{type:"string"},summary:{type:"string"},description:{type:"string"},tags:"TagList",externalDocs:"ExternalDocs",bindings:"MessageBindings"},additionalProperties:{}},MessageTraitList:(0,r.listOf)("MessageTrait"),CorrelationId:{properties:{description:{type:"string"},location:{type:"string"}},required:["location"]}}},1990:function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.isNamedType=t.normalizeTypes=t.SpecExtension=t.mapOf=t.listOf=void 0,t.listOf=function(e){return{name:`${e}List`,properties:{},items:e}},t.mapOf=function(e){return{name:`${e}Map`,properties:{},additionalProperties:()=>e}},t.SpecExtension={name:"SpecExtension",properties:{},additionalProperties:{resolvable:!0}},t.normalizeTypes=function(e,n={}){const r={};for(const t of Object.keys(e))r[t]=Object.assign(Object.assign({},e[t]),{name:t});for(const e of Object.values(r))i(e);return r.SpecExtension=t.SpecExtension,r;function i(e){if(e.additionalProperties&&(e.additionalProperties=o(e.additionalProperties)),e.items&&(e.items=o(e.items)),e.properties){const t={};for(const[r,i]of Object.entries(e.properties))t[r]=o(i),n.doNotResolveExamples&&i&&i.isExample&&(t[r]=Object.assign(Object.assign({},i),{resolvable:!1}));e.properties=t}}function o(e){if("string"==typeof e){if(!r[e])throw new Error(`Unknown type name found: ${e}`);return r[e]}return"function"==typeof e?(t,n)=>o(e(t,n)):e&&e.name?(i(e=Object.assign({},e)),e):e&&e.directResolveAs?Object.assign(Object.assign({},e),{directResolveAs:o(e.directResolveAs)}):e}},t.isNamedType=function(e){return"string"==typeof(null==e?void 0:e.name)}},4409:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Oas2Types=void 0;const r=n(1990),i=/^[0-9][0-9Xx]{2}$/,o={properties:{name:{type:"string"},in:{type:"string",enum:["query","header","path","formData","body"]},description:{type:"string"},required:{type:"boolean"},schema:"Schema",type:{type:"string",enum:["string","number","integer","boolean","array","file"]},format:{type:"string"},allowEmptyValue:{type:"boolean"},items:"ParameterItems",collectionFormat:{type:"string",enum:["csv","ssv","tsv","pipes","multi"]},default:null,maximum:{type:"integer"},exclusiveMaximum:{type:"boolean"},minimum:{type:"integer"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer"},minLength:{type:"integer"},pattern:{type:"string"},maxItems:{type:"integer"},minItems:{type:"integer"},uniqueItems:{type:"boolean"},enum:{type:"array"},multipleOf:{type:"number"},"x-example":"Example","x-examples":"ExamplesMap"},required(e){return e&&e.in?"body"===e.in?["name","in","schema"]:"array"===e.type?["name","in","type","items"]:["name","in","type"]:["name","in"]},extensionsPrefix:"x-"},s={properties:{type:{type:"string",enum:["string","number","integer","boolean","array"]},format:{type:"string"},items:"ParameterItems",collectionFormat:{type:"string",enum:["csv","ssv","tsv","pipes","multi"]},default:null,maximum:{type:"integer"},exclusiveMaximum:{type:"boolean"},minimum:{type:"integer"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer"},minLength:{type:"integer"},pattern:{type:"string"},maxItems:{type:"integer"},minItems:{type:"integer"},uniqueItems:{type:"boolean"},enum:{type:"array"},multipleOf:{type:"number"}},required(e){return e&&"array"===e.type?["type","items"]:["type"]},extensionsPrefix:"x-"},a={properties:{default:"Response"},additionalProperties:(e,t)=>i.test(t)?"Response":void 0},l={properties:{description:{type:"string"},schema:"Schema",headers:(0,r.mapOf)("Header"),examples:"Examples","x-summary":{type:"string"}},required:["description"],extensionsPrefix:"x-"},c={properties:{description:{type:"string"},type:{type:"string",enum:["string","number","integer","boolean","array"]},format:{type:"string"},items:"ParameterItems",collectionFormat:{type:"string",enum:["csv","ssv","tsv","pipes","multi"]},default:null,maximum:{type:"integer"},exclusiveMaximum:{type:"boolean"},minimum:{type:"integer"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer"},minLength:{type:"integer"},pattern:{type:"string"},maxItems:{type:"integer"},minItems:{type:"integer"},uniqueItems:{type:"boolean"},enum:{type:"array"},multipleOf:{type:"number"}},required(e){return e&&"array"===e.type?["type","items"]:["type"]},extensionsPrefix:"x-"},u={properties:{format:{type:"string"},title:{type:"string"},description:{type:"string"},default:null,multipleOf:{type:"number"},maximum:{type:"number"},minimum:{type:"number"},exclusiveMaximum:{type:"boolean"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"number"},minLength:{type:"number"},pattern:{type:"string"},maxItems:{type:"number"},minItems:{type:"number"},uniqueItems:{type:"boolean"},maxProperties:{type:"number"},minProperties:{type:"number"},required:{type:"array",items:{type:"string"}},enum:{type:"array"},type:{type:"string",enum:["object","array","string","number","integer","boolean","null"]},items:e=>Array.isArray(e)?(0,r.listOf)("Schema"):"Schema",allOf:(0,r.listOf)("Schema"),properties:"SchemaProperties",additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",discriminator:{type:"string"},readOnly:{type:"boolean"},xml:"Xml",externalDocs:"ExternalDocs",example:{isExample:!0},"x-tags":{type:"array",items:{type:"string"}},"x-nullable":{type:"boolean"},"x-extendedDiscriminator":{type:"string"},"x-additionalPropertiesName":{type:"string"},"x-explicitMappingOnly":{type:"boolean"},"x-enumDescriptions":"EnumDescriptions"},extensionsPrefix:"x-"},p={properties:{type:{enum:["basic","apiKey","oauth2"]},description:{type:"string"},name:{type:"string"},in:{type:"string",enum:["query","header"]},flow:{enum:["implicit","password","application","accessCode"]},authorizationUrl:{type:"string"},tokenUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},"x-defaultClientId":{type:"string"}},required(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in"];case"oauth2":switch(null==e?void 0:e.flow){case"implicit":return["type","flow","authorizationUrl","scopes"];case"accessCode":return["type","flow","authorizationUrl","tokenUrl","scopes"];case"application":case"password":return["type","flow","tokenUrl","scopes"];default:return["type","flow","scopes"]}default:return["type"]}},allowed(e){switch(null==e?void 0:e.type){case"basic":return["type","description"];case"apiKey":return["type","name","in","description"];case"oauth2":switch(null==e?void 0:e.flow){case"implicit":return["type","flow","authorizationUrl","description","scopes"];case"accessCode":return["type","flow","authorizationUrl","tokenUrl","description","scopes"];case"application":case"password":return["type","flow","tokenUrl","description","scopes"];default:return["type","flow","tokenUrl","authorizationUrl","description","scopes"]}default:return["type","description"]}},extensionsPrefix:"x-"};t.Oas2Types={Root:{properties:{swagger:{type:"string"},info:"Info",host:{type:"string"},basePath:{type:"string"},schemes:{type:"array",items:{type:"string"}},consumes:{type:"array",items:{type:"string"}},produces:{type:"array",items:{type:"string"}},paths:"Paths",definitions:"NamedSchemas",parameters:"NamedParameters",responses:"NamedResponses",securityDefinitions:"NamedSecuritySchemes",security:"SecurityRequirementList",tags:"TagList",externalDocs:"ExternalDocs","x-servers":"XServerList","x-tagGroups":"TagGroups","x-ignoredHeaderParameters":{type:"array",items:{type:"string"}}},required:["swagger","paths","info"],extensionsPrefix:"x-"},Tag:{properties:{name:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs","x-traitTag":{type:"boolean"},"x-displayName":{type:"string"}},required:["name"],extensionsPrefix:"x-"},TagList:(0,r.listOf)("Tag"),TagGroups:(0,r.listOf)("TagGroup"),TagGroup:{properties:{name:{type:"string"},tags:{type:"array",items:{type:"string"}}}},ExternalDocs:{properties:{description:{type:"string"},url:{type:"string"}},required:["url"],extensionsPrefix:"x-"},Example:{properties:{value:{isExample:!0},summary:{type:"string"},description:{type:"string"},externalValue:{type:"string"}},extensionsPrefix:"x-"},ExamplesMap:(0,r.mapOf)("Example"),EnumDescriptions:{properties:{},additionalProperties:{type:"string"}},SecurityRequirement:{properties:{},additionalProperties:{type:"array",items:{type:"string"}}},SecurityRequirementList:(0,r.listOf)("SecurityRequirement"),Info:{properties:{title:{type:"string"},description:{type:"string"},termsOfService:{type:"string"},contact:"Contact",license:"License",version:{type:"string"},"x-logo":"Logo"},required:["title","version"],extensionsPrefix:"x-"},Contact:{properties:{name:{type:"string"},url:{type:"string"},email:{type:"string"}},extensionsPrefix:"x-"},License:{properties:{name:{type:"string"},url:{type:"string"}},required:["name"],extensionsPrefix:"x-"},Logo:{properties:{url:{type:"string"},altText:{type:"string"},backgroundColor:{type:"string"},href:{type:"string"}},extensionsPrefix:"x-"},Paths:{properties:{},additionalProperties:(e,t)=>t.startsWith("/")?"PathItem":void 0},PathItem:{properties:{$ref:{type:"string"},parameters:"ParameterList",get:"Operation",put:"Operation",post:"Operation",delete:"Operation",options:"Operation",head:"Operation",patch:"Operation"},extensionsPrefix:"x-"},Parameter:o,ParameterItems:s,ParameterList:(0,r.listOf)("Parameter"),Operation:{properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},consumes:{type:"array",items:{type:"string"}},produces:{type:"array",items:{type:"string"}},parameters:"ParameterList",responses:"Responses",schemes:{type:"array",items:{type:"string"}},deprecated:{type:"boolean"},security:"SecurityRequirementList","x-codeSamples":"XCodeSampleList","x-code-samples":"XCodeSampleList","x-hideTryItPanel":{type:"boolean"}},required:["responses"],extensionsPrefix:"x-"},Examples:{properties:{},additionalProperties:{isExample:!0}},Header:c,Responses:a,Response:l,Schema:u,Xml:{properties:{name:{type:"string"},namespace:{type:"string"},prefix:{type:"string"},attribute:{type:"boolean"},wrapped:{type:"boolean"}},extensionsPrefix:"x-"},SchemaProperties:{properties:{},additionalProperties:"Schema"},NamedSchemas:(0,r.mapOf)("Schema"),NamedResponses:(0,r.mapOf)("Response"),NamedParameters:(0,r.mapOf)("Parameter"),NamedSecuritySchemes:(0,r.mapOf)("SecurityScheme"),SecurityScheme:p,XCodeSample:{properties:{lang:{type:"string"},label:{type:"string"},source:{type:"string"}}},XCodeSampleList:(0,r.listOf)("XCodeSample"),XServerList:(0,r.listOf)("XServer"),XServer:{properties:{url:{type:"string"},description:{type:"string"}},required:["url"]}}},4154:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Oas3Types=void 0;const r=n(1990),i=n(3873),o=/^[0-9][0-9Xx]{2}$/,s={properties:{default:"Response"},additionalProperties:(e,t)=>o.test(t)?"Response":void 0},a={properties:{externalDocs:"ExternalDocs",discriminator:"Discriminator",title:{type:"string"},multipleOf:{type:"number",minimum:0},maximum:{type:"number"},minimum:{type:"number"},exclusiveMaximum:{type:"boolean"},exclusiveMinimum:{type:"boolean"},maxLength:{type:"integer",minimum:0},minLength:{type:"integer",minimum:0},pattern:{type:"string"},maxItems:{type:"integer",minimum:0},minItems:{type:"integer",minimum:0},uniqueItems:{type:"boolean"},maxProperties:{type:"integer",minimum:0},minProperties:{type:"integer",minimum:0},required:{type:"array",items:{type:"string"}},enum:{type:"array"},type:{enum:["object","array","string","number","integer","boolean","null"]},allOf:(0,r.listOf)("Schema"),anyOf:(0,r.listOf)("Schema"),oneOf:(0,r.listOf)("Schema"),not:"Schema",properties:"SchemaProperties",items:e=>Array.isArray(e)?(0,r.listOf)("Schema"):"Schema",additionalItems:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",description:{type:"string"},format:{type:"string"},default:null,nullable:{type:"boolean"},readOnly:{type:"boolean"},writeOnly:{type:"boolean"},xml:"Xml",example:{isExample:!0},deprecated:{type:"boolean"},"x-tags":{type:"array",items:{type:"string"}},"x-additionalPropertiesName":{type:"string"},"x-explicitMappingOnly":{type:"boolean"}},extensionsPrefix:"x-"},l={properties:{},additionalProperties:e=>(0,i.isMappingRef)(e)?{type:"string",directResolveAs:"Schema"}:{type:"string"}},c={properties:{type:{enum:["apiKey","http","oauth2","openIdConnect"]},description:{type:"string"},name:{type:"string"},in:{type:"string",enum:["query","header","cookie"]},scheme:{type:"string"},bearerFormat:{type:"string"},flows:"OAuth2Flows",openIdConnectUrl:{type:"string"},"x-defaultClientId":{type:"string"}},required(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in"];case"http":return["type","scheme"];case"oauth2":return["type","flows"];case"openIdConnect":return["type","openIdConnectUrl"];default:return["type"]}},allowed(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in","description"];case"http":return["type","scheme","bearerFormat","description"];case"oauth2":return["type","flows","description"];case"openIdConnect":return["type","openIdConnectUrl","description"];default:return["type","description"]}},extensionsPrefix:"x-"};t.Oas3Types={Root:{properties:{openapi:null,info:"Info",servers:"ServerList",security:"SecurityRequirementList",tags:"TagList",externalDocs:"ExternalDocs",paths:"Paths",components:"Components","x-webhooks":"WebhooksMap","x-tagGroups":"TagGroups","x-ignoredHeaderParameters":{type:"array",items:{type:"string"}}},required:["openapi","paths","info"],extensionsPrefix:"x-"},Tag:{properties:{name:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs","x-traitTag":{type:"boolean"},"x-displayName":{type:"string"}},required:["name"],extensionsPrefix:"x-"},TagList:(0,r.listOf)("Tag"),TagGroups:(0,r.listOf)("TagGroup"),TagGroup:{properties:{name:{type:"string"},tags:{type:"array",items:{type:"string"}}},extensionsPrefix:"x-"},ExternalDocs:{properties:{description:{type:"string"},url:{type:"string"}},required:["url"],extensionsPrefix:"x-"},Server:{properties:{url:{type:"string"},description:{type:"string"},variables:"ServerVariablesMap"},required:["url"],extensionsPrefix:"x-"},ServerList:(0,r.listOf)("Server"),ServerVariable:{properties:{enum:{type:"array",items:{type:"string"}},default:{type:"string"},description:{type:"string"}},required:["default"],extensionsPrefix:"x-"},ServerVariablesMap:(0,r.mapOf)("ServerVariable"),SecurityRequirement:{properties:{},additionalProperties:{type:"array",items:{type:"string"}}},SecurityRequirementList:(0,r.listOf)("SecurityRequirement"),Info:{properties:{title:{type:"string"},version:{type:"string"},description:{type:"string"},termsOfService:{type:"string"},contact:"Contact",license:"License","x-logo":"Logo"},required:["title","version"],extensionsPrefix:"x-"},Contact:{properties:{name:{type:"string"},url:{type:"string"},email:{type:"string"}},extensionsPrefix:"x-"},License:{properties:{name:{type:"string"},url:{type:"string"}},required:["name"],extensionsPrefix:"x-"},Paths:{properties:{},additionalProperties:(e,t)=>t.startsWith("/")?"PathItem":void 0},PathItem:{properties:{$ref:{type:"string"},servers:"ServerList",parameters:"ParameterList",summary:{type:"string"},description:{type:"string"},get:"Operation",put:"Operation",post:"Operation",delete:"Operation",options:"Operation",head:"Operation",patch:"Operation",trace:"Operation"},extensionsPrefix:"x-"},Parameter:{properties:{name:{type:"string"},in:{enum:["query","header","path","cookie"]},description:{type:"string"},required:{type:"boolean"},deprecated:{type:"boolean"},allowEmptyValue:{type:"boolean"},style:{enum:["form","simple","label","matrix","spaceDelimited","pipeDelimited","deepObject"]},explode:{type:"boolean"},allowReserved:{type:"boolean"},schema:"Schema",example:{isExample:!0},examples:"ExamplesMap",content:"MediaTypesMap"},required:["name","in"],requiredOneOf:["schema","content"],extensionsPrefix:"x-"},ParameterList:(0,r.listOf)("Parameter"),Operation:{properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},parameters:"ParameterList",security:"SecurityRequirementList",servers:"ServerList",requestBody:"RequestBody",responses:"Responses",deprecated:{type:"boolean"},callbacks:"CallbacksMap","x-codeSamples":"XCodeSampleList","x-code-samples":"XCodeSampleList","x-hideTryItPanel":{type:"boolean"}},required:["responses"],extensionsPrefix:"x-"},Callback:(0,r.mapOf)("PathItem"),CallbacksMap:(0,r.mapOf)("Callback"),RequestBody:{properties:{description:{type:"string"},required:{type:"boolean"},content:"MediaTypesMap"},required:["content"],extensionsPrefix:"x-"},MediaTypesMap:{properties:{},additionalProperties:"MediaType"},MediaType:{properties:{schema:"Schema",example:{isExample:!0},examples:"ExamplesMap",encoding:"EncodingMap"},extensionsPrefix:"x-"},Example:{properties:{value:{isExample:!0},summary:{type:"string"},description:{type:"string"},externalValue:{type:"string"}},extensionsPrefix:"x-"},ExamplesMap:(0,r.mapOf)("Example"),Encoding:{properties:{contentType:{type:"string"},headers:"HeadersMap",style:{enum:["form","simple","label","matrix","spaceDelimited","pipeDelimited","deepObject"]},explode:{type:"boolean"},allowReserved:{type:"boolean"}},extensionsPrefix:"x-"},EncodingMap:(0,r.mapOf)("Encoding"),EnumDescriptions:{properties:{},additionalProperties:{type:"string"}},Header:{properties:{description:{type:"string"},required:{type:"boolean"},deprecated:{type:"boolean"},allowEmptyValue:{type:"boolean"},style:{enum:["form","simple","label","matrix","spaceDelimited","pipeDelimited","deepObject"]},explode:{type:"boolean"},allowReserved:{type:"boolean"},schema:"Schema",example:{isExample:!0},examples:"ExamplesMap",content:"MediaTypesMap"},requiredOneOf:["schema","content"],extensionsPrefix:"x-"},HeadersMap:(0,r.mapOf)("Header"),Responses:s,Response:{properties:{description:{type:"string"},headers:"HeadersMap",content:"MediaTypesMap",links:"LinksMap","x-summary":{type:"string"}},required:["description"],extensionsPrefix:"x-"},Link:{properties:{operationRef:{type:"string"},operationId:{type:"string"},parameters:null,requestBody:null,description:{type:"string"},server:"Server"},extensionsPrefix:"x-"},Logo:{properties:{url:{type:"string"},altText:{type:"string"},backgroundColor:{type:"string"},href:{type:"string"}}},Schema:a,Xml:{properties:{name:{type:"string"},namespace:{type:"string"},prefix:{type:"string"},attribute:{type:"boolean"},wrapped:{type:"boolean"}},extensionsPrefix:"x-"},SchemaProperties:{properties:{},additionalProperties:"Schema"},DiscriminatorMapping:l,Discriminator:{properties:{propertyName:{type:"string"},mapping:"DiscriminatorMapping"},required:["propertyName"],extensionsPrefix:"x-"},Components:{properties:{parameters:"NamedParameters",schemas:"NamedSchemas",responses:"NamedResponses",examples:"NamedExamples",requestBodies:"NamedRequestBodies",headers:"NamedHeaders",securitySchemes:"NamedSecuritySchemes",links:"NamedLinks",callbacks:"NamedCallbacks"},extensionsPrefix:"x-"},LinksMap:(0,r.mapOf)("Link"),NamedSchemas:(0,r.mapOf)("Schema"),NamedResponses:(0,r.mapOf)("Response"),NamedParameters:(0,r.mapOf)("Parameter"),NamedExamples:(0,r.mapOf)("Example"),NamedRequestBodies:(0,r.mapOf)("RequestBody"),NamedHeaders:(0,r.mapOf)("Header"),NamedSecuritySchemes:(0,r.mapOf)("SecurityScheme"),NamedLinks:(0,r.mapOf)("Link"),NamedCallbacks:(0,r.mapOf)("Callback"),ImplicitFlow:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},authorizationUrl:{type:"string"}},required:["authorizationUrl","scopes"],extensionsPrefix:"x-"},PasswordFlow:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["tokenUrl","scopes"],extensionsPrefix:"x-"},ClientCredentials:{properties:{refreshUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"}},required:["tokenUrl","scopes"],extensionsPrefix:"x-"},AuthorizationCode:{properties:{refreshUrl:{type:"string"},authorizationUrl:{type:"string"},scopes:{type:"object",additionalProperties:{type:"string"}},tokenUrl:{type:"string"},"x-usePkce":e=>"boolean"==typeof e?{type:"boolean"}:"XUsePkce"},required:["authorizationUrl","tokenUrl","scopes"],extensionsPrefix:"x-"},OAuth2Flows:{properties:{implicit:"ImplicitFlow",password:"PasswordFlow",clientCredentials:"ClientCredentials",authorizationCode:"AuthorizationCode"},extensionsPrefix:"x-"},SecurityScheme:c,XCodeSample:{properties:{lang:{type:"string"},label:{type:"string"},source:{type:"string"}}},XCodeSampleList:(0,r.listOf)("XCodeSample"),XUsePkce:{properties:{disableManualConfiguration:{type:"boolean"},hideClientSecretInput:{type:"boolean"}}},WebhooksMap:{properties:{},additionalProperties:()=>"PathItem"}}},2082:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.Oas3_1Types=void 0;const r=n(1990),i=n(4154),o={properties:{$id:{type:"string"},$anchor:{type:"string"},id:{type:"string"},$schema:{type:"string"},definitions:"NamedSchemas",$defs:"NamedSchemas",$vocabulary:{type:"string"},externalDocs:"ExternalDocs",discriminator:"Discriminator",title:{type:"string"},multipleOf:{type:"number",minimum:0},maximum:{type:"number"},minimum:{type:"number"},exclusiveMaximum:{type:"number"},exclusiveMinimum:{type:"number"},maxLength:{type:"integer",minimum:0},minLength:{type:"integer",minimum:0},pattern:{type:"string"},maxItems:{type:"integer",minimum:0},minItems:{type:"integer",minimum:0},uniqueItems:{type:"boolean"},maxProperties:{type:"integer",minimum:0},minProperties:{type:"integer",minimum:0},required:{type:"array",items:{type:"string"}},enum:{type:"array"},type:e=>Array.isArray(e)?{type:"array",items:{enum:["object","array","string","number","integer","boolean","null"]}}:{enum:["object","array","string","number","integer","boolean","null"]},allOf:(0,r.listOf)("Schema"),anyOf:(0,r.listOf)("Schema"),oneOf:(0,r.listOf)("Schema"),not:"Schema",if:"Schema",then:"Schema",else:"Schema",dependentSchemas:(0,r.listOf)("Schema"),prefixItems:(0,r.listOf)("Schema"),contains:"Schema",minContains:{type:"integer",minimum:0},maxContains:{type:"integer",minimum:0},patternProperties:{type:"object"},propertyNames:"Schema",unevaluatedItems:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",unevaluatedProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",summary:{type:"string"},properties:"SchemaProperties",items:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",additionalProperties:e=>"boolean"==typeof e?{type:"boolean"}:"Schema",description:{type:"string"},format:{type:"string"},contentEncoding:{type:"string"},contentMediaType:{type:"string"},default:null,readOnly:{type:"boolean"},writeOnly:{type:"boolean"},xml:"Xml",examples:{type:"array"},example:{isExample:!0},deprecated:{type:"boolean"},const:null,$comment:{type:"string"},"x-tags":{type:"array",items:{type:"string"}}},extensionsPrefix:"x-"},s={properties:{type:{enum:["apiKey","http","oauth2","openIdConnect","mutualTLS"]},description:{type:"string"},name:{type:"string"},in:{type:"string",enum:["query","header","cookie"]},scheme:{type:"string"},bearerFormat:{type:"string"},flows:"OAuth2Flows",openIdConnectUrl:{type:"string"}},required(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in"];case"http":return["type","scheme"];case"oauth2":return["type","flows"];case"openIdConnect":return["type","openIdConnectUrl"];default:return["type"]}},allowed(e){switch(null==e?void 0:e.type){case"apiKey":return["type","name","in","description"];case"http":return["type","scheme","bearerFormat","description"];case"oauth2":switch(null==e?void 0:e.flows){case"implicit":return["type","flows","authorizationUrl","refreshUrl","description","scopes"];case"password":case"clientCredentials":return["type","flows","tokenUrl","refreshUrl","description","scopes"];default:return["type","flows","authorizationUrl","refreshUrl","tokenUrl","description","scopes"]}case"openIdConnect":return["type","openIdConnectUrl","description"];default:return["type","description"]}},extensionsPrefix:"x-"};t.Oas3_1Types=Object.assign(Object.assign({},i.Oas3Types),{Info:{properties:{title:{type:"string"},version:{type:"string"},description:{type:"string"},termsOfService:{type:"string"},summary:{type:"string"},contact:"Contact",license:"License","x-logo":"Logo"},required:["title","version"],extensionsPrefix:"x-"},Root:{properties:{openapi:null,info:"Info",servers:"ServerList",security:"SecurityRequirementList",tags:"TagList",externalDocs:"ExternalDocs",paths:"Paths",webhooks:"WebhooksMap",components:"Components",jsonSchemaDialect:{type:"string"}},required:["openapi","info"],requiredOneOf:["paths","components","webhooks"],extensionsPrefix:"x-"},Schema:o,License:{properties:{name:{type:"string"},url:{type:"string"},identifier:{type:"string"}},required:["name"],extensionsPrefix:"x-"},Components:{properties:{parameters:"NamedParameters",schemas:"NamedSchemas",responses:"NamedResponses",examples:"NamedExamples",requestBodies:"NamedRequestBodies",headers:"NamedHeaders",securitySchemes:"NamedSecuritySchemes",links:"NamedLinks",callbacks:"NamedCallbacks",pathItems:"NamedPathItems"},extensionsPrefix:"x-"},NamedPathItems:(0,r.mapOf)("PathItem"),SecurityScheme:s,Operation:{properties:{tags:{type:"array",items:{type:"string"}},summary:{type:"string"},description:{type:"string"},externalDocs:"ExternalDocs",operationId:{type:"string"},parameters:"ParameterList",security:"SecurityRequirementList",servers:"ServerList",requestBody:"RequestBody",responses:"Responses",deprecated:{type:"boolean"},callbacks:"CallbacksMap","x-codeSamples":"XCodeSampleList","x-code-samples":"XCodeSampleList","x-hideTryItPanel":{type:"boolean"}},extensionsPrefix:"x-"}})},8209:function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,t,n,r){return new(n||(n=Promise))((function(i,o){function s(e){try{l(r.next(e))}catch(e){o(e)}}function a(e){try{l(r.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(s,a)}l((r=r.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:!0}),t.nextTick=t.pickDefined=t.keysOf=t.identity=t.isTruthy=t.showErrorForDeprecatedField=t.showWarningForDeprecatedField=t.doesYamlFileExist=t.isCustomRuleId=t.getMatchingStatusCodeRange=t.assignExisting=t.isNotString=t.isString=t.isNotEmptyObject=t.slash=t.isPathParameter=t.yamlAndJsonSyncReader=t.readFileAsStringSync=t.isSingular=t.validateMimeTypeOAS3=t.validateMimeType=t.splitCamelCaseIntoWords=t.omitObjectProps=t.pickObjectProps=t.readFileFromUrl=t.isEmptyArray=t.isEmptyObject=t.isPlainObject=t.isDefined=t.loadYaml=t.popStack=t.pushStack=t.stringifyYaml=t.parseYaml=void 0;const i=n(7411),o=n(7975),s=n(4536),a=n(8381),l=n(5127),c=n(970),u=n(1827),p=n(2678);var d=n(970);function f(e){return null!==e&&"object"==typeof e&&!Array.isArray(e)}function h(e,t){return t.match(/^https?:\/\//)||(e=e.replace(/^https?:\/\//,"")),s(e,t)}function m(e){return"string"==typeof e}function g(e){return!!e}function y(e,t){return`${void 0!==t?`${t}.`:""}${e}`}Object.defineProperty(t,"parseYaml",{enumerable:!0,get:function(){return d.parseYaml}}),Object.defineProperty(t,"stringifyYaml",{enumerable:!0,get:function(){return d.stringifyYaml}}),t.pushStack=function(e,t){return{prev:e,value:t}},t.popStack=function(e){var t;return null!==(t=null==e?void 0:e.prev)&&void 0!==t?t:null},t.loadYaml=function(e){return r(this,void 0,void 0,(function*(){const t=yield i.promises.readFile(e,"utf-8");return(0,c.parseYaml)(t)}))},t.isDefined=function(e){return void 0!==e},t.isPlainObject=f,t.isEmptyObject=function(e){return f(e)&&0===Object.keys(e).length},t.isEmptyArray=function(e){return Array.isArray(e)&&0===e.length},t.readFileFromUrl=function(e,t){return r(this,void 0,void 0,(function*(){const n={};for(const r of t.headers)h(e,r.matches)&&(n[r.name]=void 0!==r.envVariable?u.env[r.envVariable]||"":r.value);const r=yield(t.customFetch||a.default)(e,{headers:n});if(!r.ok)throw new Error(`Failed to load ${e}: ${r.status} ${r.statusText}`);return{body:yield r.text(),mimeType:r.headers.get("content-type")}}))},t.pickObjectProps=function(e,t){return Object.fromEntries(t.filter((t=>t in e)).map((t=>[t,e[t]])))},t.omitObjectProps=function(e,t){return Object.fromEntries(Object.entries(e).filter((([e])=>!t.includes(e))))},t.splitCamelCaseIntoWords=function(e){const t=e.split(/(?:[-._])|([A-Z][a-z]+)/).filter(g).map((e=>e.toLocaleLowerCase())),n=e.split(/([A-Z]{2,})/).filter((e=>e&&e===e.toUpperCase())).map((e=>e.toLocaleLowerCase()));return new Set([...t,...n])},t.validateMimeType=function({type:e,value:t},{report:n,location:r},i){if(!i)throw new Error(`Parameter "allowedValues" is not provided for "${"consumes"===e?"request":"response"}-mime-type" rule`);if(t[e])for(const o of t[e])i.includes(o)||n({message:`Mime type "${o}" is not allowed`,location:r.child(t[e].indexOf(o)).key()})},t.validateMimeTypeOAS3=function({type:e,value:t},{report:n,location:r},i){if(!i)throw new Error(`Parameter "allowedValues" is not provided for "${"consumes"===e?"request":"response"}-mime-type" rule`);if(t.content)for(const e of Object.keys(t.content))i.includes(e)||n({message:`Mime type "${e}" is not allowed`,location:r.child("content").child(e).key()})},t.isSingular=function(e){return l.isSingular(e)},t.readFileAsStringSync=function(e){return i.readFileSync(e,"utf-8")},t.yamlAndJsonSyncReader=function(e){const t=i.readFileSync(e,"utf-8");return(0,c.parseYaml)(t)},t.isPathParameter=function(e){return e.startsWith("{")&&e.endsWith("}")},t.slash=function(e){return/^\\\\\?\\/.test(e)?e:e.replace(/\\/g,"/")},t.isNotEmptyObject=function(e){return!!e&&Object.keys(e).length>0},t.isString=m,t.isNotString=function(e){return!m(e)},t.assignExisting=function(e,t){for(const n of Object.keys(t))e.hasOwnProperty(n)&&(e[n]=t[n])},t.getMatchingStatusCodeRange=function(e){return`${e}`.replace(/^(\d)\d\d$/,((e,t)=>`${t}XX`))},t.isCustomRuleId=function(e){return e.includes("/")},t.doesYamlFileExist=function(e){return(".yaml"===(0,o.extname)(e)||".yml"===(0,o.extname)(e))&&i.hasOwnProperty("existsSync")&&i.existsSync(e)},t.showWarningForDeprecatedField=function(e,t,n){p.logger.warn(`The '${p.colorize.red(e)}' field is deprecated. ${t?`Use ${p.colorize.green(y(t,n))} instead. `:""}Read more about this change: https://redocly.com/docs/api-registry/guides/migration-guide-config-file/#changed-properties\n`)},t.showErrorForDeprecatedField=function(e,t,n){throw new Error(`Do not use '${e}' field. ${t?`Use '${y(t,n)}' instead. `:""}\n`)},t.isTruthy=g,t.identity=function(e){return e},t.keysOf=function(e){return e?Object.keys(e):[]},t.pickDefined=function(e){if(!e)return;const t={};for(const n in e)void 0!==e[n]&&(t[n]=e[n]);return t},t.nextTick=function(){new Promise((e=>{setTimeout(e)}))}},2161:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.normalizeVisitors=void 0;const r=n(1990),i={Root:"DefinitionRoot",ServerVariablesMap:"ServerVariableMap",Paths:["PathMap","PathsMap"],CallbacksMap:"CallbackMap",MediaTypesMap:"MediaTypeMap",ExamplesMap:"ExampleMap",EncodingMap:"EncodingsMap",HeadersMap:"HeaderMap",LinksMap:"LinkMap",OAuth2Flows:"SecuritySchemeFlows",Responses:"ResponsesMap"};t.normalizeVisitors=function(e,t){const n={any:{enter:[],leave:[]}};for(const e of Object.keys(t))n[e]={enter:[],leave:[]};n.ref={enter:[],leave:[]};for(const{ruleId:t,severity:n,visitor:r}of e)a({ruleId:t,severity:n},r,null);for(const e of Object.keys(n))n[e].enter.sort(((e,t)=>t.depth-e.depth)),n[e].leave.sort(((e,t)=>e.depth-t.depth));return n;function o(e,t,i,s,a=[]){if(a.includes(t))return;a=[...a,t];const l=new Set;for(const n of Object.values(t.properties))n!==i?"object"==typeof n&&null!==n&&n.name&&l.add(n):c(e,a);t.additionalProperties&&"function"!=typeof t.additionalProperties&&(t.additionalProperties===i?c(e,a):void 0!==t.additionalProperties.name&&l.add(t.additionalProperties)),t.items&&(t.items===i?c(e,a):void 0!==t.items.name&&l.add(t.items)),t.extensionsPrefix&&l.add(r.SpecExtension);for(const t of Array.from(l.values()))o(e,t,i,s,a);function c(e,t){for(const r of t.slice(1))n[r.name]=n[r.name]||{enter:[],leave:[]},n[r.name].enter.push(Object.assign(Object.assign({},e),{visit:()=>{},depth:0,context:{isSkippedLevel:!0,seen:new Set,parent:s}}))}}function s(e,t){if(Array.isArray(t)){const n=t.find((t=>e[t]))||void 0;return n&&e[n]}return e[t]}function a(e,r,l,c=0){const u=Object.keys(t);if(0===c)u.push("any"),u.push("ref");else{if(r.any)throw new Error("any() is allowed only on top level");if(r.ref)throw new Error("ref() is allowed only on top level")}for(const p of u){const u=r[p]||s(r,i[p]),d=n[p];if(!u)continue;let f,h,m;const g="object"==typeof u;if("ref"===p&&g&&u.skip)throw new Error("ref() visitor does not support skip");"function"==typeof u?f=u:g&&(f=u.enter,h=u.leave,m=u.skip);const y={activatedOn:null,type:t[p],parent:l,isSkippedLevel:!1};if("object"==typeof u&&a(e,u,y,c+1),l&&o(e,l.type,t[p],l),f||g){if(f&&"function"!=typeof f)throw new Error("DEV: should be function");d.enter.push(Object.assign(Object.assign({},e),{visit:f||(()=>{}),skip:m,depth:c,context:y}))}if(h){if("function"!=typeof h)throw new Error("DEV: should be function");d.leave.push(Object.assign(Object.assign({},e),{visit:h,depth:c,context:y}))}}}}},5735:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.walkDocument=void 0;const r=n(3873),i=n(8209),o=n(2928),s=n(1990);function a(e){var t,n;const r={};for(;e.parent;)(null===(t=e.parent.activatedOn)||void 0===t?void 0:t.value.location)&&(r[e.parent.type.name]=null===(n=e.parent.activatedOn)||void 0===n?void 0:n.value.location),e=e.parent;return r}t.walkDocument=function(e){const{document:t,rootType:n,normalizedVisitors:l,resolvedRefMap:c,ctx:u}=e,p={},d=new Set;!function e(t,n,f,h,m){var g,y,b,v,x,w,k,S,E,O,_;const A=(e,t=j.source.absoluteRef)=>{if(!(0,r.isRef)(e))return{location:f,node:e};const n=(0,o.makeRefId)(t,e.$ref),i=c.get(n);if(!i)return{location:void 0,node:void 0};const{resolved:s,node:a,document:l,nodePointer:u,error:p}=i;return{location:s?new r.Location(l.source,u):p instanceof o.YamlParseError?new r.Location(p.source,""):void 0,node:a,error:p}},C=f;let j=f;const{node:P,location:T,error:I}=A(t),R=new Set;if((0,r.isRef)(t)){const e=l.ref.enter;for(const{visit:r,ruleId:i,severity:o,context:s}of e)R.add(s),r(t,{report:$.bind(void 0,i,o),resolve:A,rawNode:t,rawLocation:C,location:f,type:n,parent:h,key:m,parentLocations:{},oasVersion:u.oasVersion,getVisitorData:L.bind(void 0,i)},{node:P,location:T,error:I}),(null==T?void 0:T.source.absoluteRef)&&u.refTypes&&u.refTypes.set(null==T?void 0:T.source.absoluteRef,n)}if(void 0!==P&&T&&"scalar"!==n.name){j=T;const o=null===(y=null===(g=p[n.name])||void 0===g?void 0:g.has)||void 0===y?void 0:y.call(g,P);let a=!1;const c=l.any.enter.concat((null===(b=l[n.name])||void 0===b?void 0:b.enter)||[]),u=[];for(const{context:e,visit:r,skip:s,ruleId:l,severity:p}of c){if(d.has(j.pointer))break;if(e.isSkippedLevel)!e.parent.activatedOn||e.parent.activatedOn.value.nextLevelTypeActivated||e.seen.has(t)||(e.seen.add(t),a=!0,u.push(e));else if(e.parent&&e.parent.activatedOn&&(null===(v=e.activatedOn)||void 0===v?void 0:v.value.withParentNode)!==e.parent.activatedOn.value.node&&(null===(x=e.parent.activatedOn.value.nextLevelTypeActivated)||void 0===x?void 0:x.value)!==n||!e.parent&&!o){u.push(e);const o={node:P,location:T,nextLevelTypeActivated:null,withParentNode:null===(k=null===(w=e.parent)||void 0===w?void 0:w.activatedOn)||void 0===k?void 0:k.value.node,skipped:null!==(O=(null===(E=null===(S=e.parent)||void 0===S?void 0:S.activatedOn)||void 0===E?void 0:E.value.skipped)||(null==s?void 0:s(P,m,{location:f,rawLocation:C,resolve:A,rawNode:t})))&&void 0!==O&&O};e.activatedOn=(0,i.pushStack)(e.activatedOn,o);let c=e.parent;for(;c;)c.activatedOn.value.nextLevelTypeActivated=(0,i.pushStack)(c.activatedOn.value.nextLevelTypeActivated,n),c=c.parent;o.skipped||(a=!0,R.add(e),N(r,P,t,e,l,p))}}if(a||!o)if(p[n.name]=p[n.name]||new Set,p[n.name].add(P),Array.isArray(P)){const t=n.items;if(void 0!==t)for(let n=0;n!i.includes(e)))):n.extensionsPrefix&&i.push(...Object.keys(P).filter((e=>e.startsWith(n.extensionsPrefix)))),(0,r.isRef)(t)&&i.push(...Object.keys(t).filter((e=>"$ref"!==e&&!i.includes(e))));for(const o of i){let i=P[o],a=T;void 0===i&&(i=t[o],a=f);let l=n.properties[o];void 0===l&&(l=n.additionalProperties),"function"==typeof l&&(l=l(i,o)),void 0===l&&n.extensionsPrefix&&o.startsWith(n.extensionsPrefix)&&(l=s.SpecExtension),!(0,s.isNamedType)(l)&&(null==l?void 0:l.directResolveAs)&&(l=l.directResolveAs,i={$ref:i}),l&&void 0===l.name&&!1!==l.resolvable&&(l={name:"scalar",properties:{}}),(0,s.isNamedType)(l)&&("scalar"!==l.name||(0,r.isRef)(i))&&e(i,l,a.child([o]),P,o)}}const h=l.any.leave,I=((null===(_=l[n.name])||void 0===_?void 0:_.leave)||[]).concat(h);for(const e of u.reverse())if(e.isSkippedLevel)e.seen.delete(P);else if(e.activatedOn=(0,i.popStack)(e.activatedOn),e.parent){let t=e.parent;for(;t;)t.activatedOn.value.nextLevelTypeActivated=(0,i.popStack)(t.activatedOn.value.nextLevelTypeActivated),t=t.parent}for(const{context:e,visit:n,ruleId:r,severity:i}of I)!e.isSkippedLevel&&R.has(e)&&N(n,P,t,e,r,i)}if(j=f,(0,r.isRef)(t)){const e=l.ref.leave;for(const{visit:r,ruleId:i,severity:o,context:s}of e)R.has(s)&&r(t,{report:$.bind(void 0,i,o),resolve:A,rawNode:t,rawLocation:C,location:f,type:n,parent:h,key:m,parentLocations:{},oasVersion:u.oasVersion,getVisitorData:L.bind(void 0,i)},{node:P,location:T,error:I})}function N(e,t,r,i,o,s){e(t,{report:$.bind(void 0,o,s),resolve:A,rawNode:r,location:j,rawLocation:C,type:n,parent:h,key:m,parentLocations:a(i),oasVersion:u.oasVersion,ignoreNextVisitorsOnNode:()=>{d.add(j.pointer)},getVisitorData:L.bind(void 0,o)},function(e){var t;const n={};for(;e.parent;)n[e.parent.type.name]=null===(t=e.parent.activatedOn)||void 0===t?void 0:t.value.node,e=e.parent;return n}(i),i)}function $(e,t,n){const r=(n.location?Array.isArray(n.location)?n.location:[n.location]:[Object.assign(Object.assign({},j),{reportOnKey:!1})]).map((e=>Object.assign(Object.assign(Object.assign({},j),{reportOnKey:!1}),e))),i=n.forceSeverity||t;"off"!==i&&u.problems.push(Object.assign(Object.assign({ruleId:n.ruleId||e,severity:i},n),{suggest:n.suggest||[],location:r}))}function L(e){return u.visitorsData[e]=u.visitorsData[e]||{},u.visitorsData[e]}}(t.parsed,n,new r.Location(t.source,"#/"),void 0,"")}},1431:function(e,t,n){var r=n(8505);e.exports=function(e){return e?("{}"===e.substr(0,2)&&(e="\\{\\}"+e.substr(2)),g(function(e){return e.split("\\\\").join(i).split("\\{").join(o).split("\\}").join(s).split("\\,").join(a).split("\\.").join(l)}(e),!0).map(u)):[]};var i="\0SLASH"+Math.random()+"\0",o="\0OPEN"+Math.random()+"\0",s="\0CLOSE"+Math.random()+"\0",a="\0COMMA"+Math.random()+"\0",l="\0PERIOD"+Math.random()+"\0";function c(e){return parseInt(e,10)==e?parseInt(e,10):e.charCodeAt(0)}function u(e){return e.split(i).join("\\").split(o).join("{").split(s).join("}").split(a).join(",").split(l).join(".")}function p(e){if(!e)return[""];var t=[],n=r("{","}",e);if(!n)return e.split(",");var i=n.pre,o=n.body,s=n.post,a=i.split(",");a[a.length-1]+="{"+o+"}";var l=p(s);return s.length&&(a[a.length-1]+=l.shift(),a.push.apply(a,l)),t.push.apply(t,a),t}function d(e){return"{"+e+"}"}function f(e){return/^-?0\d/.test(e)}function h(e,t){return e<=t}function m(e,t){return e>=t}function g(e,t){var n=[],i=r("{","}",e);if(!i)return[e];var o=i.pre,a=i.post.length?g(i.post,!1):[""];if(/\$$/.test(i.pre))for(var l=0;l=0;if(!w&&!k)return i.post.match(/,(?!,).*\}/)?g(e=i.pre+"{"+i.body+s+i.post):[e];if(w)y=i.body.split(/\.\./);else if(1===(y=p(i.body)).length&&1===(y=g(y[0],!1).map(d)).length)return a.map((function(e){return i.pre+y[0]+e}));if(w){var S=c(y[0]),E=c(y[1]),O=Math.max(y[0].length,y[1].length),_=3==y.length?Math.abs(c(y[2])):1,A=h;E0){var I=new Array(T+1).join("0");P=j<0?"-"+I+P.slice(1):I+P}}b.push(P)}}else{b=[];for(var R=0;R(g(t),!(!n.nocomment&&"#"===t.charAt(0))&&new v(t,n).match(e));e.exports=r;const i=n(4077);r.sep=i.sep;const o=Symbol("globstar **");r.GLOBSTAR=o;const s=n(1431),a={"!":{open:"(?:(?!(?:",close:"))[^/]*?)"},"?":{open:"(?:",close:")?"},"+":{open:"(?:",close:")+"},"*":{open:"(?:",close:")*"},"@":{open:"(?:",close:")"}},l="[^/]",c=l+"*?",u=e=>e.split("").reduce(((e,t)=>(e[t]=!0,e)),{}),p=u("().*{}+?[]^$\\!"),d=u("[.("),f=/\/+/;r.filter=(e,t={})=>(n,i,o)=>r(n,e,t);const h=(e,t={})=>{const n={};return Object.keys(e).forEach((t=>n[t]=e[t])),Object.keys(t).forEach((e=>n[e]=t[e])),n};r.defaults=e=>{if(!e||"object"!=typeof e||!Object.keys(e).length)return r;const t=r,n=(n,r,i)=>t(n,r,h(e,i));return(n.Minimatch=class extends t.Minimatch{constructor(t,n){super(t,h(e,n))}}).defaults=n=>t.defaults(h(e,n)).Minimatch,n.filter=(n,r)=>t.filter(n,h(e,r)),n.defaults=n=>t.defaults(h(e,n)),n.makeRe=(n,r)=>t.makeRe(n,h(e,r)),n.braceExpand=(n,r)=>t.braceExpand(n,h(e,r)),n.match=(n,r,i)=>t.match(n,r,h(e,i)),n},r.braceExpand=(e,t)=>m(e,t);const m=(e,t={})=>(g(e),t.nobrace||!/\{(?:(?!\{).)*\}/.test(e)?[e]:s(e)),g=e=>{if("string"!=typeof e)throw new TypeError("invalid pattern");if(e.length>65536)throw new TypeError("pattern is too long")},y=Symbol("subparse");r.makeRe=(e,t)=>new v(e,t||{}).makeRe(),r.match=(e,t,n={})=>{const r=new v(t,n);return e=e.filter((e=>r.match(e))),r.options.nonull&&!e.length&&e.push(t),e};const b=e=>e.replace(/[[\]\\]/g,"\\$&");class v{constructor(e,t){g(e),t||(t={}),this.options=t,this.set=[],this.pattern=e,this.windowsPathsNoEscape=!!t.windowsPathsNoEscape||!1===t.allowWindowsEscape,this.windowsPathsNoEscape&&(this.pattern=this.pattern.replace(/\\/g,"/")),this.regexp=null,this.negate=!1,this.comment=!1,this.empty=!1,this.partial=!!t.partial,this.make()}debug(){}make(){const e=this.pattern,t=this.options;if(!t.nocomment&&"#"===e.charAt(0))return void(this.comment=!0);if(!e)return void(this.empty=!0);this.parseNegate();let n=this.globSet=this.braceExpand();t.debug&&(this.debug=(...e)=>console.error(...e)),this.debug(this.pattern,n),n=this.globParts=n.map((e=>e.split(f))),this.debug(this.pattern,n),n=n.map(((e,t,n)=>e.map(this.parse,this))),this.debug(this.pattern,n),n=n.filter((e=>-1===e.indexOf(!1))),this.debug(this.pattern,n),this.set=n}parseNegate(){if(this.options.nonegate)return;const e=this.pattern;let t=!1,n=0;for(let r=0;r>> no match, partial?",e,d,t,f),d!==a))}if("string"==typeof u?(c=p===u,this.debug("string match",u,p,c)):(c=p.match(u),this.debug("pattern match",u,p,c)),!c)return!1}if(i===a&&s===l)return!0;if(i===a)return n;if(s===l)return i===a-1&&""===e[i];throw new Error("wtf?")}braceExpand(){return m(this.pattern,this.options)}parse(e,t){g(e);const n=this.options;if("**"===e){if(!n.noglobstar)return o;e="*"}if(""===e)return"";let r="",i=!1,s=!1;const u=[],f=[];let h,m,v,x,w=!1,k=-1,S=-1,E="."===e.charAt(0),O=n.dot||E;const _=e=>"."===e.charAt(0)?"":n.dot?"(?!(?:^|\\/)\\.{1,2}(?:$|\\/))":"(?!\\.)",A=()=>{if(h){switch(h){case"*":r+=c,i=!0;break;case"?":r+=l,i=!0;break;default:r+="\\"+h}this.debug("clearStateChar %j %j",h,r),h=!1}};for(let t,o=0;o(n||(n="\\"),t+t+n+"|"))),this.debug("tail=%j\n %s",e,e,v,r);const t="*"===v.type?c:"?"===v.type?l:"\\"+v.type;i=!0,r=r.slice(0,v.reStart)+t+"\\("+e}A(),s&&(r+="\\\\");const C=d[r.charAt(0)];for(let e=f.length-1;e>-1;e--){const n=f[e],i=r.slice(0,n.reStart),o=r.slice(n.reStart,n.reEnd-8);let s=r.slice(n.reEnd);const a=r.slice(n.reEnd-8,n.reEnd)+s,l=i.split(")").length,c=i.split("(").length-l;let u=s;for(let e=0;ee.replace(/\\(.)/g,"$1"))(e);const j=n.nocase?"i":"";try{return Object.assign(new RegExp("^"+r+"$",j),{_glob:e,_src:r})}catch(e){return new RegExp("$.")}}makeRe(){if(this.regexp||!1===this.regexp)return this.regexp;const e=this.set;if(!e.length)return this.regexp=!1,this.regexp;const t=this.options,n=t.noglobstar?c:t.dot?"(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?":"(?:(?!(?:\\/|^)\\.).)*?",r=t.nocase?"i":"";let i=e.map((e=>(e=e.map((e=>"string"==typeof e?e.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"):e===o?o:e._src)).reduce(((e,t)=>(e[e.length-1]===o&&t===o||e.push(t),e)),[]),e.forEach(((t,r)=>{t===o&&e[r-1]!==o&&(0===r?e.length>1?e[r+1]="(?:\\/|"+n+"\\/)?"+e[r+1]:e[r]=n:r===e.length-1?e[r-1]+="(?:\\/|"+n+")?":(e[r-1]+="(?:\\/|\\/"+n+"\\/)"+e[r+1],e[r+1]=o))})),e.filter((e=>e!==o)).join("/")))).join("|");i="^(?:"+i+")$",this.negate&&(i="^(?!"+i+").*$");try{this.regexp=new RegExp(i,r)}catch(e){this.regexp=!1}return this.regexp}match(e,t=this.partial){if(this.debug("match",e,this.pattern),this.comment)return!1;if(this.empty)return""===e;if("/"===e&&t)return!0;const n=this.options;"/"!==i.sep&&(e=e.split(i.sep).join("/")),e=e.split(f),this.debug(this.pattern,"split",e);const r=this.set;let o;this.debug(this.pattern,"set",r);for(let t=e.length-1;t>=0&&(o=e[t],!o);t--);for(let i=0;i=0&&c>0){if(e===t)return[l,c];for(r=[],o=n.length;u>=0&&!a;)u==l?(r.push(u),l=n.indexOf(e,u+1)):1==r.length?a=[r.pop(),c]:((i=r.pop())=0?l:c;r.length&&(a=[o,s])}return a}e.exports=t,t.range=r},3998:function(e,t,n){"use strict";var r=n(1137);e.exports=function(e,t){return e?void t.then((function(t){r((function(){e(null,t)}))}),(function(t){r((function(){e(t)}))})):t}},1137:function(e){"use strict";e.exports="object"==typeof process&&"function"==typeof process.nextTick?process.nextTick:"function"==typeof setImmediate?setImmediate:function(e){setTimeout(e,0)}},2485:function(e,t){var n;!function(){"use strict";var r={}.hasOwnProperty;function i(){for(var e=[],t=0;tu;)if((a=l[u++])!=a)return!0}else for(;c>u;u++)if((e||u in l)&&l[u]===n)return e||u||0;return!e&&-1}};e.exports={includes:s(!0),indexOf:s(!1)}},1344:function(e,t,n){var r=n(6885),i=n(8664),o=n(2612),s=n(3747),a=n(2998),l=[].push,c=function(e){var t=1==e,n=2==e,c=3==e,u=4==e,p=6==e,d=7==e,f=5==e||p;return function(h,m,g,y){for(var b,v,x=o(h),w=i(x),k=r(m,g,3),S=s(w.length),E=0,O=y||a,_=t?O(h,S):n||d?O(h,0):void 0;S>E;E++)if((f||E in w)&&(v=k(b=w[E],E,x),e))if(t)_[E]=v;else if(v)switch(e){case 3:return!0;case 5:return b;case 6:return E;case 2:l.call(_,b)}else switch(e){case 4:return!1;case 7:l.call(_,b)}return p?-1:c||u?u:_}};e.exports={forEach:c(0),map:c(1),filter:c(2),some:c(3),every:c(4),find:c(5),findIndex:c(6),filterOut:c(7)}},5634:function(e,t,n){var r=n(2074),i=n(1602),o=n(6845),s=i("species");e.exports=function(e){return o>=51||!r((function(){var t=[];return(t.constructor={})[s]=function(){return{foo:1}},1!==t[e](Boolean).foo}))}},2998:function(e,t,n){var r=n(5335),i=n(8679),o=n(1602)("species");e.exports=function(e,t){var n;return i(e)&&("function"!=typeof(n=e.constructor)||n!==Array&&!i(n.prototype)?r(n)&&null===(n=n[o])&&(n=void 0):n=void 0),new(void 0===n?Array:n)(0===t?0:t)}},8569:function(e){var t={}.toString;e.exports=function(e){return t.call(e).slice(8,-1)}},3062:function(e,t,n){var r=n(3129),i=n(8569),o=n(1602)("toStringTag"),s="Arguments"==i(function(){return arguments}());e.exports=r?i:function(e){var t,n,r;return void 0===e?"Undefined":null===e?"Null":"string"==typeof(n=function(e,t){try{return e[t]}catch(e){}}(t=Object(e),o))?n:s?i(t):"Object"==(r=i(t))&&"function"==typeof t.callee?"Arguments":r}},4361:function(e,t,n){var r=n(1883),i=n(5816),o=n(7632),s=n(3610);e.exports=function(e,t){for(var n=i(t),a=s.f,l=o.f,c=0;c=74)&&(r=s.match(/Chrome\/(\d+)/))&&(i=r[1]),e.exports=i&&+i},290:function(e){e.exports=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"]},1605:function(e,t,n){var r=n(200),i=n(7632).f,o=n(7712),s=n(7485),a=n(5975),l=n(4361),c=n(4977);e.exports=function(e,t){var n,u,p,d,f,h=e.target,m=e.global,g=e.stat;if(n=m?r:g?r[h]||a(h,{}):(r[h]||{}).prototype)for(u in t){if(d=t[u],p=e.noTargetGet?(f=i(n,u))&&f.value:n[u],!c(m?u:h+(g?".":"#")+u,e.forced)&&void 0!==p){if(typeof d==typeof p)continue;l(d,p)}(e.sham||p&&p.sham)&&o(d,"sham",!0),s(n,u,d,e)}}},2074:function(e){e.exports=function(e){try{return!!e()}catch(e){return!0}}},6885:function(e,t,n){var r=n(9085);e.exports=function(e,t,n){if(r(e),void 0===t)return e;switch(n){case 0:return function(){return e.call(t)};case 1:return function(n){return e.call(t,n)};case 2:return function(n,r){return e.call(t,n,r)};case 3:return function(n,r,i){return e.call(t,n,r,i)}}return function(){return e.apply(t,arguments)}}},6492:function(e,t,n){var r=n(9720),i=n(200),o=function(e){return"function"==typeof e?e:void 0};e.exports=function(e,t){return arguments.length<2?o(r[e])||o(i[e]):r[e]&&r[e][t]||i[e]&&i[e][t]}},200:function(e,t,n){var r=function(e){return e&&e.Math==Math&&e};e.exports=r("object"==typeof globalThis&&globalThis)||r("object"==typeof window&&window)||r("object"==typeof self&&self)||r("object"==typeof n.g&&n.g)||function(){return this}()||Function("return this")()},1883:function(e,t,n){var r=n(2612),i={}.hasOwnProperty;e.exports=Object.hasOwn||function(e,t){return i.call(r(e),t)}},7708:function(e){e.exports={}},8890:function(e,t,n){var r=n(6492);e.exports=r("document","documentElement")},7694:function(e,t,n){var r=n(5077),i=n(2074),o=n(3262);e.exports=!r&&!i((function(){return 7!=Object.defineProperty(o("div"),"a",{get:function(){return 7}}).a}))},8664:function(e,t,n){var r=n(2074),i=n(8569),o="".split;e.exports=r((function(){return!Object("z").propertyIsEnumerable(0)}))?function(e){return"String"==i(e)?o.call(e,""):Object(e)}:Object},9965:function(e,t,n){var r=n(9310),i=Function.toString;"function"!=typeof r.inspectSource&&(r.inspectSource=function(e){return i.call(e)}),e.exports=r.inspectSource},9206:function(e,t,n){var r,i,o,s=n(2886),a=n(200),l=n(5335),c=n(7712),u=n(1883),p=n(9310),d=n(5904),f=n(7708),h="Object already initialized",m=a.WeakMap;if(s||p.state){var g=p.state||(p.state=new m),y=g.get,b=g.has,v=g.set;r=function(e,t){if(b.call(g,e))throw new TypeError(h);return t.facade=e,v.call(g,e,t),t},i=function(e){return y.call(g,e)||{}},o=function(e){return b.call(g,e)}}else{var x=d("state");f[x]=!0,r=function(e,t){if(u(e,x))throw new TypeError(h);return t.facade=e,c(e,x,t),t},i=function(e){return u(e,x)?e[x]:{}},o=function(e){return u(e,x)}}e.exports={set:r,get:i,has:o,enforce:function(e){return o(e)?i(e):r(e,{})},getterFor:function(e){return function(t){var n;if(!l(t)||(n=i(t)).type!==e)throw TypeError("Incompatible receiver, "+e+" required");return n}}}},8679:function(e,t,n){var r=n(8569);e.exports=Array.isArray||function(e){return"Array"==r(e)}},4977:function(e,t,n){var r=n(2074),i=/#|\.prototype\./,o=function(e,t){var n=a[s(e)];return n==c||n!=l&&("function"==typeof t?r(t):!!t)},s=o.normalize=function(e){return String(e).replace(i,".").toLowerCase()},a=o.data={},l=o.NATIVE="N",c=o.POLYFILL="P";e.exports=o},5335:function(e){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},6926:function(e){e.exports=!1},1849:function(e,t,n){var r=n(6845),i=n(2074);e.exports=!!Object.getOwnPropertySymbols&&!i((function(){var e=Symbol();return!String(e)||!(Object(e)instanceof Symbol)||!Symbol.sham&&r&&r<41}))},2886:function(e,t,n){var r=n(200),i=n(9965),o=r.WeakMap;e.exports="function"==typeof o&&/native code/.test(i(o))},3105:function(e,t,n){var r,i=n(3938),o=n(5318),s=n(290),a=n(7708),l=n(8890),c=n(3262),u=n(5904),p="prototype",d="script",f=u("IE_PROTO"),h=function(){},m=function(e){return"<"+d+">"+e+""},g=function(){try{r=document.domain&&new ActiveXObject("htmlfile")}catch(e){}var e,t,n;g=r?function(e){e.write(m("")),e.close();var t=e.parentWindow.Object;return e=null,t}(r):(t=c("iframe"),n="java"+d+":",t.style.display="none",l.appendChild(t),t.src=String(n),(e=t.contentWindow.document).open(),e.write(m("document.F=Object")),e.close(),e.F);for(var i=s.length;i--;)delete g[p][s[i]];return g()};a[f]=!0,e.exports=Object.create||function(e,t){var n;return null!==e?(h[p]=i(e),n=new h,h[p]=null,n[f]=e):n=g(),void 0===t?n:o(n,t)}},5318:function(e,t,n){var r=n(5077),i=n(3610),o=n(3938),s=n(1641);e.exports=r?Object.defineProperties:function(e,t){o(e);for(var n,r=s(t),a=r.length,l=0;a>l;)i.f(e,n=r[l++],t[n]);return e}},3610:function(e,t,n){var r=n(5077),i=n(7694),o=n(3938),s=n(874),a=Object.defineProperty;t.f=r?a:function(e,t,n){if(o(e),t=s(t,!0),o(n),i)try{return a(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(e[t]=n.value),e}},7632:function(e,t,n){var r=n(5077),i=n(9304),o=n(6843),s=n(5476),a=n(874),l=n(1883),c=n(7694),u=Object.getOwnPropertyDescriptor;t.f=r?u:function(e,t){if(e=s(e),t=a(t,!0),c)try{return u(e,t)}catch(e){}if(l(e,t))return o(!i.f.call(e,t),e[t])}},6509:function(e,t,n){var r=n(5476),i=n(4789).f,o={}.toString,s="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];e.exports.f=function(e){return s&&"[object Window]"==o.call(e)?function(e){try{return i(e)}catch(e){return s.slice()}}(e):i(r(e))}},4789:function(e,t,n){var r=n(6347),i=n(290).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,i)}},8916:function(e,t){t.f=Object.getOwnPropertySymbols},6347:function(e,t,n){var r=n(1883),i=n(5476),o=n(8186).indexOf,s=n(7708);e.exports=function(e,t){var n,a=i(e),l=0,c=[];for(n in a)!r(s,n)&&r(a,n)&&c.push(n);for(;t.length>l;)r(a,n=t[l++])&&(~o(c,n)||c.push(n));return c}},1641:function(e,t,n){var r=n(6347),i=n(290);e.exports=Object.keys||function(e){return r(e,i)}},9304:function(e,t){"use strict";var n={}.propertyIsEnumerable,r=Object.getOwnPropertyDescriptor,i=r&&!n.call({1:2},1);t.f=i?function(e){var t=r(this,e);return!!t&&t.enumerable}:n},4972:function(e,t,n){"use strict";var r=n(3129),i=n(3062);e.exports=r?{}.toString:function(){return"[object "+i(this)+"]"}},5816:function(e,t,n){var r=n(6492),i=n(4789),o=n(8916),s=n(3938);e.exports=r("Reflect","ownKeys")||function(e){var t=i.f(s(e)),n=o.f;return n?t.concat(n(e)):t}},9720:function(e,t,n){var r=n(200);e.exports=r},7485:function(e,t,n){var r=n(200),i=n(7712),o=n(1883),s=n(5975),a=n(9965),l=n(9206),c=l.get,u=l.enforce,p=String(String).split("String");(e.exports=function(e,t,n,a){var l,c=!!a&&!!a.unsafe,d=!!a&&!!a.enumerable,f=!!a&&!!a.noTargetGet;"function"==typeof n&&("string"!=typeof t||o(n,"name")||i(n,"name",t),(l=u(n)).source||(l.source=p.join("string"==typeof t?t:""))),e!==r?(c?!f&&e[t]&&(d=!0):delete e[t],d?e[t]=n:i(e,t,n)):d?e[t]=n:s(t,n)})(Function.prototype,"toString",(function(){return"function"==typeof this&&c(this).source||a(this)}))},1229:function(e){e.exports=function(e){if(null==e)throw TypeError("Can't call method on "+e);return e}},5975:function(e,t,n){var r=n(200),i=n(7712);e.exports=function(e,t){try{i(r,e,t)}catch(n){r[e]=t}return t}},5282:function(e,t,n){var r=n(3610).f,i=n(1883),o=n(1602)("toStringTag");e.exports=function(e,t,n){e&&!i(e=n?e:e.prototype,o)&&r(e,o,{configurable:!0,value:t})}},5904:function(e,t,n){var r=n(2),i=n(665),o=r("keys");e.exports=function(e){return o[e]||(o[e]=i(e))}},9310:function(e,t,n){var r=n(200),i=n(5975),o="__core-js_shared__",s=r[o]||i(o,{});e.exports=s},2:function(e,t,n){var r=n(6926),i=n(9310);(e.exports=function(e,t){return i[e]||(i[e]=void 0!==t?t:{})})("versions",[]).push({version:"3.14.0",mode:r?"pure":"global",copyright:"© 2021 Denis Pushkarev (zloirock.ru)"})},6539:function(e,t,n){var r=n(7317),i=Math.max,o=Math.min;e.exports=function(e,t){var n=r(e);return n<0?i(n+t,0):o(n,t)}},5476:function(e,t,n){var r=n(8664),i=n(1229);e.exports=function(e){return r(i(e))}},7317:function(e){var t=Math.ceil,n=Math.floor;e.exports=function(e){return isNaN(e=+e)?0:(e>0?n:t)(e)}},3747:function(e,t,n){var r=n(7317),i=Math.min;e.exports=function(e){return e>0?i(r(e),9007199254740991):0}},2612:function(e,t,n){var r=n(1229);e.exports=function(e){return Object(r(e))}},874:function(e,t,n){var r=n(5335);e.exports=function(e,t){if(!r(e))return e;var n,i;if(t&&"function"==typeof(n=e.toString)&&!r(i=n.call(e)))return i;if("function"==typeof(n=e.valueOf)&&!r(i=n.call(e)))return i;if(!t&&"function"==typeof(n=e.toString)&&!r(i=n.call(e)))return i;throw TypeError("Can't convert object to primitive value")}},3129:function(e,t,n){var r={};r[n(1602)("toStringTag")]="z",e.exports="[object z]"===String(r)},665:function(e){var t=0,n=Math.random();e.exports=function(e){return"Symbol("+String(void 0===e?"":e)+")_"+(++t+n).toString(36)}},5225:function(e,t,n){var r=n(1849);e.exports=r&&!Symbol.sham&&"symbol"==typeof Symbol.iterator},802:function(e,t,n){var r=n(1602);t.f=r},1602:function(e,t,n){var r=n(200),i=n(2),o=n(1883),s=n(665),a=n(1849),l=n(5225),c=i("wks"),u=r.Symbol,p=l?u:u&&u.withoutSetter||s;e.exports=function(e){return o(c,e)&&(a||"string"==typeof c[e])||(a&&o(u,e)?c[e]=u[e]:c[e]=p("Symbol."+e)),c[e]}},115:function(e,t,n){"use strict";var r=n(1605),i=n(2074),o=n(8679),s=n(5335),a=n(2612),l=n(3747),c=n(2057),u=n(2998),p=n(5634),d=n(1602),f=n(6845),h=d("isConcatSpreadable"),m=9007199254740991,g="Maximum allowed index exceeded",y=f>=51||!i((function(){var e=[];return e[h]=!1,e.concat()[0]!==e})),b=p("concat"),v=function(e){if(!s(e))return!1;var t=e[h];return void 0!==t?!!t:o(e)};r({target:"Array",proto:!0,forced:!y||!b},{concat:function(e){var t,n,r,i,o,s=a(this),p=u(s,0),d=0;for(t=-1,r=arguments.length;tm)throw TypeError(g);for(n=0;n=m)throw TypeError(g);c(p,d++,o)}return p.length=d,p}})},1586:function(e,t,n){var r=n(200);n(5282)(r.JSON,"JSON",!0)},6982:function(e,t,n){n(5282)(Math,"Math",!0)},5086:function(e,t,n){var r=n(3129),i=n(7485),o=n(4972);r||i(Object.prototype,"toString",o,{unsafe:!0})},3719:function(e,t,n){var r=n(1605),i=n(200),o=n(5282);r({global:!0},{Reflect:{}}),o(i.Reflect,"Reflect",!0)},7727:function(e,t,n){n(1272)("asyncIterator")},590:function(e,t,n){"use strict";var r=n(1605),i=n(5077),o=n(200),s=n(1883),a=n(5335),l=n(3610).f,c=n(4361),u=o.Symbol;if(i&&"function"==typeof u&&(!("description"in u.prototype)||void 0!==u().description)){var p={},d=function(){var e=arguments.length<1||void 0===arguments[0]?void 0:String(arguments[0]),t=this instanceof d?new u(e):void 0===e?u():u(e);return""===e&&(p[t]=!0),t};c(d,u);var f=d.prototype=u.prototype;f.constructor=d;var h=f.toString,m="Symbol(test)"==String(u("test")),g=/^Symbol\((.*)\)[^)]+$/;l(f,"description",{configurable:!0,get:function(){var e=a(this)?this.valueOf():this,t=h.call(e);if(s(p,e))return"";var n=m?t.slice(7,-1):t.replace(g,"$1");return""===n?void 0:n}}),r({global:!0,forced:!0},{Symbol:d})}},8290:function(e,t,n){n(1272)("hasInstance")},2619:function(e,t,n){n(1272)("isConcatSpreadable")},4216:function(e,t,n){n(1272)("iterator")},3534:function(e,t,n){"use strict";var r=n(1605),i=n(200),o=n(6492),s=n(6926),a=n(5077),l=n(1849),c=n(5225),u=n(2074),p=n(1883),d=n(8679),f=n(5335),h=n(3938),m=n(2612),g=n(5476),y=n(874),b=n(6843),v=n(3105),x=n(1641),w=n(4789),k=n(6509),S=n(8916),E=n(7632),O=n(3610),_=n(9304),A=n(7712),C=n(7485),j=n(2),P=n(5904),T=n(7708),I=n(665),R=n(1602),N=n(802),$=n(1272),L=n(5282),D=n(9206),M=n(1344).forEach,F=P("hidden"),z="Symbol",B="prototype",U=R("toPrimitive"),q=D.set,V=D.getterFor(z),W=Object[B],H=i.Symbol,Y=o("JSON","stringify"),G=E.f,Q=O.f,X=k.f,K=_.f,Z=j("symbols"),J=j("op-symbols"),ee=j("string-to-symbol-registry"),te=j("symbol-to-string-registry"),ne=j("wks"),re=i.QObject,ie=!re||!re[B]||!re[B].findChild,oe=a&&u((function(){return 7!=v(Q({},"a",{get:function(){return Q(this,"a",{value:7}).a}})).a}))?function(e,t,n){var r=G(W,t);r&&delete W[t],Q(e,t,n),r&&e!==W&&Q(W,t,r)}:Q,se=function(e,t){var n=Z[e]=v(H[B]);return q(n,{type:z,tag:e,description:t}),a||(n.description=t),n},ae=c?function(e){return"symbol"==typeof e}:function(e){return Object(e)instanceof H},le=function(e,t,n){e===W&&le(J,t,n),h(e);var r=y(t,!0);return h(n),p(Z,r)?(n.enumerable?(p(e,F)&&e[F][r]&&(e[F][r]=!1),n=v(n,{enumerable:b(0,!1)})):(p(e,F)||Q(e,F,b(1,{})),e[F][r]=!0),oe(e,r,n)):Q(e,r,n)},ce=function(e,t){h(e);var n=g(t),r=x(n).concat(fe(n));return M(r,(function(t){a&&!ue.call(n,t)||le(e,t,n[t])})),e},ue=function(e){var t=y(e,!0),n=K.call(this,t);return!(this===W&&p(Z,t)&&!p(J,t))&&(!(n||!p(this,t)||!p(Z,t)||p(this,F)&&this[F][t])||n)},pe=function(e,t){var n=g(e),r=y(t,!0);if(n!==W||!p(Z,r)||p(J,r)){var i=G(n,r);return!i||!p(Z,r)||p(n,F)&&n[F][r]||(i.enumerable=!0),i}},de=function(e){var t=X(g(e)),n=[];return M(t,(function(e){p(Z,e)||p(T,e)||n.push(e)})),n},fe=function(e){var t=e===W,n=X(t?J:g(e)),r=[];return M(n,(function(e){!p(Z,e)||t&&!p(W,e)||r.push(Z[e])})),r};l||(H=function(){if(this instanceof H)throw TypeError("Symbol is not a constructor");var e=arguments.length&&void 0!==arguments[0]?String(arguments[0]):void 0,t=I(e),n=function(e){this===W&&n.call(J,e),p(this,F)&&p(this[F],t)&&(this[F][t]=!1),oe(this,t,b(1,e))};return a&&ie&&oe(W,t,{configurable:!0,set:n}),se(t,e)},C(H[B],"toString",(function(){return V(this).tag})),C(H,"withoutSetter",(function(e){return se(I(e),e)})),_.f=ue,O.f=le,E.f=pe,w.f=k.f=de,S.f=fe,N.f=function(e){return se(R(e),e)},a&&(Q(H[B],"description",{configurable:!0,get:function(){return V(this).description}}),s||C(W,"propertyIsEnumerable",ue,{unsafe:!0}))),r({global:!0,wrap:!0,forced:!l,sham:!l},{Symbol:H}),M(x(ne),(function(e){$(e)})),r({target:z,stat:!0,forced:!l},{for:function(e){var t=String(e);if(p(ee,t))return ee[t];var n=H(t);return ee[t]=n,te[n]=t,n},keyFor:function(e){if(!ae(e))throw TypeError(e+" is not a symbol");if(p(te,e))return te[e]},useSetter:function(){ie=!0},useSimple:function(){ie=!1}}),r({target:"Object",stat:!0,forced:!l,sham:!a},{create:function(e,t){return void 0===t?v(e):ce(v(e),t)},defineProperty:le,defineProperties:ce,getOwnPropertyDescriptor:pe}),r({target:"Object",stat:!0,forced:!l},{getOwnPropertyNames:de,getOwnPropertySymbols:fe}),r({target:"Object",stat:!0,forced:u((function(){S.f(1)}))},{getOwnPropertySymbols:function(e){return S.f(m(e))}}),Y&&r({target:"JSON",stat:!0,forced:!l||u((function(){var e=H();return"[null]"!=Y([e])||"{}"!=Y({a:e})||"{}"!=Y(Object(e))}))},{stringify:function(e,t,n){for(var r,i=[e],o=1;arguments.length>o;)i.push(arguments[o++]);if(r=t,(f(t)||void 0!==e)&&!ae(e))return d(t)||(t=function(e,t){if("function"==typeof r&&(t=r.call(this,e,t)),!ae(t))return t}),i[1]=t,Y.apply(null,i)}}),H[B][U]||A(H[B],U,H[B].valueOf),L(H,z),T[F]=!0},6195:function(e,t,n){n(1272)("matchAll")},2957:function(e,t,n){n(1272)("match")},4100:function(e,t,n){n(1272)("replace")},3006:function(e,t,n){n(1272)("search")},4910:function(e,t,n){n(1272)("species")},2820:function(e,t,n){n(1272)("split")},6611:function(e,t,n){n(1272)("toPrimitive")},9576:function(e,t,n){n(1272)("toStringTag")},9747:function(e,t,n){n(1272)("unscopables")},8997:function(e,t,n){"use strict";var r=n(4991),i=n.n(r),o=n(6314),s=n.n(o)()(i());s.push([e.id,".ps{overflow:hidden!important;overflow-anchor:none;-ms-overflow-style:none;touch-action:auto;-ms-touch-action:auto}.ps__rail-x{display:none;opacity:0;transition:background-color .2s linear,opacity .2s linear;-webkit-transition:background-color .2s linear,opacity .2s linear;height:15px;bottom:0;position:absolute}.ps__rail-y{display:none;opacity:0;transition:background-color .2s linear,opacity .2s linear;-webkit-transition:background-color .2s linear,opacity .2s linear;width:15px;right:0;position:absolute}.ps--active-x>.ps__rail-x,.ps--active-y>.ps__rail-y{display:block;background-color:transparent}.ps:hover>.ps__rail-x,.ps:hover>.ps__rail-y,.ps--focus>.ps__rail-x,.ps--focus>.ps__rail-y,.ps--scrolling-x>.ps__rail-x,.ps--scrolling-y>.ps__rail-y{opacity:.6}.ps .ps__rail-x:hover,.ps .ps__rail-y:hover,.ps .ps__rail-x:focus,.ps .ps__rail-y:focus,.ps .ps__rail-x.ps--clicking,.ps .ps__rail-y.ps--clicking{background-color:#eee;opacity:.9}.ps__thumb-x{background-color:#aaa;border-radius:6px;transition:background-color .2s linear,height .2s ease-in-out;-webkit-transition:background-color .2s linear,height .2s ease-in-out;height:6px;bottom:2px;position:absolute}.ps__thumb-y{background-color:#aaa;border-radius:6px;transition:background-color .2s linear,width .2s ease-in-out;-webkit-transition:background-color .2s linear,width .2s ease-in-out;width:6px;right:2px;position:absolute}.ps__rail-x:hover>.ps__thumb-x,.ps__rail-x:focus>.ps__thumb-x,.ps__rail-x.ps--clicking .ps__thumb-x{background-color:#999;height:11px}.ps__rail-y:hover>.ps__thumb-y,.ps__rail-y:focus>.ps__thumb-y,.ps__rail-y.ps--clicking .ps__thumb-y{background-color:#999;width:11px}@supports (-ms-overflow-style: none){.ps{overflow:auto!important}}@media screen and (-ms-high-contrast: active),(-ms-high-contrast: none){.ps{overflow:auto!important}}\n","",{version:3,sources:["webpack://./node_modules/perfect-scrollbar/css/perfect-scrollbar.css"],names:[],mappings:"AAGA,IACE,yBAAU,CACV,oBAAiB,CACjB,uBAAoB,CACpB,iBAAc,CACd,qBACF,CAKA,YACE,YAAS,CACT,SAAS,CACT,yDAAqD,CACrD,iEAA6D,CAC7D,WAAQ,CAER,QAAQ,CAER,iBACF,CAEA,YACE,YAAS,CACT,SAAS,CACT,yDAAqD,CACrD,iEAA6D,CAC7D,UAAO,CAEP,OAAO,CAEP,iBACF,CAEA,oDAEE,aAAS,CACT,4BACF,CAEA,oJAME,UACF,CAEA,kJAME,qBAAkB,CAClB,UACF,CAKA,aACE,qBAAkB,CAnEpB,iBAoEiB,CACf,6DAAoD,CACpD,qEAA4D,CAC5D,UAAQ,CAER,UAAQ,CAER,iBACF,CAEA,aACE,qBAAkB,CA/EpB,iBAgFiB,CACf,4DAAmD,CACnD,oEAA2D,CAC3D,SAAO,CAEP,SAAO,CAEP,iBACF,CAEA,oGAGE,qBAAkB,CAClB,WACF,CAEA,oGAGE,qBAAkB,CAClB,UACF,CAGA,qCACE,IACE,uBACF,CACF,CAEA,wEACE,IACE,uBACF,CACF",sourcesContent:["/*\n * Container style\n */\n.ps {\n overflow: hidden !important;\n overflow-anchor: none;\n -ms-overflow-style: none;\n touch-action: auto;\n -ms-touch-action: auto;\n}\n\n/*\n * Scrollbar rail styles\n */\n.ps__rail-x {\n display: none;\n opacity: 0;\n transition: background-color .2s linear, opacity .2s linear;\n -webkit-transition: background-color .2s linear, opacity .2s linear;\n height: 15px;\n /* there must be 'bottom' or 'top' for ps__rail-x */\n bottom: 0px;\n /* please don't change 'position' */\n position: absolute;\n}\n\n.ps__rail-y {\n display: none;\n opacity: 0;\n transition: background-color .2s linear, opacity .2s linear;\n -webkit-transition: background-color .2s linear, opacity .2s linear;\n width: 15px;\n /* there must be 'right' or 'left' for ps__rail-y */\n right: 0;\n /* please don't change 'position' */\n position: absolute;\n}\n\n.ps--active-x > .ps__rail-x,\n.ps--active-y > .ps__rail-y {\n display: block;\n background-color: transparent;\n}\n\n.ps:hover > .ps__rail-x,\n.ps:hover > .ps__rail-y,\n.ps--focus > .ps__rail-x,\n.ps--focus > .ps__rail-y,\n.ps--scrolling-x > .ps__rail-x,\n.ps--scrolling-y > .ps__rail-y {\n opacity: 0.6;\n}\n\n.ps .ps__rail-x:hover,\n.ps .ps__rail-y:hover,\n.ps .ps__rail-x:focus,\n.ps .ps__rail-y:focus,\n.ps .ps__rail-x.ps--clicking,\n.ps .ps__rail-y.ps--clicking {\n background-color: #eee;\n opacity: 0.9;\n}\n\n/*\n * Scrollbar thumb styles\n */\n.ps__thumb-x {\n background-color: #aaa;\n border-radius: 6px;\n transition: background-color .2s linear, height .2s ease-in-out;\n -webkit-transition: background-color .2s linear, height .2s ease-in-out;\n height: 6px;\n /* there must be 'bottom' for ps__thumb-x */\n bottom: 2px;\n /* please don't change 'position' */\n position: absolute;\n}\n\n.ps__thumb-y {\n background-color: #aaa;\n border-radius: 6px;\n transition: background-color .2s linear, width .2s ease-in-out;\n -webkit-transition: background-color .2s linear, width .2s ease-in-out;\n width: 6px;\n /* there must be 'right' for ps__thumb-y */\n right: 2px;\n /* please don't change 'position' */\n position: absolute;\n}\n\n.ps__rail-x:hover > .ps__thumb-x,\n.ps__rail-x:focus > .ps__thumb-x,\n.ps__rail-x.ps--clicking .ps__thumb-x {\n background-color: #999;\n height: 11px;\n}\n\n.ps__rail-y:hover > .ps__thumb-y,\n.ps__rail-y:focus > .ps__thumb-y,\n.ps__rail-y.ps--clicking .ps__thumb-y {\n background-color: #999;\n width: 11px;\n}\n\n/* MS supports */\n@supports (-ms-overflow-style: none) {\n .ps {\n overflow: auto !important;\n }\n}\n\n@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {\n .ps {\n overflow: auto !important;\n }\n}\n"],sourceRoot:""}]),t.A=s},6314:function(e){"use strict";e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var n=e(t);return t[2]?"@media ".concat(t[2]," {").concat(n,"}"):n})).join("")},t.i=function(e,n,r){"string"==typeof e&&(e=[[null,e,""]]);var i={};if(r)for(var o=0;oe.length)&&(t=e.length);for(var n=0,r=new Array(t);n2?r:e).apply(void 0,i)}}e.memoize=s,e.debounce=a,e.bind=l,e.default={memoize:s,debounce:a,bind:l}},void 0===(r=n.apply(t,[t]))||(e.exports=r)},6364:function(e){e.exports={}},228:function(e){"use strict";var t=Object.prototype.hasOwnProperty,n="~";function r(){}function i(e,t,n){this.fn=e,this.context=t,this.once=n||!1}function o(e,t,r,o,s){if("function"!=typeof r)throw new TypeError("The listener must be a function");var a=new i(r,o||e,s),l=n?n+t:t;return e._events[l]?e._events[l].fn?e._events[l]=[e._events[l],a]:e._events[l].push(a):(e._events[l]=a,e._eventsCount++),e}function s(e,t){0==--e._eventsCount?e._events=new r:delete e._events[t]}function a(){this._events=new r,this._eventsCount=0}Object.create&&(r.prototype=Object.create(null),(new r).__proto__||(n=!1)),a.prototype.eventNames=function(){var e,r,i=[];if(0===this._eventsCount)return i;for(r in e=this._events)t.call(e,r)&&i.push(n?r.slice(1):r);return Object.getOwnPropertySymbols?i.concat(Object.getOwnPropertySymbols(e)):i},a.prototype.listeners=function(e){var t=n?n+e:e,r=this._events[t];if(!r)return[];if(r.fn)return[r.fn];for(var i=0,o=r.length,s=new Array(o);iu.depthLimit)return void a(t,e,r,s);if(void 0!==u.edgesLimit&&i+1>u.edgesLimit)return void a(t,e,r,s);if(o.push(e),Array.isArray(e))for(p=0;pt?1:0}function u(e,t,n,s){void 0===s&&(s=o());var a,l=p(e,"",0,[],void 0,0,s)||e;try{a=0===i.length?JSON.stringify(l,t,n):JSON.stringify(l,d(t),n)}catch(e){return JSON.stringify("[unable to serialize, circular reference is too complex to analyze]")}finally{for(;0!==r.length;){var c=r.pop();4===c.length?Object.defineProperty(c[0],c[1],c[3]):c[0][c[1]]=c[2]}}return a}function p(e,i,o,s,l,u,d){var f;if(u+=1,"object"==typeof e&&null!==e){for(f=0;fd.depthLimit)return void a(t,e,i,l);if(void 0!==d.edgesLimit&&o+1>d.edgesLimit)return void a(t,e,i,l);if(s.push(e),Array.isArray(e))for(f=0;f0)for(var r=0;r{for(const n of e){if("string"==typeof n&&t===n)return!0;if(n instanceof RegExp&&n.test(t))return!0}}:()=>!1}},5334:function(e,t){"use strict";const n=":A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD",r="["+n+"]["+n+"\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040]*",i=new RegExp("^"+r+"$");t.isExist=function(e){return void 0!==e},t.isEmptyObject=function(e){return 0===Object.keys(e).length},t.merge=function(e,t,n){if(t){const r=Object.keys(t),i=r.length;for(let o=0;o5&&"xml"===r)return h("InvalidXml","XML declaration allowed only at the start of the document.",g(e,t));if("?"==e[t]&&">"==e[t+1]){t++;break}}return t}function a(e,t){if(e.length>t+5&&"-"===e[t+1]&&"-"===e[t+2]){for(t+=3;t"===e[t+2]){t+=2;break}}else if(e.length>t+8&&"D"===e[t+1]&&"O"===e[t+2]&&"C"===e[t+3]&&"T"===e[t+4]&&"Y"===e[t+5]&&"P"===e[t+6]&&"E"===e[t+7]){let n=1;for(t+=8;t"===e[t]&&(n--,0===n))break}else if(e.length>t+9&&"["===e[t+1]&&"C"===e[t+2]&&"D"===e[t+3]&&"A"===e[t+4]&&"T"===e[t+5]&&"A"===e[t+6]&&"["===e[t+7])for(t+=8;t"===e[t+2]){t+=2;break}return t}t.validate=function(e,t){t=Object.assign({},i,t);const n=[];let l=!1,c=!1;"\ufeff"===e[0]&&(e=e.substr(1));for(let i=0;i"!==e[i]&&" "!==e[i]&&"\t"!==e[i]&&"\n"!==e[i]&&"\r"!==e[i];i++)b+=e[i];if(b=b.trim(),"/"===b[b.length-1]&&(b=b.substring(0,b.length-1),i--),p=b,!r.isName(p)){let t;return t=0===b.trim().length?"Invalid space after '<'.":"Tag '"+b+"' is an invalid name.",h("InvalidTag",t,g(e,i))}const v=u(e,i);if(!1===v)return h("InvalidAttr","Attributes for '"+b+"' have open quote.",g(e,i));let x=v.value;if(i=v.index,"/"===x[x.length-1]){const n=i-x.length;x=x.substring(0,x.length-1);const r=d(x,t);if(!0!==r)return h(r.err.code,r.err.msg,g(e,n+r.err.line));l=!0}else if(y){if(!v.tagClosed)return h("InvalidTag","Closing tag '"+b+"' doesn't have proper closing.",g(e,i));if(x.trim().length>0)return h("InvalidTag","Closing tag '"+b+"' can't have attributes or invalid starting.",g(e,m));if(0===n.length)return h("InvalidTag","Closing tag '"+b+"' has not been opened.",g(e,m));{const t=n.pop();if(b!==t.tagName){let n=g(e,t.tagStartPos);return h("InvalidTag","Expected closing tag '"+t.tagName+"' (opened in line "+n.line+", col "+n.col+") instead of closing tag '"+b+"'.",g(e,m))}0==n.length&&(c=!0)}}else{const r=d(x,t);if(!0!==r)return h(r.err.code,r.err.msg,g(e,i-x.length+r.err.line));if(!0===c)return h("InvalidXml","Multiple possible root nodes found.",g(e,i));-1!==t.unpairedTags.indexOf(b)||n.push({tagName:b,tagStartPos:m}),l=!0}for(i++;i0)||h("InvalidXml","Invalid '"+JSON.stringify(n.map((e=>e.tagName)),null,4).replace(/\r?\n/g,"")+"' found.",{line:1,col:1}):h("InvalidXml","Start tag expected.",1)};const l='"',c="'";function u(e,t){let n="",r="",i=!1;for(;t"===e[t]&&""===r){i=!0;break}n+=e[t]}return""===r&&{value:n,index:t,tagClosed:i}}const p=new RegExp("(\\s*)([^\\s=]+)(\\s*=)?(\\s*(['\"])(([\\s\\S])*?)\\5)?","g");function d(e,t){const n=r.getAllMatches(e,p),i={};for(let e=0;e","g"),val:">"},{regex:new RegExp("<","g"),val:"<"},{regex:new RegExp("'","g"),val:"'"},{regex:new RegExp('"',"g"),val:"""}],processEntities:!0,stopNodes:[],oneListGroup:!1};function s(e){this.options=Object.assign({},o,e),!0===this.options.ignoreAttributes||this.options.attributesGroupName?this.isAttribute=function(){return!1}:(this.ignoreAttributesFn=i(this.options.ignoreAttributes),this.attrPrefixLen=this.options.attributeNamePrefix.length,this.isAttribute=c),this.processTextOrObjNode=a,this.options.format?(this.indentate=l,this.tagEndChar=">\n",this.newLine="\n"):(this.indentate=function(){return""},this.tagEndChar=">",this.newLine="")}function a(e,t,n,r){const i=this.j2x(e,n+1,r.concat(t));return void 0!==e[this.options.textNodeName]&&1===Object.keys(e).length?this.buildTextValNode(e[this.options.textNodeName],t,i.attrStr,n):this.buildObjectNode(i.val,t,i.attrStr,n)}function l(e){return this.options.indentBy.repeat(e)}function c(e){return!(!e.startsWith(this.options.attributeNamePrefix)||e===this.options.textNodeName)&&e.substr(this.attrPrefixLen)}s.prototype.build=function(e){return this.options.preserveOrder?r(e,this.options):(Array.isArray(e)&&this.options.arrayNodeName&&this.options.arrayNodeName.length>1&&(e={[this.options.arrayNodeName]:e}),this.j2x(e,0,[]).val)},s.prototype.j2x=function(e,t,n){let r="",i="";const o=n.join(".");for(let s in e)if(Object.prototype.hasOwnProperty.call(e,s))if(void 0===e[s])this.isAttribute(s)&&(i+="");else if(null===e[s])this.isAttribute(s)||s===this.options.cdataPropName?i+="":"?"===s[0]?i+=this.indentate(t)+"<"+s+"?"+this.tagEndChar:i+=this.indentate(t)+"<"+s+"/"+this.tagEndChar;else if(e[s]instanceof Date)i+=this.buildTextValNode(e[s],s,"",t);else if("object"!=typeof e[s]){const n=this.isAttribute(s);if(n&&!this.ignoreAttributesFn(n,o))r+=this.buildAttrPairStr(n,""+e[s]);else if(!n)if(s===this.options.textNodeName){let t=this.options.tagValueProcessor(s,""+e[s]);i+=this.replaceEntitiesValue(t)}else i+=this.buildTextValNode(e[s],s,"",t)}else if(Array.isArray(e[s])){const r=e[s].length;let o="",a="";for(let l=0;l"+e+i}},s.prototype.closeTag=function(e){let t="";return-1!==this.options.unpairedTags.indexOf(e)?this.options.suppressUnpairedNode||(t="/"):t=this.options.suppressEmptyNode?"/":`>`+this.newLine;if(!1!==this.options.commentPropName&&t===this.options.commentPropName)return this.indentate(r)+`\x3c!--${e}--\x3e`+this.newLine;if("?"===t[0])return this.indentate(r)+"<"+t+n+"?"+this.tagEndChar;{let i=this.options.tagValueProcessor(t,e);return i=this.replaceEntitiesValue(i),""===i?this.indentate(r)+"<"+t+n+this.closeTag(t)+this.tagEndChar:this.indentate(r)+"<"+t+n+">"+i+"0&&this.options.processEntities)for(let t=0;t`,u=!1;continue}if(f===s.commentPropName){c+=l+`\x3c!--${d[f][0][s.textNodeName]}--\x3e`,u=!0;continue}if("?"===f[0]){const e=r(d[":@"],s),t="?xml"===f?"":l;let n=d[f][0][s.textNodeName];n=0!==n.length?" "+n:"",c+=t+`<${f}${n}${e}?>`,u=!0;continue}let m=l;""!==m&&(m+=s.indentBy);const g=l+`<${f}${r(d[":@"],s)}`,y=t(d[f],s,h,m);-1!==s.unpairedTags.indexOf(f)?s.suppressUnpairedNode?c+=g+">":c+=g+"/>":y&&0!==y.length||!s.suppressEmptyNode?y&&y.endsWith(">")?c+=g+`>${y}${l}`:(c+=g+">",y&&""!==l&&(y.includes("/>")||y.includes("`):c+=g+"/>",u=!0}return c}function n(e){const t=Object.keys(e);for(let n=0;n0&&t.processEntities)for(let n=0;n0&&(r="\n"),t(e,n,"",r)}},9400:function(e,t,n){const r=n(5334);function i(e,t){let n="";for(;t"===e[t]){if(d?"-"===e[t-1]&&"-"===e[t-2]&&(d=!1,r--):r--,0===r)break}else"["===e[t]?p=!0:f+=e[t];else{if(p&&s(e,t)){let r,o;t+=7,[r,o,t]=i(e,t+1),-1===o.indexOf("&")&&(n[u(r)]={regx:RegExp(`&${r};`,"g"),val:o})}else if(p&&a(e,t))t+=8;else if(p&&l(e,t))t+=8;else if(p&&c(e,t))t+=9;else{if(!o)throw new Error("Invalid DOCTYPE");d=!0}r++,f=""}if(0!==r)throw new Error("Unclosed DOCTYPE")}return{entities:n,i:t}}},460:function(e,t){const n={preserveOrder:!1,attributeNamePrefix:"@_",attributesGroupName:!1,textNodeName:"#text",ignoreAttributes:!0,removeNSPrefix:!1,allowBooleanAttributes:!1,parseTagValue:!0,parseAttributeValue:!1,trimValues:!0,cdataPropName:!1,numberParseOptions:{hex:!0,leadingZeros:!0,eNotation:!0},tagValueProcessor:function(e,t){return t},attributeValueProcessor:function(e,t){return t},stopNodes:[],alwaysCreateTextNode:!1,isArray:()=>!1,commentPropName:!1,unpairedTags:[],processEntities:!0,htmlEntities:!1,ignoreDeclaration:!1,ignorePiTags:!1,transformTagName:!1,transformAttributeName:!1,updateTag:function(e,t,n){return e}};t.buildOptions=function(e){return Object.assign({},n,e)},t.defaultOptions=n},7680:function(e,t,n){"use strict";const r=n(5334),i=n(3832),o=n(9400),s=n(7983),a=n(3085);function l(e){const t=Object.keys(e);for(let n=0;n0)){s||(e=this.replaceEntitiesValue(e));const r=this.options.tagValueProcessor(t,e,n,i,o);return null==r?e:typeof r!=typeof e||r!==e?r:this.options.trimValues||e.trim()===e?w(e,this.options.parseTagValue,this.options.numberParseOptions):e}}function u(e){if(this.options.removeNSPrefix){const t=e.split(":"),n="/"===e.charAt(0)?"/":"";if("xmlns"===t[0])return"";2===t.length&&(e=n+t[1])}return e}const p=new RegExp("([^\\s=]+)\\s*(=\\s*(['\"])([\\s\\S]*?)\\3)?","gm");function d(e,t,n){if(!0!==this.options.ignoreAttributes&&"string"==typeof e){const n=r.getAllMatches(e,p),i=n.length,o={};for(let e=0;e",a,"Closing Tag is not closed.");let i=e.substring(a+2,t).trim();if(this.options.removeNSPrefix){const e=i.indexOf(":");-1!==e&&(i=i.substr(e+1))}this.options.transformTagName&&(i=this.options.transformTagName(i)),n&&(r=this.saveTextToParentTag(r,n,s));const o=s.substring(s.lastIndexOf(".")+1);if(i&&-1!==this.options.unpairedTags.indexOf(i))throw new Error(`Unpaired tag can not be used as closing tag: `);let l=0;o&&-1!==this.options.unpairedTags.indexOf(o)?(l=s.lastIndexOf(".",s.lastIndexOf(".")-1),this.tagsNodeStack.pop()):l=s.lastIndexOf("."),s=s.substring(0,l),n=this.tagsNodeStack.pop(),r="",a=t}else if("?"===e[a+1]){let t=v(e,a,!1,"?>");if(!t)throw new Error("Pi Tag is not closed.");if(r=this.saveTextToParentTag(r,n,s),this.options.ignoreDeclaration&&"?xml"===t.tagName||this.options.ignorePiTags);else{const e=new i(t.tagName);e.add(this.options.textNodeName,""),t.tagName!==t.tagExp&&t.attrExpPresent&&(e[":@"]=this.buildAttributesMap(t.tagExp,s,t.tagName)),this.addChild(n,e,s)}a=t.closeIndex+1}else if("!--"===e.substr(a+1,3)){const t=b(e,"--\x3e",a+4,"Comment is not closed.");if(this.options.commentPropName){const i=e.substring(a+4,t-2);r=this.saveTextToParentTag(r,n,s),n.add(this.options.commentPropName,[{[this.options.textNodeName]:i}])}a=t}else if("!D"===e.substr(a+1,2)){const t=o(e,a);this.docTypeEntities=t.entities,a=t.i}else if("!["===e.substr(a+1,2)){const t=b(e,"]]>",a,"CDATA is not closed.")-2,i=e.substring(a+9,t);r=this.saveTextToParentTag(r,n,s);let o=this.parseTextData(i,n.tagname,s,!0,!1,!0,!0);null==o&&(o=""),this.options.cdataPropName?n.add(this.options.cdataPropName,[{[this.options.textNodeName]:i}]):n.add(this.options.textNodeName,o),a=t+2}else{let o=v(e,a,this.options.removeNSPrefix),l=o.tagName;const c=o.rawTagName;let u=o.tagExp,p=o.attrExpPresent,d=o.closeIndex;this.options.transformTagName&&(l=this.options.transformTagName(l)),n&&r&&"!xml"!==n.tagname&&(r=this.saveTextToParentTag(r,n,s,!1));const f=n;if(f&&-1!==this.options.unpairedTags.indexOf(f.tagname)&&(n=this.tagsNodeStack.pop(),s=s.substring(0,s.lastIndexOf("."))),l!==t.tagname&&(s+=s?"."+l:l),this.isItStopNode(this.options.stopNodes,s,l)){let t="";if(u.length>0&&u.lastIndexOf("/")===u.length-1)"/"===l[l.length-1]?(l=l.substr(0,l.length-1),s=s.substr(0,s.length-1),u=l):u=u.substr(0,u.length-1),a=o.closeIndex;else if(-1!==this.options.unpairedTags.indexOf(l))a=o.closeIndex;else{const n=this.readStopNodeData(e,c,d+1);if(!n)throw new Error(`Unexpected end of ${c}`);a=n.i,t=n.tagContent}const r=new i(l);l!==u&&p&&(r[":@"]=this.buildAttributesMap(u,s,l)),t&&(t=this.parseTextData(t,l,s,!0,p,!0,!0)),s=s.substr(0,s.lastIndexOf(".")),r.add(this.options.textNodeName,t),this.addChild(n,r,s)}else{if(u.length>0&&u.lastIndexOf("/")===u.length-1){"/"===l[l.length-1]?(l=l.substr(0,l.length-1),s=s.substr(0,s.length-1),u=l):u=u.substr(0,u.length-1),this.options.transformTagName&&(l=this.options.transformTagName(l));const e=new i(l);l!==u&&p&&(e[":@"]=this.buildAttributesMap(u,s,l)),this.addChild(n,e,s),s=s.substr(0,s.lastIndexOf("."))}else{const e=new i(l);this.tagsNodeStack.push(n),l!==u&&p&&(e[":@"]=this.buildAttributesMap(u,s,l)),this.addChild(n,e,s),n=e}r="",a=d}}else r+=e[a];return t.child};function h(e,t,n){const r=this.options.updateTag(t.tagname,n,t[":@"]);!1===r||("string"==typeof r?(t.tagname=r,e.addChild(t)):e.addChild(t))}const m=function(e){if(this.options.processEntities){for(let t in this.docTypeEntities){const n=this.docTypeEntities[t];e=e.replace(n.regx,n.val)}for(let t in this.lastEntities){const n=this.lastEntities[t];e=e.replace(n.regex,n.val)}if(this.options.htmlEntities)for(let t in this.htmlEntities){const n=this.htmlEntities[t];e=e.replace(n.regex,n.val)}e=e.replace(this.ampEntity.regex,this.ampEntity.val)}return e};function g(e,t,n,r){return e&&(void 0===r&&(r=0===t.child.length),void 0!==(e=this.parseTextData(e,t.tagname,n,!1,!!t[":@"]&&0!==Object.keys(t[":@"]).length,r))&&""!==e&&t.add(this.options.textNodeName,e),e=""),e}function y(e,t,n){const r="*."+n;for(const n in e){const i=e[n];if(r===i||t===i)return!0}return!1}function b(e,t,n,r){const i=e.indexOf(t,n);if(-1===i)throw new Error(r);return i+t.length-1}function v(e,t,n,r=">"){const i=function(e,t,n=">"){let r,i="";for(let o=t;o",n,`${t} is not closed`);if(e.substring(n+2,o).trim()===t&&(i--,0===i))return{tagContent:e.substring(r,n),i:o};n=o}else if("?"===e[n+1])n=b(e,"?>",n+1,"StopNode is not closed.");else if("!--"===e.substr(n+1,3))n=b(e,"--\x3e",n+3,"StopNode is not closed.");else if("!["===e.substr(n+1,2))n=b(e,"]]>",n,"StopNode is not closed.")-2;else{const r=v(e,n,">");r&&((r&&r.tagName)===t&&"/"!==r.tagExp[r.tagExp.length-1]&&i++,n=r.closeIndex)}}function w(e,t,n){if(t&&"string"==typeof e){const t=e.trim();return"true"===t||"false"!==t&&s(e,n)}return r.isExist(e)?e:""}e.exports=class{constructor(e){this.options=e,this.currentNode=null,this.tagsNodeStack=[],this.docTypeEntities={},this.lastEntities={apos:{regex:/&(apos|#39|#x27);/g,val:"'"},gt:{regex:/&(gt|#62|#x3E);/g,val:">"},lt:{regex:/&(lt|#60|#x3C);/g,val:"<"},quot:{regex:/&(quot|#34|#x22);/g,val:'"'}},this.ampEntity={regex:/&(amp|#38|#x26);/g,val:"&"},this.htmlEntities={space:{regex:/&(nbsp|#160);/g,val:" "},cent:{regex:/&(cent|#162);/g,val:"¢"},pound:{regex:/&(pound|#163);/g,val:"£"},yen:{regex:/&(yen|#165);/g,val:"¥"},euro:{regex:/&(euro|#8364);/g,val:"€"},copyright:{regex:/&(copy|#169);/g,val:"©"},reg:{regex:/&(reg|#174);/g,val:"®"},inr:{regex:/&(inr|#8377);/g,val:"₹"},num_dec:{regex:/&#([0-9]{1,7});/g,val:(e,t)=>String.fromCharCode(Number.parseInt(t,10))},num_hex:{regex:/&#x([0-9a-fA-F]{1,6});/g,val:(e,t)=>String.fromCharCode(Number.parseInt(t,16))}},this.addExternalEntities=l,this.parseXml=f,this.parseTextData=c,this.resolveNameSpace=u,this.buildAttributesMap=d,this.isItStopNode=y,this.replaceEntitiesValue=m,this.readStopNodeData=x,this.saveTextToParentTag=g,this.addChild=h,this.ignoreAttributesFn=a(this.options.ignoreAttributes)}}},2923:function(e,t,n){const{buildOptions:r}=n(460),i=n(7680),{prettify:o}=n(5629),s=n(3918);e.exports=class{constructor(e){this.externalEntities={},this.options=r(e)}parse(e,t){if("string"==typeof e);else{if(!e.toString)throw new Error("XML data is accepted in String or Bytes[] form.");e=e.toString()}if(t){!0===t&&(t={});const n=s.validate(e,t);if(!0!==n)throw Error(`${n.err.msg}:${n.err.line}:${n.err.col}`)}const n=new i(this.options);n.addExternalEntities(this.externalEntities);const r=n.parseXml(e);return this.options.preserveOrder||void 0===r?r:o(r,this.options)}addEntity(e,t){if(-1!==t.indexOf("&"))throw new Error("Entity value can't have '&'");if(-1!==e.indexOf("&")||-1!==e.indexOf(";"))throw new Error("An entity must be set without '&' and ';'. Eg. use '#xD' for ' '");if("&"===t)throw new Error("An entity with value '&' is not permitted");this.externalEntities[e]=t}}},5629:function(e,t){"use strict";function n(e,t,s){let a;const l={};for(let c=0;c0&&(l[t.textNodeName]=a):void 0!==a&&(l[t.textNodeName]=a),l}function r(e){const t=Object.keys(e);for(let e=0;e0?this.child.push({[e.tagname]:e.child,":@":e[":@"]}):this.child.push({[e.tagname]:e.child})}}},7593:function(e){var t=Object.prototype.hasOwnProperty,n=Object.prototype.toString;e.exports=function(e,r,i){if("[object Function]"!==n.call(r))throw new TypeError("iterator must be a function");var o=e.length;if(o===+o)for(var s=0;s=55296&&r<=56319&&t+1=56320&&n<=57343?1024*(r-55296)+n-56320+65536:r}function H(e){return/^\n* /.test(e)}var Y=1,G=2,Q=3,X=4,K=5;function Z(e,t,n,r,o){e.dump=function(){if(0===t.length)return e.quotingType===D?'""':"''";if(!e.noCompatMode&&(-1!==N.indexOf(t)||$.test(t)))return e.quotingType===D?'"'+t+'"':"'"+t+"'";var s=e.indent*Math.max(1,n),a=-1===e.lineWidth?-1:Math.max(Math.min(e.lineWidth,40),e.lineWidth-s),c=r||e.flowLevel>-1&&n>=e.flowLevel;switch(function(e,t,n,r,i,o,s,a){var c,p,d=0,R=null,N=!1,$=!1,L=-1!==r,M=-1,F=U(p=W(e,0))&&p!==l&&!B(p)&&p!==w&&p!==O&&p!==k&&p!==x&&p!==A&&p!==C&&p!==P&&p!==I&&p!==m&&p!==y&&p!==v&&p!==f&&p!==T&&p!==S&&p!==E&&p!==b&&p!==h&&p!==g&&p!==_&&p!==j&&function(e){return!B(e)&&e!==k}(W(e,e.length-1));if(t||s)for(c=0;c=65536?c+=2:c++){if(!U(d=W(e,c)))return K;F=F&&V(d,R,a),R=d}else{for(c=0;c=65536?c+=2:c++){if((d=W(e,c))===u)N=!0,L&&($=$||c-M-1>r&&" "!==e[M+1],M=c);else if(!U(d))return K;F=F&&V(d,R,a),R=d}$=$||L&&c-M-1>r&&" "!==e[M+1]}return N||$?n>9&&H(e)?K:s?o===D?K:G:$?X:Q:!F||s||i(e)?o===D?K:G:Y}(t,c,e.indent,a,(function(t){return function(e,t){var n,r;for(n=0,r=e.implicitTypes.length;n"+J(t,e.indent)+ee(F(function(e,t){for(var n,r,i,o=/(\n+)([^\n]*)/g,s=(i=-1!==(i=e.indexOf("\n"))?i:e.length,o.lastIndex=i,te(e.slice(0,i),t)),a="\n"===e[0]||" "===e[0];r=o.exec(e);){var l=r[1],c=r[2];n=" "===c[0],s+=l+(a||n||""===c?"":"\n")+te(c,t),a=n}return s}(t,a),s));case K:return'"'+function(e){for(var t,n="",r=0,i=0;i=65536?i+=2:i++)r=W(e,i),!(t=R[r])&&U(r)?(n+=e[i],r>=65536&&(n+=e[i+1])):n+=t||L(r);return n}(t)+'"';default:throw new i("impossible error: invalid scalar style")}}()}function J(e,t){var n=H(e)?String(t):"",r="\n"===e[e.length-1];return n+(!r||"\n"!==e[e.length-2]&&"\n"!==e?r?"":"-":"+")+"\n"}function ee(e){return"\n"===e[e.length-1]?e.slice(0,-1):e}function te(e,t){if(""===e||" "===e[0])return e;for(var n,r,i=/ [^ ]/g,o=0,s=0,a=0,l="";n=i.exec(e);)(a=n.index)-o>t&&(r=s>o?s:a,l+="\n"+e.slice(o,r),o=r+1),s=a;return l+="\n",e.length-o>t&&s>o?l+=e.slice(o,s)+"\n"+e.slice(s+1):l+=e.slice(o),l.slice(1)}function ne(e,t,n,r){var i,o,s,a="",l=e.tag;for(i=0,o=n.length;i tag resolver accepts not "'+p+'" style');r=u.represent[p](t,p)}e.dump=r}return!0}return!1}function ie(e,t,n,r,o,a,l){e.tag=null,e.dump=n,re(e,n,!1)||re(e,n,!0);var c,p=s.call(e.dump),d=r;r&&(r=e.flowLevel<0||e.flowLevel>t);var f,h,m="[object Object]"===p||"[object Array]"===p;if(m&&(h=-1!==(f=e.duplicates.indexOf(n))),(null!==e.tag&&"?"!==e.tag||h||2!==e.indent&&t>0)&&(o=!1),h&&e.usedDuplicates[f])e.dump="*ref_"+f;else{if(m&&h&&!e.usedDuplicates[f]&&(e.usedDuplicates[f]=!0),"[object Object]"===p)r&&0!==Object.keys(e.dump).length?(function(e,t,n,r){var o,s,a,l,c,p,d="",f=e.tag,h=Object.keys(n);if(!0===e.sortKeys)h.sort();else if("function"==typeof e.sortKeys)h.sort(e.sortKeys);else if(e.sortKeys)throw new i("sortKeys must be a boolean or a function");for(o=0,s=h.length;o1024)&&(e.dump&&u===e.dump.charCodeAt(0)?p+="?":p+="? "),p+=e.dump,c&&(p+=z(e,t)),ie(e,t+1,l,!0,c)&&(e.dump&&u===e.dump.charCodeAt(0)?p+=":":p+=": ",d+=p+=e.dump));e.tag=f,e.dump=d||"{}"}(e,t,e.dump,o),h&&(e.dump="&ref_"+f+e.dump)):(function(e,t,n){var r,i,o,s,a,l="",c=e.tag,u=Object.keys(n);for(r=0,i=u.length;r1024&&(a+="? "),a+=e.dump+(e.condenseFlow?'"':"")+":"+(e.condenseFlow?"":" "),ie(e,t,s,!1,!1)&&(l+=a+=e.dump));e.tag=c,e.dump="{"+l+"}"}(e,t,e.dump),h&&(e.dump="&ref_"+f+" "+e.dump));else if("[object Array]"===p)r&&0!==e.dump.length?(e.noArrayIndent&&!l&&t>0?ne(e,t-1,e.dump,o):ne(e,t,e.dump,o),h&&(e.dump="&ref_"+f+e.dump)):(function(e,t,n){var r,i,o,s="",a=e.tag;for(r=0,i=n.length;r",e.dump=c+" "+e.dump)}return!0}function oe(e,t){var n,r,i=[],o=[];for(se(e,i,o),n=0,r=o.length;n>10),56320+(e-65536&1023))}for(var C=new Array(256),j=new Array(256),P=0;P<256;P++)C[P]=_(P)?1:0,j[P]=_(P);function T(e,t){this.input=e,this.filename=t.filename||null,this.schema=t.schema||s,this.onWarning=t.onWarning||null,this.legacy=t.legacy||!1,this.json=t.json||!1,this.listener=t.listener||null,this.implicitTypes=this.schema.compiledImplicit,this.typeMap=this.schema.compiledTypeMap,this.length=e.length,this.position=0,this.line=0,this.lineStart=0,this.lineIndent=0,this.firstTabInLine=-1,this.documents=[]}function I(e,t){var n={name:e.filename,buffer:e.input.slice(0,-1),position:e.position,line:e.line,column:e.position-e.lineStart};return n.snippet=o(n),new i(t,n)}function R(e,t){throw I(e,t)}function N(e,t){e.onWarning&&e.onWarning.call(null,I(e,t))}var $={YAML:function(e,t,n){var r,i,o;null!==e.version&&R(e,"duplication of %YAML directive"),1!==n.length&&R(e,"YAML directive accepts exactly one argument"),null===(r=/^([0-9]+)\.([0-9]+)$/.exec(n[0]))&&R(e,"ill-formed argument of the YAML directive"),i=parseInt(r[1],10),o=parseInt(r[2],10),1!==i&&R(e,"unacceptable YAML version of the document"),e.version=n[0],e.checkLineBreaks=o<2,1!==o&&2!==o&&N(e,"unsupported YAML version of the document")},TAG:function(e,t,n){var r,i;2!==n.length&&R(e,"TAG directive accepts exactly two arguments"),r=n[0],i=n[1],b.test(r)||R(e,"ill-formed tag handle (first argument) of the TAG directive"),a.call(e.tagMap,r)&&R(e,'there is a previously declared suffix for "'+r+'" tag handle'),v.test(i)||R(e,"ill-formed tag prefix (second argument) of the TAG directive");try{i=decodeURIComponent(i)}catch(t){R(e,"tag prefix is malformed: "+i)}e.tagMap[r]=i}};function L(e,t,n,r){var i,o,s,a;if(t1&&(e.result+=r.repeat("\n",t-1))}function q(e,t){var n,r,i=e.tag,o=e.anchor,s=[],a=!1;if(-1!==e.firstTabInLine)return!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=s),r=e.input.charCodeAt(e.position);0!==r&&(-1!==e.firstTabInLine&&(e.position=e.firstTabInLine,R(e,"tab characters must not be used in indentation")),45===r)&&S(e.input.charCodeAt(e.position+1));)if(a=!0,e.position++,z(e,!0,-1)&&e.lineIndent<=t)s.push(null),r=e.input.charCodeAt(e.position);else if(n=e.line,H(e,t,u,!1,!0),s.push(e.result),z(e,!0,-1),r=e.input.charCodeAt(e.position),(e.line===n||e.lineIndent>t)&&0!==r)R(e,"bad indentation of a sequence entry");else if(e.lineIndentt?T=1:e.lineIndent===t?T=0:e.lineIndentt?T=1:e.lineIndent===t?T=0:e.lineIndentt)&&(v&&(s=e.line,a=e.lineStart,l=e.position),H(e,t,p,!0,i)&&(v?y=e.result:b=e.result),v||(M(e,h,m,g,y,b,s,a,l),g=y=b=null),z(e,!0,-1),u=e.input.charCodeAt(e.position)),(e.line===o||e.lineIndent>t)&&0!==u)R(e,"bad indentation of a mapping entry");else if(e.lineIndent=0))break;0===o?R(e,"bad explicit indentation width of a block scalar; it cannot be less than one"):u?R(e,"repeat of an indentation width identifier"):(p=t+o-1,u=!0)}if(k(s)){do{s=e.input.charCodeAt(++e.position)}while(k(s));if(35===s)do{s=e.input.charCodeAt(++e.position)}while(!w(s)&&0!==s)}for(;0!==s;){for(F(e),e.lineIndent=0,s=e.input.charCodeAt(e.position);(!u||e.lineIndentp&&(p=e.lineIndent),w(s))m++;else{if(e.lineIndent0){for(i=s,o=0;i>0;i--)(s=O(a=e.input.charCodeAt(++e.position)))>=0?o=(o<<4)+s:R(e,"expected hexadecimal character");e.result+=A(o),e.position++}else R(e,"unknown escape sequence");n=r=e.position}else w(a)?(L(e,n,r,!0),U(e,z(e,!1,t)),n=r=e.position):e.position===e.lineStart&&B(e)?R(e,"unexpected end of the document within a double quoted scalar"):(e.position++,r=e.position)}R(e,"unexpected end of the stream within a double quoted scalar")}(e,_)?N=!0:function(e){var t,n,r;if(42!==(r=e.input.charCodeAt(e.position)))return!1;for(r=e.input.charCodeAt(++e.position),t=e.position;0!==r&&!S(r)&&!E(r);)r=e.input.charCodeAt(++e.position);return e.position===t&&R(e,"name of an alias node must contain at least one character"),n=e.input.slice(t,e.position),a.call(e.anchorMap,n)||R(e,'unidentified alias "'+n+'"'),e.result=e.anchorMap[n],z(e,!0,-1),!0}(e)?(N=!0,null===e.tag&&null===e.anchor||R(e,"alias node should not have any properties")):function(e,t,n){var r,i,o,s,a,l,c,u,p=e.kind,d=e.result;if(S(u=e.input.charCodeAt(e.position))||E(u)||35===u||38===u||42===u||33===u||124===u||62===u||39===u||34===u||37===u||64===u||96===u)return!1;if((63===u||45===u)&&(S(r=e.input.charCodeAt(e.position+1))||n&&E(r)))return!1;for(e.kind="scalar",e.result="",i=o=e.position,s=!1;0!==u;){if(58===u){if(S(r=e.input.charCodeAt(e.position+1))||n&&E(r))break}else if(35===u){if(S(e.input.charCodeAt(e.position-1)))break}else{if(e.position===e.lineStart&&B(e)||n&&E(u))break;if(w(u)){if(a=e.line,l=e.lineStart,c=e.lineIndent,z(e,!1,-1),e.lineIndent>=t){s=!0,u=e.input.charCodeAt(e.position);continue}e.position=o,e.line=a,e.lineStart=l,e.lineIndent=c;break}}s&&(L(e,i,o,!1),U(e,e.line-a),i=o=e.position,s=!1),k(u)||(o=e.position+1),u=e.input.charCodeAt(++e.position)}return L(e,i,o,!1),!!e.result||(e.kind=p,e.result=d,!1)}(e,_,l===n)&&(N=!0,null===e.tag&&(e.tag="?")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===T&&(N=g&&q(e,P))),null===e.tag)null!==e.anchor&&(e.anchorMap[e.anchor]=e.result);else if("?"===e.tag){for(null!==e.result&&"scalar"!==e.kind&&R(e,'unacceptable node kind for ! tag; it should be "scalar", not "'+e.kind+'"'),y=0,b=e.implicitTypes.length;y"),null!==e.result&&x.kind!==e.kind&&R(e,"unacceptable node kind for !<"+e.tag+'> tag; it should be "'+x.kind+'", not "'+e.kind+'"'),x.resolve(e.result,e.tag)?(e.result=x.construct(e.result,e.tag),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):R(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")}return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||N}function Y(e){var t,n,r,i,o=e.position,s=!1;for(e.version=null,e.checkLineBreaks=e.legacy,e.tagMap=Object.create(null),e.anchorMap=Object.create(null);0!==(i=e.input.charCodeAt(e.position))&&(z(e,!0,-1),i=e.input.charCodeAt(e.position),!(e.lineIndent>0||37!==i));){for(s=!0,i=e.input.charCodeAt(++e.position),t=e.position;0!==i&&!S(i);)i=e.input.charCodeAt(++e.position);for(r=[],(n=e.input.slice(t,e.position)).length<1&&R(e,"directive name must not be less than one character in length");0!==i;){for(;k(i);)i=e.input.charCodeAt(++e.position);if(35===i){do{i=e.input.charCodeAt(++e.position)}while(0!==i&&!w(i));break}if(w(i))break;for(t=e.position;0!==i&&!S(i);)i=e.input.charCodeAt(++e.position);r.push(e.input.slice(t,e.position))}0!==i&&F(e),a.call($,n)?$[n](e,n,r):N(e,'unknown document directive "'+n+'"')}z(e,!0,-1),0===e.lineIndent&&45===e.input.charCodeAt(e.position)&&45===e.input.charCodeAt(e.position+1)&&45===e.input.charCodeAt(e.position+2)?(e.position+=3,z(e,!0,-1)):s&&R(e,"directives end mark is expected"),H(e,e.lineIndent-1,p,!1,!0),z(e,!0,-1),e.checkLineBreaks&&g.test(e.input.slice(o,e.position))&&N(e,"non-ASCII line breaks are interpreted as content"),e.documents.push(e.result),e.position===e.lineStart&&B(e)?46===e.input.charCodeAt(e.position)&&(e.position+=3,z(e,!0,-1)):e.positiona&&(t=r-a+(o=" ... ").length),n-r>a&&(n=r+a-(s=" ...").length),{str:o+e.slice(t,n).replace(/\t/g,"→")+s,pos:r-t+o.length}}function o(e,t){return r.repeat(" ",t-e.length)+e}e.exports=function(e,t){if(t=Object.create(t||null),!e.buffer)return null;t.maxLength||(t.maxLength=79),"number"!=typeof t.indent&&(t.indent=1),"number"!=typeof t.linesBefore&&(t.linesBefore=3),"number"!=typeof t.linesAfter&&(t.linesAfter=2);for(var n,s=/\r?\n|\r|\0/g,a=[0],l=[],c=-1;n=s.exec(e.buffer);)l.push(n.index),a.push(n.index+n[0].length),e.position<=n.index&&c<0&&(c=a.length-2);c<0&&(c=a.length-1);var u,p,d="",f=Math.min(e.line+t.linesAfter,l.length).toString().length,h=t.maxLength-(t.indent+f+3);for(u=1;u<=t.linesBefore&&!(c-u<0);u++)p=i(e.buffer,a[c-u],l[c-u],e.position-(a[c]-a[c-u]),h),d=r.repeat(" ",t.indent)+o((e.line-u+1).toString(),f)+" | "+p.str+"\n"+d;for(p=i(e.buffer,a[c],l[c],e.position,h),d+=r.repeat(" ",t.indent)+o((e.line+1).toString(),f)+" | "+p.str+"\n",d+=r.repeat("-",t.indent+f+3+p.pos)+"^\n",u=1;u<=t.linesAfter&&!(c+u>=l.length);u++)p=i(e.buffer,a[c+u],l[c+u],e.position-(a[c]-a[c+u]),h),d+=r.repeat(" ",t.indent)+o((e.line+u+1).toString(),f)+" | "+p.str+"\n";return d.replace(/\n$/,"")}},5388:function(e,t,n){"use strict";var r=n(1231),i=["kind","multi","resolve","construct","instanceOf","predicate","represent","representName","defaultStyle","styleAliases"],o=["scalar","sequence","mapping"];e.exports=function(e,t){var n,s;if(t=t||{},Object.keys(t).forEach((function(t){if(-1===i.indexOf(t))throw new r('Unknown option "'+t+'" is met in definition of "'+e+'" YAML type.')})),this.options=t,this.tag=e,this.kind=t.kind||null,this.resolve=t.resolve||function(){return!0},this.construct=t.construct||function(e){return e},this.instanceOf=t.instanceOf||null,this.predicate=t.predicate||null,this.represent=t.represent||null,this.representName=t.representName||null,this.defaultStyle=t.defaultStyle||null,this.multi=t.multi||!1,this.styleAliases=(n=t.styleAliases||null,s={},null!==n&&Object.keys(n).forEach((function(e){n[e].forEach((function(t){s[String(t)]=e}))})),s),-1===o.indexOf(this.kind))throw new r('Unknown kind "'+this.kind+'" is specified for "'+e+'" YAML type.')}},9342:function(e,t,n){"use strict";var r=n(5388),i="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r";e.exports=new r("tag:yaml.org,2002:binary",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n,r=0,o=e.length,s=i;for(n=0;n64)){if(t<0)return!1;r+=6}return r%8==0},construct:function(e){var t,n,r=e.replace(/[\r\n=]/g,""),o=r.length,s=i,a=0,l=[];for(t=0;t>16&255),l.push(a>>8&255),l.push(255&a)),a=a<<6|s.indexOf(r.charAt(t));return 0==(n=o%4*6)?(l.push(a>>16&255),l.push(a>>8&255),l.push(255&a)):18===n?(l.push(a>>10&255),l.push(a>>2&255)):12===n&&l.push(a>>4&255),new Uint8Array(l)},predicate:function(e){return"[object Uint8Array]"===Object.prototype.toString.call(e)},represent:function(e){var t,n,r="",o=0,s=e.length,a=i;for(t=0;t>18&63],r+=a[o>>12&63],r+=a[o>>6&63],r+=a[63&o]),o=(o<<8)+e[t];return 0==(n=s%3)?(r+=a[o>>18&63],r+=a[o>>12&63],r+=a[o>>6&63],r+=a[63&o]):2===n?(r+=a[o>>10&63],r+=a[o>>4&63],r+=a[o<<2&63],r+=a[64]):1===n&&(r+=a[o>>2&63],r+=a[o<<4&63],r+=a[64],r+=a[64]),r}})},6199:function(e,t,n){"use strict";var r=n(5388);e.exports=new r("tag:yaml.org,2002:bool",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t=e.length;return 4===t&&("true"===e||"True"===e||"TRUE"===e)||5===t&&("false"===e||"False"===e||"FALSE"===e)},construct:function(e){return"true"===e||"True"===e||"TRUE"===e},predicate:function(e){return"[object Boolean]"===Object.prototype.toString.call(e)},represent:{lowercase:function(e){return e?"true":"false"},uppercase:function(e){return e?"TRUE":"FALSE"},camelcase:function(e){return e?"True":"False"}},defaultStyle:"lowercase"})},1461:function(e,t,n){"use strict";var r=n(8433),i=n(5388),o=new RegExp("^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$"),s=/^[-+]?[0-9]+e/;e.exports=new i("tag:yaml.org,2002:float",{kind:"scalar",resolve:function(e){return null!==e&&!(!o.test(e)||"_"===e[e.length-1])},construct:function(e){var t,n;return n="-"===(t=e.replace(/_/g,"").toLowerCase())[0]?-1:1,"+-".indexOf(t[0])>=0&&(t=t.slice(1)),".inf"===t?1===n?Number.POSITIVE_INFINITY:Number.NEGATIVE_INFINITY:".nan"===t?NaN:n*parseFloat(t,10)},predicate:function(e){return"[object Number]"===Object.prototype.toString.call(e)&&(e%1!=0||r.isNegativeZero(e))},represent:function(e,t){var n;if(isNaN(e))switch(t){case"lowercase":return".nan";case"uppercase":return".NAN";case"camelcase":return".NaN"}else if(Number.POSITIVE_INFINITY===e)switch(t){case"lowercase":return".inf";case"uppercase":return".INF";case"camelcase":return".Inf"}else if(Number.NEGATIVE_INFINITY===e)switch(t){case"lowercase":return"-.inf";case"uppercase":return"-.INF";case"camelcase":return"-.Inf"}else if(r.isNegativeZero(e))return"-0.0";return n=e.toString(10),s.test(n)?n.replace("e",".e"):n},defaultStyle:"lowercase"})},4466:function(e,t,n){"use strict";var r=n(8433),i=n(5388);function o(e){return 48<=e&&e<=55}function s(e){return 48<=e&&e<=57}e.exports=new i("tag:yaml.org,2002:int",{kind:"scalar",resolve:function(e){if(null===e)return!1;var t,n,r=e.length,i=0,a=!1;if(!r)return!1;if("-"!==(t=e[i])&&"+"!==t||(t=e[++i]),"0"===t){if(i+1===r)return!0;if("b"===(t=e[++i])){for(i++;i=0?"0b"+e.toString(2):"-0b"+e.toString(2).slice(1)},octal:function(e){return e>=0?"0o"+e.toString(8):"-0o"+e.toString(8).slice(1)},decimal:function(e){return e.toString(10)},hexadecimal:function(e){return e>=0?"0x"+e.toString(16).toUpperCase():"-0x"+e.toString(16).toUpperCase().slice(1)}},defaultStyle:"decimal",styleAliases:{binary:[2,"bin"],octal:[8,"oct"],decimal:[10,"dec"],hexadecimal:[16,"hex"]}})},2369:function(e,t,n){"use strict";var r=n(5388);e.exports=new r("tag:yaml.org,2002:map",{kind:"mapping",construct:function(e){return null!==e?e:{}}})},1851:function(e,t,n){"use strict";var r=n(5388);e.exports=new r("tag:yaml.org,2002:merge",{kind:"scalar",resolve:function(e){return"<<"===e||null===e}})},9198:function(e,t,n){"use strict";var r=n(5388);e.exports=new r("tag:yaml.org,2002:null",{kind:"scalar",resolve:function(e){if(null===e)return!0;var t=e.length;return 1===t&&"~"===e||4===t&&("null"===e||"Null"===e||"NULL"===e)},construct:function(){return null},predicate:function(e){return null===e},represent:{canonical:function(){return"~"},lowercase:function(){return"null"},uppercase:function(){return"NULL"},camelcase:function(){return"Null"},empty:function(){return""}},defaultStyle:"lowercase"})},6946:function(e,t,n){"use strict";var r=n(5388),i=Object.prototype.hasOwnProperty,o=Object.prototype.toString;e.exports=new r("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function(e){if(null===e)return!0;var t,n,r,s,a,l=[],c=e;for(t=0,n=c.length;tc))return!1;var p=a.get(e);if(p&&a.get(t))return p==t;var d=-1,f=!0,h=n&o?new Ae:void 0;for(a.set(e,t),a.set(t,e);++d-1},Oe.prototype.set=function(e,t){var n=this.__data__,r=je(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this},_e.prototype.clear=function(){this.size=0,this.__data__={hash:new Ee,map:new(de||Oe),string:new Ee}},_e.prototype.delete=function(e){var t=$e(this,e).delete(e);return this.size-=t?1:0,t},_e.prototype.get=function(e){return $e(this,e).get(e)},_e.prototype.has=function(e){return $e(this,e).has(e)},_e.prototype.set=function(e,t){var n=$e(this,e),r=n.size;return n.set(e,t),this.size+=n.size==r?0:1,this},Ae.prototype.add=Ae.prototype.push=function(e){return this.__data__.set(e,r),this},Ae.prototype.has=function(e){return this.__data__.has(e)},Ce.prototype.clear=function(){this.__data__=new Oe,this.size=0},Ce.prototype.delete=function(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n},Ce.prototype.get=function(e){return this.__data__.get(e)},Ce.prototype.has=function(e){return this.__data__.has(e)},Ce.prototype.set=function(e,t){var n=this.__data__;if(n instanceof Oe){var r=n.__data__;if(!de||r.length<199)return r.push([e,t]),this.size=++n.size,this;n=this.__data__=new _e(r)}return n.set(e,t),this.size=n.size,this};var De=le?function(e){return null==e?[]:(e=Object(e),function(t){for(var n=-1,r=null==t?0:t.length,i=0,o=[];++n-1&&e%1==0&&e-1&&e%1==0&&e<=s}function Ye(e){var t=typeof e;return null!=e&&("object"==t||"function"==t)}function Ge(e){return null!=e&&"object"==typeof e}var Qe=F?function(e){return function(t){return e(t)}}(F):function(e){return Ge(e)&&He(e.length)&&!!P[Pe(e)]};function Xe(e){return null!=(t=e)&&He(t.length)&&!We(t)?function(e,t){var n=qe(e),r=!n&&Ue(e),i=!n&&!r&&Ve(e),o=!n&&!r&&!i&&Qe(e),s=n||r||i||o,a=s?function(e,t){for(var n=-1,r=Array(e);++n1&&void 0!==arguments[1])||arguments[1],i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:5e3;t(this,e),this.ctx=n,this.iframes=r,this.exclude=i,this.iframesTimeout=o}return n(e,[{key:"getContexts",value:function(){var e=[];return(void 0!==this.ctx&&this.ctx?NodeList.prototype.isPrototypeOf(this.ctx)?Array.prototype.slice.call(this.ctx):Array.isArray(this.ctx)?this.ctx:"string"==typeof this.ctx?Array.prototype.slice.call(document.querySelectorAll(this.ctx)):[this.ctx]:[]).forEach((function(t){var n=e.filter((function(e){return e.contains(t)})).length>0;-1!==e.indexOf(t)||n||e.push(t)})),e}},{key:"getIframeContents",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(){},r=void 0;try{var i=e.contentWindow;if(r=i.document,!i||!r)throw new Error("iframe inaccessible")}catch(e){n()}r&&t(r)}},{key:"isIframeBlank",value:function(e){var t="about:blank",n=e.getAttribute("src").trim();return e.contentWindow.location.href===t&&n!==t&&n}},{key:"observeIframeLoad",value:function(e,t,n){var r=this,i=!1,o=null,s=function s(){if(!i){i=!0,clearTimeout(o);try{r.isIframeBlank(e)||(e.removeEventListener("load",s),r.getIframeContents(e,t,n))}catch(e){n()}}};e.addEventListener("load",s),o=setTimeout(s,this.iframesTimeout)}},{key:"onIframeReady",value:function(e,t,n){try{"complete"===e.contentWindow.document.readyState?this.isIframeBlank(e)?this.observeIframeLoad(e,t,n):this.getIframeContents(e,t,n):this.observeIframeLoad(e,t,n)}catch(e){n()}}},{key:"waitForIframes",value:function(e,t){var n=this,r=0;this.forEachIframe(e,(function(){return!0}),(function(e){r++,n.waitForIframes(e.querySelector("html"),(function(){--r||t()}))}),(function(e){e||t()}))}},{key:"forEachIframe",value:function(t,n,r){var i=this,o=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},s=t.querySelectorAll("iframe"),a=s.length,l=0;s=Array.prototype.slice.call(s);var c=function(){--a<=0&&o(l)};a||c(),s.forEach((function(t){e.matches(t,i.exclude)?c():i.onIframeReady(t,(function(e){n(t)&&(l++,r(e)),c()}),c)}))}},{key:"createIterator",value:function(e,t,n){return document.createNodeIterator(e,t,n,!1)}},{key:"createInstanceOnIframe",value:function(t){return new e(t.querySelector("html"),this.iframes)}},{key:"compareNodeIframe",value:function(e,t,n){if(e.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_PRECEDING){if(null===t)return!0;if(t.compareDocumentPosition(n)&Node.DOCUMENT_POSITION_FOLLOWING)return!0}return!1}},{key:"getIteratorNode",value:function(e){var t=e.previousNode();return{prevNode:t,node:(null===t||e.nextNode())&&e.nextNode()}}},{key:"checkIframeFilter",value:function(e,t,n,r){var i=!1,o=!1;return r.forEach((function(e,t){e.val===n&&(i=t,o=e.handled)})),this.compareNodeIframe(e,t,n)?(!1!==i||o?!1===i||o||(r[i].handled=!0):r.push({val:n,handled:!0}),!0):(!1===i&&r.push({val:n,handled:!1}),!1)}},{key:"handleOpenIframes",value:function(e,t,n,r){var i=this;e.forEach((function(e){e.handled||i.getIframeContents(e.val,(function(e){i.createInstanceOnIframe(e).forEachNode(t,n,r)}))}))}},{key:"iterateThroughNodes",value:function(e,t,n,r,i){for(var o=this,s=this.createIterator(t,e,r),a=[],l=[],c=void 0,u=void 0;p=void 0,p=o.getIteratorNode(s),u=p.prevNode,c=p.node;)this.iframes&&this.forEachIframe(t,(function(e){return o.checkIframeFilter(c,u,e,a)}),(function(t){o.createInstanceOnIframe(t).forEachNode(e,(function(e){return l.push(e)}),r)})),l.push(c);var p;l.forEach((function(e){n(e)})),this.iframes&&this.handleOpenIframes(a,e,n,r),i()}},{key:"forEachNode",value:function(e,t,n){var r=this,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:function(){},o=this.getContexts(),s=o.length;s||i(),o.forEach((function(o){var a=function(){r.iterateThroughNodes(e,o,t,n,(function(){--s<=0&&i()}))};r.iframes?r.waitForIframes(o,a):a()}))}}],[{key:"matches",value:function(e,t){var n="string"==typeof t?[t]:t,r=e.matches||e.matchesSelector||e.msMatchesSelector||e.mozMatchesSelector||e.oMatchesSelector||e.webkitMatchesSelector;if(r){var i=!1;return n.every((function(t){return!r.call(e,t)||(i=!0,!1)})),i}return!1}}]),e}(),o=function(){function o(e){t(this,o),this.ctx=e,this.ie=!1;var n=window.navigator.userAgent;(n.indexOf("MSIE")>-1||n.indexOf("Trident")>-1)&&(this.ie=!0)}return n(o,[{key:"log",value:function(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"debug",r=this.opt.log;this.opt.debug&&"object"===(void 0===r?"undefined":e(r))&&"function"==typeof r[n]&&r[n]("mark.js: "+t)}},{key:"escapeStr",value:function(e){return e.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")}},{key:"createRegExp",value:function(e){return"disabled"!==this.opt.wildcards&&(e=this.setupWildcardsRegExp(e)),e=this.escapeStr(e),Object.keys(this.opt.synonyms).length&&(e=this.createSynonymsRegExp(e)),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),this.opt.diacritics&&(e=this.createDiacriticsRegExp(e)),e=this.createMergedBlanksRegExp(e),(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.createJoinersRegExp(e)),"disabled"!==this.opt.wildcards&&(e=this.createWildcardsRegExp(e)),this.createAccuracyRegExp(e)}},{key:"createSynonymsRegExp",value:function(e){var t=this.opt.synonyms,n=this.opt.caseSensitive?"":"i",r=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\0":"";for(var i in t)if(t.hasOwnProperty(i)){var o=t[i],s="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(i):this.escapeStr(i),a="disabled"!==this.opt.wildcards?this.setupWildcardsRegExp(o):this.escapeStr(o);""!==s&&""!==a&&(e=e.replace(new RegExp("("+this.escapeStr(s)+"|"+this.escapeStr(a)+")","gm"+n),r+"("+this.processSynomyms(s)+"|"+this.processSynomyms(a)+")"+r))}return e}},{key:"processSynomyms",value:function(e){return(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length)&&(e=this.setupIgnoreJoinersRegExp(e)),e}},{key:"setupWildcardsRegExp",value:function(e){return(e=e.replace(/(?:\\)*\?/g,(function(e){return"\\"===e.charAt(0)?"?":""}))).replace(/(?:\\)*\*/g,(function(e){return"\\"===e.charAt(0)?"*":""}))}},{key:"createWildcardsRegExp",value:function(e){var t="withSpaces"===this.opt.wildcards;return e.replace(/\u0001/g,t?"[\\S\\s]?":"\\S?").replace(/\u0002/g,t?"[\\S\\s]*?":"\\S*")}},{key:"setupIgnoreJoinersRegExp",value:function(e){return e.replace(/[^(|)\\]/g,(function(e,t,n){var r=n.charAt(t+1);return/[(|)\\]/.test(r)||""===r?e:e+"\0"}))}},{key:"createJoinersRegExp",value:function(e){var t=[],n=this.opt.ignorePunctuation;return Array.isArray(n)&&n.length&&t.push(this.escapeStr(n.join(""))),this.opt.ignoreJoiners&&t.push("\\u00ad\\u200b\\u200c\\u200d"),t.length?e.split(/\u0000+/).join("["+t.join("")+"]*"):e}},{key:"createDiacriticsRegExp",value:function(e){var t=this.opt.caseSensitive?"":"i",n=this.opt.caseSensitive?["aàáảãạăằắẳẵặâầấẩẫậäåāą","AÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćč","CÇĆČ","dđď","DĐĎ","eèéẻẽẹêềếểễệëěēę","EÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïī","IÌÍỈĨỊÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøō","OÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúủũụưừứửữựûüůū","UÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿ","YÝỲỶỸỴŸ","zžżź","ZŽŻŹ"]:["aàáảãạăằắẳẵặâầấẩẫậäåāąAÀÁẢÃẠĂẰẮẲẴẶÂẦẤẨẪẬÄÅĀĄ","cçćčCÇĆČ","dđďDĐĎ","eèéẻẽẹêềếểễệëěēęEÈÉẺẼẸÊỀẾỂỄỆËĚĒĘ","iìíỉĩịîïīIÌÍỈĨỊÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóỏõọôồốổỗộơởỡớờợöøōOÒÓỎÕỌÔỒỐỔỖỘƠỞỠỚỜỢÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúủũụưừứửữựûüůūUÙÚỦŨỤƯỪỨỬỮỰÛÜŮŪ","yýỳỷỹỵÿYÝỲỶỸỴŸ","zžżźZŽŻŹ"],r=[];return e.split("").forEach((function(i){n.every((function(n){if(-1!==n.indexOf(i)){if(r.indexOf(n)>-1)return!1;e=e.replace(new RegExp("["+n+"]","gm"+t),"["+n+"]"),r.push(n)}return!0}))})),e}},{key:"createMergedBlanksRegExp",value:function(e){return e.replace(/[\s]+/gim,"[\\s]+")}},{key:"createAccuracyRegExp",value:function(e){var t=this,n=this.opt.accuracy,r="string"==typeof n?n:n.value,i="string"==typeof n?[]:n.limiters,o="";switch(i.forEach((function(e){o+="|"+t.escapeStr(e)})),r){case"partially":default:return"()("+e+")";case"complementary":return"()([^"+(o="\\s"+(o||this.escapeStr("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~¡¿")))+"]*"+e+"[^"+o+"]*)";case"exactly":return"(^|\\s"+o+")("+e+")(?=$|\\s"+o+")"}}},{key:"getSeparatedKeywords",value:function(e){var t=this,n=[];return e.forEach((function(e){t.opt.separateWordSearch?e.split(" ").forEach((function(e){e.trim()&&-1===n.indexOf(e)&&n.push(e)})):e.trim()&&-1===n.indexOf(e)&&n.push(e)})),{keywords:n.sort((function(e,t){return t.length-e.length})),length:n.length}}},{key:"isNumeric",value:function(e){return Number(parseFloat(e))==e}},{key:"checkRanges",value:function(e){var t=this;if(!Array.isArray(e)||"[object Object]"!==Object.prototype.toString.call(e[0]))return this.log("markRanges() will only accept an array of objects"),this.opt.noMatch(e),[];var n=[],r=0;return e.sort((function(e,t){return e.start-t.start})).forEach((function(e){var i=t.callNoMatchOnInvalidRanges(e,r),o=i.start,s=i.end;i.valid&&(e.start=o,e.length=s-o,n.push(e),r=s)})),n}},{key:"callNoMatchOnInvalidRanges",value:function(e,t){var n=void 0,r=void 0,i=!1;return e&&void 0!==e.start?(r=(n=parseInt(e.start,10))+parseInt(e.length,10),this.isNumeric(e.start)&&this.isNumeric(e.length)&&r-t>0&&r-n>0?i=!0:(this.log("Ignoring invalid or overlapping range: "+JSON.stringify(e)),this.opt.noMatch(e))):(this.log("Ignoring invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:n,end:r,valid:i}}},{key:"checkWhitespaceRanges",value:function(e,t,n){var r=void 0,i=!0,o=n.length,s=t-o,a=parseInt(e.start,10)-s;return(r=(a=a>o?o:a)+parseInt(e.length,10))>o&&(r=o,this.log("End range automatically set to the max value of "+o)),a<0||r-a<0||a>o||r>o?(i=!1,this.log("Invalid range: "+JSON.stringify(e)),this.opt.noMatch(e)):""===n.substring(a,r).replace(/\s+/g,"")&&(i=!1,this.log("Skipping whitespace only range: "+JSON.stringify(e)),this.opt.noMatch(e)),{start:a,end:r,valid:i}}},{key:"getTextNodes",value:function(e){var t=this,n="",r=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,(function(e){r.push({start:n.length,end:(n+=e.textContent).length,node:e})}),(function(e){return t.matchesExclude(e.parentNode)?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}),(function(){e({value:n,nodes:r})}))}},{key:"matchesExclude",value:function(e){return i.matches(e,this.opt.exclude.concat(["script","style","title","head","html"]))}},{key:"wrapRangeInTextNode",value:function(e,t,n){var r=this.opt.element?this.opt.element:"mark",i=e.splitText(t),o=i.splitText(n-t),s=document.createElement(r);return s.setAttribute("data-markjs","true"),this.opt.className&&s.setAttribute("class",this.opt.className),s.textContent=i.textContent,i.parentNode.replaceChild(s,i),o}},{key:"wrapRangeInMappedTextNode",value:function(e,t,n,r,i){var o=this;e.nodes.every((function(s,a){var l=e.nodes[a+1];if(void 0===l||l.start>t){if(!r(s.node))return!1;var c=t-s.start,u=(n>s.end?s.end:n)-s.start,p=e.value.substr(0,s.start),d=e.value.substr(u+s.start);if(s.node=o.wrapRangeInTextNode(s.node,c,u),e.value=p+d,e.nodes.forEach((function(t,n){n>=a&&(e.nodes[n].start>0&&n!==a&&(e.nodes[n].start-=u),e.nodes[n].end-=u)})),n-=u,i(s.node.previousSibling,s.start),!(n>s.end))return!1;t=s.end}return!0}))}},{key:"wrapMatches",value:function(e,t,n,r,i){var o=this,s=0===t?0:t+1;this.getTextNodes((function(t){t.nodes.forEach((function(t){t=t.node;for(var i=void 0;null!==(i=e.exec(t.textContent))&&""!==i[s];)if(n(i[s],t)){var a=i.index;if(0!==s)for(var l=1;l1&&console.warn("Replacing with",t),m++}}else{let i=u(l(t,e[n]));if(s.verbose>1&&console.warn((!1===i?f.colour.red:f.colour.green)+"Fragment resolution",e[n],f.colour.normal),!1===i){if(r.parent[r.pkey]={},s.fatal){let t=new Error("Fragment $ref resolution failed "+e[n]);if(!s.promise)throw t;s.promise.reject(t)}}else m++,r.parent[r.pkey]=i,h[e[n]]=r.path.replace("/%24ref","")}else if(p.protocol){let t=o.resolve(i,e[n]).toString();s.verbose>1&&console.warn(f.colour.yellow+"Rewriting external url ref",e[n],"as",t,f.colour.normal),e["x-miro"]=e[n],s.externalRefs[e[n]]&&(s.externalRefs[t]||(s.externalRefs[t]=s.externalRefs[e[n]]),s.externalRefs[t].failed=s.externalRefs[e[n]].failed),e[n]=t}else if(!e["x-miro"]){let t=o.resolve(i,e[n]).toString(),r=!1;s.externalRefs[e[n]]&&(r=s.externalRefs[e[n]].failed),r||(s.verbose>1&&console.warn(f.colour.yellow+"Rewriting external ref",e[n],"as",t,f.colour.normal),e["x-miro"]=e[n],e[n]=t)}}));return c(e,{},(function(e,t,n){d(e,t)&&void 0!==e.$fixed&&delete e.$fixed})),s.verbose>1&&console.warn("Finished fragment resolution"),e}function m(e,t){if(!t.filters||!t.filters.length)return e;for(let n of t.filters)e=n(e,t);return e}function g(e,t,n,s){var c=o.parse(n.source),p=n.source.split("\\").join("/").split("/");p.pop()||p.pop();let d="",f=t.split("#");f.length>1&&(d="#"+f[1],t=f[0]),p=p.join("/");let g=(y=o.parse(t).protocol,b=c.protocol,y&&y.length>2?y:b&&b.length>2?b:"file:");var y,b;let v;if(v="file:"===g?i.resolve(p?p+"/":"",t):o.resolve(p?p+"/":"",t),n.cache[v]){n.verbose&&console.warn("CACHED",v,d);let e=u(n.cache[v]),r=n.externalRef=e;if(d&&(r=l(r,d),!1===r&&(r={},n.fatal))){let e=new Error("Cached $ref resolution failed "+v+d);if(!n.promise)throw e;n.promise.reject(e)}return r=h(r,e,t,d,v,n),r=m(r,n),s(u(r),v,n),Promise.resolve(r)}if(n.verbose&&console.warn("GET",v,d),n.handlers&&n.handlers[g])return n.handlers[g](p,t,d,n).then((function(e){return n.externalRef=e,e=m(e,n),n.cache[v]=e,s(e,v,n),e})).catch((function(e){throw n.verbose&&console.warn(e),e}));if(g&&g.startsWith("http")){const e=Object.assign({},n.fetchOptions,{agent:n.agent});return n.fetch(v,e).then((function(e){if(200!==e.status){if(n.ignoreIOErrors)return n.verbose&&console.warn("FAILED",t),n.externalRefs[t].failed=!0,'{"$ref":"'+t+'"}';throw new Error(`Received status code ${e.status}: ${v}`)}return e.text()})).then((function(e){try{let r=a.parse(e,{schema:"core",prettyErrors:!0});if(e=n.externalRef=r,n.cache[v]=u(e),d&&!1===(e=l(e,d))&&(e={},n.fatal)){let e=new Error("Remote $ref resolution failed "+v+d);if(!n.promise)throw e;n.promise.reject(e)}e=m(e=h(e,r,t,d,v,n),n)}catch(e){if(n.verbose&&console.warn(e),!n.promise||!n.fatal)throw e;n.promise.reject(e)}return s(e,v,n),e})).catch((function(e){if(n.verbose&&console.warn(e),n.cache[v]={},!n.promise||!n.fatal)throw e;n.promise.reject(e)}))}{const e='{"$ref":"'+t+'"}';return function(e,t,n,i,o){return new Promise((function(s,a){r.readFile(e,t,(function(e,t){e?n.ignoreIOErrors&&o?(n.verbose&&console.warn("FAILED",i),n.externalRefs[i].failed=!0,s(o)):a(e):s(t)}))}))}(v,n.encoding||"utf8",n,t,e).then((function(e){try{let r=a.parse(e,{schema:"core",prettyErrors:!0});if(e=n.externalRef=r,n.cache[v]=u(e),d&&!1===(e=l(e,d))&&(e={},n.fatal)){let e=new Error("File $ref resolution failed "+v+d);if(!n.promise)throw e;n.promise.reject(e)}e=m(e=h(e,r,t,d,v,n),n)}catch(e){if(n.verbose&&console.warn(e),!n.promise||!n.fatal)throw e;n.promise.reject(e)}return s(e,v,n),e})).catch((function(e){if(n.verbose&&console.warn(e),!n.promise||!n.fatal)throw e;n.promise.reject(e)}))}}function y(e){return new Promise((function(t,n){(function(e){return new Promise((function(t,n){function r(t,n,r){if(t[n]&&d(t[n],"$ref")){let o=t[n].$ref;if(!o.startsWith("#")){let s="";if(!i[o]){let t=Object.keys(i).find((function(e,t,n){return o.startsWith(e+"/")}));t&&(e.verbose&&console.warn("Found potential subschema at",t),s="/"+(o.split("#")[1]||"").replace(t.split("#")[1]||""),s=s.split("/undefined").join(""),o=t)}if(i[o]||(i[o]={resolved:!1,paths:[],extras:{},description:t[n].description}),i[o].resolved)if(i[o].failed);else if(e.rewriteRefs){let r=i[o].resolvedAt;e.verbose>1&&console.warn("Rewriting ref",o,r),t[n]["x-miro"]=o,t[n].$ref=r+s}else t[n]=u(i[o].data);else i[o].paths.push(r.path),i[o].extras[r.path]=s}}}let i=e.externalRefs;if(e.resolver.depth>0&&e.source===e.resolver.base)return t(i);c(e.openapi.definitions,{identityDetection:!0,path:"#/definitions"},r),c(e.openapi.components,{identityDetection:!0,path:"#/components"},r),c(e.openapi,{identityDetection:!0},r),t(i)}))})(e).then((function(t){for(let n in t)if(!t[n].resolved){let r=e.resolver.depth;r>0&&r++,e.resolver.actions[r].push((function(){return g(e.openapi,n,e,(function(e,r,i){if(!t[n].resolved){let o={};o.context=t[n],o.$ref=n,o.original=u(e),o.updated=e,o.source=r,i.externals.push(o),t[n].resolved=!0}let o=Object.assign({},i,{source:"",resolver:{actions:i.resolver.actions,depth:i.resolver.actions.length-1,base:i.resolver.base}});i.patch&&t[n].description&&!e.description&&"object"==typeof e&&(e.description=t[n].description),t[n].data=e;let s=(a=t[n].paths,[...new Set(a)]);var a;s=s.sort((function(e,t){const n=e.startsWith("#/components/")||e.startsWith("#/definitions/"),r=t.startsWith("#/components/")||t.startsWith("#/definitions/");return n&&!r?-1:r&&!n?1:0}));for(let r of s)if(t[n].resolvedAt&&r!==t[n].resolvedAt&&r.indexOf("x-ms-examples/")<0)i.verbose>1&&console.warn("Creating pointer to data at",r),l(i.openapi,r,{$ref:t[n].resolvedAt+t[n].extras[r],"x-miro":n+t[n].extras[r]});else{t[n].resolvedAt?i.verbose>1&&console.warn("Avoiding circular reference"):(t[n].resolvedAt=r,i.verbose>1&&console.warn("Creating initial clone of data at",r));let o=u(e);l(i.openapi,r,o)}0===i.resolver.actions[o.resolver.depth].length&&i.resolver.actions[o.resolver.depth].push((function(){return y(o)}))}))}))}})).catch((function(t){e.verbose&&console.warn(t),n(t)}));let r={options:e};r.actions=e.resolver.actions[e.resolver.depth],t(r)}))}function b(e,t,n){e.resolver.actions.push([]),y(e).then((function(r){var i;(i=r.actions,i.reduce(((e,t)=>e.then((e=>t().then(Array.prototype.concat.bind(e))))),Promise.resolve([]))).then((function(){if(e.resolver.depth>=e.resolver.actions.length)return console.warn("Ran off the end of resolver actions"),t(!0);e.resolver.depth++,e.resolver.actions[e.resolver.depth].length?setTimeout((function(){b(r.options,t,n)}),0):(e.verbose>1&&console.warn(f.colour.yellow+"Finished external resolution!",f.colour.normal),e.resolveInternal&&(e.verbose>1&&console.warn(f.colour.yellow+"Starting internal resolution!",f.colour.normal),e.openapi=p(e.openapi,e.original,{verbose:e.verbose-1}),e.verbose>1&&console.warn(f.colour.yellow+"Finished internal resolution!",f.colour.normal)),c(e.openapi,{},(function(t,n,r){d(t,n)&&(e.preserveMiro||delete t["x-miro"])})),t(e))})).catch((function(t){e.verbose&&console.warn(t),n(t)}))})).catch((function(t){e.verbose&&console.warn(t),n(t)}))}function v(e){if(e.cache||(e.cache={}),e.fetch||(e.fetch=s),e.source){let t=o.parse(e.source);(!t.protocol||t.protocol.length<=2)&&(e.source=i.resolve(e.source))}e.externals=[],e.externalRefs={},e.rewriteRefs=!0,e.resolver={},e.resolver.depth=0,e.resolver.base=e.source,e.resolver.actions=[[]]}e.exports={optionalResolve:function(e){return v(e),new Promise((function(t,n){e.resolve?b(e,t,n):t(e)}))},resolve:function(e,t,n){return n||(n={}),n.openapi=e,n.source=t,n.resolve=!0,v(n),new Promise((function(e,t){b(n,e,t)}))}}},1319:function(e){"use strict";function t(){return{depth:0,seen:new WeakMap,top:!0,combine:!1,allowRefSiblings:!1}}e.exports={getDefaultState:t,walkSchema:function e(n,r,i,o){if(void 0===i.depth&&(i=t()),null==n)return n;if(void 0!==n.$ref){let e={$ref:n.$ref};return i.allowRefSiblings&&n.description&&(e.description=n.description),o(e,r,i),e}if(i.combine&&(n.allOf&&Array.isArray(n.allOf)&&1===n.allOf.length&&delete(n=Object.assign({},n.allOf[0],n)).allOf,n.anyOf&&Array.isArray(n.anyOf)&&1===n.anyOf.length&&delete(n=Object.assign({},n.anyOf[0],n)).anyOf,n.oneOf&&Array.isArray(n.oneOf)&&1===n.oneOf.length&&delete(n=Object.assign({},n.oneOf[0],n)).oneOf),o(n,r,i),i.seen.has(n))return n;if("object"==typeof n&&null!==n&&i.seen.set(n,!0),i.top=!1,i.depth++,void 0!==n.items&&(i.property="items",e(n.items,n,i,o)),n.additionalItems&&"object"==typeof n.additionalItems&&(i.property="additionalItems",e(n.additionalItems,n,i,o)),n.additionalProperties&&"object"==typeof n.additionalProperties&&(i.property="additionalProperties",e(n.additionalProperties,n,i,o)),n.properties)for(let t in n.properties){let r=n.properties[t];i.property="properties/"+t,e(r,n,i,o)}if(n.patternProperties)for(let t in n.patternProperties){let r=n.patternProperties[t];i.property="patternProperties/"+t,e(r,n,i,o)}if(n.allOf)for(let t in n.allOf){let r=n.allOf[t];i.property="allOf/"+t,e(r,n,i,o)}if(n.anyOf)for(let t in n.anyOf){let r=n.anyOf[t];i.property="anyOf/"+t,e(r,n,i,o)}if(n.oneOf)for(let t in n.oneOf){let r=n.oneOf[t];i.property="oneOf/"+t,e(r,n,i,o)}return n.not&&(i.property="not",e(n.not,n,i,o)),i.depth--,n}}},7975:function(e){"use strict";function t(e){if("string"!=typeof e)throw new TypeError("Path must be a string. Received "+JSON.stringify(e))}function n(e,t){for(var n,r="",i=0,o=-1,s=0,a=0;a<=e.length;++a){if(a2){var l=r.lastIndexOf("/");if(l!==r.length-1){-1===l?(r="",i=0):i=(r=r.slice(0,l)).length-1-r.lastIndexOf("/"),o=a,s=0;continue}}else if(2===r.length||1===r.length){r="",i=0,o=a,s=0;continue}t&&(r.length>0?r+="/..":r="..",i=2)}else r.length>0?r+="/"+e.slice(o+1,a):r=e.slice(o+1,a),i=a-o-1;o=a,s=0}else 46===n&&-1!==s?++s:s=-1}return r}var r={resolve:function(){for(var e,r="",i=!1,o=arguments.length-1;o>=-1&&!i;o--){var s;o>=0?s=arguments[o]:(void 0===e&&(e=process.cwd()),s=e),t(s),0!==s.length&&(r=s+"/"+r,i=47===s.charCodeAt(0))}return r=n(r,!i),i?r.length>0?"/"+r:"/":r.length>0?r:"."},normalize:function(e){if(t(e),0===e.length)return".";var r=47===e.charCodeAt(0),i=47===e.charCodeAt(e.length-1);return 0!==(e=n(e,!r)).length||r||(e="."),e.length>0&&i&&(e+="/"),r?"/"+e:e},isAbsolute:function(e){return t(e),e.length>0&&47===e.charCodeAt(0)},join:function(){if(0===arguments.length)return".";for(var e,n=0;n0&&(void 0===e?e=i:e+="/"+i)}return void 0===e?".":r.normalize(e)},relative:function(e,n){if(t(e),t(n),e===n)return"";if((e=r.resolve(e))===(n=r.resolve(n)))return"";for(var i=1;ic){if(47===n.charCodeAt(a+p))return n.slice(a+p+1);if(0===p)return n.slice(a+p)}else s>c&&(47===e.charCodeAt(i+p)?u=p:0===p&&(u=0));break}var d=e.charCodeAt(i+p);if(d!==n.charCodeAt(a+p))break;47===d&&(u=p)}var f="";for(p=i+u+1;p<=o;++p)p!==o&&47!==e.charCodeAt(p)||(0===f.length?f+="..":f+="/..");return f.length>0?f+n.slice(a+u):(a+=u,47===n.charCodeAt(a)&&++a,n.slice(a))},_makeLong:function(e){return e},dirname:function(e){if(t(e),0===e.length)return".";for(var n=e.charCodeAt(0),r=47===n,i=-1,o=!0,s=e.length-1;s>=1;--s)if(47===(n=e.charCodeAt(s))){if(!o){i=s;break}}else o=!1;return-1===i?r?"/":".":r&&1===i?"//":e.slice(0,i)},basename:function(e,n){if(void 0!==n&&"string"!=typeof n)throw new TypeError('"ext" argument must be a string');t(e);var r,i=0,o=-1,s=!0;if(void 0!==n&&n.length>0&&n.length<=e.length){if(n.length===e.length&&n===e)return"";var a=n.length-1,l=-1;for(r=e.length-1;r>=0;--r){var c=e.charCodeAt(r);if(47===c){if(!s){i=r+1;break}}else-1===l&&(s=!1,l=r+1),a>=0&&(c===n.charCodeAt(a)?-1==--a&&(o=r):(a=-1,o=l))}return i===o?o=l:-1===o&&(o=e.length),e.slice(i,o)}for(r=e.length-1;r>=0;--r)if(47===e.charCodeAt(r)){if(!s){i=r+1;break}}else-1===o&&(s=!1,o=r+1);return-1===o?"":e.slice(i,o)},extname:function(e){t(e);for(var n=-1,r=0,i=-1,o=!0,s=0,a=e.length-1;a>=0;--a){var l=e.charCodeAt(a);if(47!==l)-1===i&&(o=!1,i=a+1),46===l?-1===n?n=a:1!==s&&(s=1):-1!==n&&(s=-1);else if(!o){r=a+1;break}}return-1===n||-1===i||0===s||1===s&&n===i-1&&n===r+1?"":e.slice(n,i)},format:function(e){if(null===e||"object"!=typeof e)throw new TypeError('The "pathObject" argument must be of type Object. Received type '+typeof e);return function(e,t){var n=t.dir||t.root,r=t.base||(t.name||"")+(t.ext||"");return n?n===t.root?n+r:n+"/"+r:r}(0,e)},parse:function(e){t(e);var n={root:"",dir:"",base:"",ext:"",name:""};if(0===e.length)return n;var r,i=e.charCodeAt(0),o=47===i;o?(n.root="/",r=1):r=0;for(var s=-1,a=0,l=-1,c=!0,u=e.length-1,p=0;u>=r;--u)if(47!==(i=e.charCodeAt(u)))-1===l&&(c=!1,l=u+1),46===i?-1===s?s=u:1!==p&&(p=1):-1!==s&&(p=-1);else if(!c){a=u+1;break}return-1===s||-1===l||0===p||1===p&&s===l-1&&s===a+1?-1!==l&&(n.base=n.name=0===a&&o?e.slice(1,l):e.slice(a,l)):(0===a&&o?(n.name=e.slice(1,s),n.base=e.slice(1,l)):(n.name=e.slice(a,s),n.base=e.slice(a,l)),n.ext=e.slice(s,l)),a>0?n.dir=e.slice(0,a-1):o&&(n.dir="/"),n},sep:"/",delimiter:":",win32:null,posix:null};r.posix=r,e.exports=r},5127:function(e){e.exports=function(){var e=[],t=[],n={},r={},i={};function o(e){return"string"==typeof e?new RegExp("^"+e+"$","i"):e}function s(e,t){return e===t?t:e===e.toLowerCase()?t.toLowerCase():e===e.toUpperCase()?t.toUpperCase():e[0]===e[0].toUpperCase()?t.charAt(0).toUpperCase()+t.substr(1).toLowerCase():t.toLowerCase()}function a(e,t){return e.replace(t[0],(function(n,r){var i,o,a=(i=t[1],o=arguments,i.replace(/\$(\d{1,2})/g,(function(e,t){return o[t]||""})));return s(""===n?e[r-1]:n,a)}))}function l(e,t,r){if(!e.length||n.hasOwnProperty(e))return t;for(var i=r.length;i--;){var o=r[i];if(o[0].test(t))return a(t,o)}return t}function c(e,t,n){return function(r){var i=r.toLowerCase();return t.hasOwnProperty(i)?s(r,i):e.hasOwnProperty(i)?s(r,e[i]):l(i,r,n)}}function u(e,t,n,r){return function(r){var i=r.toLowerCase();return!!t.hasOwnProperty(i)||!e.hasOwnProperty(i)&&l(i,i,n)===i}}function p(e,t,n){return(n?t+" ":"")+(1===t?p.singular(e):p.plural(e))}return p.plural=c(i,r,e),p.isPlural=u(i,r,e),p.singular=c(r,i,t),p.isSingular=u(r,i,t),p.addPluralRule=function(t,n){e.push([o(t),n])},p.addSingularRule=function(e,n){t.push([o(e),n])},p.addUncountableRule=function(e){"string"!=typeof e?(p.addPluralRule(e,"$0"),p.addSingularRule(e,"$0")):n[e.toLowerCase()]=!0},p.addIrregularRule=function(e,t){t=t.toLowerCase(),e=e.toLowerCase(),i[e]=t,r[t]=e},[["I","we"],["me","us"],["he","they"],["she","they"],["them","them"],["myself","ourselves"],["yourself","yourselves"],["itself","themselves"],["herself","themselves"],["himself","themselves"],["themself","themselves"],["is","are"],["was","were"],["has","have"],["this","these"],["that","those"],["echo","echoes"],["dingo","dingoes"],["volcano","volcanoes"],["tornado","tornadoes"],["torpedo","torpedoes"],["genus","genera"],["viscus","viscera"],["stigma","stigmata"],["stoma","stomata"],["dogma","dogmata"],["lemma","lemmata"],["schema","schemata"],["anathema","anathemata"],["ox","oxen"],["axe","axes"],["die","dice"],["yes","yeses"],["foot","feet"],["eave","eaves"],["goose","geese"],["tooth","teeth"],["quiz","quizzes"],["human","humans"],["proof","proofs"],["carve","carves"],["valve","valves"],["looey","looies"],["thief","thieves"],["groove","grooves"],["pickaxe","pickaxes"],["passerby","passersby"]].forEach((function(e){return p.addIrregularRule(e[0],e[1])})),[[/s?$/i,"s"],[/[^\u0000-\u007F]$/i,"$0"],[/([^aeiou]ese)$/i,"$1"],[/(ax|test)is$/i,"$1es"],[/(alias|[^aou]us|t[lm]as|gas|ris)$/i,"$1es"],[/(e[mn]u)s?$/i,"$1s"],[/([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$/i,"$1"],[/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i,"$1i"],[/(alumn|alg|vertebr)(?:a|ae)$/i,"$1ae"],[/(seraph|cherub)(?:im)?$/i,"$1im"],[/(her|at|gr)o$/i,"$1oes"],[/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i,"$1a"],[/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i,"$1a"],[/sis$/i,"ses"],[/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i,"$1$2ves"],[/([^aeiouy]|qu)y$/i,"$1ies"],[/([^ch][ieo][ln])ey$/i,"$1ies"],[/(x|ch|ss|sh|zz)$/i,"$1es"],[/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i,"$1ices"],[/\b((?:tit)?m|l)(?:ice|ouse)$/i,"$1ice"],[/(pe)(?:rson|ople)$/i,"$1ople"],[/(child)(?:ren)?$/i,"$1ren"],[/eaux$/i,"$0"],[/m[ae]n$/i,"men"],["thou","you"]].forEach((function(e){return p.addPluralRule(e[0],e[1])})),[[/s$/i,""],[/(ss)$/i,"$1"],[/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i,"$1fe"],[/(ar|(?:wo|[ae])l|[eo][ao])ves$/i,"$1f"],[/ies$/i,"y"],[/\b([pl]|zomb|(?:neck|cross)?t|coll|faer|food|gen|goon|group|lass|talk|goal|cut)ies$/i,"$1ie"],[/\b(mon|smil)ies$/i,"$1ey"],[/\b((?:tit)?m|l)ice$/i,"$1ouse"],[/(seraph|cherub)im$/i,"$1"],[/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$/i,"$1"],[/(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$/i,"$1sis"],[/(movie|twelve|abuse|e[mn]u)s$/i,"$1"],[/(test)(?:is|es)$/i,"$1is"],[/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i,"$1us"],[/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i,"$1um"],[/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i,"$1on"],[/(alumn|alg|vertebr)ae$/i,"$1a"],[/(cod|mur|sil|vert|ind)ices$/i,"$1ex"],[/(matr|append)ices$/i,"$1ix"],[/(pe)(rson|ople)$/i,"$1rson"],[/(child)ren$/i,"$1"],[/(eau)x?$/i,"$1"],[/men$/i,"man"]].forEach((function(e){return p.addSingularRule(e[0],e[1])})),["adulthood","advice","agenda","aid","aircraft","alcohol","ammo","analytics","anime","athletics","audio","bison","blood","bream","buffalo","butter","carp","cash","chassis","chess","clothing","cod","commerce","cooperation","corps","debris","diabetes","digestion","elk","energy","equipment","excretion","expertise","firmware","flounder","fun","gallows","garbage","graffiti","hardware","headquarters","health","herpes","highjinks","homework","housework","information","jeans","justice","kudos","labour","literature","machinery","mackerel","mail","media","mews","moose","music","mud","manga","news","only","personnel","pike","plankton","pliers","police","pollution","premises","rain","research","rice","salmon","scissors","series","sewage","shambles","shrimp","software","species","staff","swine","tennis","traffic","transportation","trout","tuna","wealth","welfare","whiting","wildebeest","wildlife","you",/pok[eé]mon$/i,/[^aeiou]ese$/i,/deer$/i,/fish$/i,/measles$/i,/o[iu]s$/i,/pox$/i,/sheep$/i].forEach(p.addUncountableRule),p}()},7022:function(){!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},r={bash:n,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|U[0-9a-fA-F]{8}|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{1,2})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?:\.\w+)*(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},parameter:{pattern:/(^|\s)-{1,2}(?:\w+:[+-]?)?\w+(?:\.\w+)*(?=[=\s]|$)/,alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:r},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:n}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:r},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:r.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:r.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|apt-cache|apt-get|aptitude|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cargo|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|docker|docker-compose|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|java|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|node|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|podman|podman-compose|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|sysctl|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vcpkg|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:case|do|done|elif|else|esac|fi|for|function|if|in|select|then|until|while)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|alias|bind|break|builtin|caller|cd|command|continue|declare|echo|enable|eval|exec|exit|export|getopts|hash|help|let|local|logout|mapfile|printf|pwd|read|readarray|readonly|return|set|shift|shopt|source|test|times|trap|type|typeset|ulimit|umask|unalias|unset)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:false|true)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},n.inside=e.languages.bash;for(var i=["comment","function-name","for-or-select","assign-left","parameter","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],o=r.variable[1].inside,s=0;s>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),Prism.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),Prism.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},Prism.languages.c.string],char:Prism.languages.c.char,comment:Prism.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:Prism.languages.c}}}}),Prism.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete Prism.languages.c.boolean},5624:function(){Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}},4511:function(){!function(e){var t=/#(?!\{).+/,n={pattern:/#\{[^}]+\}/,alias:"variable"};e.languages.coffeescript=e.languages.extend("javascript",{comment:t,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:n}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),e.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:t,interpolation:n}}}),e.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},script:{pattern:/[\s\S]+/,alias:"language-javascript",inside:e.languages.javascript}}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:n}}]}),e.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete e.languages.coffeescript["template-string"],e.languages.coffee=e.languages.coffeescript}(Prism)},2415:function(){!function(e){var t=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,n=/\b(?!)\w+(?:\s*\.\s*\w+)*\b/.source.replace(//g,(function(){return t.source}));e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!)\w+/.source.replace(//g,(function(){return t.source}))),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:t,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),e.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:import|module)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/(?:\s*:\s*)?|:\s*/.source.replace(//g,(function(){return n}))+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),e.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:e.languages.cpp}}}}),e.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),e.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend("cpp",{})}}),e.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},e.languages.cpp["base-clause"])}(Prism)},5651:function(){!function(e){function t(e,t){return e.replace(/<<(\d+)>>/g,(function(e,n){return"(?:"+t[+n]+")"}))}function n(e,n,r){return RegExp(t(e,n),r||"")}function r(e,t){for(var n=0;n>/g,(function(){return"(?:"+e+")"}));return e.replace(/<>/g,"[^\\s\\S]")}var i="bool byte char decimal double dynamic float int long object sbyte short string uint ulong ushort var void",o="class enum interface record struct",s="add alias and ascending async await by descending from(?=\\s*(?:\\w|$)) get global group into init(?=\\s*;) join let nameof not notnull on or orderby partial remove select set unmanaged value when where with(?=\\s*{)",a="abstract as base break case catch checked const continue default delegate do else event explicit extern finally fixed for foreach goto if implicit in internal is lock namespace new null operator out override params private protected public readonly ref return sealed sizeof stackalloc static switch this throw try typeof unchecked unsafe using virtual volatile while yield";function l(e){return"\\b(?:"+e.trim().replace(/ /g,"|")+")\\b"}var c=l(o),u=RegExp(l(i+" "+o+" "+s+" "+a)),p=l(o+" "+s+" "+a),d=l(i+" "+o+" "+a),f=r(/<(?:[^<>;=+\-*/%&|^]|<>)*>/.source,2),h=r(/\((?:[^()]|<>)*\)/.source,2),m=/@?\b[A-Za-z_]\w*\b/.source,g=t(/<<0>>(?:\s*<<1>>)?/.source,[m,f]),y=t(/(?!<<0>>)<<1>>(?:\s*\.\s*<<1>>)*/.source,[p,g]),b=/\[\s*(?:,\s*)*\]/.source,v=t(/<<0>>(?:\s*(?:\?\s*)?<<1>>)*(?:\s*\?)?/.source,[y,b]),x=t(/[^,()<>[\];=+\-*/%&|^]|<<0>>|<<1>>|<<2>>/.source,[f,h,b]),w=t(/\(<<0>>+(?:,<<0>>+)+\)/.source,[x]),k=t(/(?:<<0>>|<<1>>)(?:\s*(?:\?\s*)?<<2>>)*(?:\s*\?)?/.source,[w,y,b]),S={keyword:u,punctuation:/[<>()?,.:[\]]/},E=/'(?:[^\r\n'\\]|\\.|\\[Uux][\da-fA-F]{1,8})'/.source,O=/"(?:\\.|[^\\"\r\n])*"/.source,_=/@"(?:""|\\[\s\S]|[^\\"])*"(?!")/.source;e.languages.csharp=e.languages.extend("clike",{string:[{pattern:n(/(^|[^$\\])<<0>>/.source,[_]),lookbehind:!0,greedy:!0},{pattern:n(/(^|[^@$\\])<<0>>/.source,[O]),lookbehind:!0,greedy:!0}],"class-name":[{pattern:n(/(\busing\s+static\s+)<<0>>(?=\s*;)/.source,[y]),lookbehind:!0,inside:S},{pattern:n(/(\busing\s+<<0>>\s*=\s*)<<1>>(?=\s*;)/.source,[m,k]),lookbehind:!0,inside:S},{pattern:n(/(\busing\s+)<<0>>(?=\s*=)/.source,[m]),lookbehind:!0},{pattern:n(/(\b<<0>>\s+)<<1>>/.source,[c,g]),lookbehind:!0,inside:S},{pattern:n(/(\bcatch\s*\(\s*)<<0>>/.source,[y]),lookbehind:!0,inside:S},{pattern:n(/(\bwhere\s+)<<0>>/.source,[m]),lookbehind:!0},{pattern:n(/(\b(?:is(?:\s+not)?|as)\s+)<<0>>/.source,[v]),lookbehind:!0,inside:S},{pattern:n(/\b<<0>>(?=\s+(?!<<1>>|with\s*\{)<<2>>(?:\s*[=,;:{)\]]|\s+(?:in|when)\b))/.source,[k,d,m]),inside:S}],keyword:u,number:/(?:\b0(?:x[\da-f_]*[\da-f]|b[01_]*[01])|(?:\B\.\d+(?:_+\d+)*|\b\d+(?:_+\d+)*(?:\.\d+(?:_+\d+)*)?)(?:e[-+]?\d+(?:_+\d+)*)?)(?:[dflmu]|lu|ul)?\b/i,operator:/>>=?|<<=?|[-=]>|([-+&|])\1|~|\?\?=?|[-+*/%&|^!=<>]=?/,punctuation:/\?\.?|::|[{}[\];(),.:]/}),e.languages.insertBefore("csharp","number",{range:{pattern:/\.\./,alias:"operator"}}),e.languages.insertBefore("csharp","punctuation",{"named-parameter":{pattern:n(/([(,]\s*)<<0>>(?=\s*:)/.source,[m]),lookbehind:!0,alias:"punctuation"}}),e.languages.insertBefore("csharp","class-name",{namespace:{pattern:n(/(\b(?:namespace|using)\s+)<<0>>(?:\s*\.\s*<<0>>)*(?=\s*[;{])/.source,[m]),lookbehind:!0,inside:{punctuation:/\./}},"type-expression":{pattern:n(/(\b(?:default|sizeof|typeof)\s*\(\s*(?!\s))(?:[^()\s]|\s(?!\s)|<<0>>)*(?=\s*\))/.source,[h]),lookbehind:!0,alias:"class-name",inside:S},"return-type":{pattern:n(/<<0>>(?=\s+(?:<<1>>\s*(?:=>|[({]|\.\s*this\s*\[)|this\s*\[))/.source,[k,y]),inside:S,alias:"class-name"},"constructor-invocation":{pattern:n(/(\bnew\s+)<<0>>(?=\s*[[({])/.source,[k]),lookbehind:!0,inside:S,alias:"class-name"},"generic-method":{pattern:n(/<<0>>\s*<<1>>(?=\s*\()/.source,[m,f]),inside:{function:n(/^<<0>>/.source,[m]),generic:{pattern:RegExp(f),alias:"class-name",inside:S}}},"type-list":{pattern:n(/\b((?:<<0>>\s+<<1>>|record\s+<<1>>\s*<<5>>|where\s+<<2>>)\s*:\s*)(?:<<3>>|<<4>>|<<1>>\s*<<5>>|<<6>>)(?:\s*,\s*(?:<<3>>|<<4>>|<<6>>))*(?=\s*(?:where|[{;]|=>|$))/.source,[c,g,m,k,u.source,h,/\bnew\s*\(\s*\)/.source]),lookbehind:!0,inside:{"record-arguments":{pattern:n(/(^(?!new\s*\()<<0>>\s*)<<1>>/.source,[g,h]),lookbehind:!0,greedy:!0,inside:e.languages.csharp},keyword:u,"class-name":{pattern:RegExp(k),greedy:!0,inside:S},punctuation:/[,()]/}},preprocessor:{pattern:/(^[\t ]*)#.*/m,lookbehind:!0,alias:"property",inside:{directive:{pattern:/(#)\b(?:define|elif|else|endif|endregion|error|if|line|nullable|pragma|region|undef|warning)\b/,lookbehind:!0,alias:"keyword"}}}});var A=O+"|"+E,C=t(/\/(?![*/])|\/\/[^\r\n]*[\r\n]|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>/.source,[A]),j=r(t(/[^"'/()]|<<0>>|\(<>*\)/.source,[C]),2),P=/\b(?:assembly|event|field|method|module|param|property|return|type)\b/.source,T=t(/<<0>>(?:\s*\(<<1>>*\))?/.source,[y,j]);e.languages.insertBefore("csharp","class-name",{attribute:{pattern:n(/((?:^|[^\s\w>)?])\s*\[\s*)(?:<<0>>\s*:\s*)?<<1>>(?:\s*,\s*<<1>>)*(?=\s*\])/.source,[P,T]),lookbehind:!0,greedy:!0,inside:{target:{pattern:n(/^<<0>>(?=\s*:)/.source,[P]),alias:"keyword"},"attribute-arguments":{pattern:n(/\(<<0>>*\)/.source,[j]),inside:e.languages.csharp},"class-name":{pattern:RegExp(y),inside:{punctuation:/\./}},punctuation:/[:,]/}}});var I=/:[^}\r\n]+/.source,R=r(t(/[^"'/()]|<<0>>|\(<>*\)/.source,[C]),2),N=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[R,I]),$=r(t(/[^"'/()]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|<<0>>|\(<>*\)/.source,[A]),2),L=t(/\{(?!\{)(?:(?![}:])<<0>>)*<<1>>?\}/.source,[$,I]);function D(t,r){return{interpolation:{pattern:n(/((?:^|[^{])(?:\{\{)*)<<0>>/.source,[t]),lookbehind:!0,inside:{"format-string":{pattern:n(/(^\{(?:(?![}:])<<0>>)*)<<1>>(?=\}$)/.source,[r,I]),lookbehind:!0,inside:{punctuation:/^:/}},punctuation:/^\{|\}$/,expression:{pattern:/[\s\S]+/,alias:"language-csharp",inside:e.languages.csharp}}},string:/[\s\S]+/}}e.languages.insertBefore("csharp","string",{"interpolation-string":[{pattern:n(/(^|[^\\])(?:\$@|@\$)"(?:""|\\[\s\S]|\{\{|<<0>>|[^\\{"])*"/.source,[N]),lookbehind:!0,greedy:!0,inside:D(N,R)},{pattern:n(/(^|[^@\\])\$"(?:\\.|\{\{|<<0>>|[^\\"{])*"/.source,[L]),lookbehind:!0,greedy:!0,inside:D(L,$)}],char:{pattern:RegExp(E),greedy:!0}}),e.languages.dotnet=e.languages.cs=e.languages.csharp}(Prism)},2630:function(){Prism.languages.csv={value:/[^\r\n,"]+|"(?:[^"]|"")*"(?!")/,punctuation:/,/}},6378:function(){Prism.languages.go=Prism.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|false|iota|nil|true)\b/,number:[/\b0(?:b[01_]+|o[0-7_]+)i?\b/i,/\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i,/(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i],operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/}),Prism.languages.insertBefore("go","string",{char:{pattern:/'(?:\\.|[^'\\\r\n]){0,10}'/,greedy:!0}}),delete Prism.languages.go["class-name"]},4784:function(){!function(e){function t(e){return RegExp("(^(?:"+e+"):[ \t]*(?![ \t]))[^]+","i")}e.languages.http={"request-line":{pattern:/^(?:CONNECT|DELETE|GET|HEAD|OPTIONS|PATCH|POST|PRI|PUT|SEARCH|TRACE)\s(?:https?:\/\/|\/)\S*\sHTTP\/[\d.]+/m,inside:{method:{pattern:/^[A-Z]+\b/,alias:"property"},"request-target":{pattern:/^(\s)(?:https?:\/\/|\/)\S*(?=\s)/,lookbehind:!0,alias:"url",inside:e.languages.uri},"http-version":{pattern:/^(\s)HTTP\/[\d.]+/,lookbehind:!0,alias:"property"}}},"response-status":{pattern:/^HTTP\/[\d.]+ \d+ .+/m,inside:{"http-version":{pattern:/^HTTP\/[\d.]+/,alias:"property"},"status-code":{pattern:/^(\s)\d+(?=\s)/,lookbehind:!0,alias:"number"},"reason-phrase":{pattern:/^(\s).+/,lookbehind:!0,alias:"string"}}},header:{pattern:/^[\w-]+:.+(?:(?:\r\n?|\n)[ \t].+)*/m,inside:{"header-value":[{pattern:t(/Content-Security-Policy/.source),lookbehind:!0,alias:["csp","languages-csp"],inside:e.languages.csp},{pattern:t(/Public-Key-Pins(?:-Report-Only)?/.source),lookbehind:!0,alias:["hpkp","languages-hpkp"],inside:e.languages.hpkp},{pattern:t(/Strict-Transport-Security/.source),lookbehind:!0,alias:["hsts","languages-hsts"],inside:e.languages.hsts},{pattern:t(/[^:]+/.source),lookbehind:!0}],"header-name":{pattern:/^[^:]+/,alias:"keyword"},punctuation:/^:/}}};var n,r=e.languages,i={"application/javascript":r.javascript,"application/json":r.json||r.javascript,"application/xml":r.xml,"text/xml":r.xml,"text/html":r.html,"text/css":r.css,"text/plain":r.plain},o={"application/json":!0,"application/xml":!0};function s(e){var t=e.replace(/^[a-z]+\//,"");return"(?:"+e+"|\\w+/(?:[\\w.-]+\\+)+"+t+"(?![+\\w.-]))"}for(var a in i)if(i[a]){n=n||{};var l=o[a]?s(a):a;n[a.replace(/\//g,"-")]={pattern:RegExp("("+/content-type:\s*/.source+l+/(?:(?:\r\n?|\n)[\w-].*)*(?:\r(?:\n|(?!\n))|\n)/.source+")"+/[^ \t\w-][\s\S]*/.source,"i"),lookbehind:!0,inside:i[a]}}n&&e.languages.insertBefore("http","header",n)}(Prism)},6976:function(){!function(e){var t=/\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\s*[(){}[\]<>=%~.:,;?+\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/,n=/(?:[a-z]\w*\s*\.\s*)*(?:[A-Z]\w*\s*\.\s*)*/.source,r={pattern:RegExp(/(^|[^\w.])/.source+n+/[A-Z](?:[\d_A-Z]*[a-z]\w*)?\b/.source),lookbehind:!0,inside:{namespace:{pattern:/^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/,inside:{punctuation:/\./}},punctuation:/\./}};e.languages.java=e.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"/,lookbehind:!0,greedy:!0},"class-name":[r,{pattern:RegExp(/(^|[^\w.])/.source+n+/[A-Z]\w*(?=\s+\w+\s*[;,=()]|\s*(?:\[[\s,]*\]\s*)?::\s*new\b)/.source),lookbehind:!0,inside:r.inside},{pattern:RegExp(/(\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\s+)/.source+n+/[A-Z]\w*\b/.source),lookbehind:!0,inside:r.inside}],keyword:t,function:[e.languages.clike.function,{pattern:/(::\s*)[a-z_]\w*/,lookbehind:!0}],number:/\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i,operator:{pattern:/(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m,lookbehind:!0},constant:/\b[A-Z][A-Z_\d]+\b/}),e.languages.insertBefore("java","string",{"triple-quoted-string":{pattern:/"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/,greedy:!0,alias:"string"},char:{pattern:/'(?:\\.|[^'\\\r\n]){1,6}'/,greedy:!0}}),e.languages.insertBefore("java","class-name",{annotation:{pattern:/(^|[^.])@\w+(?:\s*\.\s*\w+)*/,lookbehind:!0,alias:"punctuation"},generics:{pattern:/<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/,inside:{"class-name":r,keyword:t,punctuation:/[<>(),.:]/,operator:/[?&|]/}},import:[{pattern:RegExp(/(\bimport\s+)/.source+n+/(?:[A-Z]\w*|\*)(?=\s*;)/.source),lookbehind:!0,inside:{namespace:r.inside.namespace,punctuation:/\./,operator:/\*/,"class-name":/\w+/}},{pattern:RegExp(/(\bimport\s+static\s+)/.source+n+/(?:\w+|\*)(?=\s*;)/.source),lookbehind:!0,alias:"static",inside:{namespace:r.inside.namespace,static:/\b\w+$/,punctuation:/\./,operator:/\*/,"class-name":/\w+/}}],namespace:{pattern:RegExp(/(\b(?:exports|import(?:\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\s+)(?!)[a-z]\w*(?:\.[a-z]\w*)*\.?/.source.replace(//g,(function(){return t.source}))),lookbehind:!0,inside:{punctuation:/\./}}})}(Prism)},64:function(){Prism.languages.lua={comment:/^#!.+|--(?:\[(=*)\[[\s\S]*?\]\1\]|.*)/m,string:{pattern:/(["'])(?:(?!\1)[^\\\r\n]|\\z(?:\r\n|\s)|\\(?:\r\n|[^z]))*\1|\[(=*)\[[\s\S]*?\]\2\]/,greedy:!0},number:/\b0x[a-f\d]+(?:\.[a-f\d]*)?(?:p[+-]?\d+)?\b|\b\d+(?:\.\B|(?:\.\d*)?(?:e[+-]?\d+)?\b)|\B\.\d+(?:e[+-]?\d+)?\b/i,keyword:/\b(?:and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b/,function:/(?!\d)\w+(?=\s*(?:[({]))/,operator:[/[-+*%^&|#]|\/\/?|<[<=]?|>[>=]?|[=~]=?/,{pattern:/(^|[^.])\.\.(?!\.)/,lookbehind:!0}],punctuation:/[\[\](){},;]|\.+|:+/}},9700:function(){!function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,r,i,o){if(n.language===r){var s=n.tokenStack=[];n.code=n.code.replace(i,(function(e){if("function"==typeof o&&!o(e))return e;for(var i,a=s.length;-1!==n.code.indexOf(i=t(r,a));)++a;return s[a]=e,i})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,r){if(n.language===r&&n.tokenStack){n.grammar=e.languages[r];var i=0,o=Object.keys(n.tokenStack);!function s(a){for(var l=0;l=o.length);l++){var c=a[l];if("string"==typeof c||c.content&&"string"==typeof c.content){var u=o[i],p=n.tokenStack[u],d="string"==typeof c?c:c.content,f=t(r,u),h=d.indexOf(f);if(h>-1){++i;var m=d.substring(0,h),g=new e.Token(r,e.tokenize(p,n.grammar),"language-"+r,p),y=d.substring(h+f.length),b=[];m&&b.push.apply(b,s([m])),b.push(g),y&&b.push.apply(b,s([y])),"string"==typeof c?a.splice.apply(a,[l,1].concat(b)):c.content=b}}else c.content&&s(c.content)}return a}(n.tokens)}}}})}(Prism)},4312:function(){Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(e,t){var n={};n["language-"+t]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[t]},n.cdata=/^$/i;var r={"included-cdata":{pattern://i,inside:n}};r["language-"+t]={pattern:/[\s\S]+/,inside:Prism.languages[t]};var i={};i[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,(function(){return e})),"i"),lookbehind:!0,greedy:!0,inside:r},Prism.languages.insertBefore("markup","cdata",i)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(e,t){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:Prism.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml},596:function(){Prism.languages.objectivec=Prism.languages.extend("c",{string:{pattern:/@?"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},keyword:/\b(?:asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|in|inline|int|long|register|return|self|short|signed|sizeof|static|struct|super|switch|typedef|typeof|union|unsigned|void|volatile|while)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,operator:/-[->]?|\+\+?|!=?|<>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete Prism.languages.objectivec["class-name"],Prism.languages.objc=Prism.languages.objectivec},2821:function(){!function(e){var t=/(?:\((?:[^()\\]|\\[\s\S])*\)|\{(?:[^{}\\]|\\[\s\S])*\}|\[(?:[^[\]\\]|\\[\s\S])*\]|<(?:[^<>\\]|\\[\s\S])*>)/.source;e.languages.perl={comment:[{pattern:/(^\s*)=\w[\s\S]*?=cut.*/m,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\$])#.*/,lookbehind:!0,greedy:!0}],string:[{pattern:RegExp(/\b(?:q|qq|qw|qx)(?![a-zA-Z0-9])\s*/.source+"(?:"+[/([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/([a-zA-Z0-9])(?:(?!\2)[^\\]|\\[\s\S])*\2/.source,t].join("|")+")"),greedy:!0},{pattern:/("|`)(?:(?!\1)[^\\]|\\[\s\S])*\1/,greedy:!0},{pattern:/'(?:[^'\\\r\n]|\\.)*'/,greedy:!0}],regex:[{pattern:RegExp(/\b(?:m|qr)(?![a-zA-Z0-9])\s*/.source+"(?:"+[/([^a-zA-Z0-9\s{(\[<])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/([a-zA-Z0-9])(?:(?!\2)[^\\]|\\[\s\S])*\2/.source,t].join("|")+")"+/[msixpodualngc]*/.source),greedy:!0},{pattern:RegExp(/(^|[^-])\b(?:s|tr|y)(?![a-zA-Z0-9])\s*/.source+"(?:"+[/([^a-zA-Z0-9\s{(\[<])(?:(?!\2)[^\\]|\\[\s\S])*\2(?:(?!\2)[^\\]|\\[\s\S])*\2/.source,/([a-zA-Z0-9])(?:(?!\3)[^\\]|\\[\s\S])*\3(?:(?!\3)[^\\]|\\[\s\S])*\3/.source,t+/\s*/.source+t].join("|")+")"+/[msixpodualngcer]*/.source),lookbehind:!0,greedy:!0},{pattern:/\/(?:[^\/\\\r\n]|\\.)*\/[msixpodualngc]*(?=\s*(?:$|[\r\n,.;})&|\-+*~<>!?^]|(?:and|cmp|eq|ge|gt|le|lt|ne|not|or|x|xor)\b))/,greedy:!0}],variable:[/[&*$@%]\{\^[A-Z]+\}/,/[&*$@%]\^[A-Z_]/,/[&*$@%]#?(?=\{)/,/[&*$@%]#?(?:(?:::)*'?(?!\d)[\w$]+(?![\w$]))+(?:::)*/,/[&*$@%]\d+/,/(?!%=)[$@%][!"#$%&'()*+,\-.\/:;<=>?@[\\\]^_`{|}~]/],filehandle:{pattern:/<(?![<=])\S*?>|\b_\b/,alias:"symbol"},"v-string":{pattern:/v\d+(?:\.\d+)*|\d+(?:\.\d+){2,}/,alias:"string"},function:{pattern:/(\bsub[ \t]+)\w+/,lookbehind:!0},keyword:/\b(?:any|break|continue|default|delete|die|do|else|elsif|eval|for|foreach|given|goto|if|last|local|my|next|our|package|print|redo|require|return|say|state|sub|switch|undef|unless|until|use|when|while)\b/,number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)\b/,operator:/-[rwxoRWXOezsfdlpSbctugkTBMAC]\b|\+[+=]?|-[-=>]?|\*\*?=?|\/\/?=?|=[=~>]?|~[~=]?|\|\|?=?|&&?=?|<(?:=>?|<=?)?|>>?=?|![~=]?|[%^]=?|\.(?:=|\.\.?)?|[\\?]|\bx(?:=|\b)|\b(?:and|cmp|eq|ge|gt|le|lt|ne|not|or|xor)\b/,punctuation:/[{}[\];(),:]/}}(Prism)},3554:function(){!function(e){var t=/\/\*[\s\S]*?\*\/|\/\/.*|#(?!\[).*/,n=[{pattern:/\b(?:false|true)\b/i,alias:"boolean"},{pattern:/(::\s*)\b[a-z_]\w*\b(?!\s*\()/i,greedy:!0,lookbehind:!0},{pattern:/(\b(?:case|const)\s+)\b[a-z_]\w*(?=\s*[;=])/i,greedy:!0,lookbehind:!0},/\b(?:null)\b/i,/\b[A-Z_][A-Z0-9_]*\b(?!\s*\()/],r=/\b0b[01]+(?:_[01]+)*\b|\b0o[0-7]+(?:_[0-7]+)*\b|\b0x[\da-f]+(?:_[\da-f]+)*\b|(?:\b\d+(?:_\d+)*\.?(?:\d+(?:_\d+)*)?|\B\.\d+)(?:e[+-]?\d+)?/i,i=/|\?\?=?|\.{3}|\??->|[!=]=?=?|::|\*\*=?|--|\+\+|&&|\|\||<<|>>|[?~]|[/^|%*&<>.+-]=?/,o=/[{}\[\](),:;]/;e.languages.php={delimiter:{pattern:/\?>$|^<\?(?:php(?=\s)|=)?/i,alias:"important"},comment:t,variable:/\$+(?:\w+\b|(?=\{))/,package:{pattern:/(namespace\s+|use\s+(?:function\s+)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,lookbehind:!0,inside:{punctuation:/\\/}},"class-name-definition":{pattern:/(\b(?:class|enum|interface|trait)\s+)\b[a-z_]\w*(?!\\)\b/i,lookbehind:!0,alias:"class-name"},"function-definition":{pattern:/(\bfunction\s+)[a-z_]\w*(?=\s*\()/i,lookbehind:!0,alias:"function"},keyword:[{pattern:/(\(\s*)\b(?:array|bool|boolean|float|int|integer|object|string)\b(?=\s*\))/i,alias:"type-casting",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|object|self|static|string)\b(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b(?:array(?!\s*\()|bool|callable|(?:false|null)(?=\s*\|)|float|int|iterable|mixed|never|object|self|static|string|void)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/\b(?:array(?!\s*\()|bool|float|int|iterable|mixed|object|string|void)\b/i,alias:"type-declaration",greedy:!0},{pattern:/(\|\s*)(?:false|null)\b|\b(?:false|null)(?=\s*\|)/i,alias:"type-declaration",greedy:!0,lookbehind:!0},{pattern:/\b(?:parent|self|static)(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(\byield\s+)from\b/i,lookbehind:!0},/\bclass\b/i,{pattern:/((?:^|[^\s>:]|(?:^|[^-])>|(?:^|[^:]):)\s*)\b(?:abstract|and|array|as|break|callable|case|catch|clone|const|continue|declare|default|die|do|echo|else|elseif|empty|enddeclare|endfor|endforeach|endif|endswitch|endwhile|enum|eval|exit|extends|final|finally|fn|for|foreach|function|global|goto|if|implements|include|include_once|instanceof|insteadof|interface|isset|list|match|namespace|never|new|or|parent|print|private|protected|public|readonly|require|require_once|return|self|static|switch|throw|trait|try|unset|use|var|while|xor|yield|__halt_compiler)\b/i,lookbehind:!0}],"argument-name":{pattern:/([(,]\s*)\b[a-z_]\w*(?=\s*:(?!:))/i,lookbehind:!0},"class-name":[{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self|\s+static))\s+|\bcatch\s*\()\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/(\|\s*)\b[a-z_]\w*(?!\\)\b/i,greedy:!0,lookbehind:!0},{pattern:/\b[a-z_]\w*(?!\\)\b(?=\s*\|)/i,greedy:!0},{pattern:/(\|\s*)(?:\\?\b[a-z_]\w*)+\b/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(?:\\?\b[a-z_]\w*)+\b(?=\s*\|)/i,alias:"class-name-fully-qualified",greedy:!0,inside:{punctuation:/\\/}},{pattern:/(\b(?:extends|implements|instanceof|new(?!\s+self\b|\s+static\b))\s+|\bcatch\s*\()(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:"class-name-fully-qualified",greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*\$)/i,alias:"type-declaration",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-declaration"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/\b[a-z_]\w*(?=\s*::)/i,alias:"static-context",greedy:!0},{pattern:/(?:\\?\b[a-z_]\w*)+(?=\s*::)/i,alias:["class-name-fully-qualified","static-context"],greedy:!0,inside:{punctuation:/\\/}},{pattern:/([(,?]\s*)[a-z_]\w*(?=\s*\$)/i,alias:"type-hint",greedy:!0,lookbehind:!0},{pattern:/([(,?]\s*)(?:\\?\b[a-z_]\w*)+(?=\s*\$)/i,alias:["class-name-fully-qualified","type-hint"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}},{pattern:/(\)\s*:\s*(?:\?\s*)?)\b[a-z_]\w*(?!\\)\b/i,alias:"return-type",greedy:!0,lookbehind:!0},{pattern:/(\)\s*:\s*(?:\?\s*)?)(?:\\?\b[a-z_]\w*)+\b(?!\\)/i,alias:["class-name-fully-qualified","return-type"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:n,function:{pattern:/(^|[^\\\w])\\?[a-z_](?:[\w\\]*\w)?(?=\s*\()/i,lookbehind:!0,inside:{punctuation:/\\/}},property:{pattern:/(->\s*)\w+/,lookbehind:!0},number:r,operator:i,punctuation:o};var s={pattern:/\{\$(?:\{(?:\{[^{}]+\}|[^{}]+)\}|[^{}])+\}|(^|[^\\{])\$+(?:\w+(?:\[[^\r\n\[\]]+\]|->\w+)?)/,lookbehind:!0,inside:e.languages.php},a=[{pattern:/<<<'([^']+)'[\r\n](?:.*[\r\n])*?\1;/,alias:"nowdoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<'[^']+'|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<'?|[';]$/}}}},{pattern:/<<<(?:"([^"]+)"[\r\n](?:.*[\r\n])*?\1;|([a-z_]\w*)[\r\n](?:.*[\r\n])*?\2;)/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<<(?:"[^"]+"|[a-z_]\w*)|[a-z_]\w*;$/i,alias:"symbol",inside:{punctuation:/^<<<"?|[";]$/}},interpolation:s}},{pattern:/`(?:\\[\s\S]|[^\\`])*`/,alias:"backtick-quoted-string",greedy:!0},{pattern:/'(?:\\[\s\S]|[^\\'])*'/,alias:"single-quoted-string",greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,alias:"double-quoted-string",greedy:!0,inside:{interpolation:s}}];e.languages.insertBefore("php","variable",{string:a,attribute:{pattern:/#\[(?:[^"'\/#]|\/(?![*/])|\/\/.*$|#(?!\[).*$|\/\*(?:[^*]|\*(?!\/))*\*\/|"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*')+\](?=\s*[a-z$#])/im,greedy:!0,inside:{"attribute-content":{pattern:/^(#\[)[\s\S]+(?=\]$)/,lookbehind:!0,inside:{comment:t,string:a,"attribute-class-name":[{pattern:/([^:]|^)\b[a-z_]\w*(?!\\)\b/i,alias:"class-name",greedy:!0,lookbehind:!0},{pattern:/([^:]|^)(?:\\?\b[a-z_]\w*)+/i,alias:["class-name","class-name-fully-qualified"],greedy:!0,lookbehind:!0,inside:{punctuation:/\\/}}],constant:n,number:r,operator:i,punctuation:o}},delimiter:{pattern:/^#\[|\]$/,alias:"punctuation"}}}}),e.hooks.add("before-tokenize",(function(t){/<\?/.test(t.code)&&e.languages["markup-templating"].buildPlaceholders(t,"php",/<\?(?:[^"'/#]|\/(?![*/])|("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|(?:\/\/|#(?!\[))(?:[^?\n\r]|\?(?!>))*(?=$|\?>|[\r\n])|#\[|\/\*(?:[^*]|\*(?!\/))*(?:\*\/|$))*?(?:\?>|$)/g)})),e.hooks.add("after-tokenize",(function(t){e.languages["markup-templating"].tokenizePlaceholders(t,"php")}))}(Prism)},2342:function(){Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python},4113:function(){Prism.languages.q={string:/"(?:\\.|[^"\\\r\n])*"/,comment:[{pattern:/([\t )\]}])\/.*/,lookbehind:!0,greedy:!0},{pattern:/(^|\r?\n|\r)\/[\t ]*(?:(?:\r?\n|\r)(?:.*(?:\r?\n|\r(?!\n)))*?(?:\\(?=[\t ]*(?:\r?\n|\r))|$)|\S.*)/,lookbehind:!0,greedy:!0},{pattern:/^\\[\t ]*(?:\r?\n|\r)[\s\S]+/m,greedy:!0},{pattern:/^#!.+/m,greedy:!0}],symbol:/`(?::\S+|[\w.]*)/,datetime:{pattern:/0N[mdzuvt]|0W[dtz]|\d{4}\.\d\d(?:m|\.\d\d(?:T(?:\d\d(?::\d\d(?::\d\d(?:[.:]\d\d\d)?)?)?)?)?[dz]?)|\d\d:\d\d(?::\d\d(?:[.:]\d\d\d)?)?[uvt]?/,alias:"number"},number:/\b(?![01]:)(?:0N[hje]?|0W[hj]?|0[wn]|0x[\da-fA-F]+|\d+(?:\.\d*)?(?:e[+-]?\d+)?[hjfeb]?)/,keyword:/\\\w+\b|\b(?:abs|acos|aj0?|all|and|any|asc|asin|asof|atan|attr|avgs?|binr?|by|ceiling|cols|cor|cos|count|cov|cross|csv|cut|delete|deltas|desc|dev|differ|distinct|div|do|dsave|ej|enlist|eval|except|exec|exit|exp|fby|fills|first|fkeys|flip|floor|from|get|getenv|group|gtime|hclose|hcount|hdel|hopen|hsym|iasc|identity|idesc|if|ij|in|insert|inter|inv|keys?|last|like|list|ljf?|load|log|lower|lsq|ltime|ltrim|mavg|maxs?|mcount|md5|mdev|med|meta|mins?|mmax|mmin|mmu|mod|msum|neg|next|not|null|or|over|parse|peach|pj|plist|prds?|prev|prior|rand|rank|ratios|raze|read0|read1|reciprocal|reval|reverse|rload|rotate|rsave|rtrim|save|scan|scov|sdev|select|set|setenv|show|signum|sin|sqrt|ssr?|string|sublist|sums?|sv|svar|system|tables|tan|til|trim|txf|type|uj|ungroup|union|update|upper|upsert|value|var|views?|vs|wavg|where|while|within|wj1?|wsum|ww|xasc|xbar|xcols?|xdesc|xexp|xgroup|xkey|xlog|xprev|xrank)\b/,adverb:{pattern:/['\/\\]:?|\beach\b/,alias:"function"},verb:{pattern:/(?:\B\.\B|\b[01]:|<[=>]?|>=?|[:+\-*%,!?~=|$&#@^]):?|\b_\b:?/,alias:"operator"},punctuation:/[(){}\[\];.]/}},1648:function(){!function(e){e.languages.ruby=e.languages.extend("clike",{comment:{pattern:/#.*|^=begin\s[\s\S]*?^=end/m,greedy:!0},"class-name":{pattern:/(\b(?:class|module)\s+|\bcatch\s+\()[\w.\\]+|\b[A-Z_]\w*(?=\s*\.\s*new\b)/,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/,operator:/\.{2,3}|&\.|===||[!=]?~|(?:&&|\|\||<<|>>|\*\*|[+\-*/%<>!^&|=])=?|[?:]/,punctuation:/[(){}[\].,;]/}),e.languages.insertBefore("ruby","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}});var t={pattern:/((?:^|[^\\])(?:\\{2})*)#\{(?:[^{}]|\{[^{}]*\})*\}/,lookbehind:!0,inside:{content:{pattern:/^(#\{)[\s\S]+(?=\}$)/,lookbehind:!0,inside:e.languages.ruby},delimiter:{pattern:/^#\{|\}$/,alias:"punctuation"}}};delete e.languages.ruby.function;var n="(?:"+[/([^a-zA-Z0-9\s{(\[<=])(?:(?!\1)[^\\]|\\[\s\S])*\1/.source,/\((?:[^()\\]|\\[\s\S]|\((?:[^()\\]|\\[\s\S])*\))*\)/.source,/\{(?:[^{}\\]|\\[\s\S]|\{(?:[^{}\\]|\\[\s\S])*\})*\}/.source,/\[(?:[^\[\]\\]|\\[\s\S]|\[(?:[^\[\]\\]|\\[\s\S])*\])*\]/.source,/<(?:[^<>\\]|\\[\s\S]|<(?:[^<>\\]|\\[\s\S])*>)*>/.source].join("|")+")",r=/(?:"(?:\\.|[^"\\\r\n])*"|(?:\b[a-zA-Z_]\w*|[^\s\0-\x7F]+)[?!]?|\$.)/.source;e.languages.insertBefore("ruby","keyword",{"regex-literal":[{pattern:RegExp(/%r/.source+n+/[egimnosux]{0,6}/.source),greedy:!0,inside:{interpolation:t,regex:/[\s\S]+/}},{pattern:/(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/,lookbehind:!0,greedy:!0,inside:{interpolation:t,regex:/[\s\S]+/}}],variable:/[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,symbol:[{pattern:RegExp(/(^|[^:]):/.source+r),lookbehind:!0,greedy:!0},{pattern:RegExp(/([\r\n{(,][ \t]*)/.source+r+/(?=:(?!:))/.source),lookbehind:!0,greedy:!0}],"method-definition":{pattern:/(\bdef\s+)\w+(?:\s*\.\s*\w+)?/,lookbehind:!0,inside:{function:/\b\w+$/,keyword:/^self\b/,"class-name":/^\w+/,punctuation:/\./}}}),e.languages.insertBefore("ruby","string",{"string-literal":[{pattern:RegExp(/%[qQiIwWs]?/.source+n),greedy:!0,inside:{interpolation:t,string:/[\s\S]+/}},{pattern:/("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/,greedy:!0,inside:{interpolation:t,string:/[\s\S]+/}},{pattern:/<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?[a-z_]\w*|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?/}},interpolation:t,string:/[\s\S]+/}},{pattern:/<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?'[a-z_]\w*'|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?'|'$/}},string:/[\s\S]+/}}],"command-literal":[{pattern:RegExp(/%x/.source+n),greedy:!0,inside:{interpolation:t,command:{pattern:/[\s\S]+/,alias:"string"}}},{pattern:/`(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|[^\\`#\r\n])*`/,greedy:!0,inside:{interpolation:t,command:{pattern:/[\s\S]+/,alias:"string"}}}]}),delete e.languages.ruby.string,e.languages.insertBefore("ruby","number",{builtin:/\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\b/,constant:/\b[A-Z][A-Z0-9_]*(?:[?!]|\b)/}),e.languages.rb=e.languages.ruby}(Prism)},4252:function(){Prism.languages.scala=Prism.languages.extend("java",{"triple-quoted-string":{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string"},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},keyword:/<-|=>|\b(?:abstract|case|catch|class|def|derives|do|else|enum|extends|extension|final|finally|for|forSome|given|if|implicit|import|infix|inline|lazy|match|new|null|object|opaque|open|override|package|private|protected|return|sealed|self|super|this|throw|trait|transparent|try|type|using|val|var|while|with|yield)\b/,number:/\b0x(?:[\da-f]*\.)?[\da-f]+|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e\d+)?[dfl]?/i,builtin:/\b(?:Any|AnyRef|AnyVal|Boolean|Byte|Char|Double|Float|Int|Long|Nothing|Short|String|Unit)\b/,symbol:/'[^\d\s\\]\w*/}),Prism.languages.insertBefore("scala","triple-quoted-string",{"string-interpolation":{pattern:/\b[a-z]\w*(?:"""(?:[^$]|\$(?:[^{]|\{(?:[^{}]|\{[^{}]*\})*\}))*?"""|"(?:[^$"\r\n]|\$(?:[^{]|\{(?:[^{}]|\{[^{}]*\})*\}))*")/i,greedy:!0,inside:{id:{pattern:/^\w+/,greedy:!0,alias:"function"},escape:{pattern:/\\\$"|\$[$"]/,greedy:!0,alias:"symbol"},interpolation:{pattern:/\$(?:\w+|\{(?:[^{}]|\{[^{}]*\})*\})/,greedy:!0,inside:{punctuation:/^\$\{?|\}$/,expression:{pattern:/[\s\S]+/,inside:Prism.languages.scala}}},string:/[\s\S]+/}}}),delete Prism.languages.scala["class-name"],delete Prism.languages.scala.function,delete Prism.languages.scala.constant},6966:function(){Prism.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},identifier:{pattern:/(^|[^@\\])`(?:\\[\s\S]|[^`\\]|``)*`/,greedy:!0,lookbehind:!0,inside:{punctuation:/^`|`$/}},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:FALSE|NULL|TRUE)\b/i,number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/}},4793:function(){Prism.languages.swift={comment:{pattern:/(^|[^\\:])(?:\/\/.*|\/\*(?:[^/*]|\/(?!\*)|\*(?!\/)|\/\*(?:[^*]|\*(?!\/))*\*\/)*\*\/)/,lookbehind:!0,greedy:!0},"string-literal":[{pattern:RegExp(/(^|[^"#])/.source+"(?:"+/"(?:\\(?:\((?:[^()]|\([^()]*\))*\)|\r\n|[^(])|[^\\\r\n"])*"/.source+"|"+/"""(?:\\(?:\((?:[^()]|\([^()]*\))*\)|[^(])|[^\\"]|"(?!""))*"""/.source+")"+/(?!["#])/.source),lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/(\\\()(?:[^()]|\([^()]*\))*(?=\))/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/^\)|\\\($/,alias:"punctuation"},punctuation:/\\(?=[\r\n])/,string:/[\s\S]+/}},{pattern:RegExp(/(^|[^"#])(#+)/.source+"(?:"+/"(?:\\(?:#+\((?:[^()]|\([^()]*\))*\)|\r\n|[^#])|[^\\\r\n])*?"/.source+"|"+/"""(?:\\(?:#+\((?:[^()]|\([^()]*\))*\)|[^#])|[^\\])*?"""/.source+")\\2"),lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/(\\#+\()(?:[^()]|\([^()]*\))*(?=\))/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/^\)|\\#+\($/,alias:"punctuation"},string:/[\s\S]+/}}],directive:{pattern:RegExp(/#/.source+"(?:"+/(?:elseif|if)\b/.source+"(?:[ \t]*"+/(?:![ \t]*)?(?:\b\w+\b(?:[ \t]*\((?:[^()]|\([^()]*\))*\))?|\((?:[^()]|\([^()]*\))*\))(?:[ \t]*(?:&&|\|\|))?/.source+")+|"+/(?:else|endif)\b/.source+")"),alias:"property",inside:{"directive-name":/^#\w+/,boolean:/\b(?:false|true)\b/,number:/\b\d+(?:\.\d+)*\b/,operator:/!|&&|\|\||[<>]=?/,punctuation:/[(),]/}},literal:{pattern:/#(?:colorLiteral|column|dsohandle|file(?:ID|Literal|Path)?|function|imageLiteral|line)\b/,alias:"constant"},"other-directive":{pattern:/#\w+\b/,alias:"property"},attribute:{pattern:/@\w+/,alias:"atrule"},"function-definition":{pattern:/(\bfunc\s+)\w+/,lookbehind:!0,alias:"function"},label:{pattern:/\b(break|continue)\s+\w+|\b[a-zA-Z_]\w*(?=\s*:\s*(?:for|repeat|while)\b)/,lookbehind:!0,alias:"important"},keyword:/\b(?:Any|Protocol|Self|Type|actor|as|assignment|associatedtype|associativity|async|await|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic|else|enum|extension|fallthrough|fileprivate|final|for|func|get|guard|higherThan|if|import|in|indirect|infix|init|inout|internal|is|isolated|lazy|left|let|lowerThan|mutating|none|nonisolated|nonmutating|open|operator|optional|override|postfix|precedencegroup|prefix|private|protocol|public|repeat|required|rethrows|return|right|safe|self|set|some|static|struct|subscript|super|switch|throw|throws|try|typealias|unowned|unsafe|var|weak|where|while|willSet)\b/,boolean:/\b(?:false|true)\b/,nil:{pattern:/\bnil\b/,alias:"constant"},"short-argument":/\$\d+\b/,omit:{pattern:/\b_\b/,alias:"keyword"},number:/\b(?:[\d_]+(?:\.[\de_]+)?|0x[a-f0-9_]+(?:\.[a-f0-9p_]+)?|0b[01_]+|0o[0-7_]+)\b/i,"class-name":/\b[A-Z](?:[A-Z_\d]*[a-z]\w*)?\b/,function:/\b[a-z_]\w*(?=\s*\()/i,constant:/\b(?:[A-Z_]{2,}|k[A-Z][A-Za-z_]+)\b/,operator:/[-+*/%=!<>&|^~?]+|\.[.\-+*/%=!<>&|^~?]+/,punctuation:/[{}[\]();,.:\\]/},Prism.languages.swift["string-literal"].forEach((function(e){e.inside.interpolation.inside=Prism.languages.swift}))},83:function(){!function(e){var t=/[*&][^\s[\]{},]+/,n=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,r="(?:"+n.source+"(?:[ \t]+"+t.source+")?|"+t.source+"(?:[ \t]+"+n.source+")?)",i=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-])(?:[ \t]*(?:(?![#:])|:))*/.source.replace(//g,(function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source})),o=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function s(e,t){t=(t||"").replace(/m/g,"")+"m";var n=/([:\-,[{]\s*(?:\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<>/g,(function(){return r})).replace(/<>/g,(function(){return e}));return RegExp(n,t)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<>/g,(function(){return r}))),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\s*:\s)/.source.replace(/<>/g,(function(){return r})).replace(/<>/g,(function(){return"(?:"+i+"|"+o+")"}))),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:s(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:s(/false|true/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:s(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:s(o),lookbehind:!0,greedy:!0},number:{pattern:s(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:n,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(Prism)},8848:function(e,t,n){var r=function(e){var t=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,n=0,r={},i={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(t){return t instanceof o?new o(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/=p.reach);S+=k.value.length,k=k.next){var E=k.value;if(t.length>e.length)return;if(!(E instanceof o)){var O,_=1;if(b){if(!(O=s(w,S,e,y))||O.index>=e.length)break;var A=O.index,C=O.index+O[0].length,j=S;for(j+=k.value.length;A>=j;)j+=(k=k.next).value.length;if(S=j-=k.value.length,k.value instanceof o)continue;for(var P=k;P!==t.tail&&(jp.reach&&(p.reach=N);var $=k.prev;if(I&&($=c(t,$,I),S+=I.length),u(t,$,_),k=c(t,$,new o(d,g?i.tokenize(T,g):T,v,T)),R&&c(t,k,R),_>1){var L={cause:d+","+h,reach:N};a(e,t,n,k.prev,S,L),p&&L.reach>p.reach&&(p.reach=L.reach)}}}}}}function l(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function c(e,t,n){var r=t.next,i={value:n,prev:t,next:r};return t.next=i,r.prev=i,e.length++,i}function u(e,t,n){for(var r=t.next,i=0;i"+o.content+""},!e.document)return e.addEventListener?(i.disableWorkerMessageHandler||e.addEventListener("message",(function(t){var n=JSON.parse(t.data),r=n.language,o=n.code,s=n.immediateClose;e.postMessage(i.highlight(o,i.languages[r],r)),s&&e.close()}),!1),i):i;var p=i.util.currentScript();function d(){i.manual||i.highlightAll()}if(p&&(i.filename=p.src,p.hasAttribute("data-manual")&&(i.manual=!0)),!i.manual){var f=document.readyState;"loading"===f||"interactive"===f&&p&&p.defer?document.addEventListener("DOMContentLoaded",d):window.requestAnimationFrame?window.requestAnimationFrame(d):window.setTimeout(d,16)}return i}("undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{});e.exports&&(e.exports=r),void 0!==n.g&&(n.g.Prism=r),r.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},r.languages.markup.tag.inside["attr-value"].inside.entity=r.languages.markup.entity,r.languages.markup.doctype.inside["internal-subset"].inside=r.languages.markup,r.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(r.languages.markup.tag,"addInlined",{value:function(e,t){var n={};n["language-"+t]={pattern:/(^$)/i,lookbehind:!0,inside:r.languages[t]},n.cdata=/^$/i;var i={"included-cdata":{pattern://i,inside:n}};i["language-"+t]={pattern:/[\s\S]+/,inside:r.languages[t]};var o={};o[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,(function(){return e})),"i"),lookbehind:!0,greedy:!0,inside:i},r.languages.insertBefore("markup","cdata",o)}}),Object.defineProperty(r.languages.markup.tag,"addAttribute",{value:function(e,t){r.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:r.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),r.languages.html=r.languages.markup,r.languages.mathml=r.languages.markup,r.languages.svg=r.languages.markup,r.languages.xml=r.languages.extend("markup",{}),r.languages.ssml=r.languages.xml,r.languages.atom=r.languages.xml,r.languages.rss=r.languages.xml,function(e){var t=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:"+/[^;{\s"']|\s+(?!\s)/.source+"|"+t.source+")*?"+/(?:;|(?=\s*\{))/.source),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:t,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var n=e.languages.markup;n&&(n.tag.addInlined("style","css"),n.tag.addAttribute("style","css"))}(r),r.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},r.languages.javascript=r.languages.extend("clike",{"class-name":[r.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),r.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,r.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source+/\//.source+"(?:"+/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source+"|"+/(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source+")"+/(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:r.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:r.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:r.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:r.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:r.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),r.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:r.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),r.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),r.languages.markup&&(r.languages.markup.tag.addInlined("script","javascript"),r.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),r.languages.js=r.languages.javascript,function(){if(void 0!==r&&"undefined"!=typeof document){Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector);var e={js:"javascript",py:"python",rb:"ruby",ps1:"powershell",psm1:"powershell",sh:"bash",bat:"batch",h:"c",tex:"latex"},t="data-src-status",n="loading",i="loaded",o="pre[data-src]:not(["+t+'="'+i+'"]):not(['+t+'="'+n+'"])';r.hooks.add("before-highlightall",(function(e){e.selector+=", "+o})),r.hooks.add("before-sanity-check",(function(s){var a=s.element;if(a.matches(o)){s.code="",a.setAttribute(t,n);var l=a.appendChild(document.createElement("CODE"));l.textContent="Loading…";var c=a.getAttribute("data-src"),u=s.language;if("none"===u){var p=(/\.(\w+)$/.exec(c)||[,"none"])[1];u=e[p]||p}r.util.setLanguage(l,u),r.util.setLanguage(a,u);var d=r.plugins.autoloader;d&&d.loadLanguages(u),function(e,n,o){var s=new XMLHttpRequest;s.open("GET",e,!0),s.onreadystatechange=function(){4==s.readyState&&(s.status<400&&s.responseText?function(e){a.setAttribute(t,i);var n=function(e){var t=/^\s*(\d+)\s*(?:(,)\s*(?:(\d+)\s*)?)?$/.exec(e||"");if(t){var n=Number(t[1]),r=t[2],i=t[3];return r?i?[n,Number(i)]:[n,void 0]:[n,n]}}(a.getAttribute("data-range"));if(n){var o=e.split(/\r\n?|\n/g),s=n[0],c=null==n[1]?o.length:n[1];s<0&&(s+=o.length),s=Math.max(0,Math.min(s-1,o.length)),c<0&&(c+=o.length),c=Math.max(0,Math.min(c,o.length)),e=o.slice(s,c).join("\n"),a.hasAttribute("data-start")||a.setAttribute("data-start",String(s+1))}l.textContent=e,r.highlightElement(l)}(s.responseText):s.status>=400?o("✖ Error "+s.status+" while fetching file: "+s.statusText):o("✖ Error: File does not exist or is empty"))},s.send(null)}(c,0,(function(e){a.setAttribute(t,"failed"),l.textContent=e}))}})),r.plugins.fileHighlight={highlight:function(e){for(var t,n=(e||document).querySelectorAll(o),i=0;t=n[i++];)r.highlightElement(t)}};var s=!1;r.fileHighlight=function(){s||(console.warn("Prism.fileHighlight is deprecated. Use `Prism.plugins.fileHighlight.highlight` instead."),s=!0),r.plugins.fileHighlight.highlight.apply(this,arguments)}}}()},2694:function(e,t,n){"use strict";var r=n(6925);function i(){}function o(){}o.resetWarningCache=i,e.exports=function(){function e(e,t,n,i,o,s){if(s!==r){var a=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw a.name="Invariant Violation",a}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:i};return n.PropTypes=n,n}},5556:function(e,t,n){e.exports=n(2694)()},6925:function(e){"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},2551:function(e,t,n){"use strict";var r=n(6540),i=n(194);function o(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n