Add tests to all django supported RDBMS to CI, also add macos and windows tests.

Remove development dependency on gettext - compilemessages is run in release action and during just build
This commit is contained in:
Brian Kohan 2025-08-25 00:13:19 -07:00
parent 282ac7f617
commit c64b097be2
No known key found for this signature in database
GPG Key ID: 827B6A3B1AAE16EE
11 changed files with 1942 additions and 2238 deletions

View File

@ -64,8 +64,6 @@ jobs:
enable-cache: true
- name: Install Just
uses: extractions/setup-just@v3
- name: Install gettext
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends gettext
- name: Install Dependencies
run: |
just setup ${{ steps.sp.outputs.python-path }}

View File

@ -16,10 +16,10 @@ jobs:
python-version: "3"
cache: pip
cache-dependency-path: setup.cfg
# TODO: look into using python-babel instead of requiring gettext as system dependency
- name: Setup Just
uses: extractions/setup-just@v3
- run: sudo apt-get update && sudo apt-get install -y --no-install-recommends gettext
- run: pip install -U build
- run: python -m build
- run: just build
- uses: actions/upload-artifact@v4
with:
name: dist

View File

@ -1,50 +1,700 @@
name: Test
permissions:
contents: read
on:
- pull_request
- push
- workflow_dispatch
push:
tags-ignore:
- '*'
branches:
- '*'
pull_request:
workflow_call:
workflow_dispatch:
inputs:
debug:
description: 'Open ssh debug session.'
required: true
default: false
type: boolean
schedule:
- cron: '0 13 * * *' # Runs at 6 am pacific every day
jobs:
Build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3"
cache: pip
cache-dependency-path: setup.cfg
# TODO: look into using python-babel instead of requiring gettext as system dependency
- run: sudo apt-get update && sudo apt-get install -y --no-install-recommends gettext
- run: pip install -U build
- run: python -m build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
Test:
postgres:
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
# Service containers to run with `container-job`
strategy:
fail-fast: true
fail-fast: false
matrix:
include:
- python-version: "3.9"
- python-version: "3.10"
- python-version: "3.11"
- python-version: "3.12"
- python-version: "3.13"
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
postgres-version: ['9.6', '12', 'latest']
psycopg-version: ['psycopg2', 'psycopg3']
django-version:
- '3.2' # LTS April 2024
- '4.2' # LTS April 2026
- '5.1' # December 2025
- '5.2' # LTS April 2028
exclude:
- django-version: '4.2'
postgres-version: '9.6'
- python-version: '3.11'
django-version: '3.2'
- python-version: '3.12'
django-version: '3.2'
- postgres-version: '12'
psycopg-version: 'psycopg3'
- django-version: '3.2'
psycopg-version: 'psycopg3'
- django-version: '3.2'
postgres-version: 'latest'
- postgres-version: '12'
django-version: '5.1'
- postgres-version: '9.6'
django-version: '5.1'
- postgres-version: '9.6'
django-version: '5.2'
- postgres-version: '12'
django-version: '5.2'
- python-version: '3.9'
django-version: '5.1'
- python-version: '3.9'
django-version: '5.2'
- python-version: '3.13'
django-version: '3.2'
- python-version: '3.13'
django-version: '4.2'
# https://github.com/psycopg/psycopg2/pull/1695
- python-version: '3.13'
psycopg-version: 'psycopg2'
env:
RDBMS: postgres
POSTGRES_PASSWORD: postgres
PGPASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_HOST: localhost
POSTGRES_PORT: 5432
COVERAGE_FILE: linux-py${{ matrix.python-version }}-dj${{ matrix.django-version }}-${{ matrix.psycopg-version }}-pg${{ matrix.postgres-version }}.coverage
TEST_PYTHON_VERSION: ${{ matrix.python-version }}
TEST_DJANGO_VERSION: ${{ matrix.django-version }}
TEST_DATABASE_CLIENT_VERSION: ${{ matrix.psycopg-version }}
TEST_DATABASE_VERSION: ${{ matrix.postgres-version }}
# Service containers to run with `runner-job`
services:
# Label used to access the service container
postgres:
# Docker Hub image
image: postgres:${{ matrix.postgres-version }}
# Provide the password for postgres
env:
POSTGRES_PASSWORD: postgres
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps tcp port 5432 on service container to the host
- 5432:5432
steps:
- uses: actions/checkout@v4
- name: "Set up Python ${{ matrix.python-version }}"
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
id: sp
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Create test databases
run: |
psql -h localhost -p 5432 -U postgres -d postgres -c "CREATE DATABASE test1;"
psql -h localhost -p 5432 -U postgres -d postgres -c "CREATE DATABASE test2;"
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Setup Just
uses: extractions/setup-just@v3
- name: Install Emacs
if: ${{ github.event.inputs.debug == 'true' }}
run: |
sudo apt install emacs
- name: Setup tmate session
if: ${{ github.event.inputs.debug == 'true' }}
uses: mxschmitt/action-tmate@v3.22
with:
detached: true
timeout-minutes: 60
- name: Install Release Dependencies
run: |
just setup ${{ steps.sp.outputs.python-path }}
just test-lock "Django~=${{ matrix.django-version }}.0"
- name: Run Unit Tests
run: |
just test-db ${{ matrix.psycopg-version }}
- name: Store coverage files
uses: actions/upload-artifact@v4
with:
name: ${{ env.COVERAGE_FILE }}
path: ${{ env.COVERAGE_FILE }}
sqlite:
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
env:
RDBMS: sqlite
COVERAGE_FILE: linux-py${{ matrix.python-version }}-dj${{ matrix.django-version }}-sqlite.coverage
TEST_PYTHON_VERSION: ${{ matrix.python-version }}
TEST_DJANGO_VERSION: ${{ matrix.django-version }}
TEST_DATABASE_VERSION: "sqlite"
strategy:
fail-fast: false
matrix:
python-version: [ '3.9', '3.13']
django-version:
- '3.2' # LTS April 2024
- '4.2' # LTS April 2026
- '5.2' # LTS April 2028
exclude:
- python-version: '3.9'
django-version: '5.2'
- python-version: '3.13'
django-version: '3.2'
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
id: sp
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Setup Just
uses: extractions/setup-just@v3
- name: Install Emacs
if: ${{ github.event.inputs.debug == 'true' }}
run: |
sudo apt install emacs
- name: Setup tmate session
if: ${{ github.event.inputs.debug == 'true' }}
uses: mxschmitt/action-tmate@v3.22
with:
detached: true
timeout-minutes: 60
- name: Install Release Dependencies
run: |
just setup ${{ steps.sp.outputs.python-path }}
just install
just test-lock Django~=${{ matrix.django-version }}.0
- name: Run Unit Tests
run: |
just test-db
- name: Store coverage files
uses: actions/upload-artifact@v4
with:
name: ${{ env.COVERAGE_FILE }}
path: ${{ env.COVERAGE_FILE }}
mysql:
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
strategy:
fail-fast: false
matrix:
python-version: [ '3.9', '3.13']
mysql-version: ['5.7', 'latest']
mysqlclient-version: ['1.4.3', '']
django-version:
- '3.2' # LTS April 2024
- '4.2' # LTS April 2026
- '5.2' # LTS April 2028
exclude:
- python-version: '3.13'
django-version: '3.2'
- python-version: '3.9'
django-version: '5.2'
- django-version: '3.2'
mysql-version: 'latest'
- django-version: '4.2'
mysql-version: '5.7'
- django-version: '5.2'
mysql-version: '5.7'
- mysql-version: '5.7'
mysqlclient-version: ''
- mysql-version: 'latest'
mysqlclient-version: '1.4.3'
env:
RDBMS: mysql
MYSQL_VERSION: ${{ matrix.mysql-version }}
COVERAGE_FILE: linux-py${{ matrix.python-version }}-dj${{ matrix.django-version }}-${{ matrix.mysqlclient-version }}-mysql${{ matrix.mysql-version }}.coverage
TEST_PYTHON_VERSION: ${{ matrix.python-version }}
TEST_DJANGO_VERSION: ${{ matrix.django-version }}
TEST_DATABASE_CLIENT_VERSION: ${{ matrix.mysqlclient-version }}
TEST_DATABASE_VERSION: ${{ matrix.mysql-version }}
services:
mysql:
# Docker Hub image
image: mysql:${{ matrix.mysql-version }}
# Provide the password for mysql
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_MULTIPLE_DATABASES: test1,test2
# Set health checks to wait until mysql has started
options: >-
--health-cmd "mysqladmin ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps tcp port 3306 on service container to the host
- 3306:3306
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
id: sp
with:
python-version: ${{ matrix.python-version }}
- name: Install Emacs
if: ${{ github.event.inputs.debug == 'true' }}
run: |
sudo apt install emacs
- name: Setup tmate session
if: ${{ github.event.inputs.debug == 'true' }}
uses: mxschmitt/action-tmate@v3.22
with:
detached: true
timeout-minutes: 60
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Setup Just
uses: extractions/setup-just@v3
- name: Install Release Dependencies
run: |
just setup ${{ steps.sp.outputs.python-path }}
just test-lock Django~=${{ matrix.django-version }}.0
- name: Install mysqlclient if needed
if: ${{ matrix.mysqlclient-version != '' }}
run: just test-lock mysqlclient==${{ matrix.mysqlclient-version }}
- name: Run Unit Tests
run: |
just test-db mysql
- name: Store coverage files
uses: actions/upload-artifact@v4
with:
name: ${{ env.COVERAGE_FILE }}
path: ${{ env.COVERAGE_FILE }}
mariadb:
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
env:
RDBMS: mariadb
COVERAGE_FILE: linux-py${{ matrix.python-version }}-dj${{ matrix.django-version }}-${{ matrix.mysqlclient-version }}-mariadb${{ matrix.mariadb-version }}.coverage
TEST_PYTHON_VERSION: ${{ matrix.python-version }}
TEST_DJANGO_VERSION: ${{ matrix.django-version }}
TEST_DATABASE_CLIENT_VERSION: ${{ matrix.mysqlclient-version }}
TEST_DATABASE_VERSION: ${{ matrix.mariadb-version }}
strategy:
fail-fast: false
matrix:
python-version: [ '3.9', '3.13']
mysqlclient-version: ['1.4.3', '']
mariadb-version: ['10.2', 'latest']
mariadb-healthcheck: ["mysqladmin ping", "healthcheck.sh --connect --innodb_initialized"]
django-version:
- '3.2' # LTS April 2024
- '4.2' # LTS April 2026
- '5.2' # LTS April 2028
exclude:
- python-version: '3.13'
django-version: '3.2'
- python-version: '3.9'
django-version: '5.2'
- django-version: '3.2'
mariadb-version: 'latest'
- django-version: '4.2'
mariadb-version: '10.2'
- django-version: '5.2'
mariadb-version: '10.2'
- mariadb-version: '10.2'
mysqlclient-version: ''
- mariadb-version: 'latest'
mysqlclient-version: '1.4.3'
- mariadb-version: 'latest'
mariadb-healthcheck: "mysqladmin ping"
- mariadb-version: '10.2'
mariadb-healthcheck: "healthcheck.sh --connect --innodb_initialized"
services:
mysql:
# Docker Hub image
image: mariadb:${{ matrix.mariadb-version }}
# Provide the password for mysql
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_MULTIPLE_DATABASES: test1,test2
# Set health checks to wait until mysql has started
options: >-
--health-cmd="${{ matrix.mariadb-healthcheck }}"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps tcp port 3306 on service container to the host
- 3306:3306
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
id: sp
with:
python-version: ${{ matrix.python-version }}
- name: Install Emacs
if: ${{ github.event.inputs.debug == 'true' }}
run: |
sudo apt install emacs
- name: Setup tmate session
if: ${{ github.event.inputs.debug == 'true' }}
uses: mxschmitt/action-tmate@v3.22
with:
detached: true
timeout-minutes: 60
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Setup Just
uses: extractions/setup-just@v3
- name: Install Release Dependencies
run: |
just setup ${{ steps.sp.outputs.python-path }}
just test-lock Django~=${{ matrix.django-version }}.0
- name: Install mysqlclient if needed
if: ${{ matrix.mysqlclient-version != '' }}
run: just test-lock mysqlclient==${{ matrix.mysqlclient-version }}
- name: Run Unit Tests
run: |
just test-db mysql
- name: Store coverage files
uses: actions/upload-artifact@v4
with:
name: ${{ env.COVERAGE_FILE }}
path: ${{ env.COVERAGE_FILE }}
oracle:
runs-on: ubuntu-latest
permissions:
contents: read
actions: write
env:
RDBMS: oracle
COVERAGE_FILE: linux-py${{ matrix.python-version }}-dj${{ matrix.django-version }}-oracle${{ matrix.oracle-version }}.coverage
TEST_PYTHON_VERSION: ${{ matrix.python-version }}
TEST_DJANGO_VERSION: ${{ matrix.django-version }}
TEST_DATABASE_VERSION: ${{ matrix.oracle-version }}
strategy:
fail-fast: false
matrix:
python-version: ['3.9', '3.10', '3.12']
django-version:
- '3.2' # LTS April 2024
- '4.2' # LTS April 2026
- '5.2' # LTS April 2028
oracle-version:
- '18'
- 'latest'
exclude:
- python-version: '3.9'
django-version: '5.2'
- python-version: '3.10'
django-version: '5.2'
- python-version: '3.10'
django-version: '3.2'
- python-version: '3.12'
django-version: '3.2'
- python-version: '3.12'
django-version: '4.2'
- python-version: '3.9'
django-version: '4.2'
- django-version: '3.2'
oracle-version: 'latest'
- django-version: '4.2'
oracle-version: '18'
- django-version: '5.2'
oracle-version: '18'
services:
oracle1:
image: gvenzl/oracle-xe:${{ matrix.oracle-version }}
env:
ORACLE_PASSWORD: password
ORACLE_DATABASE: test1
# Forward Oracle port
ports:
- 1521:1521
# Provide healthcheck script options for startup
options: >-
--health-cmd healthcheck.sh
--health-interval 10s
--health-timeout 5s
--health-retries 10
oracle2:
image: gvenzl/oracle-xe:${{ matrix.oracle-version }}
env:
ORACLE_PASSWORD: password
ORACLE_DATABASE: test2
# Forward Oracle port
ports:
- 1522:1521
# Provide healthcheck script options for startup
options: >-
--health-cmd healthcheck.sh
--health-interval 10s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
id: sp
uses: actions/setup-python@v5
with:
python-version: "${{ matrix.python-version }}"
cache: pip
cache-dependency-path: setup.cfg
# TODO: look into using python-babel instead of requiring gettext as system dependency
- run: sudo apt-get update && sudo apt-get install -y --no-install-recommends gettext
# TODO: look into using python-babel instead of requiring django to be ambiently installed
# so tox can build messages
- run: pip install -U tox tox-gh-actions django
# TODO: postgres setup and proper djangomain testing
- run: python -m tox
- uses: codecov/codecov-action@v3
python-version: ${{ matrix.python-version }}
- name: Install Emacs
if: ${{ github.event.inputs.debug == 'true' }}
run: |
sudo apt install emacs
- name: Setup tmate session
if: ${{ github.event.inputs.debug == 'true' }}
uses: mxschmitt/action-tmate@v3.22
with:
detached: true
timeout-minutes: 60
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Setup Just
uses: extractions/setup-just@v3
- name: Install Oracle Client
# https://askubuntu.com/questions/1512196/libaio1-on-noble
run: |
curl --output oracle-client.rpm https://download.oracle.com/otn_software/linux/instantclient/2116000/oracle-instantclient-basiclite-21.16.0.0.0-1.el8.x86_64.rpm
sudo apt install alien libaio1t64
sudo alien -i oracle-client.rpm
sudo sh -c "echo /usr/lib/oracle/21/client64/lib/ > /etc/ld.so.conf.d/oracle.conf"
sudo ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/x86_64-linux-gnu/libaio.so.1
sudo ldconfig
- name: Install Release Dependencies
run: |
just setup ${{ steps.sp.outputs.python-path }}
just test-lock Django~=${{ matrix.django-version }}.0
- name: Run Full Unit Tests
run: |
just test-db oracle
- name: Store coverage files
uses: actions/upload-artifact@v4
with:
name: ${{ env.COVERAGE_FILE }}
path: ${{ env.COVERAGE_FILE }}
windows:
runs-on: windows-latest
permissions:
contents: read
actions: write
env:
RDBMS: sqlite
COVERAGE_FILE: windows-py${{ matrix.python-version }}-dj${{ matrix.django-version }}-sqlite.coverage
TEST_PYTHON_VERSION: ${{ matrix.python-version }}
TEST_DJANGO_VERSION: ${{ matrix.django-version }}
TEST_DATABASE_VERSION: "sqlite"
strategy:
fail-fast: false
matrix:
python-version: [ '3.9', '3.13']
django-version:
- '3.2' # LTS April 2024
- '5.2' # LTS April 2028
exclude:
- python-version: '3.9'
django-version: '5.2'
- python-version: '3.13'
django-version: '3.2'
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
id: sp
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Setup Just
uses: extractions/setup-just@v3
- name: install-vim-windows
if: ${{ github.event.inputs.debug == 'true' }}
uses: rhysd/action-setup-vim@v1
- name: Setup tmate session
if: ${{ github.event.inputs.debug == 'true' }}
uses: mxschmitt/action-tmate@v3.22
with:
detached: true
timeout-minutes: 60
- name: Install Release Dependencies
run: |
just setup ${{ steps.sp.outputs.python-path }}
just install
just test-lock Django~=${{ matrix.django-version }}.0
- name: Run Unit Tests
run: |
just test
- name: Store coverage files
uses: actions/upload-artifact@v4
with:
name: ${{ env.COVERAGE_FILE }}
path: ${{ env.COVERAGE_FILE }}
macos:
runs-on: macos-latest
permissions:
contents: read
actions: write
env:
RDBMS: sqlite
COVERAGE_FILE: macos-py${{ matrix.python-version }}-dj${{ matrix.django-version }}-sqlite.coverage
TEST_PYTHON_VERSION: ${{ matrix.python-version }}
TEST_DJANGO_VERSION: ${{ matrix.django-version }}
TEST_DATABASE_VERSION: "sqlite"
strategy:
fail-fast: false
matrix:
python-version: [ '3.9', '3.13']
django-version:
- '3.2' # LTS April 2024
- '5.2' # LTS April 2028
exclude:
- python-version: '3.9'
django-version: '5.2'
- python-version: '3.13'
django-version: '3.2'
steps:
- uses: actions/checkout@v5
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
id: sp
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Setup Just
uses: extractions/setup-just@v3
- name: install-emacs-macos
if: ${{ github.event.inputs.debug == 'true' }}
run: |
brew install emacs
- name: Setup tmate session
if: ${{ github.event.inputs.debug == 'true' }}
uses: mxschmitt/action-tmate@v3.22
with:
detached: true
timeout-minutes: 60
- name: Install Release Dependencies
run: |
just setup ${{ steps.sp.outputs.python-path }}
just install
just test-lock Django~=${{ matrix.django-version }}.0
- name: Run Unit Tests
run: |
just test
- name: Store coverage files
uses: actions/upload-artifact@v4
with:
name: ${{ env.COVERAGE_FILE }}
path: ${{ env.COVERAGE_FILE }}
coverage-combine:
needs: [postgres, sqlite, mysql, mariadb, oracle, windows, macos]
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v5
id: sp
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Setup Just
uses: extractions/setup-just@v3
- name: Install Release Dependencies
run: |
just setup ${{ steps.sp.outputs.python-path }}
just install
- name: Get coverage files
uses: actions/download-artifact@v5
with:
pattern: "*.coverage"
merge-multiple: true
- run: ls -la *.coverage
- run: just coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files:
./coverage.xml

