diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 976b6cd..85cb398 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -9,6 +9,6 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - - uses: pre-commit/action@v3.0.0 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + - uses: pre-commit/action@v3.0.1 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 738377f..b1c81cb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,18 +12,18 @@ jobs: strategy: matrix: os: [ubuntu-latest] - python-version: ['3.8', '3.9', '3.10', '3.11'] - django-version: ['3.2', '4.0', '4.1', '4.2'] + python-version: ['3.10', '3.11', '3.12', '3.13'] + django-version: ['4.2', '5.0', '5.1', '5.2'] steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Cache installed requirements - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.pythonLocation }} key: ${{ runner.os }}-python-${{ env.pythonLocation }}-${{ hashFiles('pyproject.toml') }}-test-v01 @@ -37,19 +37,19 @@ jobs: DJANGO: ${{ matrix.django-version }} run: tox - name: Run tests for example - if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.8' && matrix.django-version == '3.2' }} + if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.10' && matrix.django-version == '4.2' }} run: | python -m pip install .[test,example] python -m pytest example - name: Generate coverage.xml - if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.8' && matrix.django-version == '3.2' }} - uses: actions/upload-artifact@v3 + if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.10' && matrix.django-version == '4.2' }} + uses: actions/upload-artifact@v4 with: name: tox-gh-actions-coverage path: coverage.xml if-no-files-found: error - name: Upload coverage.xml to Coveralls - if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.8' && matrix.django-version == '3.2' }} + if: ${{ matrix.os == 'ubuntu-latest' && matrix.python-version == '3.10' && matrix.django-version == '4.2' }} run: coveralls --service=github env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 110755b..f2abc2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.3.0] + +- Include `py.typed` file for type hints +- Add support to Python 3.12 and 3.13 +- Drop support to Python 3.8 and 3.9 +- Add support to Django 5.0, 5.1, and 5.2 +- Drop support to Django 3.2, 4.0, and 4.1 + ## [0.2.0] - Add support to nested prefetch lookups like `v.VirtualModel(manager=User.objects, lookup="course__facilitators")` diff --git a/django_virtual_models/__init__.py b/django_virtual_models/__init__.py index c65a20a..aa36853 100644 --- a/django_virtual_models/__init__.py +++ b/django_virtual_models/__init__.py @@ -1,7 +1,7 @@ """Top-level package for django_virtual_models.""" import logging -__version__ = "0.2.0" +__version__ = "0.3.0" # Good practice: https://docs.python-guide.org/writing/logging/#logging-in-a-library logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/django_virtual_models/py.typed b/django_virtual_models/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/django_virtual_models/query_capture/capture.py b/django_virtual_models/query_capture/capture.py index 7addb14..99b8f60 100644 --- a/django_virtual_models/query_capture/capture.py +++ b/django_virtual_models/query_capture/capture.py @@ -7,11 +7,27 @@ import time import typing from contextlib import ContextDecorator, ExitStack -from distutils.sysconfig import get_python_lib from django.conf import settings from django.db import connection +try: + from distutils.sysconfig import get_python_lib +except ImportError: + from sysconfig import get_paths + + def get_python_lib(plat_specific=False): + """ + Get the path to the Python library directory. + If plat_specific is True, return platform-specific directory, + otherwise return platform-independent directory. + To match distutils behavior, return both paths when called without arguments. + """ + paths = get_paths() + if plat_specific: + return paths["platlib"] + return [paths["purelib"], paths["platlib"]] + class CapturedQuery(typing.TypedDict): """ @@ -79,11 +95,15 @@ def _save_queries(self, execute, sql, params, many, context): """ if settings.DEBUG: # only calculate stack in DEBUG or TEST mode, to avoid production impact - python_library_directory = get_python_lib() + python_lib_paths = get_python_lib() + # Handle both single string and list return values + if isinstance(python_lib_paths, str): + python_lib_paths = [python_lib_paths] + stack = [ stack for stack in inspect.stack() - if not stack.filename.startswith(python_library_directory) + if not any(stack.filename.startswith(path) for path in python_lib_paths) and not stack.filename.startswith("/usr/local/lib/") and not stack.filename.endswith("query_capture/capture.py") ] diff --git a/example/movies/migrations/0002_rename_persondirector_movie_order_movies_pers_movie_i_957f84_idx.py b/example/movies/migrations/0002_rename_persondirector_movie_order_movies_pers_movie_i_957f84_idx.py new file mode 100644 index 0000000..8367469 --- /dev/null +++ b/example/movies/migrations/0002_rename_persondirector_movie_order_movies_pers_movie_i_957f84_idx.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2 on 2025-05-14 01:36 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("movies", "0001_initial"), + ] + + operations = [ + migrations.RenameIndex( + model_name="persondirector", + new_name="movies_pers_movie_i_957f84_idx", + old_fields=("movie", "order"), + ), + ] diff --git a/example/movies/models.py b/example/movies/models.py index 3a4b46c..36c2d19 100644 --- a/example/movies/models.py +++ b/example/movies/models.py @@ -20,7 +20,7 @@ class PersonDirector(models.Model): order = models.PositiveSmallIntegerField() class Meta: - index_together = [("movie", "order")] + indexes = [models.Index(fields=["movie", "order"])] ordering = ["movie", "order"] diff --git a/pyproject.toml b/pyproject.toml index 5537b6e..6f9386b 100755 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,7 @@ build-backend = "flit_core.buildapi" [tool.flit.module] name = "django_virtual_models" +py-typed = true [project] name = "django-virtual-models" @@ -21,13 +22,13 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Natural Language :: English", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] dependencies = [ - "django >=3.2", + "django >=4.2", "djangorestframework >=3.13.1", "typing-extensions >=4.3.0", ] @@ -57,7 +58,7 @@ test = [ "model_bakery >=1.11.0,<2.0.0", ] example = [ - "django >= 3.2,<4.0", + "django >= 4.2,<6.0", "pyyaml >= 6.0,<7.0", ] @@ -112,31 +113,31 @@ testpaths = [ [tool.tox] legacy_tox_ini = """ [tox] -envlist = {linux}-py{38,39,310,311}-django{32,40,41,42} +envlist = {linux}-py{310,311,312,313}-django{42,50,51,52} isolated_build = True [gh-actions] python = - 3.8: py38 - 3.9: py39 3.10: py310 3.11: py311 + 3.12: py312 + 3.13: py313 [gh-actions:env] OS = ubuntu-latest: linux DJANGO = - 3.2: django32 - 4.0: django40 - 4.1: django41 4.2: django42 + 5.0: django50 + 5.1: django51 + 5.2: django52 [testenv] deps = - django32: django~=3.2 - django40: django~=4.0 - django41: django~=4.1 django42: django~=4.2 + django50: django~=5.0 + django51: django~=5.1 + django52: django~=5.2 .[test] commands = coverage run -m pytest --basetemp={envtmpdir}