Showing preview only (1,086K chars total). Download the full file or copy to clipboard to get everything.
Repository: AliRn76/panther
Branch: master
Commit: 676a4623a957
Files: 178
Total size: 1.0 MB
Directory structure:
gitextract_j6xogok6/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ ├── pull_request.yaml
│ └── release.yaml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── benchmark.txt
├── docs/
│ ├── docs/
│ │ ├── admin_panel.md
│ │ ├── api.md
│ │ ├── authentications.md
│ │ ├── background_tasks.md
│ │ ├── cors.md
│ │ ├── database.md
│ │ ├── deployment.md
│ │ ├── events.md
│ │ ├── file_handling.md
│ │ ├── first_crud.md
│ │ ├── index.md
│ │ ├── log_queries.md
│ │ ├── middlewares.md
│ │ ├── model.md
│ │ ├── open_api.md
│ │ ├── query.md
│ │ ├── redis.md
│ │ ├── release_notes.md
│ │ ├── roadmap.md
│ │ ├── ruff.md
│ │ ├── serializer.md
│ │ ├── single_file.md
│ │ ├── templates.md
│ │ ├── timezone.md
│ │ ├── urls.md
│ │ ├── user_model.md
│ │ └── websocket.md
│ ├── mkdocs.yml
│ └── pre-finetuning.jsonl
├── examples/
│ ├── blog/
│ │ ├── Dockerfile
│ │ ├── Readme.md
│ │ ├── app/
│ │ │ ├── apis.py
│ │ │ ├── models.py
│ │ │ ├── serializers.py
│ │ │ └── urls.py
│ │ ├── core/
│ │ │ ├── configs.py
│ │ │ └── urls.py
│ │ ├── docker-compose.yaml
│ │ ├── main.py
│ │ ├── requirements.txt
│ │ └── user/
│ │ ├── apis.py
│ │ ├── serializers.py
│ │ └── urls.py
│ ├── broadcast_websocket.py
│ ├── file_upload_example.py
│ └── streaming_response.py
├── panther/
│ ├── __init__.py
│ ├── _load_configs.py
│ ├── _utils.py
│ ├── app.py
│ ├── authentications.py
│ ├── background_tasks.py
│ ├── base_request.py
│ ├── base_websocket.py
│ ├── caching.py
│ ├── cli/
│ │ ├── __init__.py
│ │ ├── create_project_command.py
│ │ ├── create_user_command.py
│ │ ├── main.py
│ │ ├── monitor_command.py
│ │ ├── run_command.py
│ │ ├── template.py
│ │ └── utils.py
│ ├── configs.py
│ ├── db/
│ │ ├── __init__.py
│ │ ├── connections.py
│ │ ├── cursor.py
│ │ ├── models.py
│ │ ├── queries/
│ │ │ ├── __init__.py
│ │ │ ├── base_queries.py
│ │ │ ├── mongodb_queries.py
│ │ │ ├── pantherdb_queries.py
│ │ │ └── queries.py
│ │ └── utils.py
│ ├── events.py
│ ├── exceptions.py
│ ├── file_handler.py
│ ├── generics.py
│ ├── logging.py
│ ├── main.py
│ ├── middlewares/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── cors.py
│ │ └── monitoring.py
│ ├── openapi/
│ │ ├── __init__.py
│ │ ├── schemas.py
│ │ ├── templates/
│ │ │ ├── rapidoc.html
│ │ │ ├── redoc.html
│ │ │ ├── scalar.html
│ │ │ ├── spotlight.html
│ │ │ └── swagger.html
│ │ ├── urls.py
│ │ ├── utils.py
│ │ └── views.py
│ ├── pagination.py
│ ├── panel/
│ │ ├── __init__.py
│ │ ├── authentications.py
│ │ ├── middlewares.py
│ │ ├── permissions.py
│ │ ├── templates/
│ │ │ ├── base.html
│ │ │ ├── create.html
│ │ │ ├── create.js
│ │ │ ├── detail.html
│ │ │ ├── home.html
│ │ │ ├── home.js
│ │ │ ├── login.html
│ │ │ ├── sidebar.html
│ │ │ ├── table.html
│ │ │ └── table.js
│ │ ├── urls.py
│ │ ├── utils.py
│ │ └── views.py
│ ├── permissions.py
│ ├── request.py
│ ├── response.py
│ ├── routings.py
│ ├── serializer.py
│ ├── status.py
│ ├── test.py
│ ├── throttling.py
│ ├── utils.py
│ └── websocket.py
├── profiler.py
├── pyproject.toml
├── requirements.txt
├── ruff.toml
├── setup.py
└── tests/
├── __init__.py
├── __main__.py
├── sample_project/
│ ├── app/
│ │ ├── apis.py
│ │ ├── models.py
│ │ └── urls.py
│ └── core/
│ ├── configs.py
│ ├── events.py
│ └── urls.py
├── test_api_kwargs.py
├── test_authentication.py
├── test_background_task_event_loop_isolation.py
├── test_background_tasks.py
├── test_caching.py
├── test_cli.py
├── test_config.py
├── test_cors.py
├── test_database.py
├── test_database_advance.py
├── test_events.py
├── test_file.py
├── test_find_one_or_insert_race.py
├── test_generics.py
├── test_generics_search_regex_escape.py
├── test_middlewares.py
├── test_multipart.py
├── test_openapi.py
├── test_panel.py
├── test_permission.py
├── test_request.py
├── test_request_isolation.py
├── test_response.py
├── test_response_output_model.py
├── test_routing.py
├── test_run.py
├── test_serializer.py
├── test_status.py
├── test_throttling.py
├── test_utils.py
└── test_websockets.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
buy_me_a_coffee: PantherPy
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
labels: bug
assignees: AliRn76
---
**Describe the bug**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Environment:**
- OS: [e.g. iOS]
- Python Version [e.g. 3.11.4]
- Panther Version [e.g. 2.4.0]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Describe the feature you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context about the feature request here.
================================================
FILE: .github/workflows/pull_request.yaml
================================================
name: Pull Request
on:
pull_request:
types: [ opened, synchronize ]
jobs:
tests-linux:
name: Test Linux
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install Requirements
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Start MongoDB
uses: supercharge/mongodb-github-action@1.10.0
with:
mongodb-version: '4.4'
- name: Remove Build Directory
run: rm -rf build/
- name: Run Tests
run: coverage run tests
- name: Show Coverage
run: coverage report
tests-windows:
name: Test Windows
runs-on: windows-latest
needs: [ tests-linux ]
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install Requirements
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Tests
run: python tests --not_mongodb --not_slow
tests-macos:
name: Test MacOS
runs-on: macos-latest
needs: [ tests-linux ]
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install Requirements
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Tests
run: python tests --not_mongodb --not_slow
================================================
FILE: .github/workflows/release.yaml
================================================
name: Release
on:
push:
tags:
- 'v*'
jobs:
initial-test:
name: Initial Test
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install Requirements
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Tests
run: python tests --not_mongodb --not_slow
tests-linux:
name: Test Linux Python${{ matrix.python-version }}
runs-on: ubuntu-latest
needs: [ initial-test ]
strategy:
fail-fast: false
matrix:
python-version: [ '3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install Requirements
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Tests
run: python tests --not_mongodb
tests-windows:
name: Test Windows Python${{ matrix.python-version }}
runs-on: windows-latest
needs: [ initial-test ]
strategy:
matrix:
python-version: [ '3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install Requirements
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Tests
run: python tests --not_mongodb
tests-macos:
name: Test MacOS Python${{ matrix.python-version }}
runs-on: macos-latest
needs: [ initial-test ]
strategy:
matrix:
python-version: [ '3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install Requirements
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Run Tests
run: python tests --not_mongodb
tests-mongodb:
name: Test MongoDB ${{ matrix.mongodb-version }}
runs-on: ubuntu-latest
needs: [ initial-test ]
strategy:
matrix:
mongodb-version: [ '4.2', '4.4', '5.0', '6.0' ]
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Requirements
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Install motor
run: pip install motor
- name: Start MongoDB
uses: supercharge/mongodb-github-action@1.10.0
with:
mongodb-version: ${{ matrix.mongodb-version }}
- name: Run Tests
run: python tests --mongodb
coverage:
name: Coverage
runs-on: ubuntu-latest
needs: [ tests-linux, tests-mongodb, tests-macos, tests-windows ]
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install Requirements
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Install motor
run: pip install motor
- name: Start MongoDB
uses: supercharge/mongodb-github-action@1.10.0
with:
mongodb-version: '4.4'
- name: Remove Build Directory
run: rm -rf build/
- name: Run Coverage Tests
run: coverage run -m tests
- name: Show Coverage
run: coverage report
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-n-publish:
name: Build and publish to PyPI
runs-on: ubuntu-latest
needs: [ coverage ]
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Build source and wheel distributions
run: |
python -m pip install --upgrade build twine
python -m build
twine check --strict dist/*
- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_TOKEN }}
================================================
FILE: .gitignore
================================================
.idea/
.venv*/
__pycache__/
panther.egg-info/
dist/
example/panther/
logs/
build/
*.pdb
.ruff_cache/
docs/site/*
*coverage*
.prettierrc
.vscode
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Panther
Thank you for your interest in contributing to Panther! This document provides guidelines and instructions for setting up the development environment and contributing to the project.
## Table of Contents
- [Prerequisites](#prerequisites)
- [Development Setup](#development-setup)
- [Running Tests](#running-tests)
- [Development Workflow](#development-workflow)
- [Code Style](#code-style)
- [Submitting Changes](#submitting-changes)
- [Getting Help](#getting-help)
## Prerequisites
Before you begin, ensure you have the following installed:
- **Python 3.10+** (Panther requires Python 3.10 or higher)
- **Git** for version control
- **Docker** for running test dependencies
- **pip** for package management
## Development Setup
Follow these steps to set up your development environment:
### 1. Clone the Repository
```bash
git clone https://github.com/AliRn76/panther.git
cd panther
```
### 2. Create a Virtual Environment
```bash
# Create virtual environment
python3.11 -m venv .venv
# Activate virtual environment
# On Linux/macOS:
source .venv/bin/activate
# On Windows:
# .venv\Scripts\activate
```
### 3. Install Dependencies
```bash
# Install development dependencies
pip install -r requirements.txt
# Install Panther in development mode
pip install -e .
```
## Running Tests
### Prerequisites
Before running tests, you need to start the required services using Docker:
```bash
# Start MongoDB (required for database tests)
docker run --rm -p 27017:27017 -d --name mongo mongo
# Start Redis (required for caching tests)
docker run --rm -p 6379:6379 -d --name redis redis
```
**Note:** Make sure Docker is running on your system before executing these commands.
### Running Tests
```bash
# Run all tests
python tests
# Run specific test file
python -m pytest tests/test_specific_file.py
# Run tests with specific markers
python tests --mongodb # Run only MongoDB tests
python tests --not_mongodb # Don't run MongoDB tests
python tests --slow # Run only slow tests
python tests --not_slow # Don't run slow tests
```
### Test Coverage
```bash
# Run tests with coverage
coverage run tests
# Generate coverage report
coverage report
# Generate HTML coverage report
coverage html
# Open htmlcov/index.html in your browser to view the report
```
## Development Workflow
### Making Changes
1. **Create a new branch** for your feature or fix:
```bash
git checkout -b feature/your-feature-name
# or
git checkout -b fix/your-fix-name
```
2. **Make your changes** in the source code located in the `panther/` directory.
3. **Test your changes**:
```bash
# Run tests to ensure nothing is broken
python tests
# Run linting
ruff check .
```
4. **Reinstall Panther** after making changes to see them immediately:
```bash
pip install -e .
```
### Code Style
Panther uses [Ruff](https://github.com/astral-sh/ruff) for code formatting and linting. The configuration is in `ruff.toml`.
```bash
# Format code
ruff format .
# Check for linting issues
ruff check .
# Fix auto-fixable issues
ruff check --fix .
```
### Testing Your Changes
You can test your changes in real-time by:
1. **Modifying the installed package** (for quick testing):
- Edit files in `.venv/lib/python3.11/site-packages/panther/`
- Changes are immediately available for testing
- **Important:** Remember to copy your changes back to the source files before committing
2. **Using development installation**:
- Make changes in the `panther/` directory
- Reinstall: `pip install -e .`
- Test your changes
## Submitting Changes
### Before Submitting
1. **Ensure all tests pass**:
```bash
python tests
```
2. **Check code style**:
```bash
ruff check .
ruff format .
```
3. **Update documentation** if your changes affect public APIs or behavior.
### Creating a Pull Request
1. **Push your branch** to your fork:
```bash
git push origin feature/your-feature-name
```
2. **Create a Pull Request** on GitHub with:
- Clear description of the changes
- Reference to any related issues
- Screenshots or examples if applicable
3. **Wait for review** and address any feedback from maintainers.
## Getting Help
- **Documentation**: [PantherPy.GitHub.io](https://pantherpy.github.io)
- **Issues**: [GitHub Issues](https://github.com/AliRn76/panther/issues)
- **Discussions**: [GitHub Discussions](https://github.com/AliRn76/panther/discussions)
## Additional Resources
- **Project Structure**: Explore the `panther/` directory to understand the codebase
- **Examples**: Check the `example/` directory for usage examples
- **Benchmarks**: Review `benchmark.txt` for performance data
---
Thank you for contributing to Panther! 🐾
================================================
FILE: LICENSE
================================================
BSD 3-Clause License
Copyright (c) 2022, Ali Rajab nezhad
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: README.md
================================================
[](https://pypi.org/project/panther/) [](https://pypi.org/project/panther/) [](https://codecov.io/github/AliRn76/panther) [](https://pepy.tech/project/panther) [](https://github.com/alirn76/panther/blob/main/LICENSE)
<div align="center">
<img src="https://github.com/AliRn76/panther/raw/master/docs/docs/images/logo-vertical.png" alt="Panther Logo" width="450">
# Panther
**A Fast & Friendly Web Framework for Building Async APIs with Python 3.10+**
[📚 Documentation](https://pantherpy.github.io)
</div>
---
## 🐾 Why Choose Panther?
Panther is designed to be **fast**, **simple**, and **powerful**. Here's what makes it special:
- **One of the fastest Python frameworks** available ([Benchmark](https://www.techempower.com/benchmarks/#section=data-r23&l=zijzen-pa7&c=4))
- **File-based database** ([PantherDB](https://pypi.org/project/pantherdb/)) - No external database setup required
- **Document-oriented ODM** - Supports MongoDB & PantherDB with familiar syntax
- **API caching system** - In-memory and Redis support
- **OpenAPI** - Auto-generated API documentation with multiple UI options
- **WebSocket support** - Real-time communication out of the box
- **Authentication & Permissions** - Built-in security features
- **Background tasks** - Handle long-running operations
- **Middleware & Throttling** - Extensible and configurable
---
## Quick Start
### Installation
```bash
pip install panther
```
- Create a `main.py` file with one of the examples below.
### Your First API
Here's a simple REST API endpoint that returns a "Hello World" message:
```python
from datetime import datetime, timedelta
from panther import status, Panther
from panther.app import GenericAPI
from panther.openapi.urls import url_routing as openapi_url_routing
from panther.response import Response
class HelloAPI(GenericAPI):
# Cache responses for 10 seconds
cache = timedelta(seconds=10)
def get(self):
current_time = datetime.now().isoformat()
return Response(
data={'message': f'Hello from Panther! 🐾 | {current_time}'},
status_code=status.HTTP_200_OK
)
# URL routing configuration
url_routing = {
'/': HelloAPI,
'docs/': openapi_url_routing, # Auto-generated API docs
}
# Create your Panther app
app = Panther(__name__, configs=__name__, urls=url_routing)
```
### WebSocket Echo Server
Here's a simple WebSocket echo server that sends back any message it receives:
```python
from panther import Panther
from panther.app import GenericAPI
from panther.response import HTMLResponse
from panther.websocket import GenericWebsocket
class EchoWebsocket(GenericWebsocket):
async def connect(self, **kwargs):
await self.accept()
await self.send("Connected to Panther WebSocket!")
async def receive(self, data: str | bytes):
# Echo back the received message
await self.send(f"Echo: {data}")
class WebSocketPage(GenericAPI):
def get(self):
template = """
<h2>🐾 Panther WebSocket Echo Server</h2>
<input id="msg"><button onclick="s.send(msg.value)">Send</button>
<ul id="log"></ul>
<script>
const s = new WebSocket('ws://127.0.0.1:8000/ws');
s.onmessage = e => log.innerHTML += `<li><- ${msg.value}</li><li>-> ${e.data}</li>`;
</script>
"""
return HTMLResponse(template)
url_routing = {
'': WebSocketPage,
'ws': EchoWebsocket,
}
app = Panther(__name__, configs=__name__, urls=url_routing)
```
### Run Your Application
1. **Start the development server**
```shell
$ panther run main:app
```
2. **Test your application**
- For the _API_ example: Visit [http://127.0.0.1:8000/](http://127.0.0.1:8000/) to see the "Hello World" response
- For the _WebSocket_ example: Visit [http://127.0.0.1:8000/](http://127.0.0.1:8000/) and send a message.
---
## 🙏 Acknowledgments
<div align="center">
<p>Supported by</p>
<a href="https://drive.google.com/file/d/17xe1hicIiRF7SQ-clg9SETdc19SktCbV/view?usp=sharing">
<img alt="JetBrains" src="https://github.com/AliRn76/panther/raw/master/docs/docs/images/jb_beam_50x50.png">
</a>
</div>
---
<div align="center">
<p>⭐️ If you find Panther useful, please give it a star!</p>
</div>
================================================
FILE: SECURITY.md
================================================
# Security Policy
## Supported Versions
Use this section to tell people about which versions of your project are
currently being supported with security updates.
| Version | Supported |
| ------- | ------------------ |
| > 1.7.x | :white_check_mark: |
| < 1.7 | :x: |
## Reporting a Vulnerability
It is an open-source project and we would be happy if you report an issue if you found any vulnerabilities 😃🖐️
================================================
FILE: benchmark.txt
================================================
"""
Checkout profiler.py for more information
"""
Clock type: CPU
Ordered by: totaltime, desc
name ncall tsub ttot tavg
/home/ali/dev/panther/panther/main.py:78 Panther.__call__ 1001 0.012665 0.811156 0.000810
/home/ali/dev/panther/panther/main.py:161 Panther.handle_http 1000 0.035138 0.798476 0.000798
/home/ali/dev/panther/panther/response.py:167 Response.send 1000 0.009427 0.473242 0.000473
/home/ali/dev/panther/panther/response.py:161 Response.send_headers 1000 0.012985 0.287794 0.000288
/home/ali/dev/panther/panther/middlewares/base.py:15 TestMiddleware.__call__ 1000 0.005173 0.239159 0.000239
/home/ali/dev/panther/panther/main.py:142 handle_http_endpoint 1000 0.021648 0.233986 0.000234
/home/ali/dev/panther/panther/response.py:164 Response.send_body 1000 0.009923 0.176022 0.000176
/home/ali/dev/panther/panther/app.py:120 wrapper 1000 0.008120 0.133374 0.000133
/home/ali/dev/panther/panther/app.py:136 API.handle_endpoint 1000 0.042525 0.125255 0.000125
/home/ali/dev/panther/panther/response.py:124 Response.bytes_headers 1000 0.024477 0.062525 0.000063
/home/ali/dev/panther/panther/response.py:67 Response.__init__ 1000 0.019294 0.059479 0.000059
/home/ali/dev/panther/panther/routings.py:133 find_endpoint 1000 0.026393 0.043979 0.000044
/home/ali/dev/panther/panther/request.py:40 Request.read_body 1000 0.011115 0.036803 0.000037
/home/ali/dev/panther/panther/response.py:135 prepare_data 2000/1000 0.023992 0.031797 0.000016
/home/ali/dev/panther/panther/base_request.py:129 Request.collect_path_variables 1000 0.019701 0.031360 0.000031
/home/ali/dev/panther/panther/response.py:116 Response.headers 1000 0.009755 0.025101 0.000025
/home/ali/dev/panther/panther/response.py:107 Response.body 2000 0.011792 0.024099 0.000012
/home/ali/dev/panther/panther/base_request.py:139 Request.clean_parameters 1000 0.007809 0.011867 0.000012
/home/ali/dev/panther/panther/request.py:14 Request.__init__ 1000 0.008140 0.010023 0.000010
/home/ali/dev/panther/panther/response.py:154 check_status_code 1000 0.003490 0.004959 0.000005
/home/ali/dev/panther/panther/request.py:19 Request.method 3000 0.004952 0.004952 0.000002
/home/ali/dev/panther/panther/middlewares/base.py:12 TestMiddleware.__init__ 1000 0.004111 0.004111 0.000004
/home/ali/dev/panther/panther/base_request.py:109 Request.path 2000 0.003424 0.003424 0.000002
/home/ali/dev/panther/panther/base_request.py:88 Request.__init__ 1000 0.001883 0.001883 0.000002
/home/ali/dev/panther/panther/app.py:189 API.handle_authentication 1000 0.001752 0.001752 0.000002
/home/ali/dev/panther/panther/response.py:131 Response.headers 1000 0.001583 0.001583 0.000002
/home/ali/dev/panther/panther/events.py:27 run_startups 1 0.000001 0.000001 0.000001
================================================
FILE: docs/docs/admin_panel.md
================================================
> **⚠️ WARNING: The Panther Admin Panel is currently in development. Do NOT use it in production environments!**
>
> **Contributions, feedback, and ideas are welcome!**
# Panther Admin Panel
Panther provides a built-in admin panel that allows you to easily manage your database models through a web interface.
## Enabling the Admin Panel
To enable the admin panel in your project, follow these steps:
### 1. Add the Admin Panel URLs
First, ensure your main URL configuration includes the admin panel routes. e.g. open your `core/urls.py` and add the following:
```python title="core/urls.py"
from panther.panel.urls import url_routing as panel_url_routing
url_routing = {
'panel/': panel_url_routing,
# ... other routes ...
}
```
> **Note:** If you are using a different file for your URL routing, adjust the import and assignment accordingly.
### 2. Update Your Configs (if needed)
If your project uses a custom configuration file for URLs, make sure it points to your updated URL routing. For example, in `core/configs.py`:
```python title="core/configs.py"
URLs = 'core.urls.url_routing'
```
### 3. Run Your Application
Start your Panther application as usual:
```bash
panther run main:app
```
### 4. Access the Admin Panel
Open your browser and navigate to:
[http://127.0.0.1:8000/panel/](http://127.0.0.1:8000/panel/)
You should see the Panther Admin Panel interface, where you can manage your database models.
### 5. Create an Admin User
To access the admin panel, you need at least one user account. You can create a user using the following command:
```bash
panther createuser main.py
```
Replace `main.py` with the path to your main application file if it is different. This command will create a new user based on your `USER_MODEL` (by default, `panther.db.models.BaseUser`).
Once the user is created, you can log in to the admin panel using the credentials you set during user creation.
================================================
FILE: docs/docs/api.md
================================================
# Panther API Guide
This guide assumes you have successfully set up your project and created your first CRUD following the [First CRUD](first_crud.md) guide.
In this guide, we discuss the features and structure of Panther's API system, including authentication, permissions, caching, throttling, middlewares, and more.
---
## API Request Flow
The typical flow of an API request in Panther is as follows:
```
Middlewares
├── Method
├── Authentication
├── Permissions
├── Throttling
├── Validate Input
├── Get Response From Cache
├── Call Endpoint
├── Set Response To Cache
Middlewares
```
---
## Input Model
You can validate incoming data using the `input_model` parameter. Pass a serializer to it, and Panther will send `request.data` to this serializer, placing the validated data in `request.validated_data`. As a result, `request.validated_data` will be an instance of your serializer.
> Note: `request.data` is validated only for 'POST', 'PUT', and 'PATCH' methods.
??? question "How do serializers work in Panther?"
Refer to [Serializer](serializer.md) to learn more about serializers.
```python title="app/serializers.py" linenums="1"
from panther.serializer import ModelSerializer
class UserInputSerializer(ModelSerializer):
...
```
=== "Function-Base API"
```python title="app/apis.py" linenums="1"
from panther import status
from panther.app import API
from panther.request import Request
from panther.response import Response
from app.serializers import UserInputSerializer
@API(input_model=UserInputSerializer)
async def user_api(request: Request):
request.validated_data # This is now available
...
```
=== "Class-Base API"
```python title="app/apis.py" linenums="1"
from panther import status
from panther.app import GenericAPI
from panther.response import Response
from app.serializers import UserInputSerializer
class UserAPI(GenericAPI):
input_model = UserInputSerializer
async def get(self, request: Request):
request.validated_data # This is now available
...
```
---
## Output Model
Use the `output_model` parameter to automatically serialize your API response data using a specified serializer. This ensures that the response structure is consistent and validated.
**Example Serializer:**
```python title="app/serializers.py" linenums="1"
from panther.serializer import ModelSerializer
class UserSerializer(ModelSerializer):
...
```
=== "Function-Base API"
```python title="app/apis.py" linenums="1"
from panther.app import API
from app.serializers import UserSerializer
@API(output_model=UserSerializer)
async def user_api():
...
```
=== "Class-Base API"
```python title="app/apis.py" linenums="1"
from panther.app import GenericAPI
from app.serializers import UserSerializer
class UserAPI(GenericAPI):
output_model = UserSerializer
...
```
> **Tip:** Use `output_model` to ensure your API always returns data in the expected format. For OpenAPI documentation, see the `output_schema` section.
---
## Authentication
To ensure that each request contains a valid authentication header, use the `auth` parameter.
- The `auth` parameter can be an any async function or a class with an async `__call__` method.
- If you set `auth`, Panther will use your specified authentication class or function.
- If you do not set `auth`, Panther will use the default `AUTHENTICATION` from your config **only if the request contains an authorization header**.
- If there is no authorization header in the request, authentication is bypassed, `request.user` will be `None` and you must rely on permissions to check the user and their authorization.
??? question "How do authentications work in Panther?"
Refer to [Authentications](authentications.md) to learn more about authentications.
=== "Function-Base API"
```python title="app/apis.py" linenums="1"
from panther import status
from panther.app import API
from panther.request import Request
from panther.response import Response
from app.authentications import MyAuthenticationClass
@API(auth=MyAuthenticationClass) # You can also use a function
async def user_api(request: Request):
user = request.user
return Response(data=user, status_code=status.HTTP_200_OK)
```
=== "Class-Base API"
```python title="app/apis.py" linenums="1"
from panther import status
from panther.app import GenericAPI
from panther.response import Response
from app.authentications import MyAuthenticationClass
class UserAPI(GenericAPI):
auth = MyAuthenticationClass # You can also use a function
async def get(self, request: Request):
user = request.user
return Response(data=user, status_code=status.HTTP_200_OK)
```
---
## Method
You can specify which HTTP methods are allowed for an endpoint by setting `methods` in function-based APIs. Only the following methods are supported: `['GET', 'POST', 'PUT', 'PATCH', 'DELETE']`.
> If a method is not allowed, a 405 status code will be returned.
=== "Function-Base API"
```python title="app/apis.py" linenums="1"
from panther.app import API
from panther.request import Request
@API(methods=['GET', 'POST'])
async def user_api(request: Request):
match request.method:
case 'GET':
...
case 'POST':
...
```
=== "Class-Base API"
```python title="app/apis.py" linenums="1"
from panther.app import GenericAPI
class UserAPI(GenericAPI): # (1)!
async def get(self):
...
async def post(self):
...
```
1. Now this class only accepts `GET` and `POST` requests.
---
## Permissions
You can implement your authorization logic using permission classes or functions. Any async function or class with an async `__call__` method can be used as a permission. Panther will call each permission (asynchronously).
Pass a list of permission callables to your API using the `permissions` parameter. If you pass a single permission, it will be automatically wrapped in a list.
> Each permission must be async (either an async function or a class with an async `__call__`).
**Example Permission Function:**
```python title="app/permissions.py" linenums="1"
from panther.request import Request
async def custom_permission(request: Request) -> bool:
return True
```
**Example Permission Class:**
```python title="app/permissions.py" linenums="1"
from panther.permissions import BasePermission
from panther.request import Request
class CustomPermission(BasePermission):
async def __call__(self, request: Request) -> bool:
return True
```
=== "Function-Base API"
```python title="app/apis.py" linenums="1"
from panther.app import API
from app.permissions import custom_permission, CustomPermission
@API(permissions=[custom_permission, CustomPermission])
async def user_api():
...
```
Or, if you have only one permission:
```python title="app/apis.py" linenums="1"
from panther.app import API
from app.permissions import custom_permission
@API(permissions=custom_permission)
async def user_api():
...
```
Panther will treat it as a list internally.
=== "Class-Base API"
```python title="app/apis.py" linenums="1"
from panther.app import GenericAPI
from app.permissions import CustomPermission
class UserAPI(GenericAPI):
permissions = [CustomPermission]
...
```
Or, if you have only one permission:
```python title="app/apis.py" linenums="1"
from panther.app import GenericAPI
from app.permissions import custom_permission
class UserAPI(GenericAPI):
permissions = custom_permission
...
```
Panther will treat it as a list internally.
---
## Cache
Responses can be cached for a specific amount of time per request or IP. Caching is only applied to `GET` requests. The response's headers, data, and status code will be cached.
The cache is stored in Redis (if connected) or in memory. The cache key is based on the user ID or IP, request path, query parameters, and validated data:
```
'user_id or ip - request.path - hash of query param - request.validated_data'
```
The value of `cache` should be an instance of `datetime.timedelta()`.
=== "Function-Base API"
```python title="app/apis.py" linenums="1"
from datetime import timedelta
from panther.app import API
@API(cache=timedelta(minutes=10))
async def user_api():
...
```
=== "Class-Base API"
```python title="app/apis.py" linenums="1"
from datetime import timedelta
from panther.app import GenericAPI
class UserAPI(GenericAPI):
cache = timedelta(minutes=10)
...
```
---
## Throttling
You can throttle requests using the `Throttle` class, either globally via the `THROTTLING` config or per API. The `Throttle` class has two fields: `rate` and `duration`.
If a user exceeds the allowed number of requests (`rate`) within the specified `duration`, they will receive a `429 Too Many Requests` response and be banned for the duration.
> When you set `throttling` on your API, it takes precedence over the default `THROTTLING`, and the default `THROTTLING` will not be executed.
### Setting Default Throttling
```python
from datetime import timedelta
from panther.throttling import Throttle
# Users can only make 5 requests every minute
THROTTLING = Throttle(rate=5, duration=timedelta(minutes=1))
```
### Throttling Per API
=== "Function-Base API"
```python title="app/apis.py" linenums="1"
from datetime import timedelta
from panther.app import API
from panther.throttling import Throttle
@API(throttling=Throttle(rate=5, duration=timedelta(minutes=1)))
async def user_api():
...
```
=== "Class-Base API"
```python title="app/apis.py" linenums="1"
from datetime import timedelta
from panther.app import GenericAPI
from panther.throttling import Throttle
class UserAPI(GenericAPI):
throttling = Throttle(rate=5, duration=timedelta(minutes=1))
...
```
### Customization
Throttling works with `request.user.id` or `request.client.ip`. You can customize its behavior by overriding `build_cache_key()`:
```python title="app/throttlings.py" linenums="1"
from panther.request import Request
from panther.throttling import Throttle
class CustomThrottle(Throttle):
def build_cache_key(self, request: Request) -> str:
...
```
---
## Middlewares
You can pass custom middlewares to specific APIs.
**Example Middleware:**
```python title="app/middlewares.py" linenums="1"
from panther.middlewares.base import HTTPMiddleware
from panther.request import Request
from panther.response import Response
class CustomMiddleware(HTTPMiddleware):
async def __call__(self, request: Request) -> Response:
print('Hi')
response = await self.dispatch(request=request)
print('Bye')
return response
```
=== "Function-Base API"
```python title="app/apis.py" linenums="1"
from panther.app import API
from app.middlewares import CustomMiddleware
@API(middlewares=[CustomMiddleware])
async def user_api():
...
```
=== "Class-Base API"
```python title="app/apis.py" linenums="1"
from panther.app import GenericAPI
from app.middlewares import CustomMiddleware
class UserAPI(GenericAPI):
middlewares = [CustomMiddleware]
...
```
??? question "How do middlewares work in Panther?"
Refer to [Middlewares](middlewares.md) to learn more about middlewares.
---
## Output Schema
The `output_schema` attribute is used when generating OpenAPI (Swagger) documentation.
It should be an instance of `panther.openapi.OutputSchema`, which specifies the desired response data structure and status code.
**Example Serializer:**
```python title="app/serializers.py" linenums="1"
from panther.serializer import ModelSerializer
class UserSerializer(ModelSerializer):
...
```
=== "Function-Base API"
```python title="app/apis.py" linenums="1"
from panther import status
from panther.app import API
from panther.openapi import OutputSchema
from app.serializers import UserSerializer
@API(output_schema=OutputSchema(status_code=status.HTTP_201_CREATED, model=UserSerializer))
async def user_api():
...
```
=== "Class-Base API"
```python title="app/apis.py" linenums="1"
from panther.app import GenericAPI
from panther.openapi import OutputSchema
from app.serializers import UserSerializer
class UserAPI(GenericAPI):
output_schema = OutputSchema(status_code=status.HTTP_201_CREATED, model=UserSerializer)
...
```
??? question "How does OpenAPI work in Panther?"
Refer to [OpenAPI](open_api.md) to learn more about OpenAPI.
---
## File Handling
Panther provides built-in support for file uploads through the `File` and `Image` classes.
!!! tip "Comprehensive File Handling Guide"
For detailed information about file handling, including advanced features, best practices, and troubleshooting, see the dedicated [File Handling](file_handling.md) documentation.
```python title="app/apis.py" linenums="1"
from panther.app import API
from panther.db import Model
from panther.file_handler import File
from panther.request import Request
from panther.response import Response
from panther.serializer import ModelSerializer
class FileUpload(Model):
file: File
description: str
class FileUploadSerializer(ModelSerializer):
class Config:
model = FileUpload
fields = '*'
@API(input_model=FileUploadSerializer)
async def upload_file(request: Request):
file_data = request.validated_data
file = file_data.file
# Save file to disk
saved_path = file.save("uploads/")
return Response(data={
"message": "File uploaded successfully",
"saved_path": saved_path
})
```
================================================
FILE: docs/docs/authentications.md
================================================
# Authentication in Panther
Authentication in Panther ensures that only authorized users can access your APIs and WebSocket connections. You can configure which authentication class to use in your `configs`.
---
## How Authentication Works
- The `auth` parameter for an API or WebSocket should be an async function or a class with an async `__call__` method. Panther will use this callable to verify the user.
- If you do not set `auth`, Panther will use the default `AUTHENTICATION` from your config **only if the request contains an authorization header**. If there is no authorization header, authentication is bypassed and `request.user` will be `None`.
- If authentication fails, Panther raises `HTTP_401_UNAUTHORIZED`.
- The authenticated user is available as:
- `request.user` in API views
- `self.user` in WebSocket connections
> **Note:** When authentication is bypassed (no authorization header), `request.user` will be `None` and you must rely on permissions to check the user and their authorization.
---
## Built-in Authentication Classes
Panther provides three built-in authentication classes, all based on JWT (JSON Web Token):
### 1. JWT Authentication
- Retrieves the JWT token from the `Authorization` header in the request.
- Expects the header format: `Authorization: Bearer <token>`
- Decodes the token, validates it, and fetches the corresponding user.
- By default, uses `panther.db.models.BaseUser` as the user model unless you set `USER_MODEL` in your configs.
- Handles token revocation if Redis is connected (for logout and refresh scenarios).
#### Example usage
```python
AUTHENTICATION = 'panther.authentications.JWTAuthentication'
```
#### JWT Configuration
You can customize JWT behavior by setting `JWT_CONFIG` in your configs. Example:
```python title="core/configs.py" linenums="1"
from datetime import timedelta
from panther.utils import load_env
from pathlib import Path
BASE_DIR = Path(__name__).resolve().parent
env = load_env(BASE_DIR / '.env')
SECRET_KEY = env['SECRET_KEY']
JWT_CONFIG = {
'key': SECRET_KEY, # Secret key for signing tokens (default: `SECRET_KEY`)
'algorithm': 'HS256', # Algorithm used for JWT (default: `'HS256'`)
'life_time': timedelta(days=2), # Access token lifetime (default: `timedelta(days=1)`)
'refresh_life_time': timedelta(days=10), # Refresh token lifetime (default: `2 * life_time`)
}
```
---
### 2. QueryParam JWT Authentication
- Works like `JWTAuthentication`, but expects the token in the query parameters instead of headers.
- Useful for WebSocket authentication or scenarios where headers are not available.
- Pass the token as a query parameter:
- Example: `https://example.com?authorization=<jwt_token>`
#### Example usage
```python
AUTHENTICATION = 'panther.authentications.QueryParamJWTAuthentication'
```
---
### 3. Cookie JWT Authentication
- Works like `JWTAuthentication`, but expects the token in cookies.
- Looks for `access_token` in cookies for authentication.
- Optionally, can use `refresh_token` in cookies for token refresh.
- Pass the token in cookies:
- Example: `Cookies: access_token=<jwt_token>`
#### Example usage
```python
AUTHENTICATION = 'panther.authentications.CookieJWTAuthentication'
```
---
## WebSocket Authentication
For WebSocket connections, it is recommended to use `QueryParamJWTAuthentication` since headers are not always available. To enable this, set the following in your configs:
```python
WS_AUTHENTICATION = 'panther.authentications.QueryParamJWTAuthentication'
```
---
## Custom Authentication
You can implement your own authentication logic by either:
- Inheriting from `panther.authentications.BaseAuthentication` and implementing an async `__call__` method (recommended for consistency), or
- Providing any async function or class with an async `__call__` method.
### Steps to create a custom authentication class:
1. **Inherit from `BaseAuthentication` and implement async `__call__`**
```python
from panther.request import Request
from panther.exceptions import AuthenticationAPIError
from panther.authentications import BaseAuthentication
class CustomAuthentication(BaseAuthentication):
async def __call__(self, request: Request):
# Your authentication logic here
# Return an instance of USER_MODEL (default: BaseUser)
# Or raise AuthenticationAPIError on failure
...
```
Or as a function or plain class:
```python
async def custom_authentication(request: Request):
# Your authentication logic here
# Return an instance of USER_MODEL (default: BaseUser)
# Or raise AuthenticationAPIError on failure
...
```
2. **Configure your custom authentication class or function in your configs (if you want it as default):**
```python
AUTHENTICATION = 'project_name.core.authentications.CustomAuthentication'
```
3. **Or set it per API using the `auth` parameter:**
```python
@API(auth=CustomAuthentication)
async def my_api(request: Request):
...
```
---
## Error Handling
- If authentication fails, raise `panther.exceptions.AuthenticationAPIError` with an appropriate message.
- Panther will automatically handle and return a 401 Unauthorized response.
---
## Summary
- Choose and configure the appropriate authentication class or function for your use case.
- Use JWT configuration options to control token behavior.
- For WebSocket, prefer query parameter-based authentication.
- Implement custom authentication as an async function or a class with an async `__call__` method.
- If authentication is bypassed, `request.user` will be `None` and you must use permissions to check the user and their authorization.
================================================
FILE: docs/docs/background_tasks.md
================================================
# Background Tasks in Panther
Panther can run background tasks in a separate thread at startup if you set `BACKGROUND_TASKS` to `True` in your configuration.
## Quick Start
1. **Enable background tasks**
In your `configs`, add:
```python
BACKGROUND_TASKS = True
```
2. **Create and submit a task**
```python linenums="1"
from panther.background_tasks import BackgroundTask
def do_something(name: str, age: int):
print(f"{name} is {age} years old.")
BackgroundTask(do_something, name='Ali', age=26).submit()
```
- You must call `.submit()` to add the task to the queue.
- The task function can be synchronous or asynchronous.
---
## Task Options
### 1. Interval
Control how many times a task runs:
```python
BackgroundTask(do_something, name='Ali', age=26).interval(3).submit()
```
- By default, tasks run once (`interval=1`).
- Use `interval(-1)` for infinite runs.
- Each interval is separated by the schedule you set (see below).
### 2. Scheduling
You can schedule tasks to run at specific intervals:
- **Every N seconds/minutes/hours/days/weeks:**
```python
BackgroundTask(do_something, name='Ali', age=26).every_seconds(10).submit()
BackgroundTask(do_something, name='Ali', age=26).every_minutes(5).submit()
BackgroundTask(do_something, name='Ali', age=26).every_hours(2).submit()
BackgroundTask(do_something, name='Ali', age=26).every_days(1).submit()
BackgroundTask(do_something, name='Ali', age=26).every_weeks(1).submit()
```
- Default value for each is `1` (e.g., every 1 minute).
- **Custom values:**
You can pass a custom value to any of the above, e.g., `every_days(4)` runs every 4 days.
### 3. Time of Day
Run a task at a specific time:
```python linenums="1"
from datetime import time
from panther.background_tasks import BackgroundTask
BackgroundTask(do_something, name='Ali', age=26)\
.every_days()\
.at(time(hour=8, minute=0))\
.submit()
```
- The task will run when the system time matches the specified hour, minute, and second.
### 4. Day of Week
Run a task on a specific day of the week:
```python linenums="1"
from datetime import time
from panther.background_tasks import BackgroundTask, WeekDay
BackgroundTask(do_something, name='Ali', age=26)\
.every_weeks(2)\
.on(WeekDay.SUNDAY)\
.at(time(hour=8))\
.submit()
```
- Valid days: `WeekDay.MONDAY`, `WeekDay.TUESDAY`, `WeekDay.WEDNESDAY`, `WeekDay.THURSDAY`, `WeekDay.FRIDAY`, `WeekDay.SATURDAY`, `WeekDay.SUNDAY`.
---
## Passing Arguments
You can pass arguments to your task function as positional or keyword arguments:
```python
BackgroundTask(do_something, name='Ali', age=26)
BackgroundTask(do_something, 'Ali', age=26)
BackgroundTask(do_something, 'Ali', 26)
```
---
## Important Notes & Best Practices
- **Task function** can be synchronous or asynchronous.
- **You must call `.submit()`** to add the task to the background queue.
- **Default interval** is 1 (runs once). Use `.interval(-1)` for infinite runs.
- If you try to add a task before `BACKGROUND_TASKS` is enabled, it will be ignored and a warning will be logged.
- Each task runs in its own thread when triggered.
- Tasks are checked every second for their schedule.
---
## Example: Task
```python linenums="1"
import datetime
from panther.background_tasks import BackgroundTask
async def hello(name: str):
print(f'Hello {name}')
# Run 2 times, every 5 seconds
BackgroundTask(hello, 'Ali').interval(2).every_seconds(5).submit()
# Run forever, every day at 08:00
BackgroundTask(hello, 'Saba').interval(-1).every_days().at(datetime.time(hour=8)).submit()
```
================================================
FILE: docs/docs/cors.md
================================================
# CORSMiddleware
`CORSMiddleware` is a middleware for Panther applications that handles Cross-Origin Resource Sharing (CORS). It
automatically adds the appropriate CORS headers to all HTTP responses based on your configuration, and handles
preflight (OPTIONS) requests.
## Purpose
CORS is a security feature implemented by browsers to restrict web applications running on one origin from interacting
with resources from a different origin. `CORSMiddleware` makes it easy to configure and manage CORS policies in your
Panther application.
## Configuration Options
Set the following variables in your Panther config file (e.g., `core/configs.py`) to control CORS behavior:
| Config Variable | Type | Description | Default |
|---------------------|-----------|----------------------------------------------------------------------|--------------------------------------------------------|
| _ALLOW_ORIGINS_ | list[str] | List of allowed origins. Use `["*"]` to allow all origins. | `["*"]` |
| _ALLOW_METHODS_ | list[str] | List of allowed HTTP methods. | `["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]` |
| _ALLOW_HEADERS_ | list[str] | List of allowed request headers. Use `["*"]` to allow all headers. | `["*"]` |
| _ALLOW_CREDENTIALS_ | bool | Whether to allow credentials (cookies, authorization headers, etc.). | `False` |
| _EXPOSE_HEADERS_ | list[str] | List of headers that can be exposed to the browser. | `[]` |
| _CORS_MAX_AGE_ | int | Number of seconds browsers can cache preflight responses. | `600` |
## Usage
1. Set the desired CORS config variables in your config file.
2. Add `'panther.middlewares.cors.CORSMiddleware'` to your `MIDDLEWARES` list.
### Example Configuration
```python title="e.g. core/configs.py"
ALLOW_ORIGINS = ["https://example.com", "https://another.com"]
ALLOW_METHODS = ["GET", "POST"]
ALLOW_HEADERS = ["Content-Type", "Authorization"]
ALLOW_CREDENTIALS = True
EXPOSE_HEADERS = ["X-Custom-Header"]
CORS_MAX_AGE = 3600
MIDDLEWARES = [
# ... other middlewares ...
'panther.middlewares.cors.CORSMiddleware',
]
```
## How It Works
- For every request, the middleware adds the appropriate CORS headers to the response.
- For preflight (OPTIONS) requests, it returns a 204 response with the necessary headers.
- The headers are set based on your configuration, with sensible defaults if not specified.
## Notes
- If you set `ALLOW_ORIGINS = ["*"]`, all origins are allowed.
- If you set `ALLOW_HEADERS = ["*"]`, all headers are allowed.
- If `ALLOW_CREDENTIALS` is `True`, the `Access-Control-Allow-Credentials` header is set to `true`.
- If you specify `EXPOSE_HEADERS`, those headers will be exposed to the browser.
For more details, see the source code in `panther/middlewares/cors.py`.
---
For a deeper understanding of CORS, you may also want to check
the [MDN Web Docs CORS Guide](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS).
================================================
FILE: docs/docs/database.md
================================================
# Database Support in Panther
Panther natively supports two databases: `MongoDB` and `PantherDB`. However, you can also define your own custom database connections and queries.
---
## Configuration Structure
Define your database configuration in the following format:
```python
DATABASE = {
'engine': {
'class': 'address of engine',
'arg1': 'value1',
# ... additional arguments ...
},
'query': 'address of query class', # Optional
}
```
### Notes
- **Built-in supported engines:**
- `panther.db.connections.PantherDBConnection`
- `panther.db.connections.MongoDBConnection`
- All values in `engine` (except `class`) are passed to the `__init__` method of the specified class.
- The `query` key is optional for the default supported engines, but you can customize it if needed.
---
## PantherDB
Example configuration for PantherDB:
```python
DATABASE = {
'engine': {
'class': 'panther.db.connections.PantherDBConnection',
'path': BASE_DIR / 'database.pdb', # Optional
'encryption': True # Optional, default is False
}
}
```
### Notes
- `path` is optional; you can customize the directory and filename of your database.
- `encryption` is optional and defaults to `False`.
- The `cryptography` package is required if you set `encryption` to `True`.
---
## MongoDB
Example configuration for MongoDB:
```python
DATABASE = {
'engine': {
'class': 'panther.db.connections.MongoDBConnection',
'host': 'mongodb://127.0.0.1:27017/database_name'
}
}
```
### Notes
- The parameters for the engine are the same as those for `pymongo.MongoClient`. See the [PyMongo documentation](https://pymongo.readthedocs.io/en/stable/tutorial.html#making-a-connection-with-mongoclient) for details.
---
## How Does It Work?
- Panther creates a database connection based on the `DATABASE` configuration you define in your configs.
- You can access this connection through your models, or directly via:
```python
from panther.db.connections import db
```
================================================
FILE: docs/docs/deployment.md
================================================
# Deploying Panther Applications
Panther is an ASGI-compatible web framework, which means you can deploy your Panther app using any ASGI server, such as **Uvicorn**, **Granian**, **Daphne**, or others. This guide covers best practices and options for deploying your Panther application in production.
---
## 1. Production-Ready ASGI Servers
While Panther comes with a convenient CLI, you are not limited to it. You can use any ASGI server to run your app:
- **Uvicorn** (default, recommended)
- **Granian**
- **Daphne**
- **Hypercorn**
- Any other ASGI-compliant server
### Example: Using Uvicorn Directly
```bash
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
```
- `main:app` refers to your application instance (e.g., `app = Panther(...)` in `main.py`).
- Adjust `--workers` for your server's CPU count.
### Example: Using Gunicorn with Uvicorn Workers
For robust process management and multiple workers:
```bash
gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app
```
---
## 2. Using the Panther CLI
Panther provides a built-in CLI for development and deployment. The command:
```bash
panther run main:app --reload
```
is **an alias for running Uvicorn** with your Panther app. You can use all Uvicorn options with this command.
- `--reload` is for development (auto-reloads on code changes).
- Omit `--reload` for production.
**Note:** For advanced deployment, prefer running Uvicorn (or another ASGI server) directly, as shown above.
---
## 3. Environment Variables & Configuration
- Set environment variables (e.g., `PORT`, `HOST`, `DATABASE_URL`) as needed for your deployment environment.
- Use a process manager (e.g., **systemd**, **supervisor**, **pm2**, or **Docker** (recomended)) to keep your app running and restart on failure.
---
## 4. Static Files & Reverse Proxy
Panther does not serve static files in production. Use a reverse proxy (like **Nginx** or **Caddy**) to:
- Serve static files (JS, CSS, images)
- Forward requests to your ASGI server (Uvicorn, etc.)
- Handle HTTPS/SSL termination
**Example Nginx config:**
```nginx
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
```
---
## 5. WebSocket & Multiple Workers
If your app uses WebSockets and you want to run multiple workers, configure **Redis** for pub/sub support (see [Redis Integration](redis.md)).
Alternatively, use the `--preload` flag with Gunicorn for basic multi-worker support (see [WebSocket docs](websocket.md)).
---
## 6. Example: Docker Deployment
Panther can be efficiently containerized using a multi-stage Docker build and the [uv](https://github.com/astral-sh/uv) package manager, which is significantly faster and more reliable than pip. This approach results in smaller, more secure images and faster build times.
### Advantages of Multi-Stage & uv
- **Smaller final image size**: Only production dependencies and app code are included.
- **Faster dependency installation**: `uv` is much faster than `pip` and supports modern lockfiles.
- **Better security**: Build tools and caches are left behind in the builder stage.
- **Cleaner builds**: No unnecessary files in the final image.
### Example Multi-Stage Dockerfile with uv
```dockerfile
# --- Builder ---
FROM python:3.12 AS builder
WORKDIR /app
# Create virtual environment
RUN python -m venv /opt/venv
# Install uv into system (can be reused)
RUN pip install --no-cache-dir uv
# Copy requirements and install into the venv
COPY requirements.txt .
RUN /usr/local/bin/uv pip install -r requirements.txt --python /opt/venv/bin/python
# --- Final image ---
FROM python:3.12-slim AS production
ENV PYTHONUNBUFFERED=1
ENV PATH="/opt/venv/bin:$PATH"
# Copy venv from builder
COPY --from=builder /opt/venv /opt/venv
# Copy app source
WORKDIR /app
COPY . /app
# Run the app
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```
================================================
FILE: docs/docs/events.md
================================================
# Application Events
Panther provides a simple way to execute custom logic during your application's lifecycle using event decorators. You can hook into the **startup** and **shutdown** phases to run code when your app starts or stops.
---
## Startup Event
To run code when your application starts, use the `@Event.startup` decorator:
```python title="events.py" linenums="1"
from panther.events import Event
@Event.startup
def do_something_on_startup():
print('Hello, I am at startup')
```
You can define multiple startup event handlers. They will be executed in the order they are registered.
---
## Shutdown Event
To run code when your application is shutting down, use the `@Event.shutdown` decorator:
```python title="events.py" linenums="1"
from panther.events import Event
@Event.shutdown
def do_something_on_shutdown():
print('Good Bye, I am at shutdown')
```
You can define multiple shutdown event handlers as well.
---
## Additional Notes
- **Multiple Handlers:** You can register multiple functions for both `startup` and `shutdown` events. All will be called.
- **Sync & Async Support:** Event handlers can be either synchronous or asynchronous functions.
- **Use Cases:** Common use cases include initializing resources (like database connections) on startup and cleaning up resources on shutdown.
================================================
FILE: docs/docs/file_handling.md
================================================
# File Handling in Panther
Panther provides file handling capabilities through the `File` and `Image` classes for file uploads and processing.
---
## Overview
- **`File`**: General-purpose file handling for any type of file
- **`Image`**: Specialized file handling for images with automatic MIME type validation
---
## Basic Usage
### Importing File Classes
```python
from panther.file_handler import File, Image
```
### Creating File Objects
```python
# Create from bytes (in-memory file)
file = File(
file_name='document.pdf',
content_type='application/pdf',
file=b'PDF content here...'
)
# Create image file
image = Image(
file_name='photo.jpg',
content_type='image/jpeg',
file=b'JPEG content here...'
)
```
---
## File Properties
| Property | Type | Description |
|----------|------|-------------|
| `file_name` | str | The name of the file |
| `content_type` | str | The MIME type of the file |
| `file` | bytes \| None | The file content in bytes |
| `size` | int | File size in bytes |
---
## File Methods
```python
# Read file content
content = file.read()
# Save file to disk
path = file.save("uploads/")
# Use as context manager
with file as f:
content = f.read()
```
---
## Saving Files
The `save()` method provides flexible file saving with several features:
### Basic Saving
```python
# Save with original filename
path = file.save()
# Save with custom filename
path = file.save("custom_name.txt")
# Save with overwrite
path = file.save("existing_file.txt", overwrite=True)
```
### Directory Path Handling
When you provide a path ending with `/`, it's treated as a directory:
```python
# Save to directory with original filename
path = file.save("uploads/") # Saves as "uploads/original_filename.ext"
# Save to nested directory
path = file.save("uploads/images/") # Saves as "uploads/images/original_filename.ext"
# Save to directory with custom filename
path = file.save("uploads/custom_name.txt") # Saves as "uploads/custom_name.txt"
```
### Idempotent Behavior
The `save()` method is **idempotent** - calling it multiple times on the same file instance returns the same path:
```python
file = File(file_name='test.txt', content_type='text/plain', file=b'content')
# First call - creates file and returns path
path1 = file.save("uploads/")
print(path1) # "uploads/test.txt"
# Subsequent calls - returns same path without creating new files
path2 = file.save("uploads/")
path3 = file.save("uploads/")
assert path1 == path2 == path3 # All return the same path
```
This prevents accidental creation of duplicate files with different timestamps.
### Automatic Directory Creation
The `save()` method automatically creates directories if they don't exist:
```python
# This will create the "uploads/images" directory structure if it doesn't exist
path = file.save("uploads/images/")
```
---
## Integration with Models
Files can be used as model attributes:
```python
from datetime import datetime
from panther.db import Model
from panther.file_handler import File, Image
class Document(Model):
title: str
file: File
uploaded_at: datetime
class Profile(Model):
name: str
avatar: Image
bio: str | None = None
```
When using File types in models:
- The file metadata (name, content type) is preserved
- The file path is stored in the database
- The actual file content is automatically saved to disk when inserting the model
- You can access file properties and methods on the model instance
---
## Integration with Serializers
```python
from panther.serializer import ModelSerializer
from panther.file_handler import File, Image
class FileUploadSerializer(ModelSerializer):
class Config:
model = FileUpload
fields = ['file', 'description']
required_fields = ['file']
class ImageUploadSerializer(ModelSerializer):
class Config:
model = ImageUpload
fields = ['image', 'title']
required_fields = ['image']
```
### File Validation
```python
from pydantic import field_validator
from panther import status
from panther.exceptions import APIError
from panther.file_handler import File
from panther.serializer import ModelSerializer
class DocumentUploadSerializer(ModelSerializer):
class Config:
model = DocumentUpload
fields = ['file', 'title']
required_fields = ['file']
@field_validator('file')
@classmethod
def validate_file_size(cls, file: File) -> File:
if file.size > 10 * 1024 * 1024: # 10MB limit
raise APIError(
detail='File size must be less than 10MB',
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE
)
return file
```
---
## API Integration
### File Upload API
```python
from panther.app import API
from panther.request import Request
from panther.response import Response
@API(input_model=FileUploadSerializer)
async def upload_file(request: Request):
file_data = request.validated_data
file = file_data.file
# Save file to disk
saved_path = file.save("uploads/")
# Store in database
document = await Document.insert_one({
'title': file_data.description,
'file': file,
'uploaded_at': datetime.now()
})
return Response(data={
"message": "File uploaded successfully",
"saved_path": saved_path
})
```
---
## Error Handling
```python
from panther import status
from panther.exceptions import APIError
@API(input_model=FileUploadSerializer)
async def upload_file(request: Request):
try:
file_data = request.validated_data
file = file_data.file
# Check file size
if file.size > 10 * 1024 * 1024: # 10MB limit
raise APIError(
detail="File too large. Maximum size is 10MB.",
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE
)
saved_path = file.save("uploads/")
return Response(data={"saved_path": saved_path})
except Exception as e:
raise APIError(
detail=f"File upload failed: {str(e)}",
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR
)
```
---
## Best Practices
1. **Validate file types**: Always check MIME types to prevent malicious uploads
2. **Limit file sizes**: Set reasonable size limits to prevent abuse
3. **Use context managers**: When reading files, use `with` statements for proper cleanup
4. **Handle errors gracefully**: Provide clear error messages for validation failures
5. **Secure file storage**: Always validate and sanitize file names before saving
6. **Leverage idempotency**: The `save()` method is idempotent, so you can call it multiple times safely
7. **Use directory paths**: When saving files, use directory paths ending with `/` for better organization
This guide covers the essential aspects of file handling in Panther. For more advanced features and detailed examples, refer to the API documentation and examples.
================================================
FILE: docs/docs/first_crud.md
================================================
We assume you have successfully set up your project following the [Introduction](index.md#installation) guide.
In this guide, we will create a `CRUD` (`Create`, `Retrieve`, `Update`, and `Delete`) API for managing `Book` entities.
---
## Project Structure
The final structure of your project will be as follows:
```
.
├── app
│ ├── apis.py
│ ├── models.py
│ ├── serializers.py
│ └── urls.py
└── core
├── configs.py
└── urls.py
```
## Configuring the Database
!!! question "How does the database work in Panther?"
Refer to [Database](database.md) to learn about supported databases and their functionality.
Configure the `DATABASE` settings in `core/configs.py`. In this guide, we will use `PantherDB`.
> [PantherDB](https://github.com/PantherPy/PantherDB/#readme) is a simple, file-based, document-oriented database.
```python title="core/configs.py"
DATABASE = {
'engine': {
'class': 'panther.db.connections.PantherDBConnection',
}
}
```
---
## Defining the Model
!!! question "How do models work in Panther?"
Refer to [Models](model.md) to learn more about defining models and how they function.
!!! tip "File Handling in Models"
For models that need to handle file uploads, see [File Handling](file_handling.md) for detailed information about using `File` and `Image` types in your models.
Create a model named `Book` in `app/models.py`:
```python title="app/models.py" linenums="1"
from panther.db import Model
class Book(Model):
name: str
author: str
pages_count: int
```
---
## Defining URLs
!!! question "How do URLs work in Panther?"
Refer to [URLs](urls.md) to learn more about URL definitions.
The base `urls` configuration should include all application URLs.
```python title="core/urls.py" linenums="1"
from app.urls import urls as app_urls
urls = {
'/': app_urls,
}
```
In `app/urls.py`, define the `Book` API URLs:
=== "Function-Base"
```python title="app/urls.py" linenums="1"
from app.apis import book_api, single_book_api
urls = {
'book/': book_api,
'book/<book_id>/': single_book_api,
}
```
=== "Class-Base API/ Generic API (Beta)"
```python title="app/urls.py" linenums="1"
from app.apis import BookAPI, SingleBookAPI
urls = {
'book/': BookAPI,
'book/<book_id>/': SingleBookAPI,
}
```
---
## Defining the Serializer
!!! question "How do serializers work in Panther?"
Refer to [Serializer](serializer.md) to learn more about available serializers.
!!! tip "File Handling in Serializers"
For serializers that need to handle file uploads, see [File Handling](file_handling.md) for information about validating and processing files in your serializers.
Serializers transform data between the application and API requests.
=== "Function-Base/ Class-Base API"
The serializer can be inherited from `ModelSerializer` or `pydantic.BaseModel`
```python title="app/serializers.py" linenums="1"
from panther.serializer import ModelSerializer
from app.models import Book
class BookSerializer(ModelSerializer):
class Config:
model = Book
fields = ['name', 'author', 'pages_count']
```
or
```python title="app/serializers.py" linenums="1"
import pydantic
from app.models import Book
class BookSerializer(pydantic.BaseModel):
name: str
author: str
pages_count: int
```
=== "Generic API (Beta)"
The serializer should be inherited from `ModelSerializer` to be used in `GenericAPI`.
```python title="app/serializers.py" linenums="1"
from panther.serializer import ModelSerializer
from app.models import Book
class BookSerializer(ModelSerializer):
class Config:
model = Book
fields = ['name', 'author', 'pages_count']
```
### APIs
!!! question "How do APIs work in Panther?"
Refer to [API](api.md) to learn more about API types and their usage.
!!! tip "File Upload APIs"
For APIs that handle file uploads, see [File Handling](file_handling.md) for comprehensive examples of file upload endpoints and processing.
#### Create
=== "Function-Base API"
```python title="app/apis.py" linenums="1"
from panther import status
from panther.app import API
from panther.request import Request
from panther.response import Response
from app.serializers import BookSerializer
from app.models import Book
@API(input_model=BookSerializer, methods=['POST'])
async def book_api(request: Request):
body: BookSerializer = request.validated_data
book: Book = await Book.insert_one(
name=body.name,
author=body.author,
pages_count=body.pages_count,
)
return Response(data=book, status_code=status.HTTP_201_CREATED)
```
=== "Class-Base API"
```python title="app/apis.py" linenums="1"
from panther import status
from panther.app import GenericAPI
from panther.request import Request
from panther.response import Response
from app.serializers import BookSerializer
from app.models import Book
class BookAPI(GenericAPI):
input_model = BookSerializer
async def post(self, request: Request):
body: BookSerializer = request.validated_data
book = await Book.insert_one(
name=body.name,
author=body.author,
pages_count=body.pages_count,
)
return Response(data=book, status_code=status.HTTP_201_CREATED)
```
=== "Generic API (Beta)"
```python title="app/apis.py" linenums="1"
from panther.app import CreateAPI
from app.serializers import BookSerializer
class BookAPI(CreateAPI):
input_model = BookSerializer
```
#### List
=== "Function-Base API"
```python title="app/apis.py" linenums="1"
from panther import status
from panther.app import API
from panther.request import Request
from panther.response import Response
from app.serializers import BookSerializer
from app.models import Book
@API(input_model=BookSerializer, methods=['POST', 'GET'])
async def book_api(request: Request):
...
if request.method == 'GET':
books = await Book.find()
return Response(data=books, status_code=status.HTTP_200_OK)
```
=== "Class-Base API"
```python title="app/apis.py" linenums="1"
from panther import status
from panther.app import GenericAPI
from panther.response import Response
from app.serializers import BookSerializer
from app.models import Book
class BookAPI(GenericAPI):
input_model = BookSerializer
async def get(self):
books = await Book.find()
return Response(data=books, status_code=status.HTTP_200_OK)
```
=== "Generic API (Beta)"
```python title="app/apis.py" linenums="1"
from panther.generics import CreateAPI, ListAPI
from panther.pagination import Pagination
from panther.request import Request
from app.models import Book
from app.serializers import BookSerializer
class BookAPI(CreateAPI, ListAPI):
input_model = BookSerializer
pagination = Pagination #(1)!
search_fields = ['name', 'author'] #(2)!
filter_fields = ['name', 'author'] #(3)!
sort_fields = ['name', 'pages_count'] #(4)!
async def get_query(self, request: Request, **kwargs):
return await Book.find()
```
1. `Pagination` class will look for the `limit` and `skip` in the `query params` and paginate your response and then return it on its own response template.
2. The query will be changed and looking for the value of the `search` query param in these fields,
e.g. query param is the `?search=TheLittlePrince`, we will looking for the Book with `name` or `author` of `TheLittlePrince`.
3. It will look for each value of the `filter_fields` in the `query params` and query on them,
e.g. `?name=Something&author=Ali`, it will looks for Book that its `author` is `Ali` and its `name` is `Something`.
4. The query will be sortalbe with the fields which is in `sort_fields`.
#### Retrieve
=== "Function-Base API"
```python title="app/apis.py" linenums="1"
from panther import status
from panther.app import API
from panther.request import Request
from panther.response import Response
from app.models import Book
@API(methods=['GET'])
async def single_book_api(request: Request, book_id: int):
if book := await Book.find_one(id=book_id):
return Response(data=book, status_code=status.HTTP_200_OK)
return Response(data={'detail': 'Book not found'}, status_code=status.HTTP_400_BAD_REQUEST)
```
=== "Class-Base API"
```python title="app/apis.py" linenums="1"
from panther import status
from panther.app import GenericAPI
from panther.response import Response
from app.models import Book
class SingleBookAPI(GenericAPI):
async def get(self, book_id: int):
if book := await Book.find_one(id=book_id):
return Response(data=book, status_code=status.HTTP_200_OK)
return Response(data={'detail': 'Book not found'}, status_code=status.HTTP_400_BAD_REQUEST)
```
=== "Generic API (Beta)"
```python title="app/apis.py" linenums="1"
from panther.generics import RetrieveAPI
from panther.request import Request
from app.models import Book
class SingleBookAPI(RetrieveAPI):
async def get_instance(self, request: Request, **kwargs):
return await Book.find_one_or_raise(id=kwargs['book_id'])
```
#### Update
=== "Function-Base API"
```python title="app/apis.py" linenums="1"
from panther import status
from panther.app import API
from panther.request import Request
from panther.response import Response
from app.models import Book
from app.serializers import BookSerializer
@API(input_model=BookSerializer, methods=['GET', 'PUT'])
async def single_book_api(request: Request, book_id: int):
...
if request.method == 'PUT':
is_updated = await Book.update_one({'id': book_id}, request.validated_data.model_dump())
data = {'is_updated': is_updated}
return Response(data=data, status_code=status.HTTP_200_OK)
```
=== "Class-Base API"
```python title="app/apis.py" linenums="1"
from panther import status
from panther.app import GenericAPI
from panther.response import Response
from app.models import Book
class SingleBookAPI(GenericAPI):
input_model = BookSerializer
async def put(self, request: Request, book_id: int):
is_updated = await Book.update_one({'id': book_id}, request.validated_data.model_dump())
data = {'is_updated': is_updated}
return Response(data=data, status_code=status.HTTP_200_OK)
```
=== "Generic API (Beta)"
```python title="app/apis.py" linenums="1"
from panther.generics import RetrieveAPI
from panther.request import Request
from app.models import Book
from app.serializers import BookSerializer
class SingleBookAPI(RetrieveAPI, UpdateAPI):
input_model = BookSerializer
async def get_instance(self, request: Request, **kwargs):
return await Book.find_one_or_raise(id=kwargs['book_id'])
```
#### Delete
=== "Function-Base API"
```python title="apis.py" linenums="1"
from panther import status
from panther.app import API
from panther.request import Request
from panther.response import Response
from app.models import Book
from app.serializers import BookSerializer
@API(input_model=BookSerializer, methods=['GET', 'PUT', 'DELETE'])
async def single_book_api(request: Request, book_id: int):
...
if request.method == 'DELETE':
await Book.delete_one(id=book_id)
return Response(status_code=status.HTTP_204_NO_CONTENT)
```
=== "Class-Base API"
```python title="apis.py" linenums="1"
from panther import status
from panther.app import GenericAPI
from panther.response import Response
from app.models import Book
class SingleBookAPI(GenericAPI):
async def delete(self, book_id: int):
await Book.delete_one(id=book_id)
return Response(status_code=status.HTTP_204_NO_CONTENT)
```
=== "Generic API (Beta)"
```python title="apis.py" linenums="1"
from panther.generics import RetrieveAPI
from panther.request import Request
from app.models import Book
from app.serializers import BookSerializer
class SingleBookAPI(RetrieveAPI, UpdateAPI, DeleteAPI):
input_model = BookSerializer
async def get_instance(self, request: Request, **kwargs):
return await Book.find_one_or_raise(id=kwargs['book_id'])
```
With this, you now have a complete CRUD API implementation for the `Book` entity.
================================================
FILE: docs/docs/index.md
================================================
## Panther
**A Fast & Friendly Web Framework for Building Async APIs with Python 3.10+**
<p align="center">
<img src="https://github.com/AliRn76/panther/raw/master/docs/docs/images/logo-vertical.png" alt="Panther Logo" style="width: 450px">
</p>
[](https://pypi.org/project/panther/) [](https://pypi.org/project/panther/) [](https://codecov.io/github/AliRn76/panther) [](https://pepy.tech/project/panther) [](https://github.com/alirn76/panther/blob/main/LICENSE)
---
## 🐾 Why Choose Panther?
Panther is designed to be **fast**, **simple**, and **powerful**. Here's what makes it special:
- **One of the fastest Python frameworks** available
- **File-based database** ([PantherDB](https://pypi.org/project/pantherdb/)) - No external database setup required
- **Document-oriented ODM** - Supports MongoDB & PantherDB with familiar syntax
- **API caching system** - In-memory and Redis support
- **OpenAPI/Swagger** - Auto-generated API documentation
- **WebSocket support** - Real-time communication out of the box
- **Authentication & Permissions** - Built-in security features
- **Background tasks** - Handle long-running operations
- **Middleware & Throttling** - Extensible and configurable
- **File upload handling** - Built-in File and Image classes for easy file processing
---
## Benchmark
<p align="center">
<img src="https://github.com/AliRn76/panther/raw/master/docs/docs/images/benchmark.png" alt="Benchmark" style="width: 800px">
</p>
[[TechEmpower Benchmark]](https://www.techempower.com/benchmarks/#section=data-r23&l=zijzen-pa7&c=4)
---
## Supported by
<center>
<a href="https://drive.google.com/file/d/17xe1hicIiRF7SQ-clg9SETdc19SktCbV/view?usp=sharing">
<img alt="JetBrains" src="https://github.com/AliRn76/panther/raw/master/docs/docs/images/jb_beam_50x50.png">
</a>
</center>
---
## Installation
```shell
$ pip install panther
```
---
## Getting Started
### Quick Start Guide
1. **Create a new project directory**
```shell
$ mkdir my_panther_app
$ cd my_panther_app
```
2. **Set up your environment**
```shell
$ python3 -m venv .venv
$ source .venv/bin/activate # On Windows: .\.venv\Scripts\activate
$ pip install panther
```
3. **Create your first application**
Create a `main.py` file with one of the examples below.
### Basic API Example
Here's a simple REST API endpoint that returns a "Hello World" message:
```python title="main.py" linenums="1"
from datetime import datetime, timedelta
from panther import status, Panther
from panther.app import GenericAPI
from panther.openapi.views import ScalarOpenAPI
from panther.response import Response
class HelloAPI(GenericAPI):
# Cache responses for 10 seconds
cache = timedelta(seconds=10)
def get(self):
current_time = datetime.now().isoformat()
return Response(
data={'message': f'Hello from Panther! 🐾 | {current_time}'},
status_code=status.HTTP_200_OK
)
# URL routing configuration
url_routing = {
'/': HelloAPI,
'docs/': ScalarOpenAPI, # Auto-generated API docs
}
# Create your Panther app
app = Panther(__name__, configs=__name__, urls=url_routing)
```
### WebSocket Example
Here's a simple WebSocket echo server that sends back any message it receives:
```python title="main.py" linenums="1"
from panther import Panther
from panther.app import GenericAPI
from panther.response import HTMLResponse
from panther.websocket import GenericWebsocket
class EchoWebsocket(GenericWebsocket):
async def connect(self, **kwargs):
await self.accept()
await self.send("Connected to Panther WebSocket!")
async def receive(self, data: str | bytes):
# Echo back the received message
await self.send(f"Echo: {data}")
class WebSocketPage(GenericAPI):
def get(self):
template = """
<h2>🐾 Panther WebSocket Echo Server</h2>
<input id="msg"><button onclick="s.send(msg.value)">Send</button>
<ul id="log"></ul>
<script>
const s = new WebSocket('ws://127.0.0.1:8000/ws');
s.onmessage = e => log.innerHTML += `<li><- ${msg.value}</li><li>-> ${e.data}</li>`;
</script>
"""
return HTMLResponse(template)
url_routing = {
'': WebSocketPage,
'ws': EchoWebsocket,
}
app = Panther(__name__, configs=__name__, urls=url_routing)
```
### Running Your Application
1. **Start the development server**
```shell
$ panther run main:app --reload
```
> **Note:** Panther uses [Uvicorn](https://github.com/encode/uvicorn) as the default ASGI server, but you can also use [Granian](https://pypi.org/project/granian/), [Daphne](https://pypi.org/project/daphne/), or any ASGI-compatible server.
2. **Test your application**
- For the _API_ example: Visit [http://127.0.0.1:8000/](http://127.0.0.1:8000/) to see the "Hello World" response
- For the _WebSocket_ example: Visit [http://127.0.0.1:8000/](http://127.0.0.1:8000/) and send a message.
================================================
FILE: docs/docs/log_queries.md
================================================
# Query Performance Logging
Panther provides built-in query performance monitoring that logs the execution time of each database query when enabled.
## Configuration
To enable query performance logging, set the `LOG_QUERIES` configuration option to `True`:
```python
LOG_QUERIES = True # Default is False
```
## How It Works
When `LOG_QUERIES` is enabled, Panther automatically:
- **Measures execution time** for every database query
- **Logs performance data** to `logs/query.log`
- **Includes query details** such as method name and execution time in milliseconds
## Log Format
Each log entry follows this format:
```
INFO: | [Timestamp] | [Query] [Method] takes [Duration] ms
```
### Example Log Entries
```python
INFO: | 2023-03-19 20:37:27 | [Query] User.insert_one() takes 1.612 ms
INFO: | 2023-03-19 20:37:28 | [Query] User.find() takes 45.234 ms
INFO: | 2023-03-19 20:37:29 | [Query] Post.update_one() takes 12.891 ms
INFO: | 2023-03-19 20:37:30 | [Query] Comment.delete_one() takes 3.445 ms
```
## Use Cases
Query logging is particularly useful for:
- **Development**: Identifying slow queries during development
- **Debugging**: Troubleshooting performance issues
- **Optimization**: Finding bottlenecks in database operations
- **Monitoring**: Tracking query performance over time
## Performance Considerations
Keep `LOG_QUERIES = False` in production environments for optimal performance.
Query logging adds a small overhead to each database operation, which can impact application performance under high load.
================================================
FILE: docs/docs/middlewares.md
================================================
# Middlewares in Panther
Middlewares allow you to process requests and responses globally or for all or specific APIs. They are useful for tasks such as logging, timing, and other cross-cutting concerns.
---
## Global Middlewares
To apply middlewares globally, define a `MIDDLEWARES` list in your configs for HTTP middlewares, and a `WS_MIDDLEWARE` list for WebSocket middlewares. **Only HTTP-related middlewares should be placed in `MIDDLEWARES`, and WebSocket middlewares must be placed in `WS_MIDDLEWARE`.**
> **Note:** Previously, WebSocket middlewares were also defined in `MIDDLEWARES`. Now, you must define them in `WS_MIDDLEWARE` instead.
Each item can be either a string (dotted path to the middleware class) or the class itself (useful for single-file applications):
```python
class Middleware(HTTPMiddleware):
pass
MIDDLEWARES = [
'core.middlewares.MyMiddleware', # Import by dotted path
Middleware, # Or directly by class
]
WS_MIDDLEWARE = [
'core.middlewares.MyWebsocketMiddleware', # WebSocket middleware by dotted path
MyWebsocketMiddleware, # Or directly by class
]
```
---
## Per-API Middlewares
You can assign middlewares to specific APIs, either class-based or function-based:
```python title="apis.py" linenums="1"
from panther.app import API, GenericAPI
from panther.middlewares import HTTPMiddleware
class Middleware(HTTPMiddleware):
pass
# Class-Based API
class MyAPI(GenericAPI):
middlewares = [Middleware]
# Function-Based API
@API(middlewares=[Middleware])
def my_api():
pass
```
---
## Middleware Execution Order
Middlewares are executed in the following order:
1. Global middlewares: `__call__()` (enter)
2. Per-API middlewares: `__call__()` (enter)
3. Per-API middlewares: `dispatch` (return)
4. Global middlewares: `dispatch` (return)
---
## Creating Custom Middlewares
### Types of Middlewares
Panther provides two types of middleware base classes:
- `HTTPMiddleware`: For HTTP requests only
- `WebsocketMiddleware`: For WebSocket connections only
Make sure to inherit from the correct base class:
```python
# For HTTP requests
from panther.middlewares import HTTPMiddleware
# For WebSocket connections
from panther.middlewares import WebsocketMiddleware
```
### Implementing a Middleware
- Create a class inheriting from `HTTPMiddleware` or `WebsocketMiddleware`.
- Implement an asynchronous `__call__` method.
- Always return either `await self.dispatch(...)` or a `Response`/`GenericWebsocket` instance at the end of `__call__()`.
#### Example: HTTP Middleware
```python title="middlewares.py" linenums="1"
from datetime import datetime
from panther.middlewares.base import HTTPMiddleware
from panther.request import Request
from panther.response import Response
class CustomMiddleware(HTTPMiddleware):
async def __call__(self, request: Request) -> Response:
start_time = datetime.now()
response = await self.dispatch(request=request)
duration = datetime.now() - start_time
print(f'Request took {duration.total_seconds()} seconds')
return response
```
#### Example: WebSocket Middleware
```python title="middlewares.py" linenums="1"
from datetime import datetime
from panther.middlewares.base import WebsocketMiddleware
from panther.websocket import GenericWebsocket, Websocket
class TimerMiddleware(WebsocketMiddleware):
async def __call__(self, connection: Websocket) -> GenericWebsocket:
start_time = datetime.now()
response = await self.dispatch(connection=connection)
duration = datetime.now() - start_time
print(f'Connection lasted {duration.total_seconds()} seconds')
return response
```
---
## Built-in Middlewares
Panther provides several built-in middlewares to help with common tasks. Below are the available options and how to use them:
### CORS Middleware
- **Purpose:** Enables Cross-Origin Resource Sharing (CORS) for your APIs.
- **Usage:** Add `panther.middlewares.CORSMiddleware` to your global `MIDDLEWARES` list.
- **Configuration:** Requires specific global settings. See the [CORS Middleware documentation](cors.md) for configuration details.
### Monitoring Middleware
- **Purpose:** Logs request and connection data for monitoring and analytics.
- **Usage:** Add `panther.middlewares.MonitoringMiddleware` to your global `MIDDLEWARES` list.
- **Note:** This middleware or `WebsocketMonitoringMiddleware` is required if you want to use the `panther monitor` command.
### WebSocket Monitoring Middleware
- **Purpose:** Similar to `MonitoringMiddleware`, but specifically logs data for WebSocket connections.
- **Usage:** Add `panther.middlewares.WebsocketMonitoringMiddleware` to your global `MIDDLEWARES` list if you want to monitor WebSocket traffic.
- **Note:** This middleware or `MonitoringMiddleware` is required if you want to use the `panther monitor` command.
---
## Tips
- Use global middlewares for logic that should apply to all requests.
- Use per-API middlewares for logic specific to certain endpoints.
- Always ensure your `__call__` method is asynchronous and returns the appropriate value.
================================================
FILE: docs/docs/model.md
================================================
# Panther Models
Panther models allow you to define your data schema and interact with the database using Python classes. They are built on top of `pydantic.BaseModel` and extend its functionality with database query capabilities.
## Creating a Model
To create a Panther model, define a class that inherits from `panther.db.Model`:
```python title="app/models.py" linenums="1"
from panther.db import Model
class User(Model):
username: str
age: int
is_active: bool
```
Panther models inherit from both `pydantic.BaseModel` and `panther.db.Query`, giving you access to data validation and database queries.
## Defining Attributes
You can define model attributes (columns) using Python type hints. Each attribute type is handled as follows:
### General Types
- **str**, **int**, **bool**: Saved in the database as-is.
**Example:**
```python title="app/models.py" linenums="1"
from panther.db import Model
class Product(Model):
name: str
price: int
in_stock: bool
```
### List
- **list**: Each item in the list is processed according to its type. The child type can be a primitive, a `pydantic.BaseModel`, or another `Model`.
**Example:**
```python title="app/models.py" linenums="1"
from pydantic import BaseModel
from panther.db import Model
class Book(BaseModel):
title: str
author: str
tags: list[str]
class Library(Model):
name: str
books: list[Book]
class School(Model):
name: str
libraries: list[Library]
```
### Dictionary
- **dict**: Each value in the dictionary is processed according to its type. Only plain `dict` is supported (not typed dicts like `dict[str, int]`).
**Example:**
```python title="app/models.py" linenums="1"
from panther.db import Model
class Config(Model):
settings: dict
```
### Nested Models
- **pydantic.BaseModel**: Treated like a dictionary, but with type information for each item. Each item is processed according to its type.
**Example:**
```python title="app/models.py" linenums="1"
from pydantic import BaseModel
from panther.db import Model
class Address(BaseModel):
city: str
zipcode: str
class Customer(Model):
name: str
address: Address
```
### Foreign Keys
- **panther.db.Model**: Treated as a foreign key relationship.
1. The related model's value is stored in the database.
2. Its `id` is saved in the main document (table).
3. This process is handled automatically, so you always have access to all attributes of the related model.
4. Panther retrieves the corresponding value from the database and returns it as a fully populated model instance.
**Example:**
```python title="app/models.py" linenums="1"
from panther.db import Model
class Department(Model):
name: str
class Employee(Model):
name: str
department: Department
```
### Optional Attributes
- You can make an attribute optional by using a union with `None` (e.g., `str | None`) and providing a default value (e.g., `= None`).
- If you make an attribute optional, you must assign a default value.
**Example:**
```python title="app/models.py" linenums="1"
from panther.db import Model
class Article(Model):
title: str
summary: str | None = None # Optional attribute with default value
```
### File Types
- **panther.file_handler.File**: Handles file uploads and provides file manipulation capabilities.
- **panther.file_handler.Image**: Specialized File class for image uploads with automatic MIME type validation.
!!! tip "Comprehensive File Handling Guide"
For detailed information about file handling, including advanced features, best practices, and troubleshooting, see the dedicated [File Handling](file_handling.md) documentation.
**Example:**
```python title="app/models.py" linenums="1"
from datetime import datetime
from panther.db import Model
from panther.file_handler import File, Image
class Document(Model):
title: str
file: File
uploaded_at: datetime
class Profile(Model):
name: str
avatar: Image
bio: str | None = None
```
When using File types in models:
- The file metadata (name, content type) is preserved
- The file path is stored in the database
- The actual file content is automatically saved to disk when inserting the model
- You can access file properties and methods on the model instance
**Example usage:**
```python
# Create a document with file
document = await Document.insert_one({
'title': 'My Document',
'file': File(file_name='document.pdf', content_type='application/pdf', file=file_bytes),
'uploaded_at': datetime.now()
})
# Access file properties
print(document.file.file_name) # 'document.pdf'
print(document.file.content_type) # 'application/pdf'
print(document.file.size) # File size in bytes
# Read file content
with document.file as f:
content = f.read()
```
================================================
FILE: docs/docs/open_api.md
================================================
# OpenAPI Integration in Panther
Panther automatically generates an OpenAPI specification for your APIs. This makes it easy to document, test, and share your API endpoints.
## How to Enable OpenAPI in Your Project
Panther provides multiple OpenAPI UI options that you can import directly into your project. You don't need to add the bundled `openapi.urls.url_routing` - instead, you can choose the specific UI component you prefer.
### Available OpenAPI UI Components
Panther offers several OpenAPI UI options available in `panther.openapi.views`:
- **`ScalarOpenAPI`** - Modern, customizable API documentation UI
- **`SwaggerOpenAPI`** - Classic Swagger UI interface
- **`RedocOpenAPI`** - Clean, responsive documentation interface
- **`RapiDocOpenAPI`** - Fast, lightweight API documentation
- **`SpotlightOpenAPI`** - Stoplight Elements integration
### Adding OpenAPI to Your Project
You can import and use any of these UI components directly in your URL configuration:
```python title="urls.py" linenums="1"
from panther.openapi.views import ScalarOpenAPI, SwaggerOpenAPI, RedocOpenAPI
url_routing = {
'docs/scalar/': ScalarOpenAPI, # Modern UI at /docs/scalar/
'docs/swagger/': SwaggerOpenAPI, # Classic Swagger at /docs/swagger/
'docs/redoc/': RedocOpenAPI, # Clean interface at /docs/redoc/
# Other urls
}
```
### Alternative: Using All UI Components
If you want to include all OpenAPI UI options at once, you can still use the bundled routing:
```python title="urls.py" linenums="1"
from panther.openapi.urls import url_routing as openapi_url_routing
url_routing = {
'docs/': openapi_url_routing, # Includes all UI options
# Other urls
}
```
This will make the following endpoints available:
- `/docs/scalar/` - Scalar UI
- `/docs/swagger/` - Swagger UI
- `/docs/redoc/` - ReDoc UI
- `/docs/rapidoc/` - RapiDoc UI
- `/docs/spotlight/` - Spotlight UI
## How Panther Determines Response Models and Status Codes
Panther follows a specific logic to generate the OpenAPI YAML for your APIs:
1. **`output_schema`**: Panther first looks for an `output_schema` attribute to generate the OpenAPI documentation. This is the recommended and most accurate way to specify your response model and status code.
2. **`output_model`**: If `output_schema` does not exist, Panther looks for an `output_model` attribute to generate the response type. It will also attempt to extract the status code from your source code.
3. **Source Code Analysis**: If neither `output_schema` nor `output_model` is available, Panther tries to extract the response data and status code directly from your source code using static analysis with `ast`.
For best results and more accurate documentation, always specify `output_schema` in your APIs.
## How Panther Generates OpenAPI Docs
Panther inspects your API views for an `output_schema` attribute. This attribute should be an instance of `panther.openapi.OutputSchema`, which describes the response model and status code for your endpoint.
- `model` in `OutputSchema` can be either a `pydantic.BaseModel` or a `panther.serializer.ModelSerializer`.
- `status_code` should be an integer (e.g., `status.HTTP_200_OK`).
### Example
=== "Function-Base API"
```python linenums="1"
from pydantic import BaseModel
from panther import status
from panther.app import API
from panther.openapi import OutputSchema
class UserSerializer(BaseModel):
username: str
age: int
@API(output_schema=OutputSchema(model=UserSerializer, status_code=status.HTTP_200_OK))
def user_api():
...
```
=== "Class-Base API"
```python linenums="1"
from pydantic import BaseModel
from panther import status
from panther.app import GenericAPI
from panther.openapi import OutputSchema
class UserSerializer(BaseModel):
username: str
age: int
class UserAPI(GenericAPI):
output_schema = OutputSchema(model=UserSerializer, status_code=status.HTTP_200_OK)
...
```
---
**Note:** The OpenAPI integration is currently in beta. Contributions, feedback, and ideas are very welcome!
================================================
FILE: docs/docs/query.md
================================================
# Querying Documents
Panther ODM provides a simple, async interface for interacting with your database models. This guide covers the most common operations.
---
### find_one
Get a single document matching the filter.
```python
from app.models import User
user: User = await User.find_one(id=1, name='Ali')
# or
user: User = await User.find_one({'id': 1, 'name': 'Ali'})
```
- **Returns:** An instance of the model or `None` if not found.
---
### find
Get multiple documents matching the filter.
```python
users: Cursor = await User.find(age=18, name='Ali')
```
- **Returns:** A `Cursor` object (see [Cursor Usage](#cursor-usage)).
#### Chaining
You can chain `skip()`, `limit()`, and `sort()`:
```python
users: Cursor = await User.find(age=18).skip(10).limit(10).sort([('age', -1)])
```
---
### all
Get all documents in the collection.
```python
users: Cursor = await User.all()
```
---
### first / last
Get the first or last document matching the filter.
```python
user: User = await User.first(age=18)
user: User = await User.last(age=18)
```
---
### aggregate
Perform an aggregation (MongoDB only).
```python
pipeline = [
{'$match': {...}},
{'$group': {...}},
# ...
]
users: Iterable[dict] = await User.aggregate(pipeline)
```
---
### count
Count documents matching the filter.
```python
count: int = await User.count(age=18)
```
---
## Inserting Documents
### insert_one
Insert a single document.
```python
user: User = await User.insert_one(age=18, name='Ali')
```
---
### insert_many
Insert multiple documents.
```python
users = [
{'age': 18, 'name': 'Ali'},
{'age': 17, 'name': 'Saba'},
]
users: list[User] = await User.insert_many(users)
```
---
## Updating Documents
### update
Update the current document instance.
```python
user = await User.find_one(name='Ali')
await user.update(name='Saba')
```
---
### update_one / update_many
Update one or more documents matching a filter.
```python
is_updated: bool = await User.update_one({'id': 1}, name='Ali')
updated_count: int = await User.update_many({'name': 'Saba'}, age=18)
```
---
### save
Save the document (insert or update).
```python
user = User(name='Ali')
await user.save()
```
---
## Deleting Documents
### delete
Delete the current document instance.
```python
user = await User.find_one(name='Ali')
await user.delete()
```
---
### delete_one / delete_many
Delete one or more documents matching a filter.
```python
is_deleted: bool = await User.delete_one(age=18)
deleted_count: int = await User.delete_many(age=18)
```
---
## Special Methods
### find_one_or_insert
Get or insert a document.
```python
user, is_inserted = await User.find_one_or_insert(age=18, name='Ali')
```
For correctness under concurrency, define a unique index/constraint on the queried fields.
---
### find_one_or_raise
Get a document or raise `NotFoundAPIError`.
```python
user: User = await User.find_one_or_raise(age=18)
```
---
## Cursor Usage
- The `find()` and `all()` methods return a `Cursor` object.
- You can iterate over it or use it as a list.
- For MongoDB: `from panther.db.cursor import Cursor`
- For PantherDB: `from pantherdb import Cursor`
---
## Notes
- All methods are async unless otherwise noted.
- Filters can be passed as keyword arguments or as a dictionary.
- Some features (like `aggregate`) are only available for MongoDB.
================================================
FILE: docs/docs/redis.md
================================================
# Redis Integration
Redis is a fast, in-memory key-value store commonly used for caching, session management, real-time analytics, and pub/sub messaging. Panther supports Redis natively, allowing you to leverage its power for authentication, caching, throttling, and websocket pub/sub features.
## Configuration
To enable Redis in Panther, fill out the `REDIS` block in your configuration file:
```python
REDIS = {
'class': 'panther.db.connections.RedisConnection',
'host': 'localhost', # Optional, default is 'localhost'
'port': 6379, # Optional, default is 6379
'db': 0, # Optional, default is 0
# Add any other redis-py supported parameters here
}
```
**Note:** The arguments are the same as those accepted by `redis.Redis.__init__()` from the [redis documentation](https://redis.readthedocs.io/en/latest/).
## How It Works
- Panther creates an asynchronous Redis connection based on the `REDIS` block you define in your configuration.
- You can access the Redis connection via:
```python
from panther.db.connections import redis
```
- Example usage:
```python
from panther.db.connections import redis
await redis.set('name', 'Ali')
result = await redis.get('name')
print(result)
```
## Features Using Redis
- **Authentication:** Store and retrieve JWT tokens for logout functionality.
- **Caching:** Cache responses for faster access.
- **Throttling:** Track and limit request rates.
- **WebSocket:** Manage pub/sub connections for real-time features.
================================================
FILE: docs/docs/release_notes.md
================================================
# Panther Release Notes
### 5.2.3
- Fix background task async loop mismatch with app event loop registration (#146)
- Escape Mongodb search input before building regex (#147)
### 5.2.2
- Fix function API request isolation
- Make `find_one_or_insert` race-safe on duplicate key conflicts
### 5.2.0
- Update requirements for `Python3.14` compatibility
### 5.1.0
- Add `FileResponse`
### 5.0.2
- Minor performance improvement.
### 5.0.0
- Add Built-In `OpenAPI`
- Add Built-In `AdminPanel`
- **File Handling Improvements**:
- Make `File.save()` method **idempotent** - multiple calls return the same path
- Add directory path support - paths ending with `/` are treated as directories
- Automatic directory creation when saving files
- Enhanced file path handling for both string and Path objects
- Support `Set-Cookie` in Response
- Support `application/x-www-form-urlencoded` Content Type
- Support Different `Middlewares` Per API
- Support `asyncio` in `panther shell`
- Support `Boolean` type in `load_env()`
- Add `CORSMiddleware`
- Add `RedirectResponse`
- Optimized `ORM` with smarter `ObjectID` handling
- Add CLI `createuser` command
- Deprecated:
- Usage of `auth` and `permissions` in `API`, `GenericAPI` and `GenericWebsocket` has been change, make sure to check the docs.
- `DEFAULT_CACHE_EXP` in configs.
- Rename `cache_exp_time` to `cache` in `@API` and `GenricAPI`.
- Rename `panther.throttling.Throttling` to `panther.throttling.Throttle`.
- Remove `MONITORING` and move its functionality as middleware in `panther.middelwares.monitoring.MonitoringMiddleware` .
- `BaseUser.check_password(new_password=...)` is renamed to `password`.
- `TemplateResponse(path=...)` is renamed to `name`.
- Middlewares does not support custom args for their `__init__`.
- New Style of Middleware declaration [[Documentation]](middlewares.md).
- New background task declaration, `background_tasks.add_task(BackgroundTask(...))` --> `BackgroundTask(...).submit()`.
- `REDIS` block in configs does not support `websocket_db` anymore.
- `MIDDLEWARE` does not support WebSocket middlewares anymore, you have to define them in `WS_MIDDLEWARE`.
### 4.3.7
- Improve MultiPart-FormData Regex
### 4.3.6
- Support middleware class in MIDDLEWARES
### 4.3.4
- Cache Response Headers
### 4.3.4
- Fix an issue on TemplateResponse when TEMPLATES_DIR is '.'
### 4.3.3
- Fix not provided context in TemplateResponse
### 4.3.2
- Support `Python 3.13`
### 4.3.0
- Support `Jinja2 Template Engine`
### 4.2.0
- Support `OPTIONS` method
### 4.1.3
- Add `reload()` method to queries
- Add `get_input_model()` & `get_output_model()` to `GenericAPI`
- Support `per_delete()` & `post_delete()` in `DeleteAPI`
- Support mongodb operators in `update(`) & `update_one()`
### 4.1.2
- Fix some issues for `Windows`
### 4.1.1
- Fix an issue in `Response.prepare_data()` when `data` is `Cursor`
- Split `paginate` and `template` in `Pagination`
### 4.1.0
- Support `prepare_response` in `Serializers`
- Rename `objects()` to `cursor()`
### 4.0.1
- Fix an issue in `startup` lifespan
### 4.0.0
- Move `database` and `redis` connections from `MIDDLEWARES` to their own block, `DATABASE` and `REDIS`
- Make `Database` queries `async`
- Make `Redis` queries `async`
- Add `StreamingResponse`
- Add `generics` API classes
- Add `login()` & `logout()` to `JWTAuthentication` and used it in `BaseUser`
- Support `Authentication` & `Authorization` in `Websocket`
- Rename all exceptions suffix from `Exception` to `Error` (https://peps.python.org/pep-0008/#exception-names)
- Support `pantherdb 2.0.0` (`Cursor` Added)
- Remove `watchfiles` from required dependencies
- Support `exclude` and `optional_fields` in `ModelSerializer`
- Minor Improvements
### 3.9.0
- Change the style of `ModelSerializer` usage
### 3.8.2
- Add `content-type = application/json` header in raise response of `__call__`
### 3.8.1
- Fix an issue in `_create_pantherdb_session()`
### 3.8.0
- Handle WebSocket connections when we have multiple workers with `multiprocessing.Manager`
### 3.7.0
- Add `ModelSerializer`
### 3.6.0
- Use `observable` pattern for loading database middleware and inheritance of the `Query` class
- Remove `IDType` from the `Model`
- Change `encrypt_password()` method, now uses `scrypt` + `md5`
### 3.5.1
- Set default behavior of `GenericWebsocket.connect` to ignore the connection (`reject`)
### 3.5.0
- Add `WebsocketTestClient`
### 3.4.0
- Support `WebsocketMiddleware`
### 3.3.2
- Add `content-length` to response header
### 3.3.1
- Check `ruff` installation on startup
- Fix an issue in `routing`
### 3.3.0
- Add Auto Reformat Code
### 3.2.4
- Add all() query
- Add tests for `pantherdb`, `load_configs()`, `status.py`, `Panel`, `multipart`, `request headers`
- Refactor `Headers()` class
- Check `uvloop` installation on `Panther init`
- Minor Improvement
### 3.2.1
- Move `Startup` to `__call__`
### 3.2.0
- Support `Startup` & `Shutdown` Events
### 3.1.5
- Support `Websocket` in the `monitoring`
- Refactor `collect_all_models()`
### 3.1.4
- Check ws redis connection on the `init`
- Refactor `Monitoring` class and usage
- Improve `logging` config
- Check database connection before query
### 3.1.3
- Add `Image` base class
- Add `size` to `File` base class
- Improve the way of loading `configs` in `single-file` structure
- Improve `background_tasks.py`, `generate_ws_connection_id()`
- `bpython` removed from being the default python shell
- Improve `load_middlewares()` error handling
- Print `configs` on the `run`
- Add `requirements.txt` for development
- Update `roadmap.jpg`, `README.md`
### 3.1.2
- Add new methods to `BackgroundTask`
- `every_seconds()`
- `every_minutes()`
- `every_hours()`
- `every_days()`
- `every_weeks()`
- `at()`
### 3.1.1
- Upgrade `PantherDB` version
- Add `first()`, `last()` queries
### 3.1.0
- Add `BackgroundTasks`
### 3.0.3
- Add `find_one_or_raise` query
- Add `last_login` to `BaseUser`
- Add `refresh_life_time` to `JWTConfig`
- Add `encode_refresh_token()` to `JWTAuthentication`
- Add `encrypt_password()`
- Handle `PantherException`
- Handle `RedisConnection` without `connection_pool`
### 3.0.2
- Added 'utf-8' encoding while opening the file "README.md" in setup.py
- Fixed panther shell not working issue in windows.
- Added a condition to raise error if no argument is passed to panther command in cli.
### 3.0.1
- Assume content-type is 'application/json' if it was empty
- Fix an issue on creating instance of model when query is done
### 3.0.0
- Support **Websocket**
- Implement **Built-in TestClient**
- Support Single-File Structure
- Support `bytes` as `Response.data`
- Add `methods` to `API()`
- Change `Request.pure_data` to `Request.data`
- Change `Request.data` to `Request.validated_data`
- Change `panther.middlewares.db.Middleware` to `panther.middlewares.db.DatabaseMiddleware`
- Change `panther.middlewares.redis.Middleware` to `panther.middlewares.redis.RedisMiddleware`
- Fix `panther run` command
- Minor Improvement
### 2.4.2
- Don't log content-type when it's not supported
### 2.4.1
- Fix an issue in collect_all_models() in Windows
### 2.4.0
- Handle Complex Multipart-FormData
### 2.3.3
- Fix a bug in response headers
### 2.3.2
- Fix a bug in Template
### 2.3.1
- Handle PlainTextResponse
- Handle Custom Header in Response
- Change the way of accepting 'URLs' in configs (relative -> dotted)
- Fix an issue in collect_all_models()
### 2.3.0
- Handle HTMLResponse
### 2.2.0
- Supporting File
### 2.1.6
- Fix validation errors on nested inputs
### 2.1.5
- Fix response of nested Models in _panel/<index>/
### 2.1.4
- Add access-control-allow-origin to response header
### 2.1.3
- Upgrade greenlet version in requirements for python3.12
### 2.1.2
- Add ruff.toml
- Add Coverage to workflows
- Fix a bug for running in Windows
### 2.1.1
- Fix a bug in main.py imports
### 2.1.0
- Support Sync APIs
### 2.0.0
- Supporting class-base APIs
### 1.7.20
- Fix an issue in find_endpoint()
### 1.7.19
- Fix an issue in routing
- Fix an issue on return complex dict Response
### 1.7.18
- Remove uvloop from requirements for now (we had issue in windows)
### 1.7.16
- Trying to fix requirements for windows
- Minor improvement in BaseMongoDBQuery
### 1.7.15
- Fix an issue in handling form-data
### 1.7.14
- Add Cache and Throttling doc to FirstCrud
- Fix an issue in BasePantherDBQuery._merge()
### 1.7.13
- Hotfix validation of _id in Model()
### 1.7.12
- Fix a bug in routing
### 1.7.11
- Fix an issue in template
### 1.7.10
- Fix a bug in `collect_urls` and rename it to `flatten_urls`
- Add General Tests
- Compatible with python3.10 (Not Tested)
- Working on docs
### 1.7.9
- Working on doc
### 1.7.8
- Fix a bug
- Update docs
### 1.7.8
- Fix a bug
- Update docs
### 1.7.7
- Fix a bug
### 1.7.5
- Change the way of raising exception in JWTAuthentication
- Rename User model to BaseUser
- Fix template
### 1.7.4
- Crop Logo
### 1.7.3
- Add Throttling Doc
- Fix some issue in Doc
### 1.7.2
- Add Throttling to example
- Customize install_requires in setup.py
- Improve monitoring cli command
### 1.7.1
- Rename db BaseModel to Model
- Add more docs
### 1.7.0
- Add Throttling
### 1.6.1
- Add AdminPermission
### 1.6.0
- Handle Permissions
### 1.5.2
- Improve Response data serialization
- Fix a bug in JWTAuthentication
### 1.5.1
- Fix error messages
### 1.5.0
- Refactor Mongodb ODM
- Minor Improvement
### 1.4.0
- Add QUERY_LOG
### 1.3.2
- Add Uvicorn to the setup requirements
- Update Readme
### 1.3.1
- Fix a bug in project creation template
- Fix a bug in caching
### 1.3.0
- Add PantherDB to Panther
- Remove tinydb
### 1.2.7
- Fix a bug while using tinydb
### 1.2.6
- Update Readme
### 1.2.5
- Fix install_requires issue
- Add benchmarks to docs
### 1.2.4
- Remove Uvicorn From install_requires
- Working on Docs
### 1.2.3
- Fix URL Routing
### 1.2.1
- Path Variable Handled Successfully
### 1.2.0
- Read multipart/form-data with Regex
### 1.1.9
- Refactoring code style with ruff
- Add asyncio.TaskGroup()
### 1.1.8
- Refactor cli run command
### 1.1.7
- Add benchmark pictures to doc
### 1.1.5
- Clean Readme
- Clean main.py
### 1.1.4
- Update Readme
### 1.1.3
- Fix a query in TinyDB
### 1.1.2
- Add delete_many query to TinyDB
### 1.1.1
- Add TinyDB
### 1.1.0
- Debug the Template
### 1.0.9
- Handle string exceptions (raise them as detail: error)
- Little debug on MongoQueries
### 1.0.7
- Working on queries
- Fix a bug in query methods
### 1.0.6
- Update the Template
### 1.0.4
- Debug template
### 1.0.2
- Add global config
- Split the BaseModels
- Worked on MongoQuery
- Set Mongo as default database while creating project
- Minor Improvement
### 1.0.1
- Add alembic To Project
### 1.0.
- Refactor & Complete the CLI
### 0.1.9
- Fix install_requires
### 0.1.8
- Update Readme
### 0.1.7
- Update Readme
### 0.1.6
- Handle Most Types as Data in Response
### 0.1.4
- Working On DB Connection
### 0.0.1
- Make It Ready For PyPI
================================================
FILE: docs/docs/roadmap.md
================================================
# Roadmap
## Current Status

## Contributing
We welcome contributions! Here's how you can help:
1. Report bugs and suggest features through [GitHub Issues](https://github.com/AliRn76/panther/issues)
2. Submit pull requests for bug fixes and new features
3. Improve documentation
4. Share your experience and use cases
5. Help with testing and quality assurance
## Community Feedback
Your input is valuable! If you have any feature requests or suggestions for future development, please:
1. Check existing issues to avoid duplicates
2. Create a new issue with a clear description
3. Include use cases and potential implementation ideas
4. Label the issue appropriately
================================================
FILE: docs/docs/ruff.md
================================================
# Auto-Reformatting with Ruff
Panther supports automatic code reformatting using [Ruff](https://docs.astral.sh/ruff/), a fast Python linter and formatter written in Rust.
## Quick Setup
To enable automatic code reformatting, set `AUTO_REFORMAT` to `True` in your configuration:
```python
AUTO_REFORMAT = True # Default is False
```
## How It Works
When `AUTO_REFORMAT` is enabled, Panther will automatically reformat your code:
- **On every application run** - Code is reformatted when you start your application.
- **With `--reload` flag** - Code is reformatted on every file change during development
This ensures your codebase maintains consistent formatting standards automatically.
## Installation
The auto-reformatting feature requires the Ruff package. Install it using pip:
```bash
pip install ruff
```
## Configuration
You can create a custom `ruff.toml` file in your project root to configure formatting rules:
```toml title="ruff.toml" linenums="1"
# Set the maximum line length
line-length = 120
# Set the indentation style
indent-width = 4
# Enable/disable specific rules
select = ["E", "F", "I"]
ignore = ["E501"]
[format]
# Formatting options
quote-style = "single"
indent-style = "space"
skip-magic-trailing-comma = false
line-ending = "auto"
```
## Benefits
- **Consistent Code Style**: Automatic formatting ensures all code follows the same style
- **Time Saving**: No need to manually format code or run formatters separately
- **Team Collaboration**: Everyone on the team gets consistent formatting automatically
- **Fast Performance**: Ruff is extremely fast, making the formatting process seamless
## References
- [Ruff Documentation](https://docs.astral.sh/ruff/)
- [Ruff Formatter Configuration](https://docs.astral.sh/ruff/formatter/)
- [Ruff Rules Reference](https://docs.astral.sh/ruff/rules/)
================================================
FILE: docs/docs/serializer.md
================================================
# Panther Serializers
Panther provides flexible ways to define serializers for your APIs. Serializers are responsible for validating and transforming input data. This guide covers the three supported styles and when to use each.
---
## Introduction
**serializer** in Panther is a class that defines how input data is validated and (optionally) transformed before being processed by your API logic. Panther supports three main styles:
- **Pydantic Serializer**: Use a standard Pydantic model.
- **ModelSerializer**: Generate fields from a Panther model.
- **ModelSerializer + Pydantic**: Combine model-based fields with Pydantic features and custom validation.
---
## Style 1: Pydantic Serializer
Use a regular Pydantic class as your serializer. This is the most direct approach and is ideal for simple use cases or when you want full control over the fields.
```python linenums="1"
from pydantic import BaseModel, Field
from panther.app import API
from panther.request import Request
from panther.response import Response
class UserSerializer(BaseModel):
username: str
password: str
first_name: str = Field(default='', min_length=2)
last_name: str = Field(default='', min_length=4)
@API(input_model=UserSerializer)
async def serializer_example(request: Request):
return Response(data=request.validated_data)
```
---
## Style 2: ModelSerializer
Use Panther's `ModelSerializer` to automatically generate serializer fields from your model. This is useful for DRY code and consistency between your models and serializers.
```python linenums="1"
from pydantic import Field
from panther.app import API
from panther.db import Model
from panther.request import Request
from panther.response import Response
from panther.serializer import ModelSerializer
class User(Model):
username: str
password: str
first_name: str = Field(default='', min_length=2)
last_name: str = Field(default='', min_length=4)
# Option 1: Specify fields explicitly
class UserModelSerializer(ModelSerializer):
class Config:
model = User
fields = ['username', 'first_name', 'last_name']
required_fields = ['first_name']
# Option 2: Exclude specific fields
class UserModelSerializer(ModelSerializer):
class Config:
model = User
fields = '*'
required_fields = ['first_name']
exclude = ['id', 'password']
@API(input_model=UserModelSerializer)
async def model_serializer_example(request: Request):
return Response(data=request.validated_data)
```
---
## Style 3: ModelSerializer with Pydantic Features
Combine `ModelSerializer` with Pydantic features for advanced use cases. This allows you to add custom fields, validators, and configuration.
```python linenums="1"
from pydantic import Field, field_validator, ConfigDict
from panther.app import API
from panther.db import Model
from panther.request import Request
from panther.response import Response
from panther.serializer import ModelSerializer
class User(Model):
username: str
password: str
first_name: str = Field(default='', min_length=2)
last_name: str = Field(default='', min_length=4)
class UserModelSerializer(ModelSerializer):
model_config = ConfigDict(str_to_upper=True)
age: int = Field(default=20)
is_male: bool
username: str
class Config:
model = User
fields = ['first_name', 'last_name']
required_fields = ['first_name']
optional_fields = ['last_name']
@field_validator('username')
def validate_username(cls, username):
print(f'{username=}')
return username
@API(input_model=UserModelSerializer)
async def model_serializer_example(request: Request):
return Response(data=request.validated_data)
```
---
## Comparison Table
| Feature | Pydantic Serializer | ModelSerializer | ModelSerializer + Pydantic |
|------------------------|:------------------:|:--------------:|:-------------------------:|
| Model-based fields | ❌ | ✅ | ✅ |
| Custom fields | ✅ | ❌ | ✅ |
| Pydantic validators | ✅ | ❌ | ✅ |
| Field inclusion/exclude| Manual | Configurable | Configurable |
| Best for | Simple cases | DRY, model-aligned | Advanced/Hybrid |
---
## Notes & Best Practices
- `ModelSerializer` uses your model's field types and default values for validation.
- `Config.model` and `Config.fields` are required for `ModelSerializer`.
- Use `Config.required_fields` to force fields to be required.
- Use `Config.optional_fields` to force fields to be optional.
- A field cannot be in both `required_fields` and `optional_fields`.
- If you use `required_fields` or `optional_fields`, those fields must also be listed in `fields`.
- You can use `'*'` for `fields`, `required_fields`, or `optional_fields` to include all model fields.
- `Config.exclude` is useful when `fields` is set to `'*'`.
- You can add custom fields and validators when combining `ModelSerializer` with Pydantic features.
---
## File Handling in Serializers
When working with file uploads, Panther's `File` and `Image` classes integrate seamlessly with serializers.
!!! tip "Comprehensive File Handling Guide"
For detailed information about file handling, including advanced features, best practices, and troubleshooting, see the dedicated [File Handling](file_handling.md) documentation.
### Basic File Serialization
```python title="app/serializers.py" linenums="1"
from panther.serializer import ModelSerializer
class FileUploadSerializer(ModelSerializer):
class Config:
model = FileUpload
fields = ['file', 'description']
required_fields = ['file']
class ImageUploadSerializer(ModelSerializer):
class Config:
model = ImageUpload
fields = ['image', 'title']
required_fields = ['image']
```
### File Validation
```python title="app/serializers.py" linenums="1"
from pydantic import field_validator
from panther import status
from panther.exceptions import APIError
from panther.file_handler import File
from panther.serializer import ModelSerializer
class DocumentUploadSerializer(ModelSerializer):
class Config:
model = DocumentUpload
fields = ['file', 'title']
required_fields = ['file']
@field_validator('file')
@classmethod
def validate_file_size(cls, file: File) -> File:
if file.size > 10 * 1024 * 1024: # 10MB limit
raise APIError(
detail='File size must be less than 10MB',
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE
)
return file
```
### File Properties
When working with `File` objects in serializers, you have access to:
- `file.file_name`: The original filename
- `file.content_type`: The MIME type
- `file.size`: File size in bytes
- `file.file`: The file content as bytes
================================================
FILE: docs/docs/single_file.md
================================================
# Using Panther in a Single-File Structure
If you prefer to work with `Panther` in a single Python file, follow the steps below to quickly set up and run your API.
## Step 1: Define Your APIs
Write your API endpoints as usual using the `@API()` decorator or inheriting from `GenericAPI`:
```python title="main.py" linenums="1"
from panther.app import API
@API()
async def hello_world_api():
return {'detail': 'Hello World'}
```
## Step 2: Create a URL Routing Dictionary
Map your endpoints to their respective URLs in a dictionary (commonly named `url_routing`):
```python title="main.py" linenums="6"
url_routing = {
'/': hello_world_api,
}
```
## Step 3: Initialize the Panther App
Create an instance of the `Panther` app, passing your current module name and the URL routing dictionary:
```python title="main.py" linenums="1"
from panther import Panther
from panther.app import API
@API()
async def hello_world_api():
return {'detail': 'Hello World'}
url_routing = {
'/': hello_world_api,
}
app = Panther(__name__, configs=__name__, urls=url_routing)
```
## Step 4: Run Your Project
Use the following command to start your application:
```bash
panther run main:app
```
---
## Additional Notes
- The `urls` parameter is required unless you provide the URLs via configuration.
- When you pass `configs=__name__` to the `Panther` constructor, Panther will load the configuration from the current file. If you omit this, Panther defaults to loading configuration from `core/configs.py`.
================================================
FILE: docs/docs/templates.md
================================================
# Template Responses
Panther provides `TemplateResponse` for serving HTML templates with dynamic content. This guide explains how to configure and use templates in your Panther application.
## Configuration
To use `TemplateResponse` with template files, you must configure the `TEMPLATES_DIR` setting in your application configuration.
### Setting Templates Directory
The `TEMPLATES_DIR` can be a single string or a list of strings representing template directory paths:
```python
# Single directory
TEMPLATES_DIR = 'templates/'
# Multiple directories (searched in order)
TEMPLATES_DIR = ['templates/', 'app/templates/', 'shared/templates/']
```
**Default value:** `'./'` (current directory)
## Usage
### Using Template Files (Recommended)
When you have template files, use the `name` parameter to specify the template file:
```python linenums="1"
from panther.app import API
from panther.response import TemplateResponse
@API()
def my_html():
return TemplateResponse(
name='index.html',
context={'name': 'Ali', 'title': 'Welcome'}
)
```
**Benefits:**
- Cleaner code separation
- Template reusability
- Better maintainability
### Using Inline HTML Content
For simple cases or when you need to generate HTML dynamically, you can pass the HTML content directly:
```python linenums="1"
from panther.app import API
from panther.response import TemplateResponse
@API()
def my_html():
html_content = open('index.html', 'r').read()
return TemplateResponse(
source=html_content,
context={'name': 'Ali', 'title': 'Welcome'}
)
```
**Note:** This approach requires you to manage the HTML content manually and doesn't provide the benefits of template files.
## Template Context
The `context` parameter allows you to pass variables to your templates:
```python linenums="1"
from panther.app import API
from panther.response import TemplateResponse
def get_user(user_id: int):
return ...
@API()
def user_profile(user_id: int):
user = get_user(user_id) # Your user fetching logic
return TemplateResponse(
name='profile.html',
context={
'user': user,
'page_title': f'{user.name}\'s Profile',
'is_admin': user.role == 'admin'
}
)
```
## Example Project Structure
```
my_panther_app/
├── core/
│ ├── __init__.py
│ ├── configs.py
│ └── urls.py
├── app/
│ ├── __init__.py
│ ├── urls.py
│ └── views.py
├── templates/
│ ├── base.html
│ ├── index.html
│ └── profile.html
└── main.py
```
With this structure, your configuration would be:
```python title="core/configs.py"
TEMPLATES_DIR = 'templates/'
```
================================================
FILE: docs/docs/timezone.md
================================================
# Timezone Configuration
Panther provides built-in timezone support to ensure consistent datetime handling across your application.
## Configuration
You can specify your timezone in the configuration using the `TIMEZONE` setting:
```python
TIMEZONE = 'UTC' # Options are available in `pytz.all_timezones`
```
To see all available timezone options, you can run:
```python
import pytz
print(pytz.all_timezones)
```
This will show you the complete list of timezone identifiers you can use in your `TIMEZONE` configuration.
## Using Panther's Timezone-Aware DateTime
Panther provides a utility function `panther.utils.timezone_now()` that returns the current datetime relative to your configured timezone:
```python
from panther.utils import timezone_now
# Get current datetime in your configured timezone
current_time = timezone_now()
print(current_time) # 2024-01-15 14:30:00+00:00 (if TIMEZONE='UTC')
```
## Where It's Used
The `timezone_now()` function is automatically used in several Panther components:
- **User Authentication**: `BaseUser.date_created` and `BaseUser.last_login` use timezone-aware timestamps
- **Background Tasks**: DateTime checking for scheduled and queued tasks
> Use `timezone_now()` Instead of `datetime.now()`, to maintain consistency across your application.
================================================
FILE: docs/docs/urls.md
================================================
# URL Configuration in Panther
Panther requires you to define your application's URL routing. You can provide this configuration in two ways:
1. **Global Config:** Specify a dotted path to a dictionary of URLs in your configuration file.
2. **Direct Argument:** Pass the URL dictionary directly to the `Panther(...)` constructor (commonly used in single-file applications).
## How to Configure URLs
- The `URLs` config should be a dotted path (string) pointing to your root URL dictionary.
- Example: `URLs = 'path.to.module.url_dict'`
- The target of `URLs` **must** be a Python `dict`.
- In the URL dictionary, each **key** is a URL path, and each **value** is either an endpoint (function/class) or another nested dictionary for sub-routing.
### Path Variables
You can define variables in your URL paths using angle brackets (`<variable_name>`):
- Example path: `user/<user_id>/blog/<title>/`
- The corresponding endpoint must accept parameters with the same names.
- Panther will automatically cast arguments to the expected types and raise an error if the types do not match.
=== "Function-based"
```python title="app/apis.py"
@API()
async def profile_api(user_id: int, title: str, is_alive: bool):
...
```
=== "Class-based"
```python title="app/apis.py"
class MyAPI(GenericAPI):
async def get(self, user_id: int, title: str, is_alive: bool):
...
```
---
## Example: Global Config Structure
Specify a dotted path to a dictionary of URLs in your configuration file.
```
.
├── app
│ ├── apis.py
│ └── urls.py
└── core
├── configs.py
└── urls.py
```
```python title="core/configs.py"
URLs = 'core.urls.url_routing'
```
```python title="core/urls.py" linenums="1"
from app.urls import app_urls
url_routing = {
'user/': app_urls,
}
```
```python title="app/urls.py" linenums="1"
from app.apis import *
app_urls = {
'login/': login_api,
'logout/': logout_api,
'<user_id>/blog/<title>/<is_alive>/': profile_api,
}
```
```python title="app/apis.py" linenums="1"
from panther import API
@API()
async def profile_api(user_id: int, title: str, is_alive: bool):
...
```
---
## Example: Single-File Structure
You can pass the URL dictionary directly to Panther:
```python title="main.py"
# Other codes ...
urls = {
'/': first_api,
}
app = Panther(__name__, configs=__name__, urls=urls)
```
================================================
FILE: docs/docs/user_model.md
================================================
# User Model Configuration
You can specify which model should be used as your User model by setting the `USER_MODEL` variable in your configuration files.
The value of `USER_MODEL` should be the import path (address) of a model class that inherits from `panther.db.models.BaseUser`. Panther relies on this model for user authentication and management within the AdminPanel and all built-in authentication classes.
## Usage
- **Login and Authentication in AdminPanel:** The specified User model will be used to authenticate users accessing the AdminPanel.
- **Built-in Authentication Classes:** All built-in authentication mechanisms will utilize this User model for user-related operations.
## Default Value
If you do not specify a `USER_MODEL`, Panther will use `panther.db.models.BaseUser` as the default User model.
## Example
```python
# In your configuration file
USER_MODEL = 'your_app.models.CustomUser'
```
================================================
FILE: docs/docs/websocket.md
================================================
# WebSocket Support in Panther
**WebSockets** enable you to build interactive, real-time features such as chat, notifications, and live updates.
---
## Structure & Requirements
### Creating a WebSocket Class
Create a WebSocket handler class in `app/websockets.py` by inheriting from `GenericWebsocket`:
```python title="app/websockets.py" linenums="1"
from panther.websocket import GenericWebsocket
class BookWebsocket(GenericWebsocket):
async def connect(self):
await self.accept()
print(f'{self.connection_id=}')
async def receive(self, data: str | bytes):
# Echo the received message back to the client
await self.send(data=data)
```
### Registering WebSocket URLs
Register your WebSocket class in `app/urls.py`:
```python title="app/urls.py" linenums="1"
from app.websockets import BookWebsocket
urls = {
'ws/book/': BookWebsocket,
}
```
> Panther supports WebSocket routing just like APIs.
---
## How It Works
1. **Client Connection:** The client connects to your `ws/book/` URL using the WebSocket protocol.
2. **Connection Handling:** The `connect()` method of your WebSocket class is called.
3. **Validation:** You can validate the connection using `self.headers`, `self.query_params`, etc.
4. **Accept/Reject:** Accept the connection with `self.accept()`. If not accepted, it is rejected by default.
5. **Connection ID:** Each connection gets a unique `connection_id` (accessible via `self.connection_id`). You may want to store this in a database or cache.
6. **Receiving Messages:** Incoming messages are handled by the `receive()` method. Messages can be `str` or `bytes`.
7. **Sending Messages:**
- **Within the WebSocket class:** Use `self.send(data)`.
- **Outside the WebSocket class:** Use `send_message_to_websocket()`:
```python
from panther.websocket import send_message_to_websocket
await send_message_to_websocket(connection_id='7e82d57c9ec0478787b01916910a9f45', data='New Message From WS')
```
---
## Advanced Usage
### Authentication
You can enable authentication in your WebSocket class by setting `auth` to an async function or a class with an async `__call__` method. Panther will use this callable to authenticate the user.
- If you do not set `auth`, Panther will use the default `WS_AUTHENTICATION` from your configuration **only if the request contains an authorization header/ cookie/ param/ etc.**.
- If there is no authorization header, authentication is bypassed and `self.user` will be `None`.
There are several built-in options, but we recommend `QueryParamJWTAuthentication` for WebSocket authentication.
```python
WS_AUTHENTICATION = 'panther.authentications.QueryParamJWTAuthentication'
```
This will set `self.user` to a `UserModel` instance or `None`. The connection will be rejected if any exception occurs during authentication.
```python title="app/websockets.py" linenums="1"
from panther.websocket import GenericWebsocket
from app.authentications import MyAuthenticationClass
class MyWebSocket(GenericWebsocket):
auth = MyAuthenticationClass # Or use an async function
async def connect(self):
print(self.user)
...
```
> **Note:** When authentication is bypassed (no authorization header), `self.user` will be `None` and you must rely on permissions to check the user and their authorization.
### Permissions
You can implement your authorization logic using permission classes or functions. Any async function or class with an async `__call__` method can be used as a permission. Panther will call each permission (asynchronously).
- If any return `False`, the connection will be rejected.
Pass a list of permission callables to your WebSocket class.
- If you pass a single permission, it will be automatically wrapped in a list.
> Each permission must be async (either an async function or a class with an async `__call__`).
**Example Permission Function:**
```python title="app/permissions.py" linenums="1"
from panther.websocket import Websocket
async def custom_permission(request: Websocket) -> bool:
return True
```
**Example Permission Class:**
```python title="app/permissions.py" linenums="1"
from panther.websocket import Websocket
from panther.permissions import BasePermission
class CustomPermission(BasePermission):
async def __call__(self, request: Websocket) -> bool:
return True
```
```python title="app/websockets.py" linenums="1"
from panther.websocket import GenericWebsocket
from app.permissions import custom_permission, CustomPermission
class MyWebSocket(GenericWebsocket):
permissions = [custom_permission, CustomPermission] # Or just one
async def connect(self):
...
```
### Multiple Workers & Redis
- **Recommended:** For running WebSockets with multiple workers, add Redis to your configuration. [See Adding Redis](redis.md)
- **Without Redis:** If you do not use Redis but want to run WebSockets with multiple workers (e.g., with Gunicorn), use the `--preload` flag:
```shell
gunicorn -w 10 -k uvicorn.workers.UvicornWorker main:app --preload
```
- **Uvicorn Limitation:** WebSockets do not work properly when using uvicorn directly with the `--workers` flag (e.g., `uvicorn main:app --workers 4`). This is because each worker process maintains its own separate WebSocket connections, and there's no shared state between workers. Use Gunicorn with the `--preload` flag or add Redis for proper WebSocket support with multiple workers.
### Closing Connections
- **Within the WebSocket class:**
```python
from panther import status
await self.close(code=status.WS_1000_NORMAL_CLOSURE, reason='Closing connection')
```
- **Outside the WebSocket class:**
```python
from panther import status
from panther.websocket import close_websocket_connection
await close_websocket_connection(connection_id='7e82d57c9ec0478787b01916910a9f45', code=status.WS_1008_POLICY_VIOLATION, reason='Closing connection')
```
### Path Variables
You can define path variables in your WebSocket URL. These will be passed to the `connect()` method:
```python linenums="1"
from panther.websocket import GenericWebsocket
class UserWebsocket(GenericWebsocket):
async def connect(self, user_id: int, room_id: str):
await self.accept()
urls = {
'/ws/<user_id>/<room_id>/': UserWebsocket
}
```
---
## Example
### Example Client Code
**Here's a simple example using JavaScript:**
```js title="websocket.js" linenums="1"
const ws = new WebSocket('ws://127.0.0.1:8000/ws/book/');
ws.onopen = () => {
ws.send('Hello, server!');
};
ws.onmessage = (event) => {
console.log('Received:', event.data);
};
ws.onclose = () => {
console.log('Connection closed');
};
```
### Echo Example
**Full echo example with WebSocket:**
```python title="main.py" linenums="1"
from panther import Panther
from panther.app import GenericAPI
from panther.response import HTMLResponse
from panther.websocket import GenericWebsocket
class EchoWebsocket(GenericWebsocket):
async def connect(self, **kwargs):
await self.accept()
async def receive(self, data: str | bytes):
await self.send(data)
class MainPage(GenericAPI):
def get(self):
template = """
<input id="msg"><button onclick="s.send(msg.value)">Send</button>
<ul id="log"></ul>
<script>
const s = new WebSocket('ws://127.0.0.1:8000/ws');
s.onmessage = e => log.innerHTML += `<li><- ${msg.value}</li><li>-> ${e.data}</li>`;
</script>
"""
return HTMLResponse(template)
url_routing = {
'': MainPage,
'ws': EchoWebsocket,
}
app = Panther(__name__, configs=__name__, urls=url_routing)
```
**Run** with `panther run main:app` and **visit** `http://127.0.0.1:8000`.
---
## Tips & Notes
- **Connection Validation:** Always validate connections in `connect()` using headers or query parameters as needed.
- **Connection IDs:** Store `connection_id` if you need to send messages to clients outside the WebSocket class.
- **Multiple Workers:** Use Redis for scaling WebSockets across multiple workers.
- **Error Handling:** Implement error handling in your WebSocket methods for production use.
- **Security:** Always validate and sanitize incoming data.
---
Enjoy building with Panther WebSockets!
================================================
FILE: docs/mkdocs.yml
================================================
site_name: Panther
site_description: Fast & Friendly, Web Framework For Building Async APIs
site_url: https://pantherpy.github.io
repo_name: AliRn76/Panther
repo_url: https://github.com/AliRn76/Panther
edit_uri: edit/master/docs/
theme:
name: 'material'
custom_dir: docs
palette:
- scheme: 'default'
media: '(prefers-color-scheme: light)'
primary: 'blue'
toggle:
icon: 'material/lightbulb'
name: 'Switch to dark mode'
- scheme: 'slate'
media: '(prefers-color-scheme: dark)'
primary: 'blue'
toggle:
icon: 'material/lightbulb-outline'
name: 'Switch to light mode'
features:
- navigation.path
- navigation.footer
- content.code.copy
- content.code.annotate
icon:
repo: fontawesome/brands/github
nav:
- Introduction: 'index.md'
- First CRUD: 'first_crud.md'
- APIs: 'api.md'
- Serializers: 'serializer.md'
- URLs: 'urls.md'
- Models: 'model.md'
- File Handling: 'file_handling.md'
- Database: 'database.md'
- Query: 'query.md'
- Redis: 'redis.md'
- Events: 'events.md'
- Background Tasks: 'background_tasks.md'
- Authentications: 'authentications.md'
- Middlewares: 'middlewares.md'
- WebSocket: 'websocket.md'
- Single-File Application: 'single_file.md'
- Cross-Origin Resource Sharing (CORS): 'cors.md'
- Open API (Swagger): 'open_api.md'
- Advance Configs:
- User Model: 'user_model.md'
- Template Response: 'templates.md'
- Timezone: 'timezone.md'
- Auto-Reformatting (Ruff): 'ruff.md'
- Log Queries: 'log_queries.md'
- Admin Panel: 'admin_panel.md'
- Deployment: 'deployment.md'
- Roadmap: 'roadmap.md'
- Release Notes: 'release_notes.md'
markdown_extensions:
- admonition
- pymdownx.details
- pymdownx.superfences
- pymdownx.snippets
- pymdownx.tabbed:
alternate_style: true
- toc:
permalink: "#"
================================================
FILE: docs/pre-finetuning.jsonl
================================================
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What is Panther?"}, {"role": "assistant", "content": "Panther is a fast, async web framework for building APIs with Python 3.10+. It features a file-based database (PantherDB), MongoDB support, built-in authentication, permissions, OpenAPI docs, WebSocket support, background tasks, middleware, and more. Panther is designed for simplicity, speed, and developer productivity."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I install Panther?"}, {"role": "assistant", "content": "You can install Panther using pip:\n```bash\npip install panther\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What is the recommended project structure for a Panther app?"}, {"role": "assistant", "content": "A typical Panther project structure looks like this:\n```.\n├── app\n│ ├── apis.py\n│ ├── models.py\n│ ├── serializers.py\n│ └── urls.py\n└── core\n ├── configs.py\n └── urls.py\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I create a CRUD API for a Book model in Panther?"}, {"role": "assistant", "content": "1. Define the model in app/models.py:\n```python\nfrom panther.db import Model\n\nclass Book(Model):\n name: str\n author: str\n pages_count: int\n```\n2. Create a serializer in app/serializers.py:\n```python\nfrom panther.serializer import ModelSerializer\nfrom app.models import Book\n\nclass BookSerializer(ModelSerializer):\n class Config:\n model = Book\n fields = ['name', 'author', 'pages_count']\n```\n3. Define API endpoints in app/apis.py (example for function-based):\n```python\nfrom panther.app import API\nfrom panther.request import Request\nfrom panther.response import Response\nfrom app.models import Book\nfrom app.serializers import BookSerializer\n\n@API(input_model=BookSerializer, methods=['POST', 'GET'])\nasync def book_api(request: Request):\n if request.method == 'POST':\n body = request.validated_data\n book = await Book.insert_one(**body.model_dump())\n return Response(data=book)\n elif request.method == 'GET':\n books = await Book.find()\n return Response(data=books)\n```\n4. Add URLs in app/urls.py:\n```python\nfrom app.apis import book_api\nurls = {\n 'book/': book_api,\n}\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I enable authentication in Panther APIs?"}, {"role": "assistant", "content": "Set the auth parameter to an authentication class or function in your API definition. Panther will use this to validate the request. Example:\n```python\nfrom panther.app import API\nfrom panther.authentications import JWTAuthentication\n@API(auth=JWTAuthentication)\nasync def user_api(request):\n ...\n```\nIn your configs, set:\n```python\nAUTHENTICATION = 'panther.authentications.JWTAuthentication'\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What does the auth attribute do in Panther APIs?"}, {"role": "assistant", "content": "The auth parameter can be any async function or a class with an async __call__ method. If you set auth, Panther will use your specified authentication class or function. If you do not set auth, Panther will use the default AUTHENTICATION from your config only if the request contains an authorization header. If there is no authorization header, authentication is bypassed, request.user will be None and you must rely on permissions to check the user and their authorization."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I access the authenticated user in a Panther API?"}, {"role": "assistant", "content": "After successful authentication, the authenticated user is available as request.user in your API function or method. This will be an instance of your USER_MODEL (default: BaseUser) or None if authentication was bypassed."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What happens if authentication fails in Panther?"}, {"role": "assistant", "content": "If authentication fails, Panther automatically raises HTTP_401_UNAUTHORIZED and returns a 401 Unauthorized response to the client. You do not need to handle authentication errors manually."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "Can I use a custom authentication class with the auth attribute?"}, {"role": "assistant", "content": "Yes, you can pass a custom authentication class or function to the auth attribute. This will override the default AUTHENTICATION from your configs for that specific API. Example:\n```python\n@API(auth=CustomAuthentication)\nasync def my_api(request):\n ...\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What built-in authentication types does Panther support?"}, {"role": "assistant", "content": "Panther provides three built-in authentication classes, all based on JWT (JSON Web Token):\n1. JWTAuthentication: Reads the token from the 'Authorization' header as 'Bearer <token>'.\n2. QueryParamJWTAuthentication: Reads the token from the 'authorization' query parameter (useful for WebSockets).\n3. CookieJWTAuthentication: Reads the token from the 'access_token' cookie.\nYou can set the authentication class in your configs with the AUTHENTICATION variable."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I configure JWT settings in Panther?"}, {"role": "assistant", "content": "You can customize JWT behavior by setting the JWT_CONFIG variable in your configs. Example:\n```python\nfrom datetime import timedelta\nJWT_CONFIG = {\n 'key': SECRET_KEY,\n 'algorithm': 'HS256',\n 'life_time': timedelta(days=2),\n 'refresh_life_time': timedelta(days=10),\n}\n```\nThis controls the secret key, algorithm, access token lifetime, and refresh token lifetime."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I implement custom authentication in Panther?"}, {"role": "assistant", "content": "To implement custom authentication, create a class inheriting from panther.authentications.BaseAuthentication and implement the async __call__ method. Return a user instance or raise AuthenticationAPIError on failure. Example:\n```python\nfrom panther.authentications import BaseAuthentication\nclass CustomAuthentication(BaseAuthentication):\n async def __call__(self, request):\n # Your logic here\n return user\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What happens if I don't set auth in a Panther API?"}, {"role": "assistant", "content": "If you don't set auth, Panther will use the default AUTHENTICATION from your configs only if the request contains an authorization header. If there is no authorization header, authentication is bypassed, request.user will be None, and you must rely on permissions to check user authorization."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I define a model in Panther?"}, {"role": "assistant", "content": "To define a model, inherit from panther.db.Model and use type hints for fields. Example:\n```python\nfrom panther.db import Model\nclass User(Model):\n username: str\n age: int\n is_active: bool\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What field types does Panther support in models?"}, {"role": "assistant", "content": "Panther supports various field types including:\n- Basic types: str, int, float, bool\n- Complex types: list, dict, tuple\n- Date/time: datetime, date, time\n- Optional fields: type | None\n\nExample:\n```python\nfrom panther.db import Model\nfrom datetime import datetime\nclass Product(Model):\n name: str\n price: float\n is_available: bool\n created_at: datetime\n tags: list[str]\n description: str | str\n metadata: dict\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I create nested models in Panther?"}, {"role": "assistant", "content": "Use Pydantic BaseModel for nested fields or another Panther model. Example:\n```python\nfrom panther.db import Model\nfrom pydantic import BaseModel\n\nclass Address(BaseModel):\n street: str\n city: str\n zipcode: str\n country: str\n\nclass User(Model):\n username: str\n email: str\n address: Address # Nested model\n phone_numbers: list[str]\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I handle foreign keys in Panther models?"}, {"role": "assistant", "content": "Use another Panther model as a field type for foreign keys. Panther will handle the relationship. Example:\n```python\nfrom panther.db import Model\n\nclass Department(Model):\n name: str\n description: str\n\nclass Employee(Model):\n name: str\n email: str\n department: Department # Foreign key relationship\n manager: Employee # Self-referencing foreign key\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I create one-to-many relationships in Panther?"}, {"role": "assistant", "content": "Use a list of the related model for one-to-many relationships. Example:\n```python\nfrom panther.db import Model\n\nclass Author(Model):\n name: str\n email: str\n books: list['Book'] # One author can have many books\n\nclass Book(Model):\n title: str\n author: Author # Many books can belong to one author\n isbn: str\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I create many-to-many relationships in Panther?"}, {"role": "assistant", "content": "Use lists on both sides of the relationship for many-to-many. Example:\n```python\nfrom panther.db import Model\n\nclass Student(Model):\n name: str\n courses: list['Course'] # Many students can take many courses\n\nclass Course(Model):\n name: str\n students: list[Student] # Many courses can have many students\n instructor: str\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I add validation to model fields in Panther?"}, {"role": "assistant", "content": "Use Pydantic Field with validation rules. Example:\n```python\nfrom panther.db import Model\nfrom pydantic import Field, EmailStr\n\nclass User(Model):\n username: str = Field(min_length=3, max_length=50, regex=r'^[a-zA-Z0-9_]+$')\n email: EmailStr\n age: int = Field(ge=0, le=150)\n password: str = Field(min_length=8)\n bio: str | None = Field(max_length=500, default=None)\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I set default values in Panther models?"}, {"role": "assistant", "content": "Use default values in field definitions or Pydantic Field. Example:\n```python\nfrom panther.db import Model\nfrom panther.utils import timezone_now\nfrom pydantic import Field\nfrom datetime import datetime\n\nclass Post(Model):\n title: str\n content: str\n is_published: bool = False # Default value\n created_at: datetime = Field(default_factory=timezone_now)\n tags: list[str] = Field(default_factory=list)\n views: int = 0\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I create computed fields in Panther models?"}, {"role": "assistant", "content": "Use Pydantic computed fields with @property or @field_validator. Example:\n```python\nfrom panther.db import Model\nfrom pydantic import computed_field, field_validator\nfrom datetime import datetime\n\nclass Order(Model):\n items: list[dict]\n tax_rate: float = 0.1\n \n @computed_field\n def total_amount(self) -> float:\n subtotal = sum(item['price'] * item['quantity'] for item in self.items)\n return subtotal * (1 + self.tax_rate)\n \n @field_validator('items')\n def validate_items(cls, v):\n if not v:\n raise ValueError('Order must have at least one item')\n return v\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I handle model inheritance in Panther?"}, {"role": "assistant", "content": "You can inherit from other Panther models to create base classes. Example:\n```python\nfrom panther.db import Model\nfrom panther.utils import timezone_now\nfrom datetime import datetime\n\nclass BaseModel(Model):\n created_at: datetime = Field(default_factory=timezone_now)\n updated_at: datetime = Field(default_factory=timezone_now)\n is_active: bool = True\n\nclass User(BaseModel):\n username: str\n email: str\n\nclass Product(BaseModel):\n name: str\n price: float\n category: str\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I create complex nested structures in Panther models?"}, {"role": "assistant", "content": "Combine nested models, lists, and dictionaries for complex structures. Example:\n```python\nfrom panther.db import Model\nfrom pydantic import BaseModel\n\nclass Address(BaseModel):\n street: str\n city: str\n country: str\n\nclass ContactInfo(BaseModel):\n phone: str\n email: str\n website: str | None\n\nclass Company(Model):\n name: str\n address: Address\n contacts: ContactInfo\n departments: list[dict[str, str]]\n employees: list['Employee']\n metadata: dict[str, any]\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I handle model serialization in Panther?"}, {"role": "assistant", "content": "Panther models are automatically serializable. Use model_dump() for dictionaries and model_dump_json() for JSON. Example:\n```python\nfrom panther.db import Model\n\nclass User(Model):\n username: str\n email: str\n profile: dict\n\n# Serialization\nuser = User(username='john', email='john@example.com', profile={'age': 25})\nuser_dict = user.model_dump()\nuser_json = user.model_dump_json()\n\n# Deserialization\nuser_from_dict = User(**user_dict)\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I create model methods in Panther?"}, {"role": "assistant", "content": "Add methods to your model classes for business logic. Example:\n```python\nfrom panther.db import Model\nfrom datetime import datetime, timedelta\n\nclass User(Model):\n username: str\n email: str\n last_login: datetime\n is_active: bool = True\n \n def is_recently_active(self, days: int = 7) -> bool:\n return self.last_login > datetime.now() - timedelta(days=days)\n \n def get_display_name(self) -> str:\n return f'{self.username} ({self.email})'\n \n async def update_last_login(self):\n self.last_login = datetime.now()\n await self.save()\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I handle model validation errors in Panther?"}, {"role": "assistant", "content": "Use try-catch blocks to handle validation errors. Example:\n```python\nfrom panther.db import Model\nfrom pydantic import ValidationError\n\nclass User(Model):\n username: str = Field(min_length=3)\n email: str = Field(regex=r'^[^@]+@[^@]+\\.[^@]+$')\n age: int = Field(ge=0, le=150)\n\n# Handle validation\nasync def create_user(user_data: dict):\n try:\n user = User(**user_data)\n await user.save()\n return user\n except ValidationError as e:\n print(f'Validation error: {e.errors()}')\n raise\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What is the best practice for designing models in Panther?"}, {"role": "assistant", "content": "Use descriptive field names. Keep models focused and single-purpose. Use proper type hints. Handle validation with Pydantic Field. Use forward references for circular dependencies. Create base models for common fields. Keep nested structures manageable. Document complex relationships. Use computed fields for derived data. Handle errors gracefully."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I use serializers in Panther?"}, {"role": "assistant", "content": "Serializers transform data between your models and API requests/responses. Inherit from ModelSerializer and set the Config.model and fields. Example:\n```python\nfrom panther.serializer import ModelSerializer\nfrom app.models import User\nclass UserSerializer(ModelSerializer):\n class Config:\n model = User\n fields = ['username', 'age', 'is_active']\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I create a WebSocket endpoint in Panther?"}, {"role": "assistant", "content": "Inherit from GenericWebsocket and implement connect and receive methods. Register the class in your urls. Example:\n```python\nfrom panther.websocket import GenericWebsocket\nclass EchoWebsocket(GenericWebsocket):\n async def connect(self):\n await self.accept()\n async def receive(self, data):\n await self.send(data)\nurls = {\n 'ws/echo/': EchoWebsocket,\n}\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I implement custom permissions in Panther APIs?"}, {"role": "assistant", "content": "Create a class inheriting from panther.permissions.BasePermission and implement async classmethod authorization. Add your permission to the permissions list in your API. Example:\n```python\nfrom panther.permissions import BasePermission\nclass CustomPermission(BasePermission):\n @classmethod\n async def authorization(cls, request):\n return request.user.is_admin\nfrom panther.app import API\n@API(permissions=[CustomPermission])\nasync def admin_api(request):\n ...\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I document my Panther APIs with OpenAPI?"}, {"role": "assistant", "content": "Use the output_schema attribute with panther.openapi.OutputSchema in your API. Example:\n```python\nfrom panther import status\nfrom panther.app import API\nfrom panther.openapi import OutputSchema\nfrom app.serializers import UserSerializer\n@API(output_schema=OutputSchema(model=UserSerializer, status_code=status.HTTP_200_OK))\nasync def user_api():\n ...\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I use middleware in Panther?"}, {"role": "assistant", "content": "To use global middleware, add them to the MIDDLEWARES list in your configs. For per-API middleware, set the middlewares attribute on your API class or use the middlewares argument in the @API decorator."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I use background tasks in Panther?"}, {"role": "assistant", "content": "1. Set BACKGROUND_TASKS = True in your configs.\n2. Use BackgroundTask to schedule tasks:\n```python\nfrom panther.background_tasks import BackgroundTask\ndef do_something(name):\n print(f'Hello {name}')\nBackgroundTask(do_something, name='Ali').submit()\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How does caching work in Panther APIs?"}, {"role": "assistant", "content": "Caching in Panther allows you to store API responses for GET requests for a specified duration. Set the cache attribute to a timedelta value on your API. Panther will cache the response based on user or IP, request path, query parameters, and validated data."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I enable caching in Panther APIs?"}, {"role": "assistant", "content": "Set the cache attribute to a timedelta value on your API. Panther will cache GET responses for the specified duration. Example:\n```python\nfrom datetime import timedelta\nfrom panther.app import API\n@API(cache=timedelta(minutes=10))\nasync def my_api():\n ...\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How is the cache key built in Panther?"}, {"role": "assistant", "content": "The cache key in Panther is built from the user ID or IP, request path, a hash of the query parameters, and validated data. This ensures that cached responses are unique per user (or IP) and request parameters."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "Can I cache non-GET requests in Panther?"}, {"role": "assistant", "content": "No, Panther only caches GET requests. POST, PUT, PATCH, and DELETE requests are not cached as they modify data and should always return fresh responses."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I clear the cache in Panther?"}, {"role": "assistant", "content": "Panther automatically manages cache expiration based on the timedelta you set. You can also manually clear the cache by restarting your application or using Panther's cache management utilities if available."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What is the best practice for using cache in Panther?"}, {"role": "assistant", "content": "Use caching for GET endpoints that return data that doesn't change frequently to improve performance. Set appropriate cache durations based on how often your data changes. Be careful not to cache sensitive or user-specific data inappropriately."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How does caching work with authentication in Panther?"}, {"role": "assistant", "content": "When caching is enabled, Panther includes the user ID in the cache key if the user is authenticated, or the IP address if not authenticated. This ensures that cached responses are user-specific and secure."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "Can I disable caching for specific APIs in Panther?"}, {"role": "assistant", "content": "Yes, you can disable caching for specific APIs by setting cache=None or not setting the cache attribute at all. This will override any global cache settings for that specific API."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How does caching affect API performance in Panther?"}, {"role": "assistant", "content": "Caching significantly improves API performance by serving cached responses instead of executing the full request logic. This reduces database queries, computation time, and response latency, especially for frequently accessed data."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How does throttling work in Panther APIs?"}, {"role": "assistant", "content": "Throttling in Panther limits the number of requests a user or IP can make in a given time period. Set the throttling attribute on your API to an instance of panther.throttling.Throttle. If the rate is exceeded, Panther returns a 429 Too Many Requests error."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I enable throttling in Panther?"}, {"role": "assistant", "content": "Set the throttling attribute on your API to an instance of panther.throttling.Throttle. Example:\n```python\nfrom datetime import timedelta\nfrom panther.app import API\nfrom panther.throttling import Throttle\n@API(throttling=Throttle(rate=5, duration=timedelta(minutes=1)))\nasync def my_api():\n ...\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I set global throttling in Panther?"}, {"role": "assistant", "content": "Set the THROTTLING variable in your configs to a Throttle instance. This will apply to all APIs unless overridden by a per-API throttling setting. Example:\n```python\nfrom datetime import timedelta\nfrom panther.throttling import Throttle\nTHROTTLING = Throttle(rate=10, duration=timedelta(minutes=1))\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How does Panther handle requests that exceed the throttle limit?"}, {"role": "assistant", "content": "If a user exceeds the allowed number of requests within the throttle duration, Panther returns a 429 Too Many Requests response and bans the user for the duration."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "Can I customize how throttling keys are built in Panther?"}, {"role": "assistant", "content": "Yes, you can customize throttling behavior by subclassing panther.throttling.Throttle and overriding the build_cache_key method. Example:\n```python\nfrom panther.throttling import Throttle\nclass CustomThrottle(Throttle):\n def build_cache_key(self, request):\n # Custom logic here\n return ...\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What is the best practice for using throttling in Panther?"}, {"role": "assistant", "content": "Use throttling to protect your APIs from abuse. Set appropriate rates and durations for your use case. Consider using different throttling rules for different types of endpoints (e.g., stricter limits for authentication endpoints)."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do throttling and caching work together in Panther?"}, {"role": "assistant", "content": "Throttling and caching are independent features. Throttling checks happen before caching, so even if a response is cached, the request still counts toward the throttle limit. This prevents abuse even for cached endpoints."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "Can I use different throttling rules for different HTTP methods?"}, {"role": "assistant", "content": "Yes, you can set different throttling rules for different HTTP methods by creating separate API endpoints or using custom throttling logic in your build_cache_key method to include the HTTP method in the cache key."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How does throttling work with authentication in Panther?"}, {"role": "assistant", "content": "Throttling can be based on user ID (if authenticated) or IP address (if not authenticated). When a user is authenticated, throttling is typically per-user. When not authenticated, it's per-IP address to prevent abuse."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What happens if I don't set any throttling in Panther?"}, {"role": "assistant", "content": "If you don't set any throttling (neither global nor per-API), there are no rate limits on your APIs. This means users can make unlimited requests, which could lead to abuse or performance issues in production."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I enable and use the admin panel in Panther?"}, {"role": "assistant", "content": "1. Add the admin panel URLs in your main URL config:\n```python\nfrom panther.panel.urls import url_routing as panel_url_routing\nurl_routing = {\n 'panel/': panel_url_routing,\n}\n```\n2. Start your app and visit http://127.0.0.1:8000/panel/ in your browser."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How does the CORS middleware work in Panther?"}, {"role": "assistant", "content": "The CORS middleware in Panther automatically adds Cross-Origin Resource Sharing (CORS) headers to HTTP responses based on your configuration. It handles preflight (OPTIONS) requests and sets headers like Access-Control-Allow-Origin, Access-Control-Allow-Methods, and others."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I enable CORS in Panther?"}, {"role": "assistant", "content": "1. Set CORS config variables in your configs, e.g. ALLOW_ORIGINS, ALLOW_METHODS, etc.\n2. Add 'panther.middlewares.cors.CORSMiddleware' to your MIDDLEWARES list.\nExample:\n```python\nALLOW_ORIGINS = ['*']\nMIDDLEWARES = [\n 'panther.middlewares.cors.CORSMiddleware',\n]\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What CORS configuration options does Panther support?"}, {"role": "assistant", "content": "Panther supports several CORS configuration options:\n- ALLOW_ORIGINS: List of allowed origins (e.g., ['https://example.com'])\n- ALLOW_METHODS: List of allowed HTTP methods (e.g., ['GET', 'POST'])\n- ALLOW_HEADERS: List of allowed headers (e.g., ['Content-Type', 'Authorization'])\n- ALLOW_CREDENTIALS: Boolean for allowing credentials\n- EXPOSE_HEADERS: List of headers to expose\n- CORS_MAX_AGE: Integer for preflight cache duration"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I configure specific CORS settings in Panther?"}, {"role": "assistant", "content": "Set the CORS configuration variables in your configs. Example:\n```python\nALLOW_ORIGINS = ['https://example.com', 'https://app.example.com']\nALLOW_METHODS = ['GET', 'POST', 'PUT', 'DELETE']\nALLOW_HEADERS = ['Content-Type', 'Authorization', 'X-Custom-Header']\nALLOW_CREDENTIALS = True\nEXPOSE_HEADERS = ['X-Custom-Header']\nCORS_MAX_AGE = 3600\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What is the difference between ALLOW_ORIGINS=['*'] and specific origins?"}, {"role": "assistant", "content": "ALLOW_ORIGINS=['*'] allows requests from any origin, which is convenient for development but not recommended for production. Specific origins (e.g., ['https://example.com']) are more secure as they only allow requests from trusted domains."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I handle preflight requests with CORS in Panther?"}, {"role": "assistant", "content": "The CORS middleware automatically handles preflight (OPTIONS) requests. When a browser sends a preflight request, the middleware responds with the appropriate CORS headers based on your configuration. You don't need to handle OPTIONS requests manually."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "Can I use CORS with credentials in Panther?"}, {"role": "assistant", "content": "Yes, you can enable credentials by setting ALLOW_CREDENTIALS = True in your configs. However, when using credentials, you cannot use ALLOW_ORIGINS = ['*'] - you must specify specific origins for security reasons."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What is the best practice for CORS configuration in Panther?"}, {"role": "assistant", "content": "For development, you can use ALLOW_ORIGINS = ['*'] for convenience. For production, always specify exact origins, enable only necessary methods and headers, and set appropriate CORS_MAX_AGE. Consider security implications when allowing credentials."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I debug CORS issues in Panther?"}, {"role": "assistant", "content": "To debug CORS issues, check your browser's developer console for CORS error messages. Verify that your ALLOW_ORIGINS includes the correct origin, that ALLOW_METHODS includes the HTTP method being used, and that the CORS middleware is properly added to your MIDDLEWARES list."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "Can I apply CORS only to specific APIs in Panther?"}, {"role": "assistant", "content": "The CORS middleware applies globally when added to MIDDLEWARES. If you need different CORS settings for different APIs, you would need to create custom middleware or handle CORS headers manually in those specific endpoints."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I log database query performance in Panther?"}, {"role": "assistant", "content": "Set 'LOG_QUERIES = True' in your configs. Panther will log query execution times to 'logs/query.log'. Example:\n```python\nLOG_QUERIES = True\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I use environment variables in Panther configs?"}, {"role": "assistant", "content": "You can use Panther's 'load_env' utility to load variables from a .env file. Example:\n```python\nfrom panther.utils import load_env\nfrom pathlib import Path\nBASE_DIR = Path(__name__).resolve().parent\nenv = load_env(BASE_DIR / '.env')\nSECRET_KEY = env['SECRET_KEY']\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I define a model with nested fields or foreign keys in Panther?"}, {"role": "assistant", "content": "You can use pydantic.BaseModel for nested fields or another Panther model for foreign keys. Example:\n```python\nfrom panther.db import Model\nfrom pydantic import BaseModel\nclass Address(BaseModel):\n city: str\n zipcode: str\nclass User(Model):\n username: str\n address: Address\nclass Department(Model):\n name: str\nclass Employee(Model):\n name: str\n department: Department\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "What OpenAPI UI types does Panther support?"}, {"role": "assistant", "content": "Panther supports several OpenAPI UI types out of the box. You can use ScalarOpenAPI, SwaggerOpenAPI, RedocOpenAPI, RapiDocOpenAPI, and SpotlightOpenAPI. Import them from panther.openapi.views and register them in your URL routing. Example:\n```python\nfrom panther.openapi.views import ScalarOpenAPI, SwaggerOpenAPI, RedocOpenAPI\nurl_routing = {\n 'docs/scalar/': ScalarOpenAPI,\n 'docs/swagger/': SwaggerOpenAPI,\n 'docs/redoc/': RedocOpenAPI,\n}\n```\nYou can also use the bundled openapi_url_routing to include all UI types at once:\n```python\nfrom panther.openapi.urls import url_routing as openapi_url_routing\nurl_routing = {\n 'docs/': openapi_url_routing,\n}\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How does Panther determine response models and status codes for OpenAPI docs?"}, {"role": "assistant", "content": "Panther follows a specific logic to generate OpenAPI documentation:\n1. output_schema: Panther first looks for an output_schema attribute to generate the OpenAPI documentation. This is the recommended and most accurate way.\n2. output_model: If output_schema does not exist, Panther looks for an output_model attribute to generate the response type and attempts to extract the status code from your source code.\n3. Source Code Analysis: If neither is available, Panther tries to extract the response data and status code directly from your source code using static analysis with ast.\nFor best results, always specify output_schema in your APIs."}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How do I add pagination, filtering, and sorting to a Panther List API?"}, {"role": "assistant", "content": "Inherit from ListAPI and set the pagination, search_fields, filter_fields, and sort_fields attributes. Example:\n```python\nfrom panther.generics import ListAPI\nfrom panther.pagination import Pagination\nclass BookAPI(ListAPI):\n pagination = Pagination\n search_fields = ['name', 'author']\n filter_fields = ['name', 'author']\n sort_fields = ['name', 'pages_count']\n```"}]}
{"messages": [{"role": "system", "content": "You are learning a new Python backend framework called Panther. I will teach you each component step by step."}, {"role": "user", "content": "How does Panther handle error responses?"}, {"role": "assistant", "content": "Panther raises exceptions like AuthenticationAPIError, AuthorizationAPIError, and NotFoundAPIError for common error cases. You can raise these in your code to return standard error responses. Example:\n```python\nfrom panther.exceptions import NotFoundAPIError\nif not user:\n raise NotFoundAPIError('User not found')\n```"}]}
{"messages": [{"role": "system",
gitextract_j6xogok6/
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ ├── pull_request.yaml
│ └── release.yaml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── benchmark.txt
├── docs/
│ ├── docs/
│ │ ├── admin_panel.md
│ │ ├── api.md
│ │ ├── authentications.md
│ │ ├── background_tasks.md
│ │ ├── cors.md
│ │ ├── database.md
│ │ ├── deployment.md
│ │ ├── events.md
│ │ ├── file_handling.md
│ │ ├── first_crud.md
│ │ ├── index.md
│ │ ├── log_queries.md
│ │ ├── middlewares.md
│ │ ├── model.md
│ │ ├── open_api.md
│ │ ├── query.md
│ │ ├── redis.md
│ │ ├── release_notes.md
│ │ ├── roadmap.md
│ │ ├── ruff.md
│ │ ├── serializer.md
│ │ ├── single_file.md
│ │ ├── templates.md
│ │ ├── timezone.md
│ │ ├── urls.md
│ │ ├── user_model.md
│ │ └── websocket.md
│ ├── mkdocs.yml
│ └── pre-finetuning.jsonl
├── examples/
│ ├── blog/
│ │ ├── Dockerfile
│ │ ├── Readme.md
│ │ ├── app/
│ │ │ ├── apis.py
│ │ │ ├── models.py
│ │ │ ├── serializers.py
│ │ │ └── urls.py
│ │ ├── core/
│ │ │ ├── configs.py
│ │ │ └── urls.py
│ │ ├── docker-compose.yaml
│ │ ├── main.py
│ │ ├── requirements.txt
│ │ └── user/
│ │ ├── apis.py
│ │ ├── serializers.py
│ │ └── urls.py
│ ├── broadcast_websocket.py
│ ├── file_upload_example.py
│ └── streaming_response.py
├── panther/
│ ├── __init__.py
│ ├── _load_configs.py
│ ├── _utils.py
│ ├── app.py
│ ├── authentications.py
│ ├── background_tasks.py
│ ├── base_request.py
│ ├── base_websocket.py
│ ├── caching.py
│ ├── cli/
│ │ ├── __init__.py
│ │ ├── create_project_command.py
│ │ ├── create_user_command.py
│ │ ├── main.py
│ │ ├── monitor_command.py
│ │ ├── run_command.py
│ │ ├── template.py
│ │ └── utils.py
│ ├── configs.py
│ ├── db/
│ │ ├── __init__.py
│ │ ├── connections.py
│ │ ├── cursor.py
│ │ ├── models.py
│ │ ├── queries/
│ │ │ ├── __init__.py
│ │ │ ├── base_queries.py
│ │ │ ├── mongodb_queries.py
│ │ │ ├── pantherdb_queries.py
│ │ │ └── queries.py
│ │ └── utils.py
│ ├── events.py
│ ├── exceptions.py
│ ├── file_handler.py
│ ├── generics.py
│ ├── logging.py
│ ├── main.py
│ ├── middlewares/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── cors.py
│ │ └── monitoring.py
│ ├── openapi/
│ │ ├── __init__.py
│ │ ├── schemas.py
│ │ ├── templates/
│ │ │ ├── rapidoc.html
│ │ │ ├── redoc.html
│ │ │ ├── scalar.html
│ │ │ ├── spotlight.html
│ │ │ └── swagger.html
│ │ ├── urls.py
│ │ ├── utils.py
│ │ └── views.py
│ ├── pagination.py
│ ├── panel/
│ │ ├── __init__.py
│ │ ├── authentications.py
│ │ ├── middlewares.py
│ │ ├── permissions.py
│ │ ├── templates/
│ │ │ ├── base.html
│ │ │ ├── create.html
│ │ │ ├── create.js
│ │ │ ├── detail.html
│ │ │ ├── home.html
│ │ │ ├── home.js
│ │ │ ├── login.html
│ │ │ ├── sidebar.html
│ │ │ ├── table.html
│ │ │ └── table.js
│ │ ├── urls.py
│ │ ├── utils.py
│ │ └── views.py
│ ├── permissions.py
│ ├── request.py
│ ├── response.py
│ ├── routings.py
│ ├── serializer.py
│ ├── status.py
│ ├── test.py
│ ├── throttling.py
│ ├── utils.py
│ └── websocket.py
├── profiler.py
├── pyproject.toml
├── requirements.txt
├── ruff.toml
├── setup.py
└── tests/
├── __init__.py
├── __main__.py
├── sample_project/
│ ├── app/
│ │ ├── apis.py
│ │ ├── models.py
│ │ └── urls.py
│ └── core/
│ ├── configs.py
│ ├── events.py
│ └── urls.py
├── test_api_kwargs.py
├── test_authentication.py
├── test_background_task_event_loop_isolation.py
├── test_background_tasks.py
├── test_caching.py
├── test_cli.py
├── test_config.py
├── test_cors.py
├── test_database.py
├── test_database_advance.py
├── test_events.py
├── test_file.py
├── test_find_one_or_insert_race.py
├── test_generics.py
├── test_generics_search_regex_escape.py
├── test_middlewares.py
├── test_multipart.py
├── test_openapi.py
├── test_panel.py
├── test_permission.py
├── test_request.py
├── test_request_isolation.py
├── test_response.py
├── test_response_output_model.py
├── test_routing.py
├── test_run.py
├── test_serializer.py
├── test_status.py
├── test_throttling.py
├── test_utils.py
└── test_websockets.py
SYMBOL INDEX (1592 symbols across 100 files)
FILE: examples/blog/app/apis.py
class PostAPI (line 19) | class PostAPI(ListAPI, CreateAPI):
method get_query (line 27) | async def get_query(self, request: Request, **kwargs):
class PostDetailAPI (line 32) | class PostDetailAPI(UpdateAPI, RetrieveAPI, DeleteAPI):
method get_instance (line 36) | async def get_instance(self, request: Request, **kwargs) -> Model:
class CommentAPI (line 40) | class CommentAPI(GenericAPI):
method post (line 44) | async def post(self, request: Request, post_id: str):
FILE: examples/blog/app/models.py
class Post (line 4) | class Post(Model):
class Comment (line 9) | class Comment(Model):
FILE: examples/blog/app/serializers.py
class PostSerializer (line 8) | class PostSerializer(ModelSerializer):
class Config (line 9) | class Config:
class PostOutputSerializer (line 14) | class PostOutputSerializer(ModelSerializer):
class Config (line 15) | class Config:
class PostDetailOutputSerializer (line 20) | class PostDetailOutputSerializer(ModelSerializer):
class Config (line 21) | class Config:
method to_response (line 25) | async def to_response(self, instance: Any, data: dict) -> dict:
class CommentSerializer (line 30) | class CommentSerializer(ModelSerializer):
class Config (line 31) | class Config:
FILE: examples/blog/user/apis.py
class RegisterAPI (line 12) | class RegisterAPI(GenericAPI):
method post (line 16) | async def post(self, request: Request, **kwargs):
class LoginAPI (line 27) | class LoginAPI(GenericAPI):
method post (line 30) | async def post(self, request: Request):
FILE: examples/blog/user/serializers.py
class UserSerializer (line 5) | class UserSerializer(ModelSerializer):
class Config (line 6) | class Config:
class UserOutputSerializer (line 11) | class UserOutputSerializer(ModelSerializer):
class Config (line 12) | class Config:
FILE: examples/broadcast_websocket.py
class User (line 8) | class User(Model):
class BroadcastWebsocket (line 20) | class BroadcastWebsocket(GenericWebsocket):
method connect (line 21) | async def connect(self, **kwargs):
method receive (line 25) | async def receive(self, data: str | bytes):
class MainPage (line 31) | class MainPage(GenericAPI):
method get (line 32) | def get(self):
FILE: examples/file_upload_example.py
class Document (line 29) | class Document(Model):
class Profile (line 36) | class Profile(Model):
class DocumentUploadSerializer (line 43) | class DocumentUploadSerializer(ModelSerializer):
class Config (line 44) | class Config:
class ProfileUploadSerializer (line 50) | class ProfileUploadSerializer(ModelSerializer):
class Config (line 51) | class Config:
function upload_document (line 59) | async def upload_document(request: Request):
function upload_profile_image (line 108) | async def upload_profile_image(request: Request):
function list_documents (line 142) | async def list_documents(request: Request):
function get_document (line 164) | async def get_document(document_id: str):
FILE: examples/streaming_response.py
class MainPage (line 10) | class MainPage(GenericAPI):
method generator (line 11) | def generator(self):
method get (line 17) | def get(self):
class ProgressStream (line 21) | class ProgressStream(GenericAPI):
method generator (line 22) | def generator(self):
method get (line 37) | def get(self):
class LogStream (line 41) | class LogStream(GenericAPI):
method generator (line 42) | def generator(self):
method get (line 64) | def get(self):
class ChatStream (line 68) | class ChatStream(GenericAPI):
method generator (line 69) | def generator(self):
method get (line 90) | def get(self):
class ErrorHandlingStream (line 94) | class ErrorHandlingStream(GenericAPI):
method generator (line 95) | def generator(self):
method get (line 110) | def get(self):
FILE: panther/__init__.py
function version (line 6) | def version():
FILE: panther/_load_configs.py
function load_configs_module (line 48) | def load_configs_module(module_name: str, /) -> dict:
function load_redis (line 60) | def load_redis(_configs: dict, /) -> None:
function load_timezone (line 75) | def load_timezone(_configs: dict, /) -> None:
function load_templates_dir (line 80) | def load_templates_dir(_configs: dict, /) -> None:
function load_database (line 98) | def load_database(_configs: dict, /) -> None:
function load_secret_key (line 122) | def load_secret_key(_configs: dict, /) -> None:
function load_throttling (line 127) | def load_throttling(_configs: dict, /) -> None:
function load_user_model (line 132) | def load_user_model(_configs: dict, /) -> None:
function load_log_queries (line 138) | def load_log_queries(_configs: dict, /) -> None:
function load_middlewares (line 143) | def load_middlewares(_configs: dict, /) -> None:
function load_auto_reformat (line 206) | def load_auto_reformat(_configs: dict, /) -> None:
function load_background_tasks (line 211) | def load_background_tasks(_configs: dict, /) -> None:
function load_other_configs (line 217) | def load_other_configs(_configs: dict, /) -> None:
function load_urls (line 224) | def load_urls(_configs: dict, /, urls: dict | None) -> None:
function load_authentication_class (line 257) | def load_authentication_class(_configs: dict, /) -> None:
function load_jwt_config (line 268) | def load_jwt_config(_configs: dict, /) -> None:
function load_websocket_connections (line 290) | def load_websocket_connections():
function check_endpoints_inheritance (line 304) | def check_endpoints_inheritance():
function _exception_handler (line 316) | def _exception_handler(field: str, error: str | Exception) -> PantherError:
function _deprecated_warning (line 320) | def _deprecated_warning(field: str, message: str):
FILE: panther/_utils.py
function import_class (line 26) | def import_class(dotted_path: str, /) -> type[Any]:
function read_multipart_form_data (line 51) | def read_multipart_form_data(boundary: str, body: bytes) -> dict:
function is_function_async (line 95) | def is_function_async(func: Callable) -> bool:
function traceback_message (line 103) | def traceback_message(exception: Exception) -> str:
function reformat_code (line 108) | def reformat_code(base_dir):
function check_function_type_endpoint (line 116) | def check_function_type_endpoint(endpoint: types.FunctionType):
function check_class_type_endpoint (line 124) | def check_class_type_endpoint(endpoint: Callable):
function async_next (line 135) | def async_next(iterator: Iterator):
function to_async_generator (line 148) | async def to_async_generator(generator: Generator) -> AsyncGenerator:
function validate_api_auth (line 156) | def validate_api_auth(auth):
function validate_api_permissions (line 197) | def validate_api_permissions(permissions):
function check_api_deprecations (line 238) | def check_api_deprecations(cache, **kwargs):
function detect_mime_type (line 337) | def detect_mime_type(file_path: str | Path) -> str:
FILE: panther/app.py
class API (line 40) | class API:
method __init__ (line 58) | def __init__(
method __call__ (line 88) | def __call__(self, func):
method handle_endpoint (line 116) | async def handle_endpoint(self, request: Request) -> Response:
class GenericAPI (line 174) | class GenericAPI:
method __init_subclass__ (line 190) | def __init_subclass__(cls, **kwargs):
method get (line 205) | async def get(self, *args, **kwargs):
method post (line 208) | async def post(self, *args, **kwargs):
method put (line 211) | async def put(self, *args, **kwargs):
method patch (line 214) | async def patch(self, *args, **kwargs):
method delete (line 217) | async def delete(self, *args, **kwargs):
method call_method (line 220) | async def call_method(self, request: Request):
FILE: panther/authentications.py
class BaseAuthentication (line 28) | class BaseAuthentication:
method __call__ (line 30) | async def __call__(self, request: Request | Websocket):
method exception (line 36) | def exception(cls, message: str | Exception, /) -> type[Authentication...
class JWTAuthentication (line 41) | class JWTAuthentication(BaseAuthentication):
method __call__ (line 53) | async def __call__(self, request: Request | Websocket) -> Model | None:
method get_authorization_header (line 70) | def get_authorization_header(cls, request: Request | Websocket) -> lis...
method get_token (line 77) | def get_token(cls, auth_header):
method decode_jwt (line 96) | async def decode_jwt(cls, token: str) -> dict:
method get_user (line 108) | async def get_user(cls, payload: dict) -> Model:
method encode_jwt (line 122) | def encode_jwt(cls, user_id: str, token_type: Literal['access', 'refre...
method login (line 143) | async def login(cls, user) -> dict:
method logout (line 151) | async def logout(cls, user) -> None:
method refresh (line 157) | async def refresh(cls, user):
method revoke_token_in_cache (line 174) | async def revoke_token_in_cache(cls, token: str, exp: int) -> None:
method is_token_revoked (line 184) | async def is_token_revoked(cls, token: str) -> bool:
class QueryParamJWTAuthentication (line 190) | class QueryParamJWTAuthentication(JWTAuthentication):
method get_authorization_header (line 198) | def get_authorization_header(cls, request: Request | Websocket) -> str...
method get_token (line 204) | def get_token(cls, auth_header) -> str:
class CookieJWTAuthentication (line 208) | class CookieJWTAuthentication(JWTAuthentication):
method __call__ (line 215) | async def __call__(self, request: Request | Websocket) -> Model:
method get_authorization_header (line 223) | def get_authorization_header(cls, request: Request | Websocket) -> str...
method get_token (line 229) | def get_token(cls, auth_header) -> str:
FILE: panther/background_tasks.py
function register_application_event_loop (line 38) | def register_application_event_loop(loop: asyncio.AbstractEventLoop | No...
function get_application_event_loop (line 43) | def get_application_event_loop() -> asyncio.AbstractEventLoop | None:
class WeekDay (line 55) | class WeekDay(Enum):
class BackgroundTask (line 65) | class BackgroundTask:
method __init__ (line 73) | def __init__(self, func: 'Callable', *args: Any, **kwargs: Any):
method interval (line 84) | def interval(self, interval: int, /) -> Self:
method every_seconds (line 89) | def every_seconds(self, seconds: int = 1, /) -> Self:
method every_minutes (line 95) | def every_minutes(self, minutes: int = 1, /) -> Self:
method every_hours (line 101) | def every_hours(self, hours: int = 1, /) -> Self:
method every_days (line 107) | def every_days(self, days: int = 1, /) -> Self:
method every_weeks (line 113) | def every_weeks(self, weeks: int = 1, /) -> Self:
method on (line 119) | def on(self, day_of_week: WeekDay, /) -> Self:
method at (line 126) | def at(self, _time: datetime.time, /) -> Self:
method _should_wait (line 138) | def _should_wait(self) -> bool:
method __call__ (line 165) | def __call__(self) -> bool:
method submit (line 195) | def submit(self) -> Self:
class BackgroundTasks (line 201) | class BackgroundTasks(Singleton):
method __init__ (line 204) | def __init__(self):
method add_task (line 208) | def add_task(self, task: BackgroundTask):
method initialize (line 219) | def initialize(self):
method _run_task (line 225) | def _run_task(self, task: BackgroundTask):
method _run_tasks (line 232) | def _run_tasks(self):
method _is_instance_of_task (line 241) | def _is_instance_of_task(cls, task: Any, /) -> bool:
FILE: panther/base_request.py
class Headers (line 11) | class Headers:
method __init__ (line 33) | def __init__(self, headers: list):
method __getattr__ (line 37) | def __getattr__(self, item: str):
method __getitem__ (line 42) | def __getitem__(self, item: str):
method __str__ (line 47) | def __str__(self):
method __contains__ (line 51) | def __contains__(self, item):
method __dict__ (line 57) | def __dict__(self):
method get_cookies (line 60) | def get_cookies(self) -> dict:
class Address (line 78) | class Address:
method __init__ (line 79) | def __init__(self, ip, port):
method __str__ (line 83) | def __str__(self):
class BaseRequest (line 87) | class BaseRequest:
method __init__ (line 88) | def __init__(self, scope: dict, receive: Callable, send: Callable):
method headers (line 98) | def headers(self) -> Headers:
method query_params (line 104) | def query_params(self) -> dict:
method path (line 110) | def path(self) -> str:
method server (line 114) | def server(self) -> Address:
method client (line 118) | def client(self) -> Address:
method http_version (line 122) | def http_version(self) -> str:
method scheme (line 126) | def scheme(self) -> str:
method collect_path_variables (line 129) | def collect_path_variables(self, found_path: str):
method clean_parameters (line 136) | def clean_parameters(self, function_annotations: dict) -> dict:
FILE: panther/base_websocket.py
class PubSub (line 25) | class PubSub:
method __init__ (line 26) | def __init__(self, manager: SyncManager):
method subscribe (line 30) | def subscribe(self):
method publish (line 35) | def publish(self, msg):
class WebsocketConnections (line 40) | class WebsocketConnections(Singleton):
method __init__ (line 41) | def __init__(self, pubsub_connection: Redis | SyncManager):
method __call__ (line 49) | async def __call__(self):
method _handle_received_message (line 85) | async def _handle_received_message(self, received_message):
method publish (line 105) | async def publish(self, connection_id: str, action: Literal['send', 'c...
method listen (line 113) | async def listen(self, connection: Websocket):
method listen_connection (line 146) | async def listen_connection(self, connection: Websocket):
method connection_accepted (line 162) | async def connection_accepted(self, connection: Websocket) -> None:
method connection_closed (line 170) | async def connection_closed(self, connection: Websocket, from_server: ...
method start (line 180) | async def start(self):
method handle_authentication (line 196) | async def handle_authentication(cls, connection: Websocket):
method handle_permissions (line 207) | async def handle_permissions(cls, connection: Websocket):
class Websocket (line 221) | class Websocket(BaseRequest):
method __init_subclass__ (line 228) | def __init_subclass__(cls, **kwargs):
method connect (line 232) | async def connect(self, **kwargs) -> None:
method receive (line 235) | async def receive(self, data: str | bytes) -> None:
method accept (line 238) | async def accept(self, subprotocol: str | None = None, headers: dict |...
method send (line 242) | async def send(self, data: any = None) -> None:
method send_text (line 251) | async def send_text(self, text_data: str) -> None:
method send_bytes (line 254) | async def send_bytes(self, bytes_data: bytes) -> None:
method close (line 257) | async def close(self, code: int = status.WS_1000_NORMAL_CLOSURE, reaso...
method connection_id (line 262) | def connection_id(self) -> str:
method is_connected (line 268) | def is_connected(self) -> bool:
method is_rejected (line 272) | def is_rejected(self) -> bool:
method change_state (line 275) | def change_state(self, state: Literal['Accepted', 'Closed', 'Rejected'...
FILE: panther/caching.py
function api_cache_key (line 18) | def api_cache_key(request: Request, duration: timedelta | None = None) -...
function get_response_from_cache (line 30) | async def get_response_from_cache(*, request: Request, duration: timedel...
function set_response_in_cache (line 48) | async def set_response_in_cache(*, request: Request, response: Response,...
FILE: panther/cli/create_project_command.py
class CreateProject (line 27) | class CreateProject:
method __init__ (line 33) | def __init__(self):
method create (line 84) | def create(self, args: list) -> None:
method _create_file (line 124) | def _create_file(self, *, path: str, data: str):
method collect_creation_data (line 155) | def collect_creation_data(self):
method ask (line 178) | def ask(
method progress (line 197) | def progress(self, step: int, /, extra_rows: int = 0):
method _to_boolean (line 208) | def _to_boolean(cls, _input: str) -> bool:
method _is_boolean (line 212) | def _is_boolean(cls, _input: str) -> bool:
method _to_str (line 216) | def _to_str(cls, _input: bool) -> str:
method _check_all_directories (line 220) | def _check_all_directories(cls, base_directory: str, return_error: boo...
FILE: panther/cli/create_user_command.py
function get_password (line 15) | def get_password(prompt: str) -> str:
function get_username (line 72) | def get_username(prompt: str, user_model: BaseUser, username_field: str):
function load_application_file (line 81) | def load_application_file(file_location: str):
function create_user (line 92) | def create_user(args) -> None:
FILE: panther/cli/main.py
function shell (line 14) | def shell(args) -> None:
function version (line 33) | def version() -> None:
function start (line 37) | def start() -> None:
FILE: panther/cli/monitor_command.py
class Monitoring (line 26) | class Monitoring:
method __init__ (line 27) | def __init__(self):
method monitor (line 31) | def monitor(self) -> None:
method initialize (line 62) | def initialize(self) -> str:
method generate_table (line 84) | def generate_table(self) -> Panel:
method update_rows (line 106) | def update_rows(self, *args, **kwargs):
method _clean_response_time (line 113) | def _clean_response_time(cls, response_time: str) -> str:
FILE: panther/cli/run_command.py
function run (line 9) | def run(args: list[str]) -> None:
FILE: panther/cli/utils.py
function import_error (line 82) | def import_error(message: str | Exception, package: str | None = None) -...
function cli_error (line 89) | def cli_error(message: str | Exception) -> None:
function cli_warning (line 94) | def cli_warning(message: str | Exception, hint: str = None) -> None:
function cli_info (line 100) | def cli_info(message: str) -> None:
function print_help_message (line 105) | def print_help_message():
function print_info (line 109) | def print_info(config: Config):
FILE: panther/configs.py
class JWTConfig (line 11) | class JWTConfig:
method __init__ (line 12) | def __init__(
method __eq__ (line 31) | def __eq__(self, other):
class QueryObservable (line 40) | class QueryObservable:
method observe (line 44) | def observe(cls, observer):
method update (line 48) | def update(cls):
class Config (line 54) | class Config:
method refresh (line 79) | def refresh(self):
method vars (line 97) | def vars(self) -> dict[str, typing.Any]:
method __setattr__ (line 101) | def __setattr__(self, key, value):
method __getattr__ (line 106) | def __getattr__(self, item: str):
method __setitem__ (line 112) | def __setitem__(self, key, value):
method __getitem__ (line 115) | def __getitem__(self, item):
FILE: panther/db/connections.py
class BaseDatabaseConnection (line 24) | class BaseDatabaseConnection:
method __init__ (line 25) | def __init__(self, *args, **kwargs):
method init (line 30) | def init(self, *args, **kwargs):
method session (line 35) | def session(self):
class MongoDBConnection (line 39) | class MongoDBConnection(BaseDatabaseConnection):
method init (line 40) | def init(
method session (line 73) | def session(self):
method client (line 77) | def client(self):
class PantherDBConnection (line 81) | class PantherDBConnection(BaseDatabaseConnection):
method init (line 82) | def init(self, path: str | None = None, encryption: bool = False):
method session (line 94) | def session(self):
method client (line 98) | def client(self):
class DatabaseConnection (line 102) | class DatabaseConnection(Singleton):
method session (line 104) | def session(self):
method is_defined (line 108) | def is_defined(self):
method client (line 112) | def client(self):
class RedisConnection (line 116) | class RedisConnection(Singleton, _Redis):
method __init__ (line 119) | def __init__(
method sync_ping (line 137) | def sync_ping(self):
method execute_command (line 142) | async def execute_command(self, *args, **options):
method create_connection_for_websocket (line 148) | def create_connection_for_websocket(self) -> _Redis:
FILE: panther/db/cursor.py
class Cursor (line 23) | class Cursor(_Cursor):
method __init__ (line 26) | def __init__(self, collection, *args, cls=None, **kwargs):
method __aiter__ (line 36) | def __aiter__(self) -> Self:
method __iter__ (line 39) | def __iter__(self) -> Self:
method next (line 42) | async def next(self) -> Self:
method __anext__ (line 45) | async def __anext__(self) -> Self:
method __next__ (line 51) | def __next__(self) -> Self:
method __getitem__ (line 57) | def __getitem__(self, index: int | slice) -> Cursor[Self] | Self:
FILE: panther/db/models.py
function validate_object_id (line 26) | def validate_object_id(value, handler):
class Model (line 43) | class Model(PydanticBaseModel, Query):
method __init_subclass__ (line 44) | def __init_subclass__(cls, **kwargs):
method _id (line 52) | def _id(self):
class BaseUser (line 63) | class BaseUser(Model):
method insert_one (line 72) | def insert_one(cls, _document: dict | None = None, /, **kwargs) -> Self:
method login (line 76) | async def login(self) -> dict:
method refresh_tokens (line 81) | async def refresh_tokens(self) -> dict:
method logout (line 85) | async def logout(self) -> dict:
method set_password (line 88) | async def set_password(self, password: str):
method check_password (line 101) | def check_password(self, password: str) -> bool:
FILE: panther/db/queries/base_queries.py
class BaseQuery (line 31) | class BaseQuery:
method _merge (line 33) | def _merge(cls, *args, is_mongo: bool = False) -> dict:
method _clean_error_message (line 38) | def _clean_error_message(cls, validation_error: ValidationError, is_up...
method _validate_data (line 47) | def _validate_data(cls, *, data: dict, is_updating: bool = False):
method _get_annotation_type (line 56) | def _get_annotation_type(cls, annotation: Any) -> type | None:
method _create_list (line 86) | async def _create_list(cls, field_type: type, value: Any) -> Any:
method _create_field (line 119) | async def _create_field(cls, model: type, field_name: str, value: Any)...
method _create_model_instance (line 181) | async def _create_model_instance(cls, document: dict, is_updating: boo...
method _clean_value (line 197) | async def _clean_value(cls, value: Any) -> dict[str, Any] | list[Any]:
method _extract_type (line 223) | async def _extract_type(cls, field_name: str) -> Any:
method _process_document (line 240) | async def _process_document(cls, document):
method find_one (line 264) | async def find_one(cls, *args, **kwargs) -> Self | None:
method find (line 269) | async def find(cls, *args, **kwargs) -> list[Self] | Cursor:
method first (line 274) | async def first(cls, *args, **kwargs) -> Self | None:
method last (line 279) | async def last(cls, *args, **kwargs):
method aggregate (line 284) | async def aggregate(cls, *args, **kwargs) -> Iterator[dict]:
method count (line 290) | async def count(cls, *args, **kwargs) -> int:
method insert_one (line 296) | async def insert_one(cls, *args, **kwargs) -> Self:
method insert_many (line 301) | async def insert_many(cls, *args, **kwargs) -> list[Self]:
method delete (line 306) | async def delete(self) -> None:
method delete_one (line 311) | async def delete_one(cls, *args, **kwargs) -> bool:
method delete_many (line 316) | async def delete_many(cls, *args, **kwargs) -> int:
method update (line 321) | async def update(self, *args, **kwargs) -> None:
method update_one (line 326) | async def update_one(cls, *args, **kwargs) -> bool:
method update_many (line 331) | async def update_many(cls, *args, **kwargs) -> int:
FILE: panther/db/queries/mongodb_queries.py
class BaseMongoDBQuery (line 33) | class BaseMongoDBQuery(BaseQuery):
method _merge (line 35) | def _merge(cls, *args, is_mongo: bool = True) -> dict:
method find_one (line 45) | async def find_one(cls, _filter: dict | None = None, /, **kwargs) -> S...
method find (line 51) | async def find(cls, _filter: dict | None = None, /, **kwargs) -> Cursor:
method first (line 55) | async def first(cls, _filter: dict | None = None, /, **kwargs) -> Self...
method last (line 62) | async def last(cls, _filter: dict | None = None, /, **kwargs) -> Self ...
method aggregate (line 69) | async def aggregate(cls, pipeline: Sequence[dict]) -> Iterable[dict]:
method count (line 74) | async def count(cls, _filter: dict | None = None, /, **kwargs) -> int:
method insert_one (line 79) | async def insert_one(cls, document: dict) -> Self:
method insert_many (line 84) | async def insert_many(cls, documents: Iterable[dict]) -> list[Self]:
method delete_one (line 99) | async def delete_one(cls, _filter: dict | None = None, /, **kwargs) ->...
method delete_many (line 104) | async def delete_many(cls, _filter: dict | None = None, /, **kwargs) -...
method update_one (line 110) | async def update_one(cls, _filter: dict, _update: dict | None = None, ...
method update_many (line 144) | async def update_many(cls, _filter: dict, _update: dict | None = None,...
FILE: panther/db/queries/pantherdb_queries.py
class BasePantherDBQuery (line 22) | class BasePantherDBQuery(BaseQuery):
method _merge (line 24) | def _merge(cls, *args, is_mongo: bool = False) -> dict:
method find_one (line 29) | async def find_one(cls, _filter: dict | None = None, /, **kwargs) -> S...
method find (line 35) | async def find(cls, _filter: dict | None = None, /, **kwargs) -> Cursor:
method first (line 42) | async def first(cls, _filter: dict | None = None, /, **kwargs) -> Self...
method last (line 48) | async def last(cls, _filter: dict | None = None, /, **kwargs) -> Self ...
method aggregate (line 54) | async def aggregate(cls, *args, **kwargs):
method count (line 60) | async def count(cls, _filter: dict | None = None, /, **kwargs) -> int:
method insert_one (line 65) | async def insert_one(cls, document: dict) -> Self:
method insert_many (line 70) | async def insert_many(cls, documents: Iterable[dict]) -> list[Self]:
method delete_one (line 75) | async def delete_one(cls, _filter: dict | None = None, /, **kwargs) ->...
method delete_many (line 79) | async def delete_many(cls, _filter: dict | None = None, /, **kwargs) -...
method update_one (line 84) | async def update_one(cls, _filter: dict, _update: dict | None = None, ...
method update_many (line 107) | async def update_many(cls, _filter: dict, _update: dict | None = None,...
FILE: panther/db/queries/queries.py
function _is_duplicate_key_error (line 27) | def _is_duplicate_key_error(error: Exception) -> bool:
class Query (line 35) | class Query(BaseQuery):
method __init_subclass__ (line 36) | def __init_subclass__(cls, **kwargs):
method _reload_bases (line 40) | def _reload_bases(cls, parent):
method find_one (line 56) | async def find_one(cls, _filter: dict | None = None, /, **kwargs) -> S...
method find (line 76) | async def find(cls, _filter: dict | None = None, /, **kwargs) -> Panth...
method first (line 96) | async def first(cls, _filter: dict | None = None, /, **kwargs) -> Self...
method last (line 116) | async def last(cls, _filter: dict | None = None, /, **kwargs) -> Self ...
method aggregate (line 136) | async def aggregate(cls, pipeline: Sequence[dict]) -> Iterable[dict]:
method count (line 162) | async def count(cls, _filter: dict | None = None, /, **kwargs) -> int:
method insert_one (line 183) | async def insert_one(cls, _document: dict | None = None, /, **kwargs) ...
method insert_many (line 225) | async def insert_many(cls, documents: Iterable[dict]) -> list[Self]:
method delete (line 246) | async def delete(self) -> None:
method delete_one (line 264) | async def delete_one(cls, _filter: dict | None = None, /, **kwargs) ->...
method delete_many (line 284) | async def delete_many(cls, _filter: dict | None = None, /, **kwargs) -...
method update (line 304) | async def update(self, _update: dict | None = None, /, **kwargs) -> None:
method update_one (line 327) | async def update_one(cls, _filter: dict, _update: dict | None = None, ...
method update_many (line 347) | async def update_many(cls, _filter: dict, _update: dict | None = None,...
method all (line 366) | async def all(cls) -> list[Self] | Cursor:
method find_one_or_insert (line 380) | async def find_one_or_insert(cls, _filter: dict | None = None, /, **kw...
method find_one_or_raise (line 410) | async def find_one_or_raise(cls, _filter: dict | None = None, /, **kwa...
method exists (line 429) | async def exists(cls, _filter: dict | None = None, /, **kwargs) -> bool:
method save (line 446) | async def save(self) -> None:
method reload (line 472) | async def reload(self):
FILE: panther/db/utils.py
function log_query (line 15) | def log_query(func):
function check_connection (line 29) | def check_connection(func):
function prepare_id_for_query (line 42) | def prepare_id_for_query(*args, is_mongo: bool = False):
function _convert_to_object_id (line 54) | def _convert_to_object_id(_id):
FILE: panther/events.py
class Event (line 11) | class Event(Singleton):
method startup (line 16) | def startup(cls, func):
method shutdown (line 25) | def shutdown(cls, func):
method run_startups (line 34) | async def run_startups(cls):
method run_shutdowns (line 45) | def run_shutdowns(cls):
method clear (line 63) | def clear(cls):
FILE: panther/exceptions.py
class PantherError (line 4) | class PantherError(Exception):
class DatabaseError (line 8) | class DatabaseError(Exception):
class BaseError (line 12) | class BaseError(Exception):
method __init__ (line 16) | def __init__(self, detail: str | dict | list = None, status_code: int ...
class APIError (line 22) | class APIError(BaseError):
class WebsocketError (line 27) | class WebsocketError(BaseError):
class RedirectAPIError (line 32) | class RedirectAPIError(APIError):
method __init__ (line 33) | def __init__(self, url: str, status_code: int = status.HTTP_302_FOUND):
class BadRequestAPIError (line 37) | class BadRequestAPIError(APIError):
class AuthenticationAPIError (line 42) | class AuthenticationAPIError(APIError):
class AuthorizationAPIError (line 47) | class AuthorizationAPIError(APIError):
class NotFoundAPIError (line 52) | class NotFoundAPIError(APIError):
class MethodNotAllowedAPIError (line 57) | class MethodNotAllowedAPIError(APIError):
class UnprocessableEntityError (line 62) | class UnprocessableEntityError(APIError):
class UpgradeRequiredError (line 67) | class UpgradeRequiredError(APIError):
class ThrottlingAPIError (line 72) | class ThrottlingAPIError(APIError):
class InvalidPathVariableAPIError (line 77) | class InvalidPathVariableAPIError(APIError):
method __init__ (line 78) | def __init__(self, value: str, variable_type: type):
FILE: panther/file_handler.py
class File (line 12) | class File(BaseModel):
method __init__ (line 19) | def __init__(self, **data):
method __enter__ (line 27) | def __enter__(self):
method __exit__ (line 33) | def __exit__(self, exc_type, exc_value, traceback):
method read (line 38) | def read(self, size: int = -1) -> bytes:
method seek (line 42) | def seek(self, offset: int, whence: int = 0):
method tell (line 46) | def tell(self) -> int:
method write (line 50) | def write(self, data: bytes):
method save (line 58) | def save(self, path: str | None = None, overwrite: bool = False) -> str:
method size (line 100) | def size(self) -> int:
method _ensure_buffer (line 107) | def _ensure_buffer(self):
method __repr__ (line 116) | def __repr__(self) -> str:
class Image (line 122) | class Image(File):
method validate_content_type (line 125) | def validate_content_type(cls, content_type: str) -> str:
FILE: panther/generics.py
class RetrieveAPI (line 28) | class RetrieveAPI(GenericAPI):
method get_instance (line 30) | async def get_instance(self, request: Request, **kwargs) -> Model:
method get (line 37) | async def get(self, request: Request, **kwargs):
class ListAPI (line 42) | class ListAPI(GenericAPI):
method get_query (line 49) | async def get_query(self, request: Request, **kwargs):
method get (line 56) | async def get(self, request: Request, **kwargs):
method prepare_cursor (line 60) | async def prepare_cursor(self, request: Request, **kwargs) -> tuple[Cu...
method process_filters (line 81) | def process_filters(self, query_params: dict, cursor: Cursor | Panther...
method process_search (line 90) | def process_search(self, query_params: dict) -> dict:
method process_sort (line 100) | def process_sort(self, query_params: dict) -> list:
method process_pagination (line 111) | def process_pagination(self, query_params: dict, cursor: Cursor | Pant...
class CreateAPI (line 116) | class CreateAPI(GenericAPI):
method post (line 117) | async def post(self, request: Request, **kwargs):
class UpdateAPI (line 122) | class UpdateAPI(GenericAPI):
method get_instance (line 124) | async def get_instance(self, request: Request, **kwargs) -> Model:
method put (line 131) | async def put(self, request: Request, **kwargs):
method patch (line 136) | async def patch(self, request: Request, **kwargs):
class DeleteAPI (line 142) | class DeleteAPI(GenericAPI):
method get_instance (line 144) | async def get_instance(self, request: Request, **kwargs) -> Model:
method pre_delete (line 151) | async def pre_delete(self, instance, request: Request, **kwargs):
method post_delete (line 155) | async def post_delete(self, instance, request: Request, **kwargs):
method delete (line 159) | async def delete(self, request: Request, **kwargs):
FILE: panther/logging.py
class FileHandler (line 9) | class FileHandler(logging.FileHandler):
method __init__ (line 10) | def __init__(self, filename, mode='a', encoding=None, delay=False, err...
FILE: panther/main.py
class Panther (line 35) | class Panther:
method __init__ (line 36) | def __init__(self, name: str, configs: str | None = None, urls: dict |...
method load_configs (line 64) | def load_configs(self) -> None:
method __call__ (line 86) | async def __call__(self, scope: dict, receive: Callable, send: Callabl...
method handle_ws_endpoint (line 104) | async def handle_ws_endpoint(connection: Websocket):
method handle_ws (line 127) | async def handle_ws(cls, scope: dict, receive: Callable, send: Callabl...
method handle_http_endpoint (line 147) | async def handle_http_endpoint(request: Request) -> Response:
method handle_http (line 165) | async def handle_http(cls, scope: dict, receive: Callable, send: Calla...
method __del__ (line 200) | def __del__(self):
FILE: panther/middlewares/base.py
class HTTPMiddleware (line 9) | class HTTPMiddleware:
method __init__ (line 12) | def __init__(self, dispatch: typing.Callable):
method __call__ (line 15) | async def __call__(self, request: Request) -> Response:
class WebsocketMiddleware (line 19) | class WebsocketMiddleware:
method __init__ (line 22) | def __init__(self, dispatch: typing.Callable):
method __call__ (line 25) | async def __call__(self, connection: Websocket) -> GenericWebsocket:
FILE: panther/middlewares/cors.py
class CORSMiddleware (line 7) | class CORSMiddleware(HTTPMiddleware):
method __call__ (line 36) | async def __call__(self, request: Request) -> Response:
FILE: panther/middlewares/monitoring.py
class MonitoringMiddleware (line 11) | class MonitoringMiddleware(HTTPMiddleware):
method __call__ (line 17) | async def __call__(self, request: Request):
class WebsocketMonitoringMiddleware (line 28) | class WebsocketMonitoringMiddleware(WebsocketMiddleware):
method __call__ (line 36) | async def __call__(self, connection: Websocket):
FILE: panther/openapi/schemas.py
class OutputSchema (line 6) | class OutputSchema:
method __init__ (line 21) | def __init__(
FILE: panther/openapi/utils.py
class EndpointParser (line 23) | class EndpointParser:
method __init__ (line 31) | def __init__(self, endpoint: Any, http_method: str):
method _parse_ast (line 42) | def _parse_ast(self) -> None:
method _parse_class_methods (line 65) | def _parse_class_methods(self, class_body: list[ast.stmt]) -> None:
method _parse_function_body (line 81) | def _parse_function_body(self, function_body: list[ast.stmt]) -> None:
method _extract_dict_data (line 98) | def _extract_dict_data(self, keys: list[ast.expr], values: list[ast.ex...
method _extract_variable_data (line 110) | def _extract_variable_data(self, function_body: list[ast.stmt], variab...
method _parse_call (line 142) | def _parse_call(self, function_body: list[ast.stmt], args: list[ast.ex...
method _parse_response_call (line 165) | def _parse_response_call(
method _parse_status_code_argument (line 183) | def _parse_status_code_argument(self, function_body: list[ast.stmt], v...
method _parse_data_argument (line 205) | def _parse_data_argument(self, function_body: list[ast.stmt], value: a...
method _extract_status_code_from_value (line 224) | def _extract_status_code_from_value(self, value: ast.expr) -> None:
method _find_variable_assignments (line 235) | def _find_variable_assignments(self, function_body: list[ast.stmt], va...
class OpenAPIGenerator (line 247) | class OpenAPIGenerator:
method get_model_name (line 259) | def get_model_name(cls, model: type[BaseModel]) -> str:
method extract_path_parameters (line 266) | def extract_path_parameters(cls, endpoint: Any, endpoint_name: str, ht...
method _get_function_signature (line 299) | def _get_function_signature(cls, endpoint: Any, http_method: str):
method _create_parameter_schema (line 308) | def _create_parameter_schema(cls, param_name: str, param_info: inspect...
method parse_docstring (line 323) | def parse_docstring(cls, docstring: str) -> tuple[str, str]:
method extract_field_constraints (line 335) | def extract_field_constraints(cls, field: Any) -> dict[str, Any]:
method enrich_schema_with_constraints (line 348) | def enrich_schema_with_constraints(cls, schema: dict[str, Any], model:...
method generate_operation_content (line 361) | def generate_operation_content(cls, endpoint: Any, http_method: str, s...
method _extract_operation_tags (line 408) | def _extract_operation_tags(cls, endpoint: Any, response_parser: Endpo...
method _extract_endpoint_metadata (line 415) | def _extract_endpoint_metadata(cls, endpoint: Any, description: str) -...
method _build_response_schema (line 456) | def _build_response_schema(
method _build_error_responses (line 490) | def _build_error_responses(cls, endpoint: Any) -> dict[int, dict[str, ...
method _build_request_body (line 505) | def _build_request_body(cls, endpoint: Any, http_method: str, schemas:...
method generate_openapi_spec (line 527) | def generate_openapi_spec(cls) -> dict[str, Any]:
FILE: panther/openapi/views.py
class ScalarOpenAPI (line 7) | class ScalarOpenAPI(GenericAPI):
method get (line 10) | def get(self):
class SwaggerOpenAPI (line 14) | class SwaggerOpenAPI(GenericAPI):
method get (line 17) | def get(self):
class RedocOpenAPI (line 21) | class RedocOpenAPI(GenericAPI):
method get (line 24) | def get(self):
class RapiDocOpenAPI (line 28) | class RapiDocOpenAPI(GenericAPI):
method get (line 31) | def get(self):
class SpotlightOpenAPI (line 35) | class SpotlightOpenAPI(GenericAPI):
method get (line 38) | def get(self):
FILE: panther/pagination.py
class Pagination (line 6) | class Pagination:
method __init__ (line 22) | def __init__(self, query_params: dict, cursor: Cursor | PantherDBCursor):
method get_limit (line 27) | def get_limit(self, query_params: dict) -> int:
method get_skip (line 30) | def get_skip(self, query_params: dict) -> int:
method build_next_params (line 33) | def build_next_params(self):
method build_previous_params (line 37) | def build_previous_params(self):
method paginate (line 41) | def paginate(self):
method template (line 44) | async def template(self, response: list):
FILE: panther/panel/authentications.py
class AdminCookieJWTAuthentication (line 6) | class AdminCookieJWTAuthentication(CookieJWTAuthentication):
method __call__ (line 7) | async def __call__(self, request: Request):
FILE: panther/panel/middlewares.py
class RedirectToSlashMiddleware (line 6) | class RedirectToSlashMiddleware(HTTPMiddleware):
method __call__ (line 7) | async def __call__(self, request: Request):
FILE: panther/panel/permissions.py
class IsAuthenticated (line 6) | class IsAuthenticated(BasePermission):
method __call__ (line 7) | async def __call__(self, request: Request):
FILE: panther/panel/templates/create.js
function toggleObjectVisibility (line 2) | function toggleObjectVisibility(checkbox, contentId) {
function createObjectInputs (line 13) | function createObjectInputs(objectSchema, container, prefix = "") {
function toggleArrayVisibility (line 49) | function toggleArrayVisibility(checkbox, contentId) {
function createSimpleArrayField (line 62) | function createSimpleArrayField(fieldName, container, fullFieldName, fie...
function addSimpleArrayRow (line 112) | function addSimpleArrayRow(arrayName, containerId) {
function reindexArrayItems (line 141) | function reindexArrayItems(containerId) {
function createNestedObjectField (line 155) | function createNestedObjectField(
function createObjectInputs (line 237) | function createObjectInputs(objectSchema, container, prefix = "") {
function createBasicInput (line 276) | function createBasicInput(fieldName, field, container, fullFieldName) {
function createArrayField (line 350) | function createArrayField(fieldName, itemType, container, fullFieldName) {
function addArrayRow (line 377) | function addArrayRow(arrayName, itemType, containerId) {
function reindexComplexArrayItems (line 406) | function reindexComplexArrayItems(containerId) {
function openObjectModal (line 420) | function openObjectModal(fieldName, objectType) {
function populateFormWithExistingData (line 463) | function populateFormWithExistingData(data) {
function populateNestedData (line 505) | function populateNestedData(data, prefix = "") {
function populateItemField (line 585) | function populateItemField(container, fieldPath, value) {
function determineItemType (line 596) | function determineItemType(item) {
function showToast (line 750) | function showToast(title, message, type) {
function createToastContainer (line 769) | function createToastContainer() {
function createLogContainer (line 826) | function createLogContainer() {
function captureFormState (line 876) | function captureFormState() {
function logMessage (line 947) | function logMessage(title, data, type = "info") {
function escapeHtml (line 1009) | function escapeHtml(str) {
function attachFormMonitors (line 1019) | function attachFormMonitors() {
function initFormLogger (line 1208) | function initFormLogger() {
FILE: panther/panel/templates/home.js
function getCurrentTableIndex (line 1) | function getCurrentTableIndex() {
function selectTable (line 7) | function selectTable(element) {
function setActiveTableFromUrl (line 13) | function setActiveTableFromUrl() {
FILE: panther/panel/templates/table.js
function pythonToJSON (line 9) | function pythonToJSON(str) {
function goToCreatePage (line 31) | function goToCreatePage() {
function getDataType (line 38) | function getDataType(value) {
function initializeRecords (line 45) | function initializeRecords() {
function formatValue (line 65) | function formatValue(value, type) {
function toggleDropdown (line 133) | function toggleDropdown(button) {
function renderTable (line 162) | function renderTable() {
function renderPaginationControls (line 247) | function renderPaginationControls(totalRows) {
function changePage (line 269) | function changePage(direction) {
function getCurrentTableIndex (line 281) | function getCurrentTableIndex() {
function selectTable (line 287) | function selectTable(element, event) {
function setActiveTableFromUrl (line 308) | function setActiveTableFromUrl() {
FILE: panther/panel/utils.py
function _ref_name (line 11) | def _ref_name(ref: str) -> str:
function clean_model_schema (line 16) | def clean_model_schema(schema: dict) -> dict:
function get_models (line 98) | def get_models():
function serialize_models (line 109) | def serialize_models(data: Any):
FILE: panther/panel/views.py
class LoginView (line 17) | class LoginView(GenericAPI):
method get (line 20) | def get(self, request: Request):
method post (line 23) | async def post(self, request: Request):
class HomeView (line 52) | class HomeView(GenericAPI):
method get (line 56) | def get(self):
class TableView (line 60) | class TableView(GenericAPI):
method get (line 65) | async def get(self, request: Request, index: int):
class CreateView (line 82) | class CreateView(GenericAPI):
method get (line 87) | async def get(self, request: Request, index: int):
method post (line 97) | async def post(self, request: Request, index: int):
class DetailView (line 106) | class DetailView(GenericAPI):
method get (line 111) | async def get(self, index: int, document_id: str):
method put (line 119) | async def put(self, request: Request, index: int, document_id: str):
method delete (line 125) | async def delete(self, index: int, document_id: str):
FILE: panther/permissions.py
class BasePermission (line 7) | class BasePermission:
method __call__ (line 9) | async def __call__(self, request: Request | Websocket) -> bool:
class IsAuthenticated (line 13) | class IsAuthenticated(BasePermission):
method __call__ (line 14) | async def __call__(self, request: Request) -> bool:
class IsAuthenticatedOrReadonly (line 18) | class IsAuthenticatedOrReadonly(BasePermission):
method __call__ (line 19) | async def __call__(self, request: Request) -> bool:
FILE: panther/request.py
class Request (line 17) | class Request(BaseRequest):
method __init__ (line 18) | def __init__(self, scope: dict, receive: Callable, send: Callable):
method method (line 24) | def method(self) -> Literal['GET', 'POST', 'PUT', 'PATCH', 'DELETE']:
method data (line 28) | def data(self) -> dict | bytes:
method read_body (line 44) | async def read_body(self) -> None:
method validate_data (line 53) | def validate_data(self, model):
FILE: panther/response.py
class Cookie (line 41) | class Cookie:
class Response (line 68) | class Response:
method __init__ (line 80) | def __init__(
method __str__ (line 120) | def __str__(self):
method body (line 128) | def body(self) -> bytes:
method bytes_headers (line 145) | def bytes_headers(self) -> list[tuple[bytes, bytes]]:
method send (line 152) | async def send(self, send, receive):
method serialize_output (line 156) | async def serialize_output(self, output_model: type[BaseModel]):
class StreamingResponse (line 185) | class StreamingResponse(Response):
method __init__ (line 202) | def __init__(self, *args, **kwargs):
method listen_to_disconnection (line 206) | async def listen_to_disconnection(self, receive):
method bytes_headers (line 212) | def bytes_headers(self) -> list[tuple[bytes, bytes]]:
method body (line 219) | async def body(self) -> AsyncGenerator:
method send (line 234) | async def send(self, send, receive):
class HTMLResponse (line 247) | class HTMLResponse(Response):
method body (line 260) | def body(self) -> bytes:
class PlainTextResponse (line 266) | class PlainTextResponse(Response):
method body (line 278) | def body(self) -> bytes:
class FileResponse (line 284) | class FileResponse(Response):
method __init__ (line 293) | def __init__(self, file_path: str, headers: dict | NoneType = None, st...
class TemplateResponse (line 307) | class TemplateResponse(HTMLResponse):
method __init__ (line 324) | def __init__(
class RedirectResponse (line 363) | class RedirectResponse(Response):
method __init__ (line 372) | def __init__(
FILE: panther/routings.py
function flatten_urls (line 12) | def flatten_urls(urls: dict) -> dict:
function _flattening_urls (line 16) | def _flattening_urls(data: dict | Callable, url: str = ''):
function _is_url_endpoint_valid (line 38) | def _is_url_endpoint_valid(url: str, endpoint: Callable):
function finalize_urls (line 49) | def finalize_urls(urls: dict) -> dict:
function check_urls_path_variables (line 70) | def check_urls_path_variables(urls: dict, path: str = '') -> None:
function _merge (line 92) | def _merge(destination: MutableMapping, *sources) -> MutableMapping:
function _simplify_urls (line 96) | def _simplify_urls(urls):
function _deepmerge (line 112) | def _deepmerge(dst, src):
function _is_recursive_merge (line 125) | def _is_recursive_merge(a, b):
function find_endpoint (line 134) | def find_endpoint(path: str) -> tuple[Callable | None, str]:
FILE: panther/serializer.py
class MetaModelSerializer (line 14) | class MetaModelSerializer:
method __new__ (line 17) | def __new__(cls, cls_name: str, bases: tuple[type[typing.Any], ...], n...
method check_config (line 45) | def check_config(cls, cls_name: str, namespace: dict) -> None:
method collect_fields (line 143) | def collect_fields(cls, config: typing.Callable, namespace: dict) -> d...
method collect_model_config (line 200) | def collect_model_config(cls, config: typing.Callable, namespace: dict...
method convert_str_to_model (line 212) | def convert_str_to_model(cls, model_cls):
class ModelSerializer (line 221) | class ModelSerializer(metaclass=MetaModelSerializer):
method to_response (line 253) | async def to_response(self, instance: Any, data: dict) -> dict:
FILE: panther/status.py
function is_informational (line 101) | def is_informational(status_code: int):
function is_success (line 105) | def is_success(status_code: int):
function is_redirect (line 109) | def is_redirect(status_code: int):
function is_client_error (line 113) | def is_client_error(status_code: int):
function is_server_error (line 117) | def is_server_error(status_code: int):
FILE: panther/test.py
class RequestClient (line 12) | class RequestClient:
method __init__ (line 13) | def __init__(self, app: Callable):
method send (line 17) | async def send(self, data: dict):
method receive (line 23) | async def receive(self):
method request (line 30) | async def request(
class APIClient (line 82) | class APIClient:
method __init__ (line 83) | def __init__(self, app: Callable):
method _send_request (line 86) | async def _send_request(
method options (line 103) | async def options(
method get (line 117) | async def get(
method post (line 131) | async def post(
method put (line 148) | async def put(
method patch (line 165) | async def patch(
method delete (line 182) | async def delete(
class WebsocketClient (line 197) | class WebsocketClient:
method __init__ (line 198) | def __init__(self, app: Callable):
method send (line 202) | async def send(self, data: dict):
method receive (line 205) | async def receive(self):
method connect (line 208) | def connect(
FILE: panther/throttling.py
class Throttle (line 15) | class Throttle:
method time_window (line 20) | def time_window(self) -> datetime:
method build_cache_key (line 23) | def build_cache_key(self, request: Request) -> str:
method get_request_count (line 31) | async def get_request_count(self, request: Request) -> int:
method increment_request_count (line 43) | async def increment_request_count(self, request: Request) -> None:
method check_and_increment (line 57) | async def check_and_increment(self, request: Request) -> None:
FILE: panther/utils.py
class Singleton (line 20) | class Singleton(object):
method __new__ (line 23) | def __new__(cls, *args, **kwargs):
function load_env (line 29) | def load_env(env_file: str | Path, /) -> dict[str, str]:
function generate_secret_key (line 52) | def generate_secret_key() -> str:
function round_datetime (line 56) | def round_datetime(dt: datetime, delta: timedelta) -> datetime:
function generate_hash_value_from_string (line 75) | def generate_hash_value_from_string(string_value: str, /) -> str:
function scrypt (line 81) | def scrypt(password: str, salt: bytes, digest: bool = False) -> str | by...
function timezone_now (line 102) | def timezone_now():
function run_coroutine (line 106) | def run_coroutine(coroutine):
FILE: panther/websocket.py
class GenericWebsocket (line 11) | class GenericWebsocket(Websocket):
method __init__ (line 16) | def __init__(self, parent):
method connect (line 19) | async def connect(self, **kwargs):
method receive (line 24) | async def receive(self, data: str | bytes):
method send (line 30) | async def send(self, data: any = None):
method close (line 37) | async def close(self, code: int = status.WS_1000_NORMAL_CLOSURE, reaso...
function send_message_to_websocket (line 44) | async def send_message_to_websocket(connection_id: str, data: any):
function close_websocket_connection (line 48) | async def close_websocket_connection(connection_id: str, code: int = sta...
FILE: profiler.py
function hello_world (line 28) | async def hello_world():
function main (line 32) | def main():
FILE: setup.py
function panther_version (line 7) | def panther_version() -> str:
FILE: tests/__main__.py
function run_test_file (line 13) | def run_test_file(path, flags: list) -> int:
function main (line 18) | def main():
FILE: tests/sample_project/app/apis.py
function first_api (line 5) | def first_api():
function second_api (line 10) | def second_api():
FILE: tests/sample_project/app/models.py
class User (line 4) | class User(BaseUser):
FILE: tests/sample_project/core/events.py
function startup (line 5) | async def startup():
function shutdown (line 10) | async def shutdown():
FILE: tests/test_api_kwargs.py
function request_api (line 11) | async def request_api(request: Request):
function req_api (line 17) | async def req_api(req: Request):
function integer_path_api (line 23) | async def integer_path_api(age: int):
function boolean_path_api (line 29) | async def boolean_path_api(is_alive: bool):
function string_path_api (line 35) | async def string_path_api(name: str):
function unknown_path_api (line 41) | async def unknown_path_api(subject):
function all_kwargs_api (line 47) | async def all_kwargs_api(request: Request, is_alive: bool, age: int, nam...
function unordered_kwargs_api (line 57) | async def unordered_kwargs_api(subject, is_alive: bool, name: str, reque...
class AllKwargsAPI (line 66) | class AllKwargsAPI(GenericAPI):
method get (line 67) | async def get(self, request: Request, is_alive: bool, age: int, name: ...
class TestKwargs (line 89) | class TestKwargs(IsolatedAsyncioTestCase):
method setUpClass (line 91) | def setUpClass(cls) -> None:
method tearDownClass (line 96) | def tearDownClass(cls):
method test_request (line 99) | async def test_request(self):
method test_req (line 103) | async def test_req(self):
method test_integer (line 108) | async def test_integer(self):
method test_invalid_integer (line 113) | async def test_invalid_integer(self):
method test_boolean_true (line 118) | async def test_boolean_true(self):
method test_boolean_false (line 131) | async def test_boolean_false(self):
method test_invalid_boolean (line 148) | async def test_invalid_boolean(self):
method test_string (line 153) | async def test_string(self):
method test_unknown (line 158) | async def test_unknown(self):
method test_all (line 163) | async def test_all(self):
method test_unordered (line 168) | async def test_unordered(self):
method test_class_based (line 173) | async def test_class_based(self):
FILE: tests/test_authentication.py
class CustomAuth (line 13) | class CustomAuth:
method __call__ (line 14) | async def __call__(self, request: Request):
function auth_required_api (line 19) | async def auth_required_api(request: Request):
function refresh_token_api (line 24) | async def refresh_token_api(request: Request):
function logout_api (line 29) | async def logout_api(request: Request):
function custom_auth_api (line 34) | async def custom_auth_api(request: Request):
function custom_auth (line 38) | async def custom_auth(req):
function custom_auth_function_api (line 43) | async def custom_auth_function_api(request: Request):
class User (line 56) | class User(BaseUser):
class TestJWTAuthentication (line 73) | class TestJWTAuthentication(IsolatedAsyncioTestCase):
method setUp (line 88) | def setUp(self) -> None:
method tearDown (line 92) | def tearDown(self) -> None:
method tearDownClass (line 97) | def tearDownClass(cls):
method test_user_auth_required_without_auth_class (line 100) | async def test_user_auth_required_without_auth_class(self):
method test_user_auth_required_without_token (line 109) | async def test_user_auth_required_without_token(self):
method test_user_auth_required_with_bad_token_1 (line 116) | async def test_user_auth_required_with_bad_token_1(self):
method test_user_auth_required_with_bad_token2 (line 125) | async def test_user_auth_required_with_bad_token2(self):
method test_user_auth_required_with_bad_token3 (line 136) | async def test_user_auth_required_with_bad_token3(self):
method test_user_auth_required_with_bad_token4 (line 148) | async def test_user_auth_required_with_bad_token4(self):
method test_user_auth_required_with_invalid_token_signature (line 157) | async def test_user_auth_required_with_invalid_token_signature(self):
method test_user_auth_required_with_token_without_user_id (line 166) | async def test_user_auth_required_with_token_without_user_id(self):
method test_user_auth_required_with_token_user_not_found (line 175) | async def test_user_auth_required_with_token_user_not_found(self):
method test_user_auth_required_with_token (line 184) | async def test_user_auth_required_with_token(self):
method test_invalid_refresh_token (line 201) | async def test_invalid_refresh_token(self):
method test_refresh_token (line 216) | async def test_refresh_token(self):
method test_cookie_authentication_without_token (line 229) | async def test_cookie_authentication_without_token(self):
method test_cookie_authentication_invalid_token (line 244) | async def test_cookie_authentication_invalid_token(self):
method test_cookie_authentication (line 261) | async def test_cookie_authentication(self):
method test_cookie_invalid_refresh_token (line 283) | async def test_cookie_invalid_refresh_token(self):
method test_cookie_refresh_token (line 303) | async def test_cookie_refresh_token(self):
method test_query_param_authentication_without_token (line 323) | async def test_query_param_authentication_without_token(self):
method test_query_param_authentication (line 335) | async def test_query_param_authentication(self):
method test_query_param_invalid_refresh_token (line 357) | async def test_query_param_invalid_refresh_token(self):
method test_query_param_refresh_token (line 377) | async def test_query_param_refresh_token(self):
method test_custom_auth_class (line 394) | async def test_custom_auth_class(self):
method test_custom_auth_function (line 401) | async def test_custom_auth_function(self):
method test_invalid_custom_auth_class (line 408) | async def test_invalid_custom_auth_class(self):
method test_no_param_custom_auth_class (line 423) | async def test_no_param_custom_auth_class(self):
method test_invalid_custom_auth_function (line 438) | async def test_invalid_custom_auth_function(self):
method test_no_param_custom_auth_function (line 452) | async def test_no_param_custom_auth_function(self):
method test_class_based_api_invalid_auth_type (line 466) | async def test_class_based_api_invalid_auth_type(self):
method test_class_based_api_sync_function_auth (line 482) | async def test_class_based_api_sync_function_auth(self):
method test_class_based_api_no_param_function_auth (line 495) | async def test_class_based_api_no_param_function_auth(self):
method test_class_based_api_sync_class_auth (line 508) | async def test_class_based_api_sync_class_auth(self):
method test_class_based_api_no_param_class_auth (line 522) | async def test_class_based_api_no_param_class_auth(self):
method test_class_based_api_class_without_call (line 536) | async def test_class_based_api_class_without_call(self):
method test_class_based_api_class_with_initiated_auth (line 549) | async def test_class_based_api_class_with_initiated_auth(self):
method test_logout (line 565) | async def test_logout(self):
FILE: tests/test_background_task_event_loop_isolation.py
function _set_application_loop (line 9) | def _set_application_loop(loop: asyncio.AbstractEventLoop | None) -> None:
function _create_loop_bound_future (line 16) | async def _create_loop_bound_future(loop: asyncio.AbstractEventLoop) -> ...
class TestBackgroundTaskEventLoopIsolation (line 20) | class TestBackgroundTaskEventLoopIsolation(TestCase):
method test_async_background_task_avoids_event_loop_mismatch (line 21) | def test_async_background_task_avoids_event_loop_mismatch(self):
FILE: tests/test_background_tasks.py
class TestBackgroundTasks (line 13) | class TestBackgroundTasks(TestCase):
method setUp (line 14) | def setUp(self):
method tearDown (line 18) | def tearDown(self):
method tearDownClass (line 23) | def tearDownClass(cls):
method test_background_tasks_singleton (line 26) | def test_background_tasks_singleton(self):
method test_initialization (line 30) | def test_initialization(self):
method test_add_single_task (line 38) | def test_add_single_task(self):
method test_add_wrong_task (line 47) | def test_add_wrong_task(self):
method test_add_task_with_false_background_task (line 64) | def test_add_task_with_false_background_task(self):
method test_add_task_with_args (line 77) | def test_add_task_with_args(self):
method test_add_task_with_kwargs (line 90) | def test_add_task_with_kwargs(self):
method test_add_task_with_custom_interval (line 103) | def test_add_task_with_custom_interval(self):
method test_add_task_with_custom_interval_default_timedelta (line 116) | def test_add_task_with_custom_interval_default_timedelta(self):
method test_add_multiple_tasks (line 130) | def test_add_multiple_tasks(self):
method test_concurrent_tasks (line 149) | def test_concurrent_tasks(self):
method test_add_task_with_custom_every_3_seconds (line 167) | def test_add_task_with_custom_every_3_seconds(self):
method test_submit_single_task (line 182) | def test_submit_single_task(self):
method test_task_at_3second_later (line 195) | def test_task_at_3second_later(self):
method test_task_on_specific_weekday (line 210) | def test_task_on_specific_weekday(self):
FILE: tests/test_caching.py
function without_cache_api (line 16) | async def without_cache_api():
function expired_cache_api (line 22) | async def expired_cache_api():
function expired_cache_html_response (line 28) | async def expired_cache_html_response():
class TestInMemoryCaching (line 40) | class TestInMemoryCaching(IsolatedAsyncioTestCase):
method setUpClass (line 42) | def setUpClass(cls) -> None:
method tearDownClass (line 47) | def tearDownClass(cls):
method test_without_cache (line 50) | async def test_without_cache(self):
method test_with_cache_5second_exp_time (line 59) | async def test_with_cache_5second_exp_time(self):
method test_with_cache_content_type (line 85) | async def test_with_cache_content_type(self):
FILE: tests/test_cli.py
function interactive_cli_1_mock_responses (line 28) | def interactive_cli_1_mock_responses(index=None):
function interactive_cli_2_mock_responses (line 38) | def interactive_cli_2_mock_responses(index=None):
class TestCLI (line 48) | class TestCLI(IsolatedAsyncioTestCase):
method setUpClass (line 50) | def setUpClass(cls) -> None:
method tearDown (line 53) | def tearDown(self) -> None:
method tearDownClass (line 58) | def tearDownClass(cls) -> None:
method test_print_info (line 63) | async def test_print_info(self):
method test_create_normal_template_with_interactive_cli (line 92) | async def test_create_normal_template_with_interactive_cli(self):
method test_create_single_file_template_with_interactive_cli (line 107) | async def test_create_single_file_template_with_interactive_cli(self):
method test_create_on_existence_directory (line 121) | async def test_create_on_existence_directory(self):
method test_create_project (line 143) | async def test_create_project(self):
method test_create_user_success (line 161) | async def test_create_user_success(self, mock_load_application_file, m...
method test_create_user_duplicate (line 184) | async def test_create_user_duplicate(self, mock_load_application_file,...
method test_create_user_keyboard_interrupt (line 204) | async def test_create_user_keyboard_interrupt(
FILE: tests/test_config.py
class User (line 22) | class User(Model):
class Book (line 26) | class Book(Model):
class Author (line 30) | class Author(Model):
class DummyAPI (line 34) | class DummyAPI(GenericAPI):
class DummyWS (line 38) | class DummyWS(GenericWebsocket):
function my_startup1 (line 43) | def my_startup1():
function my_startup2 (line 48) | def my_startup2():
function my_shutdown1 (line 53) | def my_shutdown1():
function my_shutdown2 (line 58) | def my_shutdown2():
class TestConfig (line 62) | class TestConfig(TestCase):
method test_loading_known_configs (line 63) | def test_loading_known_configs(self):
method test_loading_unknown_configs (line 193) | def test_loading_unknown_configs(self):
method test_loading_unknown_configs_invalid_name (line 206) | def test_loading_unknown_configs_invalid_name(self):
method test_refresh_configs (line 219) | def test_refresh_configs(self):
method test_loading_unknown_config_types (line 296) | def test_loading_unknown_config_types(self):
FILE: tests/test_cors.py
function simple_api (line 10) | async def simple_api():
class TestCORSMiddlewares (line 25) | class TestCORSMiddlewares(IsolatedAsyncioTestCase):
method setUpClass (line 27) | def setUpClass(cls) -> None:
method tearDownClass (line 32) | def tearDownClass(cls) -> None:
method test_default_cors_headers (line 35) | async def test_default_cors_headers(self):
method test_preflight_options (line 55) | async def test_preflight_options(self):
method test_custom_origins (line 63) | async def test_custom_origins(self):
method test_credentials (line 72) | async def test_credentials(self):
method test_expose_headers (line 79) | async def test_expose_headers(self):
FILE: tests/test_database.py
class Book (line 19) | class Book(Model):
class Viewer (line 25) | class Viewer(BaseModel):
class Library (line 29) | class Library(Model):
class _BaseDatabaseTestCase (line 35) | class _BaseDatabaseTestCase:
method test_insert_one (line 37) | async def test_insert_one(self):
method test_insert_many_with_insert_one (line 48) | async def test_insert_many_with_insert_one(self):
method test_insert_many (line 52) | async def test_insert_many(self):
method test_find_one_not_found (line 67) | async def test_find_one_not_found(self):
method test_find_one_in_many_when_its_last (line 76) | async def test_find_one_in_many_when_its_last(self):
method test_find_one_in_many_when_its_middle (line 96) | async def test_find_one_in_many_when_its_middle(self):
method test_first (line 118) | async def test_first(self):
method test_first_not_found (line 136) | async def test_first_not_found(self):
method test_last (line 145) | async def test_last(self):
method test_last_not_found (line 164) | async def test_last_not_found(self):
method test_find (line 173) | async def test_find(self):
method test_find_not_found (line 194) | async def test_find_not_found(self):
method test_find_without_filter (line 208) | async def test_find_without_filter(self):
method test_all (line 224) | async def test_all(self):
method test_aggregation (line 241) | async def test_aggregation(self):
method test_count_all (line 257) | async def test_count_all(self):
method test_count_with_filter (line 267) | async def test_count_with_filter(self):
method test_count_not_found (line 281) | async def test_count_not_found(self):
method test_delete_one (line 292) | async def test_delete_one(self):
method test_delete_self (line 309) | async def test_delete_self(self):
method test_delete_one_not_found (line 322) | async def test_delete_one_not_found(self):
method test_delete_many (line 336) | async def test_delete_many(self):
method test_delete_many_not_found (line 354) | async def test_delete_many_not_found(self):
method test_update_one (line 370) | async def test_update_one(self):
method test_update_self (line 396) | async def test_update_self(self):
method test_update_one_not_found (line 423) | async def test_update_one_not_found(self):
method test_update_many (line 441) | async def test_update_many(self):
method test_update_many_not_found (line 473) | async def test_update_many_not_found(self):
method test_save_update (line 490) | async def test_save_update(self):
method test_save_insert (line 514) | async def test_save_insert(self):
method test_save_nested_models (line 526) | async def test_save_nested_models(self):
method _insert_many (line 538) | async def _insert_many(cls) -> int:
method _insert_many_with_specific_params (line 547) | async def _insert_many_with_specific_params(
class TestPantherDB (line 561) | class TestPantherDB(_BaseDatabaseTestCase, IsolatedAsyncioTestCase):
method setUpClass (line 565) | def setUpClass(cls) -> None:
method tearDown (line 570) | def tearDown(self) -> None:
method tearDownClass (line 574) | def tearDownClass(cls):
method test_aggregation (line 578) | async def test_aggregation(self):
class TestMongoDB (line 583) | class TestMongoDB(_BaseDatabaseTestCase, IsolatedAsyncioTestCase):
method setUpClass (line 587) | def setUpClass(cls) -> None:
method setUp (line 596) | def setUp(self):
method tearDown (line 599) | def tearDown(self) -> None:
method tearDownClass (line 603) | def tearDownClass(cls):
FILE: tests/test_database_advance.py
class Book (line 24) | class Book(Model):
class BookDetail (line 28) | class BookDetail(BaseModel):
class Viewer (line 34) | class Viewer(BaseModel):
class Library (line 38) | class Library(Model):
class TestBook (line 44) | class TestBook(Model):
class Author (line 50) | class Author(Model):
class InvalidModel (line 60) | class InvalidModel(Model):
class Publisher (line 64) | class Publisher(Model):
class _BaseDatabaseTestCase (line 70) | class _BaseDatabaseTestCase:
method test_insert_one (line 71) | async def test_insert_one(self):
method test_insert_one_with_model_obj (line 118) | async def test_insert_one_with_model_obj(self):
method test_insert_one_with_model_dump (line 167) | async def test_insert_one_with_model_dump(self):
method test_insert_many (line 215) | async def test_insert_many(self):
method test_find_one (line 276) | async def test_find_one(self):
method test_find (line 324) | async def test_find(self):
method test_insert_one_invalid_model (line 375) | async def test_insert_one_invalid_model(self):
method test_find_one_or_insert_existing (line 386) | async def test_find_one_or_insert_existing(self):
method test_find_one_or_insert_new (line 396) | async def test_find_one_or_insert_new(self):
method test_find_one_or_raise_found (line 405) | async def test_find_one_or_raise_found(self):
method test_find_one_or_raise_not_found (line 414) | async def test_find_one_or_raise_not_found(self):
method test_exists_true (line 422) | async def test_exists_true(self):
method test_exists_false (line 430) | async def test_exists_false(self):
method test_cursor_operations (line 436) | async def test_cursor_operations(self):
method test_cursor_sort (line 454) | async def test_cursor_sort(self):
method test_update_with_complex_data (line 481) | async def test_update_with_complex_data(self):
method test_bulk_operations (line 512) | async def test_bulk_operations(self):
method test_empty_collection_operations (line 532) | async def test_empty_collection_operations(self):
method test_model_validation_errors (line 554) | async def test_model_validation_errors(self):
method test_complex_nested_queries (line 563) | async def test_complex_nested_queries(self):
method test_save_operations (line 593) | async def test_save_operations(self):
method test_concurrent_operations (line 616) | async def test_concurrent_operations(self):
method test_large_dataset_operations (line 633) | async def test_large_dataset_operations(self):
method test_edge_cases (line 654) | async def test_edge_cases(self):
method test_relationship_integrity (line 670) | async def test_relationship_integrity(self):
method test_error_handling_edge_cases (line 691) | async def test_error_handling_edge_cases(self):
method test_performance_optimizations (line 707) | async def test_performance_optimizations(self):
method test_data_consistency (line 721) | async def test_data_consistency(self):
method test_partial_update (line 742) | async def test_partial_update(self):
method test_first (line 760) | async def test_first(self):
method test_last (line 769) | async def test_last(self):
method test_save_nested_models (line 778) | async def test_save_nested_models(self):
class TestPantherDB (line 791) | class TestPantherDB(_BaseDatabaseTestCase, IsolatedAsyncioTestCase):
method setUpClass (line 795) | def setUpClass(cls) -> None:
method tearDown (line 802) | def tearDown(self) -> None:
method tearDownClass (line 810) | def tearDownClass(cls):
class TestMongoDB (line 816) | class TestMongoDB(_BaseDatabaseTestCase, IsolatedAsyncioTestCase):
method setUpClass (line 820) | def setUpClass(cls) -> None:
method setUp (line 829) | def setUp(self):
method tearDown (line 832) | def tearDown(self) -> None:
method tearDownClass (line 840) | def tearDownClass(cls) -> None:
method test_insert_one_raw_document (line 843) | async def test_insert_one_raw_document(self):
method test_aggregation_pipeline (line 908) | async def test_aggregation_pipeline(self):
method test_mongodb_specific_features (line 932) | async def test_mongodb_specific_features(self):
method test_mongodb_complex_queries (line 950) | async def test_mongodb_complex_queries(self):
method test_mongodb_bulk_operations (line 972) | async def test_mongodb_bulk_operations(self):
method test_mongodb_data_types (line 988) | async def test_mongodb_data_types(self):
method test_mongodb_error_handling (line 1015) | async def test_mongodb_error_handling(self):
FILE: tests/test_events.py
class TestEvents (line 10) | class TestEvents(IsolatedAsyncioTestCase):
method setUp (line 11) | def setUp(self):
method tearDownClass (line 15) | def tearDownClass(cls):
method test_async_startup (line 18) | async def test_async_startup(self):
method test_sync_startup (line 35) | async def test_sync_startup(self):
method test_startup (line 52) | async def test_startup(self):
method test_sync_shutdown (line 75) | async def test_sync_shutdown(self):
method shutdown_event (line 92) | async def shutdown_event(self):
method test_async_shutdown (line 95) | def test_async_shutdown(self):
method test_shutdown (line 109) | def test_shutdown(self):
method test_singleton_pattern (line 129) | def test_singleton_pattern(self):
FILE: tests/test_file.py
class TestFile (line 10) | class TestFile(unittest.TestCase):
method test_file_init_with_bytes (line 11) | def test_file_init_with_bytes(self):
method test_file_init_with_path (line 19) | def test_file_init_with_path(self):
method test_file_read_and_seek (line 28) | def test_file_read_and_seek(self):
method test_file_write_in_memory (line 39) | def test_file_write_in_memory(self):
method test_file_write_on_disk_raises (line 45) | def test_file_write_on_disk_raises(self):
method test_file_save_overwrite (line 57) | def test_file_save_overwrite(self):
method test_file_save_no_overwrite (line 69) | def test_file_save_no_overwrite(self):
method test_file_save_idempotent (line 81) | def test_file_save_idempotent(self):
method test_file_save_idempotent_with_different_paths (line 102) | def test_file_save_idempotent_with_different_paths(self):
method test_file_save_directory_path (line 118) | def test_file_save_directory_path(self):
method test_file_save_nested_directory_path (line 131) | def test_file_save_nested_directory_path(self):
method test_file_save_directory_path_with_custom_filename (line 144) | def test_file_save_directory_path_with_custom_filename(self):
method test_file_save_directory_creation (line 157) | def test_file_save_directory_creation(self):
method test_file_save_directory_path_idempotent (line 170) | def test_file_save_directory_path_idempotent(self):
method test_file_save_without_path_uses_filename (line 186) | def test_file_save_without_path_uses_filename(self):
method test_file_repr_and_str (line 207) | def test_file_repr_and_str(self):
method test_file_ensure_buffer_error (line 213) | def test_file_ensure_buffer_error(self):
class TestDetectMimeType (line 225) | class TestDetectMimeType(unittest.TestCase):
method test_extension_based_detection (line 226) | def test_extension_based_detection(self):
method test_png_magic_number (line 232) | def test_png_magic_number(self):
method test_pdf_magic_number (line 238) | def test_pdf_magic_number(self):
method test_zip_magic_number (line 244) | def test_zip_magic_number(self):
method test_jpeg_magic_number (line 253) | def test_jpeg_magic_number(self):
method test_gif_magic_number (line 259) | def test_gif_magic_number(self):
method test_bmp_magic_number (line 265) | def test_bmp_magic_number(self):
method test_ico_magic_number (line 271) | def test_ico_magic_number(self):
method test_tiff_magic_number (line 277) | def test_tiff_magic_number(self):
method test_mp4_magic_number (line 283) | def test_mp4_magic_number(self):
method test_fallback_octet_stream (line 289) | def test_fallback_octet_stream(self):
FILE: tests/test_find_one_or_insert_race.py
class RaceBook (line 13) | class RaceBook(Model):
class TestFindOneOrInsertRaceMongoDB (line 18) | class TestFindOneOrInsertRaceMongoDB(IsolatedAsyncioTestCase):
method setUpClass (line 22) | def setUpClass(cls) -> None:
method setUp (line 31) | def setUp(self):
method tearDown (line 34) | def tearDown(self) -> None:
method tearDownClass (line 38) | def tearDownClass(cls) -> None:
method test_find_one_or_insert_with_unique_index_returns_single_record_under_concurrency (line 41) | async def test_find_one_or_insert_with_unique_index_returns_single_rec...
FILE: tests/test_generics.py
class User (line 17) | class User(Model):
class Person (line 21) | class Person(User):
class RetrieveAPITest (line 25) | class RetrieveAPITest(RetrieveAPI):
method get_instance (line 26) | async def get_instance(self, request: Request, **kwargs) -> Model:
class ListAPITest (line 30) | class ListAPITest(ListAPI):
method get_query (line 31) | async def get_query(self, request: Request, **kwargs):
class FullListAPITest (line 35) | class FullListAPITest(ListAPI):
method get_query (line 41) | async def get_query(self, request: Request, **kwargs):
class UserSerializer (line 45) | class UserSerializer(ModelSerializer):
class Config (line 46) | class Config:
class UpdateAPITest (line 51) | class UpdateAPITest(UpdateAPI):
method get_instance (line 54) | async def get_instance(self, request: Request, **kwargs) -> Model:
class CreateAPITest (line 58) | class CreateAPITest(CreateAPI):
class DeleteAPITest (line 62) | class DeleteAPITest(DeleteAPI):
method get_instance (line 63) | async def get_instance(self, request: Request, **kwargs) -> Model:
class _BaseGenericTestCases (line 77) | class _BaseGenericTestCases:
method test_retrieve (line 78) | async def test_retrieve(self):
method test_list (line 84) | async def test_list(self):
method test_list_features (line 90) | async def test_list_features(self):
method test_update (line 195) | async def test_update(self):
method test_create (line 205) | async def test_create(self):
method test_delete (line 214) | async def test_delete(self):
class TestPantherDBGeneric (line 223) | class TestPantherDBGeneric(_BaseGenericTestCases, IsolatedAsyncioTestCase):
method setUpClass (line 227) | def setUpClass(cls) -> None:
method tearDown (line 235) | def tearDown(self) -> None:
method tearDownClass (line 240) | def tearDownClass(cls):
class TestMongoDBGeneric (line 246) | class TestMongoDBGeneric(_BaseGenericTestCases, IsolatedAsyncioTestCase):
method setUpClass (line 250) | def setUpClass(cls) -> None:
method setUp (line 259) | def setUp(self):
method tearDown (line 263) | def tearDown(self) -> None:
method tearDownClass (line 268) | def tearDownClass(cls):
FILE: tests/test_generics_search_regex_escape.py
function _run_regex_search (line 13) | def _run_regex_search(pattern: str, text: str, repeats: int, done: mp.Qu...
class ListAPISearchTest (line 20) | class ListAPISearchTest(ListAPI):
method get_query (line 23) | async def get_query(self, request: Request, **kwargs):
class TestListAPISearchRegexEscape (line 27) | class TestListAPISearchRegexEscape(TestCase):
method setUp (line 33) | def setUp(self):
method tearDown (line 37) | def tearDown(self):
method test_mongodb_search_pattern_escapes_user_input (line 40) | def test_mongodb_search_pattern_escapes_user_input(self):
method test_raw_regex_stalls_on_near_miss_payload_but_escaped_pattern_completes (line 47) | def test_raw_regex_stalls_on_near_miss_payload_but_escaped_pattern_com...
method _extract_mongodb_search_pattern (line 66) | def _extract_mongodb_search_pattern(self, user_input: str) -> str:
method _find_redos_trigger_length (line 70) | def _find_redos_trigger_length(self, *, raw_pattern: str, escaped_patt...
method _run_with_deadline (line 104) | def _run_with_deadline(
FILE: tests/test_middlewares.py
class MyMiddleware (line 13) | class MyMiddleware(HTTPMiddleware):
method __call__ (line 14) | async def __call__(self, request: Request):
class BeforeMiddleware1 (line 21) | class BeforeMiddleware1(HTTPMiddleware):
method __call__ (line 22) | async def __call__(self, request: Request):
class BeforeMiddleware2 (line 27) | class BeforeMiddleware2(HTTPMiddleware):
method __call__ (line 28) | async def __call__(self, request: Request):
class AfterMiddleware1 (line 33) | class AfterMiddleware1(HTTPMiddleware):
method __call__ (line 34) | async def __call__(self, request: Request):
class AfterMiddleware2 (line 40) | class AfterMiddleware2(HTTPMiddleware):
method __call__ (line 41) | async def __call__(self, request: Request):
class AfterMiddleware3 (line 47) | class AfterMiddleware3(HTTPMiddleware):
method __call__ (line 48) | async def __call__(self, request: Request):
class MyWSMiddleware1 (line 54) | class MyWSMiddleware1(WebsocketMiddleware):
method __call__ (line 55) | async def __call__(self, connection: Websocket):
class MyWSMiddleware2 (line 60) | class MyWSMiddleware2(WebsocketMiddleware):
method __call__ (line 61) | async def __call__(self, connection: Websocket):
class PrivateMiddleware1 (line 66) | class PrivateMiddleware1(HTTPMiddleware):
method __call__ (line 67) | async def __call__(self, request: Request):
class PrivateMiddleware2 (line 74) | class PrivateMiddleware2(HTTPMiddleware):
method __call__ (line 75) | async def __call__(self, request: Request):
function handle_middlewares (line 83) | async def handle_middlewares(request: Request):
function handle_private_empty_middlewares (line 91) | async def handle_private_empty_middlewares(request: Request):
function handle_private_middlewares (line 99) | async def handle_private_middlewares(request: Request):
class WebsocketHandleMiddlewares (line 106) | class WebsocketHandleMiddlewares(GenericWebsocket):
method connect (line 107) | async def connect(self):
class TestMiddleware (line 124) | class TestMiddleware(IsolatedAsyncioTestCase):
method tearDown (line 125) | def tearDown(self):
method test_before_base_middleware (line 128) | async def test_before_base_middleware(self):
method test_after_base_middleware (line 138) | async def test_after_base_middleware(self):
method test_a_normal_middleware (line 148) | async def test_a_normal_middleware(self):
method test_websocket_middleware_in_http (line 157) | async def test_websocket_middleware_in_http(self):
method test_private_empty_middleware (line 176) | async def test_private_empty_middleware(self):
method test_private_middleware (line 183) | async def test_private_middleware(self):
method test_middlewares_order (line 196) | async def test_middlewares_order(self):
class TestWebsocketMiddleware (line 230) | class TestWebsocketMiddleware(TestCase):
method tearDownClass (line 232) | def tearDownClass(cls):
method setUp (line 235) | def setUp(self):
method tearDown (line 238) | def tearDown(self):
method test_websocket_middleware (line 241) | def test_websocket_middleware(self):
method test_http_middleware_in_websocket (line 259) | def test_http_middleware_in_websocket(self):
method test_base_middleware (line 278) | def test_base_middleware(self):
FILE: tests/test_multipart.py
function flat_multipart_api (line 11) | async def flat_multipart_api(request: Request):
function single_file_multipart_api (line 16) | async def single_file_multipart_api(request: Request):
function several_file_multipart_api (line 25) | async def several_file_multipart_api(request: Request):
function complex_multipart_api (line 41) | async def complex_multipart_api(request: Request):
function multipart_api (line 59) | async def multipart_api(request: Request):
class TestMultipart (line 72) | class TestMultipart(IsolatedAsyncioTestCase):
method setUpClass (line 133) | def setUpClass(cls) -> None:
method tearDownClass (line 138) | def tearDownClass(cls):
method test_flat_multipart (line 141) | async def test_flat_multipart(self):
method test_single_file_multipart (line 150) | async def test_single_file_multipart(self):
method test_several_file_multipart (line 164) | async def test_several_file_multipart(self):
method test_complex_multipart (line 185) | async def test_complex_multipart(self):
method test_multiline_multipart (line 208) | async def test_multiline_multipart(self):
FILE: tests/test_openapi.py
function my_api1 (line 16) | def my_api1():
function my_api2 (line 21) | def my_api2():
function my_api3 (line 27) | def my_api3():
function my_api4 (line 33) | async def my_api4():
function my_api5 (line 38) | async def my_api5():
function my_api6 (line 43) | async def my_api6():
function my_api7 (line 48) | def my_api7():
function my_api8 (line 54) | def my_api8():
function my_api9 (line 60) | def my_api9():
function my_api10 (line 65) | def my_api10():
function my_api11 (line 71) | def my_api11():
function my_api12 (line 77) | def my_api12():
function my_api13 (line 83) | def my_api13():
function my_api14 (line 90) | def my_api14():
function my_api15 (line 97) | def my_api15():
function my_api16 (line 104) | def my_api16():
function my_api17 (line 110) | def my_api17():
class API18 (line 114) | class API18(GenericAPI):
method get (line 115) | def get(self, *args, **kwargs):
method post (line 118) | def post(self, *args, **kwargs):
method put (line 122) | async def put(self, *args, **kwargs):
method patch (line 126) | def patch(self, *args, **kwargs):
method delete (line 130) | def delete(self, *args, **kwargs):
function my_api19 (line 135) | def my_api19():
function my_api20 (line 141) | def my_api20():
function my_api21 (line 147) | def my_api21():
function my_api22 (line 153) | def my_api22():
function my_api23 (line 159) | def my_api23():
class CustomBaseModel (line 164) | class CustomBaseModel(BaseModel):
function my_api24 (line 170) | def my_api24():
class CustomModel (line 175) | class CustomModel(Model):
function my_api25 (line 180) | def my_api25():
function my_api26 (line 186) | def my_api26():
function my_api27 (line 191) | def my_api27():
class TestOpenAPI (line 195) | class TestOpenAPI(IsolatedAsyncioTestCase):
method setUpClass (line 197) | def setUpClass(cls) -> None:
method tearDownClass (line 202) | def tearDownClass(cls) -> None:
method test_swagger (line 207) | async def test_swagger(self):
method test_scalar (line 227) | async def test_scalar(self):
method test_redoc (line 250) | async def test_redoc(self):
method test_rapidoc (line 275) | async def test_rapidoc(self):
method test_spotlight (line 301) | async def test_spotlight(self):
method test_my_api1 (line 330) | async def test_my_api1(self):
method test_my_api2 (line 335) | async def test_my_api2(self):
method test_my_api3 (line 340) | async def test_my_api3(self):
method test_my_api4 (line 345) | async def test_my_api4(self):
method test_my_api5 (line 350) | async def test_my_api5(self):
method test_my_api6 (line 355) | async def test_my_api6(self):
method test_my_api7 (line 360) | async def test_my_api7(self):
method test_my_api8 (line 365) | async def test_my_api8(self):
method test_my_api9 (line 370) | async def test_my_api9(self):
method test_my_api10 (line 375) | async def test_my_api10(self):
method test_my_api11 (line 380) | async def test_my_api11(self):
method test_my_api12 (line 385) | async def test_my_api12(self):
method test_my_api13 (line 390) | async def test_my_api13(self):
method test_my_api14 (line 395) | async def test_my_api14(self):
method test_my_api15 (line 400) | async def test_my_api15(self):
method test_my_api16 (line 405) | async def test_my_api16(self):
method test_my_api17 (line 410) | async def test_my_api17(self):
method test_my_api18_get (line 415) | async def test_my_api18_get(self):
method test_my_api18_post (line 420) | async def test_my_api18_post(self):
method test_my_api18_put (line 425) | async def test_my_api18_put(self):
method test_my_api18_patch (line 430) | async def test_my_api18_patch(self):
method test_my_api18_delete (line 435) | async def test_my_api18_delete(self):
method test_my_api19 (line 440) | async def test_my_api19(self):
method test_my_api20 (line 445) | async def test_my_api20(self):
method test_my_api21 (line 450) | async def test_my_api21(self):
method test_my_api22 (line 455) | async def test_my_api22(self):
method test_my_api23 (line 460) | async def test_my_api23(self):
method test_my_api24 (line 465) | async def test_my_api24(self):
method test_my_api25 (line 470) | async def test_my_api25(self):
method test_my_api26 (line 475) | async def test_my_api26(self):
method test_my_api27 (line 480) | async def test_my_api27(self):
FILE: tests/test_panel.py
class CustomUser (line 13) | class CustomUser(BaseUser):
class CustomModel (line 18) | class CustomModel(Model):
class TestPanel (line 37) | class TestPanel(IsolatedAsyncioTestCase):
method setUpClass (line 39) | def setUpClass(cls) -> None:
method tearDown (line 43) | def tearDown(self) -> None:
method tearDownClass (line 48) | def tearDownClass(cls):
method check_in_cookies (line 53) | def check_in_cookies(cls, *args, cookies):
method test_login_view_get (line 62) | async def test_login_view_get(self):
method test_login_view_post_success (line 68) | async def test_login_view_post_success(self):
method test_login_view_post_user_not_found (line 82) | async def test_login_view_post_user_not_found(self):
method test_login_view_post_wrong_password (line 92) | async def test_login_view_post_wrong_password(self):
method test_login_view_post_with_redirect (line 107) | async def test_login_view_post_with_redirect(self):
method test_home_view_without_auth (line 123) | async def test_home_view_without_auth(self):
method test_home_view_with_auth (line 128) | async def test_home_view_with_auth(self):
method test_table_view_without_auth (line 141) | async def test_table_view_without_auth(self):
method test_table_view_with_auth_empty_data (line 146) | async def test_table_view_with_auth_empty_data(self):
method test_table_view_with_auth_with_data (line 159) | async def test_table_view_with_auth_with_data(self):
method test_create_view_get_without_auth (line 179) | async def test_create_view_get_without_auth(self):
method test_create_view_get_with_auth (line 184) | async def test_create_view_get_with_auth(self):
method test_create_view_post_without_auth (line 197) | async def test_create_view_post_without_auth(self):
method test_create_view_post_with_auth_user_model (line 202) | async def test_create_view_post_with_auth_user_model(self):
method test_create_view_post_with_auth_regular_model (line 221) | async def test_create_view_post_with_auth_regular_model(self):
method test_detail_view_get_without_auth (line 240) | async def test_detail_view_get_without_auth(self):
method test_detail_view_get_with_auth (line 245) | async def test_detail_view_get_with_auth(self):
method test_detail_view_put_without_auth (line 263) | async def test_detail_view_put_without_auth(self):
method test_detail_view_put_with_auth (line 268) | async def test_detail_view_put_with_auth(self):
method test_detail_view_delete_without_auth (line 289) | async def test_detail_view_delete_without_auth(self):
method test_detail_view_delete_with_auth (line 294) | async def test_detail_view_delete_with_auth(self):
method test_invalid_model_index (line 315) | async def test_invalid_model_index(self):
method test_invalid_document_id (line 327) | async def test_invalid_document_id(self):
method test_middleware_redirect_to_slash (line 339) | async def test_middleware_redirect_to_slash(self):
method test_cookie_authentication_invalid_token (line 350) | async def test_cookie_authentication_invalid_token(self):
method test_cookie_authentication_expired_token (line 356) | async def test_cookie_authentication_expired_token(self):
FILE: tests/test_permission.py
class AlwaysDeniedPermission (line 12) | class AlwaysDeniedPermission(BasePermission):
method __call__ (line 13) | async def __call__(self, request: Request) -> bool:
class NotInheritedPermission (line 17) | class NotInheritedPermission:
method __call__ (line 18) | async def __call__(self, request: Request) -> bool:
class SyncPermission (line 22) | class SyncPermission(BasePermission):
method __call__ (line 23) | def __call__(self, request: Request) -> bool:
class WithoutParamPermission (line 27) | class WithoutParamPermission:
method __call__ (line 28) | def __call__(self) -> bool:
function without_param_permission (line 32) | def without_param_permission():
class WithoutCallPermission (line 36) | class WithoutCallPermission:
function sync_permission (line 40) | def sync_permission(req):
function accept_permission (line 44) | async def accept_permission(req):
function deny_permission (line 48) | async def deny_permission(req):
function without_permission_api (line 53) | async def without_permission_api(request: Request):
function denied_permission_api (line 58) | async def denied_permission_api(request: Request):
function single_denied_permission_api (line 63) | async def single_denied_permission_api(request: Request):
function not_inherited_permission_api (line 68) | async def not_inherited_permission_api(request: Request):
function multiple_permissions_api (line 73) | async def multiple_permissions_api(request: Request):
class TestPermission (line 86) | class TestPermission(IsolatedAsyncioTestCase):
method setUp (line 87) | def setUp(self) -> None:
method tearDownClass (line 92) | def tearDownClass(cls):
method test_without_permission (line 95) | async def test_without_permission(self):
method test_denied_permission (line 100) | async def test_denied_permission(self):
method test_single_denied_permission (line 106) | async def test_single_denied_permission(self):
method test_not_inherited_permission (line 112) | async def test_not_inherited_permission(self):
method test_multiple_permission (line 118) | async def test_multiple_permission(self):
method test_sync_permission (line 124) | async def test_sync_permission(self):
method test_sync_permission_classbased (line 135) | async def test_sync_permission_classbased(self):
method test_without_call_permission (line 145) | async def test_without_call_permission(self):
method test_without_call_permission_classbased (line 156) | async def test_without_call_permission_classbased(self):
method test_sync_function_permission (line 166) | async def test_sync_function_permission(self):
method test_sync_function_permission_classbased (line 177) | async def test_sync_function_permission_classbased(self):
method test_class_without_param_permission (line 187) | async def test_class_without_param_permission(self):
method test_class_without_param_permission_classbased (line 198) | async def test_class_without_param_permission_classbased(self):
method test_function_without_param_permission (line 208) | async def test_function_without_param_permission(self):
method test_function_without_param_permission_classbased (line 219) | async def test_function_without_param_permission_classbased(self):
FILE: tests/test_request.py
function request_path (line 14) | async def request_path(request: Request):
function request_client (line 19) | async def request_client(request: Request):
function request_query_params (line 24) | async def request_query_params(request: Request):
function request_data (line 29) | async def request_data(request: Request):
function request_path_variables (line 34) | async def request_path_variables(name: str, age: int, is_alive: bool):
function request_header (line 39) | async def request_header(request: Request):
function request_header_str (line 44) | async def request_header_str(request: Request):
function request_header_contains (line 49) | async def request_header_contains(request: Request, header: str):
function request_header_by_attr (line 54) | async def request_header_by_attr(request: Request):
function request_header_by_item (line 59) | async def request_header_by_item(request: Request):
class AllMethods (line 64) | class AllMethods(GenericAPI):
method get (line 65) | def get(self, *args, **kwargs):
method post (line 68) | def post(self, *args, **kwargs):
method put (line 71) | def put(self, *args, **kwargs):
method patch (line 74) | def patch(self, *args, **kwargs):
method delete (line 77) | def delete(self, *args, **kwargs):
function all_methods (line 82) | async def all_methods():
class GetMethod (line 86) | class GetMethod(GenericAPI):
method get (line 87) | def get(self, *args, **kwargs):
function get_method (line 92) | async def get_method():
class PostMethod (line 96) | class PostMethod(GenericAPI):
method post (line 97) | def post(self, *args, **kwargs):
function post_method (line 102) | async def post_method():
class PutMethod (line 106) | class PutMethod(GenericAPI):
method put (line 107) | def put(self, *args, **kwargs):
function put_method (line 112) | async def put_method():
class PatchMethod (line 116) | class PatchMethod(GenericAPI):
method patch (line 117) | def patch(self, *args, **kwargs):
function patch_method (line 122) | async def patch_method():
class DeleteMethod (line 126) | class DeleteMethod(GenericAPI):
method delete (line 127) | def delete(self, *args, **kwargs):
function delete_method (line 132) | async def delete_method():
class GetPostPatchMethods (line 136) | class GetPostPatchMethods(GenericAPI):
method get (line 137) | def get(self, *args, **kwargs):
method post (line 140) | def post(self, *args, **kwargs):
method patch (line 143) | def patch(self, *args, **kwargs):
function get_post_patch_methods (line 148) | async def get_post_patch_methods():
class TestRequest (line 180) | class TestRequest(IsolatedAsyncioTestCase):
method setUpClass (line 182) | def setUpClass(cls) -> None:
method tearDownClass (line 187) | def tearDownClass(cls) -> None:
method test_path (line 190) | async def test_path(self):
method test_client (line 195) | async def test_client(self):
method test_query_params (line 200) | async def test_query_params(self):
method test_data (line 208) | async def test_data(self):
method test_path_variables (line 214) | async def test_path_variables(self):
method test_headers_none (line 221) | async def test_headers_none(self):
method test_headers_content_type (line 226) | async def test_headers_content_type(self):
method test_headers_full_items (line 231) | async def test_headers_full_items(self):
method test_headers_unknown_items (line 254) | async def test_headers_unknown_items(self):
method test_headers_authorization_by_getattr (line 267) | async def test_headers_authorization_by_getattr(self):
method test_headers_authorization_by_getitem (line 274) | async def test_headers_authorization_by_getitem(self):
method test_headers_str (line 281) | async def test_headers_str(self):
method test_headers_contains (line 285) | async def test_headers_contains(self):
method test_method_all (line 292) | async def test_method_all(self):
method test_method_get (line 318) | async def test_method_get(self):
method test_method_post (line 344) | async def test_method_post(self):
method test_method_put (line 370) | async def test_method_put(self):
method test_method_patch (line 396) | async def test_method_patch(self):
method test_method_delete (line 422) | async def test_method_delete(self):
method test_method_get_post_patch (line 448) | async def test_method_get_post_patch(self):
FILE: tests/test_request_isolation.py
class RequestIsolationRaceAuth (line 12) | class RequestIsolationRaceAuth:
method reset (line 17) | def reset(cls) -> None:
method __call__ (line 21) | async def __call__(self, request: Request) -> str:
function function_api_race_endpoint (line 36) | async def function_api_race_endpoint(name: str, request: Request):
class ClassBasedRaceEndpoint (line 40) | class ClassBasedRaceEndpoint(GenericAPI):
method get (line 43) | async def get(self, name: str, request: Request):
class TestAPIRequestIsolation (line 53) | class TestAPIRequestIsolation(IsolatedAsyncioTestCase):
method setUpClass (line 55) | def setUpClass(cls) -> None:
method tearDownClass (line 60) | def tearDownClass(cls) -> None:
method test_function_api_keeps_request_isolation_under_concurrency (line 63) | async def test_function_api_keeps_request_isolation_under_concurrency(...
method test_class_based_apis_keep_request_isolation (line 75) | async def test_class_based_apis_keep_request_isolation(self):
FILE: tests/test_response.py
function return_nothing (line 23) | async def return_nothing():
class ReturnNothing (line 27) | class ReturnNothing(GenericAPI):
method get (line 28) | def get(self):
function return_none (line 33) | async def return_none():
class ReturnNone (line 37) | class ReturnNone(GenericAPI):
method get (line 38) | def get(self):
function return_string (line 43) | async def return_string():
class ReturnString (line 47) | class ReturnString(GenericAPI):
method get (line 48) | def get(self):
function return_dict (line 53) | async def return_dict():
class ReturnDict (line 57) | class ReturnDict(GenericAPI):
method get (line 58) | def get(self):
function return_list (line 63) | async def return_list():
class ReturnList (line 67) | class ReturnList(GenericAPI):
method get (line 68) | def get(self):
function return_tuple (line 73) | async def return_tuple():
class ReturnTuple (line 77) | class ReturnTuple(GenericAPI):
method get (line 78) | def get(self):
function return_response_none (line 83) | async def return_response_none():
class ReturnResponseNone (line 87) | class ReturnResponseNone(GenericAPI):
method get (line 88) | def get(self):
function return_response_dict (line 93) | async def return_response_dict():
class ReturnResponseDict (line 97) | class ReturnResponseDict(GenericAPI):
method get (line 98) | def get(self):
function return_response_list (line 103) | async def return_response_list():
class ReturnResponseList (line 107) | class ReturnResponseList(GenericAPI):
method get (line 108) | def get(self):
function return_response_tuple (line 113) | async def return_response_tuple():
class ReturnResponseTuple (line 117) | class ReturnResponseTuple(GenericAPI):
method get (line 118) | def get(self):
class CustomUser (line 122) | class CustomUser(BaseModel):
class CustomBook (line 128) | class CustomBook(BaseModel):
class ReturnResponseBaseModel (line 134) | class ReturnResponseBaseModel(GenericAPI):
method get (line 135) | def get(self):
class ReturnResponseNestedBaseModel (line 139) | class ReturnResponseNestedBaseModel(GenericAPI):
method get (line 140) | def get(self):
class CustomProduct (line 161) | class CustomProduct(Model):
class ReturnResponseModel (line 165) | class ReturnResponseModel(GenericAPI):
method get (line 166) | def get(self):
function return_html_response (line 171) | async def return_html_response():
class ReturnHTMLResponse (line 175) | class ReturnHTMLResponse(GenericAPI):
method get (line 176) | def get(self):
function return_template_response (line 181) | async def return_template_response() -> TemplateResponse:
class ReturnTemplateResponse (line 185) | class ReturnTemplateResponse(GenericAPI):
method get (line 186) | def get(self) -> TemplateResponse:
function return_file_not_found_response (line 194) | async def return_file_not_found_response():
function return_file_response (line 199) | async def return_file_response():
function return_plain_response (line 204) | async def return_plain_response():
class ReturnPlainResponse (line 208) | class ReturnPlainResponse(GenericAPI):
method get (line 209) | def get(self):
class ReturnStreamingResponse (line 213) | class ReturnStreamingResponse(GenericAPI):
method get (line 214) | def get(self):
class ReturnAsyncStreamingResponse (line 221) | class ReturnAsyncStreamingResponse(GenericAPI):
method get (line 222) | async def get(self):
function full_cookie_api (line 231) | def full_cookie_api():
function multiple_cookies_api (line 247) | def multiple_cookies_api():
function default_cookies_api (line 275) | def default_cookies_api():
class TestResponses (line 319) | class TestResponses(IsolatedAsyncioTestCase):
method setUpClass (line 321) | def setUpClass(cls) -> None:
method tearDownClass (line 326) | def tearDownClass(cls):
method test_nothing (line 329) | async def test_nothing(self):
method test_nothing_cls (line 338) | async def test_nothing_cls(self):
method test_none (line 347) | async def test_none(self):
method test_none_cls (line 356) | async def test_none_cls(self):
method test_dict (line 365) | async def test_dict(self):
method test_dict_cls (line 374) | async def test_dict_cls(self):
method test_string (line 383) | async def test_string(self):
method test_string_cls (line 392) | async def test_string_cls(self):
method test_list (line 401) | async def test_list(self):
method test_list_cls (line 410) | async def test_list_cls(self):
method test_tuple (line 419) | async def test_tuple(self):
method test_tuple_cls (line 428) | async def test_tuple_cls(self):
method test_basemodel (line 437) | async def test_basemodel(self):
method test_nested_basemodel (line 446) | async def test_nested_basemodel(self):
method test_model (line 476) | async def test_model(self):
method test_response_none (line 485) | async def test_response_none(self):
method test_response_none_cls (line 494) | async def test_response_none_cls(self):
method test_response_dict (line 503) | async def test_response_dict(self):
method test_response_dict_cls (line 512) | async def test_response_dict_cls(self):
method test_response_list (line 521) | async def test_response_list(self):
method test_response_list_cls (line 530) | async def test_response_list_cls(self):
method test_response_tuple (line 539) | async def test_response_tuple(self):
method test_response_tuple_cls (line 548) | async def test_response_tuple_cls(self):
method test_response_html (line 557) | async def test_response_html(self):
method test_response_html_cls (line 566) | async def test_response_html_cls(self):
method test_response_template (line 575) | async def test_response_template(self) -> None:
method test_response_template_cls (line 584) | async def test_response_template_cls(self) -> None:
method test_response_file_not_found (line 593) | async def test_response_file_not_found(self):
method test_response_file (line 602) | async def test_response_file(self):
method test_response_plain (line 615) | async def test_response_plain(self):
method test_response_plain_cls (line 624) | async def test_response_plain_cls(self):
method test_streaming_response (line 633) | async def test_streaming_response(self):
method test_async_streaming_response (line 640) | async def test_async_streaming_response(self):
method test_full_cookie (line 647) | async def test_full_cookie(self):
method test_multiple_cookies (line 667) | async def test_multiple_cookies(self):
method test_default_cookie (line 684) | async def test_default_cookie(self):
FILE: tests/test_response_output_model.py
class OutputModelWithAlias (line 15) | class OutputModelWithAlias(BaseModel):
class OutputModelWithPrepare (line 20) | class OutputModelWithPrepare(BaseModel):
method to_response (line 23) | async def to_response(self, instance, data):
class User (line 28) | class User(Model):
class UserOutputSerializer (line 33) | class UserOutputSerializer(BaseModel):
class OutputModelWithInstanceCheck (line 38) | class OutputModelWithInstanceCheck(BaseModel):
method to_response (line 42) | async def to_response(self, instance, data):
class OutputModelWithInstanceCheckIterable (line 48) | class OutputModelWithInstanceCheckIterable(BaseModel):
method to_response (line 52) | async def to_response(self, instance, data):
class TestResponsesOutputModel (line 57) | class TestResponsesOutputModel(IsolatedAsyncioTestCase):
method setUpClass (line 61) | def setUpClass(cls) -> None:
method tearDown (line 68) | def tearDown(self) -> None:
method tearDownClass (line 72) | def tearDownClass(cls):
method test_dict (line 76) | async def test_dict(self):
method test_iterable (line 81) | async def test_iterable(self):
method test_to_response (line 86) | async def test_to_response(self):
method test_iterable_to_response (line 91) | async def test_iterable_to_response(self):
method test_type_error (line 96) | async def test_type_error(self):
method test_model (line 101) | async def test_model(self):
method test_to_response_instance_argument (line 107) | async def test_to_response_instance_argument(self):
method test_iterable_to_response_instance_argument (line 116) | async def test_iterable_to_response_instance_argument(self):
method test_model_instance_to_response_argument (line 132) | async def test_model_instance_to_response_argument(self):
FILE: tests/test_routing.py
class TestRoutingFunctions (line 15) | class TestRoutingFunctions(TestCase):
method tearDown (line 16) | def tearDown(self) -> None:
method tearDownClass (line 20) | def tearDownClass(cls):
method test_flatten_urls_ellipsis_endpoints (line 24) | def test_flatten_urls_ellipsis_endpoints(self):
method test_flatten_urls_none_endpoints (line 50) | def test_flatten_urls_none_endpoints(self):
method test_flatten_urls_invalid_url (line 76) | def test_flatten_urls_invalid_url(self):
method test_flatten_urls_empty_url (line 105) | def test_flatten_urls_empty_url(self):
method test_flatten_urls_nested_empty_urls (line 120) | def test_flatten_urls_nested_empty_urls(self):
method test_flatten_urls_slash_url (line 149) | def test_flatten_urls_slash_url(self):
method test_flatten_urls_nested_slash_urls (line 164) | def test_flatten_urls_nested_slash_urls(self):
method test_flatten_urls_simple_urls (line 193) | def test_flatten_urls_simple_urls(self):
method test_flatten_urls_simple_nested_urls (line 212) | def test_flatten_urls_simple_nested_urls(self):
method test_flatten_urls_simple_nested_urls_without_slash_at_end (line 233) | def test_flatten_urls_simple_nested_urls_without_slash_at_end(self):
method test_flatten_urls_complex_nested_urls (line 254) | def test_flatten_urls_complex_nested_urls(self):
method test_finalize_empty_url (line 303) | def test_finalize_empty_url(self):
method test_finalize_empty_url_nested (line 319) | def test_finalize_empty_url_nested(self):
method test_finalize_root_url (line 351) | def test_finalize_root_url(self):
method test_finalize_root_url_nested (line 367) | def test_finalize_root_url_nested(self):
method test_finalize_root_and_empty_url_nested (line 399) | def test_finalize_root_and_empty_url_nested(self):
method test_finalize_empty_and_root_url_nested (line 431) | def test_finalize_empty_and_root_url_nested(self):
method test_finalize_urls (line 463) | def test_finalize_urls(self):
method test_finalize_urls_same_pre_path_variable (line 531) | def test_finalize_urls_same_pre_path_variable(self):
method test_finalize_urls_with_same_level_path_variables (line 552) | def test_finalize_urls_with_same_level_path_variables(self):
method test_find_endpoint_root_url (line 595) | def test_find_endpoint_root_url(self):
method test_find_endpoint_success (line 608) | def test_find_endpoint_success(self):
method test_find_endpoint_success_path (line 679) | def test_find_endpoint_success_path(self):
method test_find_endpoint_not_found (line 761) | def test_find_endpoint_not_found(self):
method test_find_endpoint_not_found_path (line 788) | def test_find_endpoint_not_found_path(self):
method test_find_endpoint_not_found_last_is_path_variable (line 815) | def test_find_endpoint_not_found_last_is_path_variable(self):
method test_find_endpoint_not_found_path_last_is_path_variable (line 842) | def test_find_endpoint_not_found_path_last_is_path_variable(self):
method test_find_endpoint_not_found_too_many (line 869) | def test_find_endpoint_not_found_too_many(self):
method test_find_endpoint_not_found_not_enough (line 883) | def test_find_endpoint_not_found_not_enough(self):
method test_find_endpoint_same_pre_path_variable (line 897) | def test_find_endpoint_same_pre_path_variable(self):
method test_find_endpoint_same_pre_path_variable_path (line 924) | def test_find_endpoint_same_pre_path_variable_path(self):
method test_find_endpoint_same_pre_key (line 951) | def test_find_endpoint_same_pre_key(self):
method test_find_endpoint_same_pre_key_path (line 979) | def test_find_endpoint_same_pre_key_path(self):
method test_find_endpoint_with_params (line 1007) | def test_find_endpoint_with_params(self):
method test_collect_path_variables (line 1025) | def test_collect_path_variables(self):
method test_find_endpoint_complete (line 1059) | def test_find_endpoint_complete(self):
FILE: tests/test_run.py
class TestRun (line 11) | class TestRun(TestCase):
method setUpClass (line 13) | def setUpClass(cls) -> None:
method tearDownClass (line 17) | def tearDownClass(cls) -> None:
method tearDown (line 21) | def tearDown(self) -> None:
method test_init (line 24) | def test_init(self):
method test_load_configs (line 28) | def test_load_configs(self):
FILE: tests/test_serializer.py
class Book (line 21) | class Book(Model):
class Author (line 27) | class Author(Model):
class NotRequiredFieldsSerializer (line 32) | class NotRequiredFieldsSerializer(ModelSerializer):
class Config (line 33) | class Config:
class RequiredFieldsSerializer (line 38) | class RequiredFieldsSerializer(ModelSerializer):
class Config (line 39) | class Config:
class OnlyRequiredFieldsSerializer (line 44) | class OnlyRequiredFieldsSerializer(ModelSerializer):
class Config (line 45) | class Config:
class WithValidatorsSerializer (line 51) | class WithValidatorsSerializer(ModelSerializer):
class Config (line 52) | class Config:
method validate_other (line 58) | def validate_other(cls, field):
method validate_pages_count (line 62) | def validate_pages_count(cls, field):
class WithClassFieldsSerializer (line 66) | class WithClassFieldsSerializer(ModelSerializer):
class Config (line 69) | class Config:
class WithForeignKeySerializer (line 75) | class WithForeignKeySerializer(ModelSerializer):
class Config (line 76) | class Config:
function not_required (line 82) | async def not_required(request: Request):
function required (line 87) | async def required(request: Request):
function only_required (line 92) | async def only_required(request: Request):
function with_validators (line 97) | async def with_validators(request: Request):
function with_class_fields (line 102) | async def with_class_fields(request: Request):
function with_foreign_key (line 107) | async def with_foreign_key(request: Request):
class TestModelSerializer (line 121) | class TestModelSerializer(IsolatedAsyncioTestCase):
method setUpClass (line 123) | def setUpClass(cls) -> None:
method tearDown (line 127) | def tearDown(self) -> None:
method tearDownClass (line 132) | def tearDownClass(cls) -> None:
method test_not_required_fields_empty_response (line 138) | async def test_not_required_fields_empty_response(self):
method test_not_required_fields_full_response (line 144) | async def test_not_required_fields_full_response(self):
method test_required_fields_error (line 150) | async def test_required_fields_error(self):
method test_required_fields_success (line 156) | async def test_required_fields_success(self):
method test_only_required_fields_error (line 162) | async def test_only_required_fields_error(self):
method test_only_required_fields_success (line 168) | async def test_only_required_fields_success(self):
method test_with_validators (line 174) | async def test_with_validators(self):
method test_with_class_fields_success (line 180) | async def test_with_class_fields_success(self):
method test_foreign_key (line 193) | async def test_foreign_key(self):
method test_define_class_without_meta (line 206) | async def test_define_class_without_meta(self):
method test_define_class_without_model (line 217) | async def test_define_class_without_model(self):
method test_define_class_without_fields (line 229) | async def test_define_class_without_fields(self):
method test_define_class_with_invalid_fields (line 241) | async def test_define_class_with_invalid_fields(self):
method test_define_class_with_invalid_required_fields (line 254) | async def test_define_class_with_invalid_required_fields(self):
method test_define_class_with_invalid_model (line 268) | async def test_define_class_with_invalid_model(self):
method test_define_class_with_invalid_exclude_1 (line 283) | async def test_define_class_with_invalid_exclude_1(self):
method test_define_class_with_invalid_exclude_2 (line 298) | async def test_define_class_with_invalid_exclude_2(self):
method test_with_star_fields_with_exclude3 (line 313) | async def test_with_star_fields_with_exclude3(self):
method test_with_simple_model_config (line 329) | async def test_with_simple_model_config(self):
method test_with_inner_model_config (line 342) | async def test_with_inner_model_config(self):
method test_with_dual_model_config (line 354) | async def test_with_dual_model_config(self):
method test_serializer_doc (line 368) | async def test_serializer_doc(self):
method test_with_exclude (line 387) | async def test_with_exclude(self):
method test_with_star_fields (line 399) | async def test_with_star_fields(self):
method test_with_star_fields_with_exclude (line 411) | async def test_with_star_fields_with_exclude(self):
FILE: tests/test_status.py
class TestStatus (line 6) | class TestStatus(TestCase):
method setUpClass (line 8) | def setUpClass(cls):
method test_status_numbers (line 87) | def test_status_numbers(self):
method test_status_is_informational (line 91) | def test_status_is_informational(self):
method test_status_is_success (line 98) | def test_status_is_success(self):
method test_status_is_redirect (line 105) | def test_status_is_redirect(self):
method test_status_is_client_error (line 112) | def test_status_is_client_error(self):
method test_status_is_server_error (line 119) | def test_status_is_server_error(self):
method test_statuses_text (line 126) | def test_statuses_text(self):
FILE: tests/test_throttling.py
function without_throttling_api (line 14) | async def without_throttling_api():
function with_throttling_api (line 19) | async def with_throttling_api():
function throttling_headers_api (line 24) | async def throttling_headers_api():
class TestThrottling (line 37) | class TestThrottling(IsolatedAsyncioTestCase):
method setUpClass (line 39) | def setUpClass(cls) -> None:
method tearDownClass (line 44) | def tearDownClass(cls):
method test_without_throttling (line 47) | async def test_without_throttling(self):
method test_with_throttling (line 61) | async def test_with_throttling(self):
method test_throttling_header (line 94) | async def test_throttling_header(self):
method test_global_throttling (line 107) | async def test_global_throttling(self):
FILE: tests/test_utils.py
class TestLoadEnvFile (line 12) | class TestLoadEnvFile(TestCase):
method tearDown (line 19) | def tearDown(self) -> None:
method _create_env_file (line 22) | def _create_env_file(self, file_data):
method test_load_env_invalid_file (line 26) | def test_load_env_invalid_file(self):
method test_load_env_double_quote (line 32) | def test_load_env_double_quote(self):
method test_load_env_single_quote (line 44) | def test_load_env_single_quote(self):
method test_load_env_no_quote (line 56) | def test_load_env_no_quote(self):
method test_load_env_no_space (line 69) | def test_load_env_no_space(self):
method test_load_env_not_striped (line 81) | def test_load_env_not_striped(self):
method test_load_env_and_read_from_system_env (line 93) | def test_load_env_and_read_from_system_env(self):
class TestUtilFunctions (line 106) | class TestUtilFunctions(TestCase):
method test_round_datetime_second_1 (line 107) | def test_round_datetime_second_1(self):
method test_round_datetime_second_2 (line 115) | def test_round_datetime_second_2(self):
method test_round_datetime_minute_1 (line 123) | def test_round_datetime_minute_1(self):
method test_round_datetime_minute_2 (line 131) | def test_round_datetime_minute_2(self):
method test_round_datetime_hour_1 (line 139) | def test_round_datetime_hour_1(self):
method test_round_datetime_hour_2 (line 147) | def test_round_datetime_hour_2(self):
method test_round_datetime_day_1 (line 155) | def test_round_datetime_day_1(self):
method test_round_datetime_day_2 (line 163) | def test_round_datetime_day_2(self):
method test_generate_hash_value_from_string (line 171) | def test_generate_hash_value_from_string(self):
class TestLoadConfigs (line 180) | class TestLoadConfigs(TestCase):
method tearDown (line 181) | def tearDown(self):
method tearDownClass (line 185) | def tearDownClass(cls):
method test_urls_not_found (line 188) | def test_urls_not_found(self):
method test_urls_cant_be_dict (line 203) | def test_urls_cant_be_dict(self):
method test_urls_not_string (line 222) | def test_urls_not_string(self):
method test_urls_invalid_target (line 237) | def test_urls_invalid_target(self):
method test_urls_invalid_module_path (line 252) | def test_urls_invalid_module_path(self):
method test_middlewares_invalid_path (line 267) | def test_middlewares_invalid_path(self):
method test_middlewares_invalid_structure (line 293) | def test_middlewares_invalid_structure(self):
method test_middlewares_too_many_args (line 313) | def test_middlewares_too_many_args(self):
method test_middlewares_without_args (line 333) | def test_middlewares_without_args(self):
method test_middlewares_invalid_middleware_parent (line 342) | def test_middlewares_invalid_middleware_parent(self):
method test_jwt_auth_without_secret_key (line 362) | def test_jwt_auth_without_secret_key(self):
method test_jwt_auth_with_secret_key (line 379) | def test_jwt_auth_with_secret_key(self):
method test_check_function_endpoint_decorator (line 395) | def test_check_function_endpoint_decorator(self):
method test_check_class_endpoint_inheritance (line 410) | def test_check_class_endpoint_inheritance(self):
method test_check_websocket_inheritance (line 425) | def test_check_websocket_inheritance(self):
function invalid_api (line 441) | def invalid_api():
class InvalidAPI (line 445) | class InvalidAPI:
class InvalidWebsocket (line 449) | class InvalidWebsocket:
class CorrectTestMiddleware (line 453) | class CorrectTestMiddleware(HTTPMiddleware):
class TestMiddleware (line 457) | class TestMiddleware:
FILE: tests/test_websockets.py
class WithoutAcceptWebsocket (line 12) | class WithoutAcceptWebsocket(GenericWebsocket):
method connect (line 13) | async def connect(self):
class CloseOnConnectWebsocket (line 17) | class CloseOnConnectWebsocket(GenericWebsocket):
method connect (line 18) | async def connect(self):
class CustomCloseWebsocket (line 23) | class CustomCloseWebsocket(GenericWebsocket):
method connect (line 24) | async def connect(self):
class MessageOnConnectWebsocket (line 29) | class MessageOnConnectWebsocket(GenericWebsocket):
method connect (line 30) | async def connect(self):
class QueryParamWebsocket (line 36) | class QueryParamWebsocket(GenericWebsocket):
method connect (line 37) | async def connect(self):
class PathVariableWebsocket (line 43) | class PathVariableWebsocket(GenericWebsocket):
method connect (line 44) | async def connect(self, name: str, age: int, is_male: bool):
class SendAllTypesWebsocket (line 52) | class SendAllTypesWebsocket(GenericWebsocket):
method connect (line 53) | async def connect(self):
class WebsocketWithoutAuthentication (line 75) | class WebsocketWithoutAuthentication(GenericWebsocket):
method connect (line 76) | async def connect(self):
class WebsocketWithAuthentication (line 82) | class WebsocketWithAuthentication(GenericWebsocket):
method connect (line 85) | async def connect(self):
class Permission (line 91) | class Permission:
method __call__ (line 92) | async def __call__(self, connection) -> bool:
class WebsocketWithPermission (line 96) | class WebsocketWithPermission(GenericWebsocket):
method connect (line 99) | async def connect(self):
class WebsocketWithoutPermission (line 105) | class WebsocketWithoutPermission(GenericWebsocket):
method connect (line 108) | async def connect(self):
class TestWebsocket (line 129) | class TestWebsocket(TestCase):
method setUpClass (line 131) | def setUpClass(cls) -> None:
method tearDownClass (line 135) | def tearDownClass(cls):
method test_without_accept (line 138) | def test_without_accept(self):
method test_close_on_connect (line 145) | def test_close_on_connect(self):
method test_custom_close (line 156) | def test_custom_close(self):
method test_query_params (line 167) | def test_query_params(self):
method test_message_after_connect (line 184) | def test_message_after_connect(self):
method test_path_variables (line 198) | def test_path_variables(self):
method test_all_types (line 212) | def test_all_types(self):
method test_without_auth (line 258) | def test_without_auth(self):
method test_with_auth_failed (line 272) | def test_with_auth_failed(self):
method test_with_auth_success (line 287) | def test_with_auth_success(self):
method test_with_permission (line 310) | def test_with_permission(self):
method test_without_permission (line 325) | def test_without_permission(self):
Condensed preview — 178 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,107K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 74,
"preview": "# These are supported funding model platforms\n\nbuy_me_a_coffee: PantherPy\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 430,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\nlabels: bug\nassignees: AliRn76\n\n---\n\n**Describe the bug**"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 290,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n**De"
},
{
"path": ".github/workflows/pull_request.yaml",
"chars": 1782,
"preview": "name: Pull Request\n\non:\n pull_request:\n types: [ opened, synchronize ]\n\njobs:\n\n tests-linux:\n name: Test Linux\n "
},
{
"path": ".github/workflows/release.yaml",
"chars": 4920,
"preview": "name: Release\n\non:\n push:\n tags:\n - 'v*'\n\njobs:\n\n initial-test:\n name: Initial Test\n runs-on: ubuntu-lat"
},
{
"path": ".gitignore",
"chars": 143,
"preview": ".idea/\n.venv*/\n__pycache__/\npanther.egg-info/\ndist/\nexample/panther/\nlogs/\nbuild/\n*.pdb\n.ruff_cache/\ndocs/site/*\n*covera"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 5202,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
},
{
"path": "CONTRIBUTING.md",
"chars": 4778,
"preview": "# Contributing to Panther\n\nThank you for your interest in contributing to Panther! This document provides guidelines and"
},
{
"path": "LICENSE",
"chars": 1524,
"preview": "BSD 3-Clause License\n\nCopyright (c) 2022, Ali Rajab nezhad\nAll rights reserved.\n\nRedistribution and use in source and bi"
},
{
"path": "README.md",
"chars": 4626,
"preview": "[](https://pypi.org/project/panther/) [ guide.\n\nIn this "
},
{
"path": "docs/docs/index.md",
"chars": 5342,
"preview": "## Panther\n**A Fast & Friendly Web Framework for Building Async APIs with Python 3.10+**\n\n<p align=\"center\">\n <img src="
},
{
"path": "docs/docs/log_queries.md",
"chars": 1543,
"preview": "# Query Performance Logging\n\nPanther provides built-in query performance monitoring that logs the execution time of each"
},
{
"path": "docs/docs/middlewares.md",
"chars": 5172,
"preview": "# Middlewares in Panther\n\nMiddlewares allow you to process requests and responses globally or for all or specific APIs. "
},
{
"path": "docs/docs/model.md",
"chars": 4807,
"preview": "# Panther Models\n\nPanther models allow you to define your data schema and interact with the database using Python classe"
},
{
"path": "docs/docs/open_api.md",
"chars": 4166,
"preview": "# OpenAPI Integration in Panther\n\nPanther automatically generates an OpenAPI specification for your APIs. This makes it "
},
{
"path": "docs/docs/query.md",
"chars": 3391,
"preview": "# Querying Documents\n\nPanther ODM provides a simple, async interface for interacting with your database models. This gui"
},
{
"path": "docs/docs/redis.md",
"chars": 1547,
"preview": "# Redis Integration\n\nRedis is a fast, in-memory key-value store commonly used for caching, session management, real-time"
},
{
"path": "docs/docs/release_notes.md",
"chars": 11112,
"preview": "# Panther Release Notes\n\n### 5.2.3 \n- Fix background task async loop mismatch with app event loop registration (#146)\n- "
},
{
"path": "docs/docs/roadmap.md",
"chars": 773,
"preview": "# Roadmap\n\n## Current Status\n\n, "
},
{
"path": "docs/docs/serializer.md",
"chars": 7047,
"preview": "# Panther Serializers\n\nPanther provides flexible ways to define serializers for your APIs. Serializers are responsible f"
},
{
"path": "docs/docs/single_file.md",
"chars": 1522,
"preview": "# Using Panther in a Single-File Structure\n\nIf you prefer to work with `Panther` in a single Python file, follow the ste"
},
{
"path": "docs/docs/templates.md",
"chars": 2663,
"preview": "# Template Responses\n\nPanther provides `TemplateResponse` for serving HTML templates with dynamic content. This guide ex"
},
{
"path": "docs/docs/timezone.md",
"chars": 1305,
"preview": "# Timezone Configuration\n\nPanther provides built-in timezone support to ensure consistent datetime handling across your "
},
{
"path": "docs/docs/urls.md",
"chars": 2411,
"preview": "# URL Configuration in Panther\n\nPanther requires you to define your application's URL routing. You can provide this conf"
},
{
"path": "docs/docs/user_model.md",
"chars": 923,
"preview": "# User Model Configuration\n\nYou can specify which model should be used as your User model by setting the `USER_MODEL` va"
},
{
"path": "docs/docs/websocket.md",
"chars": 8327,
"preview": "# WebSocket Support in Panther\n\n**WebSockets** enable you to build interactive, real-time features such as chat, notific"
},
{
"path": "docs/mkdocs.yml",
"chars": 1913,
"preview": "site_name: Panther\nsite_description: Fast & Friendly, Web Framework For Building Async APIs\nsite_url: https://pantherpy"
},
{
"path": "docs/pre-finetuning.jsonl",
"chars": 204231,
"preview": "{\"messages\": [{\"role\": \"system\", \"content\": \"You are learning a new Python backend framework called Panther. I will teac"
},
{
"path": "examples/blog/Dockerfile",
"chars": 573,
"preview": "# --- Builder ---\nFROM python:3.12 AS builder\nWORKDIR /app\n\n# Create virtual environment\nRUN python -m venv /opt/venv\n\n#"
},
{
"path": "examples/blog/Readme.md",
"chars": 163,
"preview": "A blog with Post and Comment. Post has title and content. Comment belongs to a Post. Anonymous users can view posts. Onl"
},
{
"path": "examples/blog/app/apis.py",
"chars": 1856,
"preview": "from datetime import timedelta\n\nfrom panther import status\nfrom panther.app import GenericAPI\nfrom panther.db import Mod"
},
{
"path": "examples/blog/app/models.py",
"chars": 175,
"preview": "from panther.db import Model\n\n\nclass Post(Model):\n title: str\n content: str\n\n\nclass Comment(Model):\n content: s"
},
{
"path": "examples/blog/app/serializers.py",
"chars": 764,
"preview": "from typing import Any\n\nfrom app.models import Comment, Post\n\nfrom panther.serializer import ModelSerializer\n\n\nclass Pos"
},
{
"path": "examples/blog/app/urls.py",
"chars": 173,
"preview": "from app.apis import CommentAPI, PostAPI, PostDetailAPI\n\nurl_routing = {\n 'posts': PostAPI,\n 'comments/<post_id>':"
},
{
"path": "examples/blog/core/configs.py",
"chars": 1231,
"preview": "import os\nfrom datetime import timedelta\n\nfrom panther.throttling import Throttle\n\n# Generate Secret Key with `panther.u"
},
{
"path": "examples/blog/core/urls.py",
"chars": 356,
"preview": "from app.urls import url_routing as app_url_routing\nfrom user.urls import url_routing as user_url_routing\n\nfrom panther."
},
{
"path": "examples/blog/docker-compose.yaml",
"chars": 565,
"preview": "volumes:\n redis_volume:\n\nservices:\n application:\n build: .\n restart: always\n volumes:\n - ./:/app\n por"
},
{
"path": "examples/blog/main.py",
"chars": 53,
"preview": "from panther import Panther\n\napp = Panther(__name__)\n"
},
{
"path": "examples/blog/requirements.txt",
"chars": 63,
"preview": "panther==5.0.0b8\nredis==6.2.0\npython-jose==3.5.0\nuvloop==0.21.0"
},
{
"path": "examples/blog/user/apis.py",
"chars": 1414,
"preview": "from user.serializers import UserOutputSerializer, UserSerializer\n\nfrom panther import status\nfrom panther.app import Ge"
},
{
"path": "examples/blog/user/serializers.py",
"chars": 354,
"preview": "from panther.db.models import BaseUser\nfrom panther.serializer import ModelSerializer\n\n\nclass UserSerializer(ModelSerial"
},
{
"path": "examples/blog/user/urls.py",
"chars": 104,
"preview": "from user.apis import LoginAPI, RegisterAPI\n\nurl_routing = {'register': RegisterAPI, 'login': LoginAPI}\n"
},
{
"path": "examples/broadcast_websocket.py",
"chars": 1687,
"preview": "from panther import Panther\nfrom panther.app import GenericAPI\nfrom panther.db import Model\nfrom panther.response import"
},
{
"path": "examples/file_upload_example.py",
"chars": 5571,
"preview": "\"\"\"\nFile Upload Example for Panther\n\nThis example demonstrates how to handle file uploads in Panther using\nthe File and "
},
{
"path": "examples/streaming_response.py",
"chars": 3881,
"preview": "import json\nfrom datetime import datetime\nfrom time import sleep\n\nfrom panther import Panther\nfrom panther.app import Ge"
},
{
"path": "panther/__init__.py",
"chars": 110,
"preview": "from panther.main import Panther # noqa: F401\n\n__version__ = '5.2.3'\n\n\ndef version():\n return __version__\n"
},
{
"path": "panther/_load_configs.py",
"chars": 11710,
"preview": "import logging\nimport sys\nimport types\nfrom importlib import import_module\nfrom multiprocessing import Manager\n\nimport j"
},
{
"path": "panther/_utils.py",
"chars": 12317,
"preview": "import asyncio\nimport importlib\nimport inspect\nimport logging\nimport mimetypes\nimport re\nimport subprocess\nimport traceb"
},
{
"path": "panther/app.py",
"chars": 9102,
"preview": "import functools\nimport inspect\nimport logging\nfrom collections.abc import Callable\nfrom datetime import timedelta\nfrom "
},
{
"path": "panther/authentications.py",
"chars": 7858,
"preview": "import logging\nimport time\nfrom abc import abstractmethod\nfrom datetime import datetime, timezone\nfrom typing import Lit"
},
{
"path": "panther/background_tasks.py",
"chars": 8149,
"preview": "\"\"\"\nExample:\n-------------------------------------------------------------\n>>> import datetime\n\n\n>>> async def hello(nam"
},
{
"path": "panther/base_request.py",
"chars": 4962,
"preview": "from collections.abc import Callable\nfrom typing import TYPE_CHECKING\nfrom urllib.parse import parse_qsl\n\nfrom panther.e"
},
{
"path": "panther/base_websocket.py",
"chars": 10813,
"preview": "from __future__ import annotations\n\nimport asyncio\nimport inspect\nimport logging\nfrom multiprocessing.managers import Sy"
},
{
"path": "panther/caching.py",
"chars": 2288,
"preview": "import logging\nfrom collections import namedtuple\nfrom datetime import datetime, timedelta\n\nimport orjson as json\n\nfrom "
},
{
"path": "panther/cli/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "panther/cli/create_project_command.py",
"chars": 9644,
"preview": "from collections.abc import Callable\nfrom pathlib import Path\n\nfrom rich import print as rich_print\nfrom rich.console im"
},
{
"path": "panther/cli/create_user_command.py",
"chars": 4104,
"preview": "import importlib.util\nimport os\nimport sys\n\nfrom rich import print as rich_print\nfrom rich.console import Console\nfrom r"
},
{
"path": "panther/cli/main.py",
"chars": 1639,
"preview": "import logging\nimport sys\n\nfrom panther import version as panther_version\nfrom panther.cli.create_project_command import"
},
{
"path": "panther/cli/monitor_command.py",
"chars": 4462,
"preview": "import contextlib\nimport logging\nimport os\nimport platform\nimport signal\nfrom collections import deque\nfrom pathlib impo"
},
{
"path": "panther/cli/run_command.py",
"chars": 356,
"preview": "import contextlib\nimport sys\n\nimport uvicorn\n\nfrom panther.cli.utils import cli_error\n\n\ndef run(args: list[str]) -> None"
},
{
"path": "panther/cli/template.py",
"chars": 4495,
"preview": "from datetime import datetime\n\nfrom panther import version\nfrom panther.utils import generate_secret_key\n\napis_py = \"\"\"f"
},
{
"path": "panther/cli/utils.py",
"chars": 5950,
"preview": "import logging\nimport platform\n\nfrom rich import print as rprint\n\nfrom panther.configs import Config\nfrom panther.except"
},
{
"path": "panther/configs.py",
"chars": 3914,
"preview": "import typing\nfrom collections.abc import Callable\nfrom dataclasses import dataclass, field\nfrom datetime import timedel"
},
{
"path": "panther/db/__init__.py",
"chars": 50,
"preview": "from panther.db.models import Model # noqa: F401\n"
},
{
"path": "panther/db/connections.py",
"chars": 4269,
"preview": "import asyncio\nimport contextlib\nfrom abc import abstractmethod\nfrom typing import TYPE_CHECKING, Any\n\nfrom pantherdb im"
},
{
"path": "panther/db/cursor.py",
"chars": 1836,
"preview": "from __future__ import annotations\n\nfrom sys import version_info\n\nfrom panther.utils import run_coroutine\n\ntry:\n from"
},
{
"path": "panther/db/models.py",
"chars": 3206,
"preview": "import contextlib\nimport os\nimport sys\nfrom datetime import datetime\nfrom typing import Annotated, ClassVar\n\nfrom pydant"
},
{
"path": "panther/db/queries/__init__.py",
"chars": 45,
"preview": "from panther.db.queries.queries import Query\n"
},
{
"path": "panther/db/queries/base_queries.py",
"chars": 13161,
"preview": "from __future__ import annotations\n\nimport operator\nimport types\nimport typing\nfrom abc import abstractmethod\nfrom colle"
},
{
"path": "panther/db/queries/mongodb_queries.py",
"chars": 7269,
"preview": "from __future__ import annotations\n\nimport typing\nfrom sys import version_info\n\nfrom panther.db.connections import db\nfr"
},
{
"path": "panther/db/queries/pantherdb_queries.py",
"chars": 5276,
"preview": "from __future__ import annotations\n\nfrom collections.abc import Iterable\nfrom sys import version_info\n\nfrom pantherdb im"
},
{
"path": "panther/db/queries/queries.py",
"chars": 14103,
"preview": "import sys\nfrom collections.abc import Iterable, Sequence\n\nfrom pantherdb import Cursor as PantherDBCursor\n\nfrom panther"
},
{
"path": "panther/db/utils.py",
"chars": 1715,
"preview": "import logging\nfrom time import perf_counter\n\nfrom panther.configs import config\n\ntry:\n # Only required if user wants"
},
{
"path": "panther/events.py",
"chars": 1902,
"preview": "import asyncio\nimport logging\n\nfrom panther._utils import is_function_async\nfrom panther.background_tasks import get_app"
},
{
"path": "panther/exceptions.py",
"chars": 2246,
"preview": "from panther import status\n\n\nclass PantherError(Exception):\n pass\n\n\nclass DatabaseError(Exception):\n pass\n\n\nclass "
},
{
"path": "panther/file_handler.py",
"chars": 4277,
"preview": "from functools import cached_property\nfrom io import BufferedReader, BytesIO\nfrom pathlib import Path\n\nfrom pydantic imp"
},
{
"path": "panther/generics.py",
"chars": 6767,
"preview": "import contextlib\nimport logging\nimport re\nfrom abc import abstractmethod\n\nfrom pantherdb import Cursor as PantherDBCurs"
},
{
"path": "panther/logging.py",
"chars": 2075,
"preview": "import logging\nfrom pathlib import Path\n\nfrom panther.configs import config\n\nLOGS_DIR = config.BASE_DIR / 'logs'\n\n\nclass"
},
{
"path": "panther/main.py",
"chars": 7495,
"preview": "import asyncio\nimport logging\nimport sys\nfrom collections.abc import Callable\nfrom logging.config import dictConfig\nfrom"
},
{
"path": "panther/middlewares/__init__.py",
"chars": 262,
"preview": "from panther.middlewares.base import HTTPMiddleware, WebsocketMiddleware # noqa: F401\nfrom panther.middlewares.cors imp"
},
{
"path": "panther/middlewares/base.py",
"chars": 721,
"preview": "import typing\n\nfrom panther.base_websocket import Websocket\nfrom panther.request import Request\nfrom panther.response im"
},
{
"path": "panther/middlewares/cors.py",
"chars": 3032,
"preview": "from panther.configs import config\nfrom panther.middlewares import HTTPMiddleware\nfrom panther.request import Request\nfr"
},
{
"path": "panther/middlewares/monitoring.py",
"chars": 1487,
"preview": "import logging\nfrom time import perf_counter\n\nfrom panther.base_websocket import Websocket\nfrom panther.middlewares impo"
},
{
"path": "panther/openapi/__init__.py",
"chars": 49,
"preview": "from panther.openapi.schemas import OutputSchema\n"
},
{
"path": "panther/openapi/schemas.py",
"chars": 1199,
"preview": "import pydantic\n\nfrom panther.serializer import ModelSerializer\n\n\nclass OutputSchema:\n \"\"\"\n Configuration class fo"
},
{
"path": "panther/openapi/templates/rapidoc.html",
"chars": 668,
"preview": "<!doctype html>\n<html>\n <head>\n <title>RapiDoc Inline JSON</title>\n <meta charset=\"utf-8\">\n <meta name=\"viewpo"
},
{
"path": "panther/openapi/templates/redoc.html",
"chars": 547,
"preview": "<!doctype html>\n<html>\n <head>\n <title>ReDoc API Reference</title>\n <meta charset=\"utf-8\" />\n <meta name=\"view"
},
{
"path": "panther/openapi/templates/scalar.html",
"chars": 446,
"preview": "<!doctype html>\n<html>\n <head>\n <title>Scalar API Reference</title>\n <meta charset=\"utf-8\" />\n <meta\n nam"
},
{
"path": "panther/openapi/templates/spotlight.html",
"chars": 702,
"preview": "<!doctype html>\n<html>\n<head>\n <title>Stoplight Elements with CSS</title>\n <link rel=\"stylesheet\" href=\"https://unpkg."
},
{
"path": "panther/openapi/templates/swagger.html",
"chars": 533,
"preview": "<!doctype html>\n<html>\n <head>\n <title>Swagger UI</title>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" con"
},
{
"path": "panther/openapi/urls.py",
"chars": 289,
"preview": "from panther.openapi.views import RapiDocOpenAPI, RedocOpenAPI, ScalarOpenAPI, SpotlightOpenAPI, SwaggerOpenAPI\n\nurl_rou"
},
{
"path": "panther/openapi/utils.py",
"chars": 24486,
"preview": "\"\"\"\nOpenAPI utilities for parsing endpoints and generating OpenAPI documentation.\n\nThis module provides classes and func"
},
{
"path": "panther/openapi/views.py",
"chars": 1306,
"preview": "from panther.app import GenericAPI\nfrom panther.openapi import OutputSchema\nfrom panther.openapi.utils import OpenAPIGen"
},
{
"path": "panther/pagination.py",
"chars": 1644,
"preview": "from pantherdb import Cursor as PantherDBCursor\n\nfrom panther.db.cursor import Cursor\n\n\nclass Pagination:\n \"\"\"\n Re"
},
{
"path": "panther/panel/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "panther/panel/authentications.py",
"chars": 466,
"preview": "from panther.authentications import CookieJWTAuthentication\nfrom panther.exceptions import AuthenticationAPIError, Redir"
},
{
"path": "panther/panel/middlewares.py",
"chars": 379,
"preview": "from panther.middlewares import HTTPMiddleware\nfrom panther.request import Request\nfrom panther.response import Redirect"
},
{
"path": "panther/panel/permissions.py",
"chars": 329,
"preview": "from panther.exceptions import RedirectAPIError\nfrom panther.permissions import BasePermission\nfrom panther.request impo"
},
{
"path": "panther/panel/templates/base.html",
"chars": 327,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width,"
},
{
"path": "panther/panel/templates/create.html",
"chars": 581,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n<div class=\"max-w-4xl mx-auto\">\n <h1 class=\"text-2xl font-semibold mb-6\""
},
{
"path": "panther/panel/templates/create.js",
"chars": 40180,
"preview": "const schema = JSON.parse(`{{fields|tojson|safe}}`);\nfunction toggleObjectVisibility(checkbox, contentId) {\n const cont"
},
{
"path": "panther/panel/templates/detail.html",
"chars": 1490,
"preview": "{% extends \"base.html\" %} {% block content %}\n<div class=\"container mx-auto px-4 py-8\">\n <div class=\"flex justify-betwe"
},
{
"path": "panther/panel/templates/home.html",
"chars": 238,
"preview": "{% extends \"base.html\" %} {% block content %}\n<div class=\"flex gap-4\">\n {% include \"sidebar.html\" %}\n <table class=\"w-"
},
{
"path": "panther/panel/templates/home.js",
"chars": 849,
"preview": "function getCurrentTableIndex() {\n const path = window.location.pathname;\n const match = path.match(/\\/admin\\/(\\d+)/);"
},
{
"path": "panther/panel/templates/login.html",
"chars": 1625,
"preview": "{% extends \"base.html\" %}\n\n{% block content %}\n <div class=\"flex items-center justify-center min-h-screen\">\n <div cl"
},
{
"path": "panther/panel/templates/sidebar.html",
"chars": 618,
"preview": "\n <aside class=\"w-64 bg-gray-800 p-4 border-gray-700 shrink-0 rounded\">\n <h2 class=\"text-lg font-semibold mb-4"
},
{
"path": "panther/panel/templates/table.html",
"chars": 2176,
"preview": "{% extends \"base.html\" %} {% block content %}\n<div class=\"flex items-start gap-4\">\n <aside class=\"w-64 bg-gray-800 p-4 "
},
{
"path": "panther/panel/templates/table.js",
"chars": 10414,
"preview": "const fields = JSON.parse(`{{fields|tojson|safe}}`);\nconst fieldsObject = fields.fields;\nconst fieldsArray = Object.entr"
},
{
"path": "panther/panel/urls.py",
"chars": 253,
"preview": "from panther.panel.views import CreateView, DetailView, HomeView, LoginView, TableView\n\nurl_routing = {\n '': HomeView"
},
{
"path": "panther/panel/utils.py",
"chars": 4138,
"preview": "from collections import defaultdict\nfrom types import NoneType\nfrom typing import Any\n\nfrom pydantic import BaseModel\n\nf"
},
{
"path": "panther/panel/views.py",
"chars": 4689,
"preview": "import logging\n\nfrom panther import status\nfrom panther.app import GenericAPI\nfrom panther.configs import config\nfrom pa"
},
{
"path": "panther/permissions.py",
"chars": 533,
"preview": "from abc import abstractmethod\n\nfrom panther.request import Request\nfrom panther.websocket import Websocket\n\n\nclass Base"
},
{
"path": "panther/request.py",
"chars": 2737,
"preview": "import logging\nfrom collections.abc import Callable\nfrom typing import Literal\nfrom urllib.parse import parse_qsl\n\nimpor"
},
{
"path": "panther/response.py",
"chars": 13260,
"preview": "import asyncio\nimport logging\nfrom collections.abc import AsyncGenerator, Generator\nfrom dataclasses import dataclass\nfr"
},
{
"path": "panther/routings.py",
"chars": 6482,
"preview": "import re\nimport types\nfrom collections import Counter\nfrom collections.abc import Callable, Mapping, MutableMapping\nfro"
},
{
"path": "panther/serializer.py",
"chars": 10637,
"preview": "import datetime\nimport typing\nfrom sys import version_info\nfrom typing import Any\n\nimport pydantic\nimport pydantic.field"
},
{
"path": "panther/status.py",
"chars": 3346,
"preview": "\"\"\"\nHTTP codes\nhttps://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml\nAnd RFC 2324 - https://tools.i"
},
{
"path": "panther/test.py",
"chars": 7557,
"preview": "import asyncio\nfrom collections.abc import Callable\nfrom typing import Literal\n\nimport orjson as json\n\nfrom panther.resp"
},
{
"path": "panther/throttling.py",
"chars": 2630,
"preview": "from collections import defaultdict\nfrom dataclasses import dataclass\nfrom datetime import datetime, timedelta\n\nfrom pan"
},
{
"path": "panther/utils.py",
"chars": 4244,
"preview": "import asyncio\nimport base64\nimport hashlib\nimport logging\nimport os\nfrom datetime import datetime, timedelta\nfrom pathl"
},
{
"path": "panther/websocket.py",
"chars": 1657,
"preview": "from __future__ import annotations\n\nfrom typing import Callable\n\nfrom panther import status\nfrom panther._utils import E"
},
{
"path": "profiler.py",
"chars": 942,
"preview": "\"\"\"\nPanther Framework Performance Profiler\n\nPrerequisites:\n- Install ali: https://github.com/nakabonne/ali\n- Install yap"
},
{
"path": "pyproject.toml",
"chars": 198,
"preview": "[build-system]\nrequires = [\n\t\"setuptools>=42\",\n\t\"wheel\"\n]\nbuild-backend = \"setuptools.build_meta\"\n\n[tool.pytest.ini_opti"
},
{
"path": "requirements.txt",
"chars": 106,
"preview": ".\npython-jose\nfaker\ncoverage\npytest\ncryptography~=42.0\nwebsockets\nmotor\nmkdocs\nmkdocs-material\nruff\nyappi\n"
},
{
"path": "ruff.toml",
"chars": 997,
"preview": "exclude = [\n \".venv\",\n\t\"build\",\n \".git\",\n]\nselect = [\n \"ALL\"\n]\nignore = [\n \"C901\", \"N818\", \"N805\", \"D1\", \"D2"
},
{
"path": "setup.py",
"chars": 1788,
"preview": "import re\nimport sys\n\nfrom setuptools import setup\n\n\ndef panther_version() -> str:\n with open('panther/__init__.py') "
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/__main__.py",
"chars": 2066,
"preview": "import argparse\nimport os\nimport sys\n\nimport pytest\nfrom _pytest.config import ExitCode\n\nTEST_DIR = os.path.dirname(__fi"
},
{
"path": "tests/sample_project/app/apis.py",
"chars": 99,
"preview": "from panther.app import API\n\n\n@API()\ndef first_api():\n pass\n\n\n@API()\ndef second_api():\n pass\n"
},
{
"path": "tests/sample_project/app/models.py",
"chars": 76,
"preview": "from panther.db.models import BaseUser\n\n\nclass User(BaseUser):\n age: int\n"
},
{
"path": "tests/sample_project/app/urls.py",
"chars": 101,
"preview": "from app.apis import first_api, second_api\n\nurls = {\n '/': first_api,\n 'second': second_api,\n}\n"
},
{
"path": "tests/sample_project/core/configs.py",
"chars": 736,
"preview": "from datetime import timedelta\nfrom pathlib import Path\n\nfrom panther.throttling import Throttle\nfrom panther.utils impo"
},
{
"path": "tests/sample_project/core/events.py",
"chars": 162,
"preview": "from panther.events import Event\n\n\n@Event.startup\nasync def startup():\n print('Starting Up')\n\n\n@Event.startup\nasync d"
},
{
"path": "tests/sample_project/core/urls.py",
"chars": 59,
"preview": "from app.urls import urls\n\nurl_routing = {\n '': urls,\n}\n"
},
{
"path": "tests/test_api_kwargs.py",
"chars": 5255,
"preview": "from unittest import IsolatedAsyncioTestCase\n\nfrom panther import Panther\nfrom panther.app import API, GenericAPI\nfrom p"
},
{
"path": "tests/test_authentication.py",
"chars": 20749,
"preview": "from pathlib import Path\nfrom unittest import IsolatedAsyncioTestCase\n\nfrom panther import Panther\nfrom panther.app impo"
},
{
"path": "tests/test_background_task_event_loop_isolation.py",
"chars": 2187,
"preview": "import asyncio\nfrom unittest import TestCase\nfrom threading import Event, Thread\n\nimport panther.background_tasks as bac"
},
{
"path": "tests/test_background_tasks.py",
"chars": 6386,
"preview": "import datetime\nimport time\nfrom unittest import TestCase\n\nimport pytest\n\nfrom panther.background_tasks import Backgroun"
},
{
"path": "tests/test_caching.py",
"chars": 2744,
"preview": "import asyncio\nimport time\nfrom datetime import timedelta\nfrom unittest import IsolatedAsyncioTestCase\n\nimport pytest\n\nf"
},
{
"path": "tests/test_cli.py",
"chars": 8887,
"preview": "import os\nimport shutil\nimport sys\nfrom io import StringIO\nfrom pathlib import Path\nfrom unittest import skipIf\nfrom uni"
},
{
"path": "tests/test_config.py",
"chars": 10460,
"preview": "from datetime import timedelta\nfrom pathlib import Path\nfrom unittest import TestCase\n\nimport jinja2\n\nfrom panther impor"
},
{
"path": "tests/test_cors.py",
"chars": 3658,
"preview": "from unittest import IsolatedAsyncioTestCase\n\nfrom panther import Panther\nfrom panther.app import API\nfrom panther.confi"
},
{
"path": "tests/test_database.py",
"chars": 18070,
"preview": "import random\nfrom pathlib import Path\nfrom unittest import IsolatedAsyncioTestCase\n\nimport faker\nimport pytest\nfrom pan"
},
{
"path": "tests/test_database_advance.py",
"chars": 37159,
"preview": "import asyncio\nimport contextlib\nfrom pathlib import Path\nfrom typing import List\nfrom unittest import IsolatedAsyncioTe"
},
{
"path": "tests/test_events.py",
"chars": 4144,
"preview": "import logging\nfrom unittest import IsolatedAsyncioTestCase\n\nfrom panther.configs import config\nfrom panther.events impo"
},
{
"path": "tests/test_file.py",
"chars": 12876,
"preview": "import platform\nimport tempfile\nimport unittest\nfrom pathlib import Path\n\nfrom panther._utils import detect_mime_type\nfr"
},
{
"path": "tests/test_find_one_or_insert_race.py",
"chars": 2485,
"preview": "import asyncio\nfrom unittest import IsolatedAsyncioTestCase\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom panther"
},
{
"path": "tests/test_generics.py",
"chars": 9040,
"preview": "from pathlib import Path\nfrom unittest import IsolatedAsyncioTestCase\n\nimport pytest\n\nfrom panther import Panther\nfrom p"
},
{
"path": "tests/test_generics_search_regex_escape.py",
"chars": 4266,
"preview": "import multiprocessing as mp\nimport re\nimport time\nfrom typing import ClassVar\nfrom unittest import TestCase\n\nfrom panth"
},
{
"path": "tests/test_middlewares.py",
"chars": 10252,
"preview": "from unittest import IsolatedAsyncioTestCase, TestCase\n\nfrom panther import Panther\nfrom panther.app import API\nfrom pan"
},
{
"path": "tests/test_multipart.py",
"chars": 7600,
"preview": "from unittest import IsolatedAsyncioTestCase\n\nfrom panther import Panther\nfrom panther.app import API\nfrom panther.confi"
},
{
"path": "tests/test_openapi.py",
"chars": 15145,
"preview": "from unittest import IsolatedAsyncioTestCase\n\nfrom pydantic import BaseModel\n\nfrom panther import Panther, status\nfrom p"
},
{
"path": "tests/test_panel.py",
"chars": 14818,
"preview": "from pathlib import Path\nfrom unittest import IsolatedAsyncioTestCase\n\nfrom panther import Panther\nfrom panther.configs "
},
{
"path": "tests/test_permission.py",
"chars": 6909,
"preview": "from unittest import IsolatedAsyncioTestCase\n\nfrom panther import Panther\nfrom panther.app import API, GenericAPI\nfrom p"
},
{
"path": "tests/test_request.py",
"chars": 15239,
"preview": "from unittest import IsolatedAsyncioTestCase\n\nimport orjson as json\n\nfrom panther import Panther\nfrom panther.app import"
},
{
"path": "tests/test_request_isolation.py",
"chars": 2846,
"preview": "import asyncio\nfrom typing import ClassVar\nfrom unittest import IsolatedAsyncioTestCase\n\nfrom panther import Panther\nfro"
},
{
"path": "tests/test_response.py",
"chars": 23972,
"preview": "import platform\nfrom unittest import IsolatedAsyncioTestCase\n\nfrom pydantic import BaseModel\n\nfrom panther import Panthe"
},
{
"path": "tests/test_response_output_model.py",
"chars": 5782,
"preview": "from pathlib import Path\nfrom typing import ClassVar\nfrom unittest import IsolatedAsyncioTestCase\n\nfrom pydantic import "
},
{
"path": "tests/test_routing.py",
"chars": 34664,
"preview": "import random\nfrom unittest import TestCase\n\nfrom panther.base_request import BaseRequest\nfrom panther.configs import co"
},
{
"path": "tests/test_run.py",
"chars": 1737,
"preview": "import sys\nfrom datetime import timedelta\nfrom pathlib import Path\nfrom unittest import TestCase\n\nimport tests.sample_pr"
},
{
"path": "tests/test_serializer.py",
"chars": 14290,
"preview": "from pathlib import Path\nfrom unittest import IsolatedAsyncioTestCase\n\nfrom pydantic import ConfigDict, Field, field_val"
},
{
"path": "tests/test_status.py",
"chars": 5337,
"preview": "from unittest import TestCase\n\nfrom panther import status\n\n\nclass TestStatus(TestCase):\n @classmethod\n def setUpCl"
},
{
"path": "tests/test_throttling.py",
"chars": 3448,
"preview": "import asyncio\nfrom datetime import datetime, timedelta\nfrom unittest import IsolatedAsyncioTestCase\n\nfrom panther impor"
},
{
"path": "tests/test_utils.py",
"chars": 15299,
"preview": "import os\nfrom datetime import datetime, timedelta\nfrom pathlib import Path\nfrom unittest import TestCase\n\nfrom panther "
},
{
"path": "tests/test_websockets.py",
"chars": 11256,
"preview": "from unittest import TestCase\n\nimport orjson as json\n\nfrom panther import Panther, status\nfrom panther.authentications i"
}
]
About this extraction
This page contains the full source code of the AliRn76/panther GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 178 files (1.0 MB), approximately 247.7k tokens, and a symbol index with 1592 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.