View File

@ -1,18 +0,0 @@
import sys
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomBuildHook(BuildHookInterface):
"""
Hatch build hook to run Django's compilemessages command
during the build process. This is necessary to ensure that
translation files are compiled and included in the build output.
See https://hatch.pypa.io/latest/plugins/build-hook/custom/
"""
def initialize(self, version, build_data):
from django.core import management
management.call_command("compilemessages", stdout=sys.stderr, verbosity=1)

View File

@ -89,6 +89,7 @@ build-docs: build-docs-html
# build docs and package
build: build-docs-html
@just manage compilemessages
uv build
# open the html documentation
@ -179,6 +180,12 @@ test-lock +PACKAGES: _lock-python
test *TESTS:
@just run pytest --cov-append {{ TESTS }}
test-db DB_CLIENT="dev":
# No Optional Dependency Unit Tests
# todo clean this up, rerunning a lot of tests
uv sync --group {{ DB_CLIENT }}
@just run pytest --cov-append
# run the pre-commit checks
precommit:
@just run pre-commit

View File

@ -78,9 +78,6 @@ exclude = ["src/polymorphic/tests"]
packages = ["src/polymorphic"]
artifacts = ["*.mo"]
[tool.hatch.build.targets.wheel.hooks.custom]
require-runtime-dependencies = true # We need Django so we have compilemessages
[tool.doc8]
max-line-length = 100
sphinx = true
@ -175,5 +172,11 @@ psycopg2 = [
"psycopg2>=2.9.10",
]
psycopg3 = [
"psycopg>=3.2.5",
"psycopg",
]
mysql = [
"mysqlclient>=1.4.0",
]
oracle = [
"cx-oracle>=8.3.0",
]

File diff suppressed because it is too large Load Diff

View File

@ -13,101 +13,101 @@ from polymorphic.showfields import ShowFieldContent, ShowFieldType, ShowFieldTyp
class PlainA(models.Model):
field1 = models.CharField(max_length=10)
field1 = models.CharField(max_length=30)
class PlainB(PlainA):
field2 = models.CharField(max_length=10)
field2 = models.CharField(max_length=30)
class PlainC(PlainB):
field3 = models.CharField(max_length=10)
field3 = models.CharField(max_length=30)
class Model2A(ShowFieldType, PolymorphicModel):
field1 = models.CharField(max_length=10)
field1 = models.CharField(max_length=30)
polymorphic_showfield_deferred = True
class Model2B(Model2A):
field2 = models.CharField(max_length=10)
field2 = models.CharField(max_length=30)
class Model2C(Model2B):
field3 = models.CharField(max_length=10)
field3 = models.CharField(max_length=30)
class Model2D(Model2C):
field4 = models.CharField(max_length=10)
field4 = models.CharField(max_length=30)
class ModelExtraA(ShowFieldTypeAndContent, PolymorphicModel):
field1 = models.CharField(max_length=10)
field1 = models.CharField(max_length=30)
class ModelExtraB(ModelExtraA):
field2 = models.CharField(max_length=10)
field2 = models.CharField(max_length=30)
class ModelExtraC(ModelExtraB):
field3 = models.CharField(max_length=10)
field3 = models.CharField(max_length=30)
class ModelExtraExternal(models.Model):
topic = models.CharField(max_length=10)
topic = models.CharField(max_length=30)
class ModelShow1(ShowFieldType, PolymorphicModel):
field1 = models.CharField(max_length=10)
field1 = models.CharField(max_length=30)
m2m = models.ManyToManyField("self")
class ModelShow2(ShowFieldContent, PolymorphicModel):
field1 = models.CharField(max_length=10)
field1 = models.CharField(max_length=30)
m2m = models.ManyToManyField("self")
class ModelShow3(ShowFieldTypeAndContent, PolymorphicModel):
field1 = models.CharField(max_length=10)
field1 = models.CharField(max_length=30)
m2m = models.ManyToManyField("self")
class ModelShow1_plain(PolymorphicModel):
field1 = models.CharField(max_length=10)
field1 = models.CharField(max_length=30)
class ModelShow2_plain(ModelShow1_plain):
field2 = models.CharField(max_length=10)
field2 = models.CharField(max_length=30)
class Base(ShowFieldType, PolymorphicModel):
polymorphic_showfield_deferred = True
field_b = models.CharField(max_length=10)
field_b = models.CharField(max_length=30)
class ModelX(Base):
field_x = models.CharField(max_length=10)
field_x = models.CharField(max_length=30)
class ModelY(Base):
field_y = models.CharField(max_length=10)
field_y = models.CharField(max_length=30)
class Enhance_Plain(models.Model):
field_p = models.CharField(max_length=10)
field_p = models.CharField(max_length=30)
class Enhance_Base(ShowFieldTypeAndContent, PolymorphicModel):
base_id = models.AutoField(primary_key=True)
field_b = models.CharField(max_length=10)
field_b = models.CharField(max_length=30)
class Enhance_Inherit(Enhance_Base, Enhance_Plain):
field_i = models.CharField(max_length=10)
field_i = models.CharField(max_length=30)
class RelationBase(ShowFieldTypeAndContent, PolymorphicModel):
field_base = models.CharField(max_length=10)
field_base = models.CharField(max_length=30)
fk = models.ForeignKey(
"self", on_delete=models.CASCADE, null=True, related_name="relationbase_set"
)
@ -115,15 +115,15 @@ class RelationBase(ShowFieldTypeAndContent, PolymorphicModel):
class RelationA(RelationBase):
field_a = models.CharField(max_length=10)
field_a = models.CharField(max_length=30)
class RelationB(RelationBase):
field_b = models.CharField(max_length=10)
field_b = models.CharField(max_length=30)
class RelationBC(RelationB):
field_c = models.CharField(max_length=10)
field_c = models.CharField(max_length=30)
class RelatingModel(models.Model):
@ -132,23 +132,23 @@ class RelatingModel(models.Model):
class One2OneRelatingModel(PolymorphicModel):
one2one = models.OneToOneField(Model2A, on_delete=models.CASCADE)
field1 = models.CharField(max_length=10)
field1 = models.CharField(max_length=30)
class One2OneRelatingModelDerived(One2OneRelatingModel):
field2 = models.CharField(max_length=10)
field2 = models.CharField(max_length=30)
class ModelUnderRelParent(PolymorphicModel):
field1 = models.CharField(max_length=10)
_private = models.CharField(max_length=10)
field1 = models.CharField(max_length=30)
_private = models.CharField(max_length=30)
class ModelUnderRelChild(PolymorphicModel):
parent = models.ForeignKey(
ModelUnderRelParent, on_delete=models.CASCADE, related_name="children"
)
_private2 = models.CharField(max_length=10)
_private2 = models.CharField(max_length=30)
class MyManagerQuerySet(PolymorphicQuerySet):
@ -169,24 +169,24 @@ class MyManager(PolymorphicManager):
class ModelWithMyManager(ShowFieldTypeAndContent, Model2A):
objects = MyManager()
field4 = models.CharField(max_length=10)
field4 = models.CharField(max_length=30)
class ModelWithMyManagerNoDefault(ShowFieldTypeAndContent, Model2A):
objects = PolymorphicManager()
my_objects = MyManager()
field4 = models.CharField(max_length=10)
field4 = models.CharField(max_length=30)
class ModelWithMyManagerDefault(ShowFieldTypeAndContent, Model2A):
my_objects = MyManager()
objects = PolymorphicManager()
field4 = models.CharField(max_length=10)
field4 = models.CharField(max_length=30)
class ModelWithMyManager2(ShowFieldTypeAndContent, Model2A):
objects = MyManagerQuerySet.as_manager()
field4 = models.CharField(max_length=10)
field4 = models.CharField(max_length=30)
class ModelArticle(PolymorphicModel):
@ -194,11 +194,11 @@ class ModelArticle(PolymorphicModel):
class ModelPackage(ModelArticle):
name = models.CharField(max_length=100)
name = models.CharField(max_length=300)
class ModelComponent(ModelArticle):
name = models.CharField(max_length=100)
name = models.CharField(max_length=300)
class ModelOrderLine(models.Model):
@ -207,7 +207,7 @@ class ModelOrderLine(models.Model):
class MROBase1(ShowFieldType, PolymorphicModel):
objects = MyManager()
field1 = models.CharField(max_length=10) # needed as MyManager uses it
field1 = models.CharField(max_length=30) # needed as MyManager uses it
class MROBase2(MROBase1):
@ -231,7 +231,7 @@ class ParentModelWithManager(PolymorphicModel):
class ChildModelWithManager(PolymorphicModel):
# Also test whether foreign keys receive the manager:
field1 = models.CharField(max_length=10) # needed as MyManager uses it
field1 = models.CharField(max_length=30) # needed as MyManager uses it
fk = models.ForeignKey(
ParentModelWithManager, on_delete=models.CASCADE, related_name="childmodel_set"
)
@ -266,11 +266,11 @@ class PlainChildModelWithManager(models.Model):
class BlogBase(ShowFieldTypeAndContent, PolymorphicModel):
name = models.CharField(max_length=10)
name = models.CharField(max_length=30)
class BlogA(BlogBase):
info = models.CharField(max_length=10)
info = models.CharField(max_length=30)
class BlogB(BlogBase):
@ -279,20 +279,20 @@ class BlogB(BlogBase):
class BlogEntry(ShowFieldTypeAndContent, PolymorphicModel):
blog = models.ForeignKey(BlogA, on_delete=models.CASCADE)
text = models.CharField(max_length=10)
text = models.CharField(max_length=30)
class BlogEntry_limit_choices_to(ShowFieldTypeAndContent, PolymorphicModel):
blog = models.ForeignKey(BlogBase, on_delete=models.CASCADE)
text = models.CharField(max_length=10)
text = models.CharField(max_length=30)
class ModelFieldNameTest(ShowFieldType, PolymorphicModel):
modelfieldnametest = models.CharField(max_length=10)
modelfieldnametest = models.CharField(max_length=30)
class InitTestModel(ShowFieldType, PolymorphicModel):
bar = models.CharField(max_length=100)
bar = models.CharField(max_length=300)
def __init__(self, *args, **kwargs):
kwargs["bar"] = self.x()
@ -334,15 +334,15 @@ class UUIDResearchProject(UUIDProject):
class UUIDPlainA(models.Model):
uuid_primary_key = models.UUIDField(primary_key=True, default=uuid.uuid1)
field1 = models.CharField(max_length=10)
field1 = models.CharField(max_length=30)
class UUIDPlainB(UUIDPlainA):
field2 = models.CharField(max_length=10)
field2 = models.CharField(max_length=30)
class UUIDPlainC(UUIDPlainB):
field3 = models.CharField(max_length=10)
field3 = models.CharField(max_length=30)
# base -> proxy
@ -358,14 +358,14 @@ class ProxyChild(ProxyBase):
class NonProxyChild(ProxyBase):
name = models.CharField(max_length=10)
name = models.CharField(max_length=30)
# base -> proxy -> real models
class ProxiedBase(ShowFieldTypeAndContent, PolymorphicModel):
name = models.CharField(max_length=10)
name = models.CharField(max_length=30)
class ProxyModelBase(ProxiedBase):
@ -374,16 +374,16 @@ class ProxyModelBase(ProxiedBase):
class ProxyModelA(ProxyModelBase):
field1 = models.CharField(max_length=10)
field1 = models.CharField(max_length=30)
class ProxyModelB(ProxyModelBase):
field2 = models.CharField(max_length=10)
field2 = models.CharField(max_length=30)
# test bad field name
# class TestBadFieldModel(ShowFieldType, PolymorphicModel):
# instance_of = models.CharField(max_length=10)
# instance_of = models.CharField(max_length=30)
# validation error: "polymorphic.relatednameclash: Accessor for field 'polymorphic_ctype' clashes
@ -435,18 +435,18 @@ class SwappedModel(AbstractModel):
class InlineParent(models.Model):
title = models.CharField(max_length=10)
title = models.CharField(max_length=30)
class InlineModelA(PolymorphicModel):
parent = models.ForeignKey(
InlineParent, related_name="inline_children", on_delete=models.CASCADE
)
field1 = models.CharField(max_length=10)
field1 = models.CharField(max_length=30)
class InlineModelB(InlineModelA):
field2 = models.CharField(max_length=10)
field2 = models.CharField(max_length=30)
class AbstractProject(PolymorphicModel):
@ -475,30 +475,30 @@ class RubberDuck(Duck):
class MultiTableBase(PolymorphicModel):
field1 = models.CharField(max_length=10)
field1 = models.CharField(max_length=30)
class MultiTableDerived(MultiTableBase):
field2 = models.CharField(max_length=10)
field2 = models.CharField(max_length=30)
class SubclassSelectorAbstractBaseModel(PolymorphicModel):
base_field = models.CharField(max_length=10, default="test_bf")
base_field = models.CharField(max_length=30, default="test_bf")
class SubclassSelectorAbstractModel(SubclassSelectorAbstractBaseModel):
abstract_field = models.CharField(max_length=10, default="test_af")
abstract_field = models.CharField(max_length=30, default="test_af")
class Meta:
abstract = True
class SubclassSelectorAbstractConcreteModel(SubclassSelectorAbstractModel):
concrete_field = models.CharField(max_length=10, default="test_cf")
concrete_field = models.CharField(max_length=30, default="test_cf")
class SubclassSelectorProxyBaseModel(PolymorphicModel):
base_field = models.CharField(max_length=10, default="test_bf")
base_field = models.CharField(max_length=30, default="test_bf")
class SubclassSelectorProxyModel(SubclassSelectorProxyBaseModel):
@ -507,8 +507,8 @@ class SubclassSelectorProxyModel(SubclassSelectorProxyBaseModel):
class SubclassSelectorProxyConcreteModel(SubclassSelectorProxyModel):
concrete_field = models.CharField(max_length=10, default="test_cf")
concrete_field = models.CharField(max_length=30, default="test_cf")
class NonPolymorphicParent(PolymorphicModel, Group):
test = models.CharField(max_length=22, default="test_non_polymorphic_parent")
test = models.CharField(max_length=30, default="test_non_polymorphic_parent")

View File

@ -1,16 +1,93 @@
import dj_database_url
import os
DEBUG = False
DATABASES = {
"default": dj_database_url.config(
env="PRIMARY_DATABASE",
default="sqlite://:memory:",
),
"secondary": dj_database_url.config(
env="SECONDARY_DATABASE",
default="sqlite://:memory:",
),
}
rdbms = os.environ.get("RDBMS", "sqlite")
if rdbms == "sqlite": # pragma: no cover
DATABASES = {
"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"},
"secondary": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"},
}
elif rdbms == "postgres": # pragma: no cover
creds = {
"USER": os.environ.get("POSTGRES_USER", "postgres"),
"PASSWORD": os.environ.get("POSTGRES_PASSWORD", ""),
"HOST": os.environ.get("POSTGRES_HOST", ""),
"PORT": os.environ.get("POSTGRES_PORT", ""),
}
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "test1",
**creds,
},
"secondary": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "test2",
**creds,
},
}
elif rdbms == "mysql": # pragma: no cover
dbs = os.environ.get("MYSQL_MULTIPLE_DATABASES", "test1,test2").split(",")
creds = {
"USER": os.environ.get("MYSQL_USER", "root"),
"PASSWORD": os.environ.get("MYSQL_PASSWORD", "root"),
"HOST": os.environ.get("MYSQL_HOST", "127.0.0.1"),
"PORT": os.environ.get("MYSQL_PORT", "3306"),
}
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": dbs[0],
**creds,
},
"secondary": {
"ENGINE": "django.db.backends.mysql",
"NAME": dbs[1],
**creds,
},
}
elif rdbms == "mariadb": # pragma: no cover
dbs = os.environ.get("MYSQL_MULTIPLE_DATABASES", "test1,test2").split(",")
creds = {
"USER": os.environ.get("MYSQL_USER", "root"),
"PASSWORD": os.environ.get("MYSQL_ROOT_PASSWORD", "root"),
"HOST": os.environ.get("MYSQL_HOST", "127.0.0.1"),
"PORT": os.environ.get("MYSQL_PORT", "3306"),
}
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
"NAME": dbs[0],
**creds,
},
"secondary": {
"ENGINE": "django.db.backends.mysql",
"NAME": dbs[1],
**creds,
},
}
elif rdbms == "oracle": # pragma: no cover
dbs = os.environ.get("ORACLE_DATABASES", "test1,test2").split(",")
ports = os.environ.get("ORACLE_PORTS", "1521,1522").split(",")
creds = {
"USER": os.environ.get("ORACLE_USER", "system"),
"PASSWORD": os.environ.get("ORACLE_PASSWORD", "password"),
}
DATABASES = {
"default": {
"ENGINE": "django.db.backends.oracle",
"NAME": f"{os.environ.get('ORACLE_HOST', 'localhost')}:{ports[0]}/{dbs[0]}",
**creds,
},
"secondary": {
"ENGINE": "django.db.backends.oracle",
"NAME": f"{os.environ.get('ORACLE_HOST', 'localhost')}:{ports[1]}/{dbs[1]}",
**creds,
},
}
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
INSTALLED_APPS = (
"django.contrib.auth",

View File

@ -4,8 +4,8 @@ import uuid
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models import Case, Count, FilteredRelation, Q, Sum, When
from django.db.utils import IntegrityError
from django.db.models import Case, Count, FilteredRelation, Q, Sum, When, F
from django.db.utils import IntegrityError, NotSupportedError
from django.test import TransactionTestCase
from polymorphic import query_translate
@ -138,33 +138,17 @@ class PolymorphicTests(TransactionTestCase):
<BlogA: id 1, name (CharField) "B1", info (CharField) "i1"> ]"""
assert repr(BlogBase.objects.order_by("-name")).strip() == expected.strip()
# test ordering for field in one subclass only
# MySQL and SQLite return this order
expected1 = """
[ <BlogA: id 8, name (CharField) "B5", info (CharField) "i5">,
<BlogA: id 7, name (CharField) "B4", info (CharField) "i4">,
<BlogA: id 6, name (CharField) "B3", info (CharField) "i3">,
<BlogA: id 5, name (CharField) "B2", info (CharField) "i2">,
<BlogA: id 1, name (CharField) "B1", info (CharField) "i1">,
<BlogB: id 2, name (CharField) "Bb1">,
<BlogB: id 3, name (CharField) "Bb2">,
<BlogB: id 4, name (CharField) "Bb3"> ]"""
# PostgreSQL returns this order
expected2 = """
[ <BlogB: id 2, name (CharField) "Bb1">,
<BlogB: id 3, name (CharField) "Bb2">,
<BlogB: id 4, name (CharField) "Bb3">,
<BlogA: id 8, name (CharField) "B5", info (CharField) "i5">,
<BlogA: id 7, name (CharField) "B4", info (CharField) "i4">,
<BlogA: id 6, name (CharField) "B3", info (CharField) "i3">,
<BlogA: id 5, name (CharField) "B2", info (CharField) "i2">,
<BlogA: id 1, name (CharField) "B1", info (CharField) "i1"> ]"""
assert repr(BlogBase.objects.order_by("-BlogA___info")).strip() in (
expected1.strip(),
expected2.strip(),
)
# different RDBMS return different orders for the nulls, and we can't use F
# and nulls_first or nulls_last here to standardize it, so our test is
# conditional
blog_names = [blg.name for blg in BlogBase.objects.order_by("-BlogA___info")]
ordered = blog_names[:3]
if all([name.startswith("Bb") for name in ordered]):
ordered = blog_names[3:]
else:
assert all([name.startswith("Bb") for name in blog_names[-3:]])
ordered = blog_names[:-3]
assert ordered == ["B5", "B4", "B3", "B2", "B1"]
def test_limit_choices_to(self):
"""
@ -524,6 +508,8 @@ class PolymorphicTests(TransactionTestCase):
)
def test_extra_method(self):
from django.db import connection
a, b, c, d = self.create_model2abcd()
objects = Model2A.objects.extra(where=[f"id IN ({b.id}, {c.id})"])
@ -531,11 +517,18 @@ class PolymorphicTests(TransactionTestCase):
objects, [Model2B, Model2C], transform=lambda o: o.__class__, ordered=False
)
objects = Model2A.objects.extra(
select={"select_test": "field1 = 'A1'"},
where=["field1 = 'A1' OR field1 = 'B1'"],
order_by=["-id"],
)
if connection.vendor == "oracle":
objects = Model2A.objects.extra(
select={"select_test": "CASE WHEN field1 = 'A1' THEN 1 ELSE 0 END"},
where=["field1 = 'A1' OR field1 = 'B1'"],
order_by=["-id"],
)
else:
objects = Model2A.objects.extra(
select={"select_test": "field1 = 'A1'"},
where=["field1 = 'A1' OR field1 = 'B1'"],
order_by=["-id"],
)
self.assertQuerySetEqual(objects, [Model2B, Model2A], transform=lambda o: o.__class__)
ModelExtraA.objects.create(field1="A1")
@ -723,7 +716,7 @@ class PolymorphicTests(TransactionTestCase):
== '<RelationBase: id 1, field_base (CharField) "base", fk (ForeignKey) None, m2m (ManyToManyField) 0>'
)
objects = oa.relationbase_set.all()
objects = oa.relationbase_set.order_by("pk").all()
assert (
repr(objects[0])
== '<RelationB: id 3, field_base (CharField) "B1", fk (ForeignKey) RelationA, field_b (CharField) "B2", m2m (ManyToManyField) 1>'
@ -741,7 +734,7 @@ class PolymorphicTests(TransactionTestCase):
)
oa = RelationA.objects.get()
objects = oa.m2m.all()
objects = oa.m2m.order_by("pk").all()
assert (
repr(objects[0])
== '<RelationA: id 2, field_base (CharField) "A1", fk (ForeignKey) RelationBase, field_a (CharField) "A2", m2m (ManyToManyField) 2>'
@ -1151,14 +1144,21 @@ class PolymorphicTests(TransactionTestCase):
)
def test_bulk_create_ignore_conflicts(self):
ArtProject.objects.bulk_create(
[
ArtProject(topic="Painting with Tim", artist="T. Turner"),
ArtProject.objects.create(topic="Sculpture with Tim", artist="T. Turner"),
],
ignore_conflicts=True,
)
assert ArtProject.objects.count() == 2
try:
ArtProject.objects.bulk_create(
[
ArtProject(topic="Painting with Tim", artist="T. Turner"),
ArtProject.objects.create(topic="Sculpture with Tim", artist="T. Turner"),
],
ignore_conflicts=True,
)
assert ArtProject.objects.count() == 2
except NotSupportedError:
from django.db import connection
assert connection.vendor in ("oracle"), (
f"{connection.vendor} should support ignore_conflicts"
)
def test_bulk_create_no_ignore_conflicts(self):
with pytest.raises(IntegrityError):

39
uv.lock
View File

@ -448,6 +448,20 @@ toml = [
{ name = "tomli", marker = "python_full_version <= '3.11'" },
]
[[package]]
name = "cx-oracle"
version = "8.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/16/13c265afc984796fe38ee928733569b599cfd657245ddd1afad238b66656/cx_Oracle-8.3.0.tar.gz", hash = "sha256:3b2d215af4441463c97ea469b9cc307460739f89fdfa8ea222ea3518f1a424d9", size = 363886, upload-time = "2021-11-04T22:08:34.141Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/b7/c2d0223fb4f1013b090cf82f3ce56f36f33b79a48f9c33b36717c2977b04/cx_Oracle-8.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b6a23da225f03f50a81980c61dbd6a358c3575f212ca7f4c22bb65a9faf94f7f", size = 892553, upload-time = "2021-11-04T22:08:43.072Z" },
{ url = "https://files.pythonhosted.org/packages/68/5b/8294aa9db4b54a3be10bec66c812d2527ca02a3290b1475c0e5faa1e70bc/cx_Oracle-8.3.0-cp310-cp310-win32.whl", hash = "sha256:715a8bbda5982af484ded14d184304cc552c1096c82471dd2948298470e88a04", size = 147835, upload-time = "2021-11-04T22:08:44.125Z" },
{ url = "https://files.pythonhosted.org/packages/67/a3/b671dee7f34971acf553df0c28818da6bf53b1a64901303f904c12ff443a/cx_Oracle-8.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:07f01608dfb6603a8f2a868fc7c7bdc951480f187df8dbc50f4d48c884874e6a", size = 213059, upload-time = "2021-11-04T22:08:46.164Z" },
{ url = "https://files.pythonhosted.org/packages/63/23/d495b40eb7950e8c4d3a8f22a92552b5d173b92479c60ad7f9355ad51666/cx_Oracle-8.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:df412238a9948340591beee9ec64fa62a2efacc0d91107034a7023e2991fba97", size = 888235, upload-time = "2021-11-04T22:09:02.072Z" },
{ url = "https://files.pythonhosted.org/packages/a5/a5/3f0c75239755194635a4ab4e4a2241413d66d2b70672ee6d9c9dede2d1da/cx_Oracle-8.3.0-cp39-cp39-win32.whl", hash = "sha256:70d3cf030aefd71f99b45beba77237b2af448adf5e26be0db3d0d3dee6ea4230", size = 148181, upload-time = "2021-11-04T22:09:03.568Z" },
{ url = "https://files.pythonhosted.org/packages/2d/f7/bc903192bed4c2738735753a796b894815a78472648d0983beca68b91196/cx_Oracle-8.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:bf01ce87edb4ef663b2e5bd604e1e0154d2cc2f12b60301f788b569d9db8a900", size = 213158, upload-time = "2021-11-04T22:09:04.718Z" },
]
[[package]]
name = "decorator"
version = "5.2.1"
@ -565,6 +579,12 @@ docs = [
{ name = "sphinx-rtd-theme" },
{ name = "sphinxcontrib-django" },
]
mysql = [
{ name = "mysqlclient" },
]
oracle = [
{ name = "cx-oracle" },
]
psycopg2 = [
{ name = "psycopg2" },
]
@ -600,8 +620,10 @@ docs = [
{ name = "sphinx-rtd-theme", specifier = ">=3.0.2" },
{ name = "sphinxcontrib-django", specifier = ">=2.5" },
]
mysql = [{ name = "mysqlclient", specifier = ">=1.4.0" }]
oracle = [{ name = "cx-oracle", specifier = ">=8.3.0" }]
psycopg2 = [{ name = "psycopg2", specifier = ">=2.9.10" }]
psycopg3 = [{ name = "psycopg", specifier = ">=3.2.5" }]
psycopg3 = [{ name = "psycopg" }]
[[package]]
name = "doc8"
@ -1005,6 +1027,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]]
name = "mysqlclient"
version = "2.2.7"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/61/68/810093cb579daae426794bbd9d88aa830fae296e85172d18cb0f0e5dd4bc/mysqlclient-2.2.7.tar.gz", hash = "sha256:24ae22b59416d5fcce7e99c9d37548350b4565baac82f95e149cac6ce4163845", size = 91383, upload-time = "2025-01-10T12:06:00.763Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/24/cdaaef42aac7d53c0a01bb638da64961c293b1b6d204efd47400a68029d4/mysqlclient-2.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:2e3c11f7625029d7276ca506f8960a7fd3c5a0a0122c9e7404e6a8fe961b3d22", size = 207748, upload-time = "2025-01-10T11:56:24.357Z" },
{ url = "https://files.pythonhosted.org/packages/ef/e3/3e2de3f93cd60dd63bd229ec3e3b679f682982614bf513d046c2722aa4ce/mysqlclient-2.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:a22d99d26baf4af68ebef430e3131bb5a9b722b79a9fcfac6d9bbf8a88800687", size = 207745, upload-time = "2025-01-10T11:56:28.67Z" },
{ url = "https://files.pythonhosted.org/packages/bb/b5/2a8a4bcba3440550f358b839638fe8ec9146fa3c9194890b4998a530c926/mysqlclient-2.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:4b4c0200890837fc64014cc938ef2273252ab544c1b12a6c1d674c23943f3f2e", size = 208032, upload-time = "2025-01-10T11:56:29.879Z" },
{ url = "https://files.pythonhosted.org/packages/29/01/e80141f1cd0459e4c9a5dd309dee135bbae41d6c6c121252fdd853001a8a/mysqlclient-2.2.7-cp313-cp313-win_amd64.whl", hash = "sha256:201a6faa301011dd07bca6b651fe5aaa546d7c9a5426835a06c3172e1056a3c5", size = 208000, upload-time = "2025-01-10T11:56:32.293Z" },
{ url = "https://files.pythonhosted.org/packages/0e/e0/524b0777524e0d410f71987f556dd6a0e3273fdb06cd6e91e96afade7220/mysqlclient-2.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:199dab53a224357dd0cb4d78ca0e54018f9cee9bf9ec68d72db50e0a23569076", size = 207857, upload-time = "2025-01-10T11:56:33.666Z" },
{ url = "https://files.pythonhosted.org/packages/16/cc/5b1570be9f8597ee41e2a0bd7b62ba861ec2c81898d9449f3d6bfbe15d29/mysqlclient-2.2.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:92af368ed9c9144737af569c86d3b6c74a012a6f6b792eb868384787b52bb585", size = 207800, upload-time = "2025-01-10T11:56:36.023Z" },
{ url = "https://files.pythonhosted.org/packages/20/40/b5d03494c1caa8f4da171db41d8d9d5b0d8959f893761597d97420083362/mysqlclient-2.2.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:977e35244fe6ef44124e9a1c2d1554728a7b76695598e4b92b37dc2130503069", size = 207965, upload-time = "2025-01-10T11:56:37.252Z" },
]
[[package]]
name = "nh3"
version = "0.3.0"