diff --git a/django/contrib/gis/db/backends/base/operations.py b/django/contrib/gis/db/backends/base/operations.py index af85b83df6..4b1be4bedc 100644 --- a/django/contrib/gis/db/backends/base/operations.py +++ b/django/contrib/gis/db/backends/base/operations.py @@ -3,6 +3,7 @@ from django.contrib.gis.db.models.functions import Distance from django.contrib.gis.measure import ( Area as AreaMeasure, Distance as DistanceMeasure, ) +from django.db.utils import NotSupportedError from django.utils.functional import cached_property @@ -105,7 +106,7 @@ class BaseSpatialOperations: def check_expression_support(self, expression): if isinstance(expression, self.disallowed_aggregates): - raise NotImplementedError( + raise NotSupportedError( "%s spatial aggregation is not supported by this database backend." % expression.name ) super().check_expression_support(expression) @@ -115,7 +116,7 @@ class BaseSpatialOperations: def spatial_function_name(self, func_name): if func_name in self.unsupported_functions: - raise NotImplementedError("This backend doesn't support the %s function." % func_name) + raise NotSupportedError("This backend doesn't support the %s function." % func_name) return self.function_names.get(func_name, self.geom_func_prefix + func_name) # Routines for getting the OGC-compliant models. diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 51c8d5006e..4fb9e61ceb 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -13,7 +13,7 @@ from django.contrib.gis.measure import Distance from django.core.exceptions import ImproperlyConfigured from django.db.backends.postgresql.operations import DatabaseOperations from django.db.models import Func, Value -from django.db.utils import ProgrammingError +from django.db.utils import NotSupportedError, ProgrammingError from django.utils.functional import cached_property from django.utils.version import get_version_tuple @@ -231,7 +231,7 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations): geom_type = f.geom_type if f.geography: if f.srid != 4326: - raise NotImplementedError('PostGIS only supports geography columns with an SRID of 4326.') + raise NotSupportedError('PostGIS only supports geography columns with an SRID of 4326.') return 'geography(%s,%d)' % (geom_type, f.srid) else: diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py index 3dea6cbd13..fc46823b19 100644 --- a/django/contrib/gis/db/models/functions.py +++ b/django/contrib/gis/db/models/functions.py @@ -9,6 +9,7 @@ from django.db.models import ( ) from django.db.models.expressions import Func, Value from django.db.models.functions import Cast +from django.db.utils import NotSupportedError from django.utils.functional import cached_property NUMERIC_TYPES = (int, float, Decimal) @@ -123,7 +124,7 @@ class Area(OracleToleranceMixin, GeoFunc): def as_sql(self, compiler, connection, **extra_context): if not connection.features.supports_area_geodetic and self.geo_field.geodetic(connection): - raise NotImplementedError('Area on geodetic coordinate systems not supported.') + raise NotSupportedError('Area on geodetic coordinate systems not supported.') return super().as_sql(compiler, connection, **extra_context) def as_sqlite(self, compiler, connection, **extra_context): @@ -316,7 +317,7 @@ class Length(DistanceResultMixin, OracleToleranceMixin, GeoFunc): def as_sql(self, compiler, connection, **extra_context): if self.geo_field.geodetic(connection) and not connection.features.supports_length_geodetic: - raise NotImplementedError("This backend doesn't support Length on geodetic fields") + raise NotSupportedError("This backend doesn't support Length on geodetic fields") return super().as_sql(compiler, connection, **extra_context) def as_postgresql(self, compiler, connection): @@ -372,7 +373,7 @@ class Perimeter(DistanceResultMixin, OracleToleranceMixin, GeoFunc): def as_postgresql(self, compiler, connection): function = None if self.geo_field.geodetic(connection) and not self.source_is_geography(): - raise NotImplementedError("ST_Perimeter cannot use a non-projected non-geography field.") + raise NotSupportedError("ST_Perimeter cannot use a non-projected non-geography field.") dim = min(f.dim for f in self.get_source_fields()) if dim > 2: function = connection.ops.perimeter3d @@ -380,7 +381,7 @@ class Perimeter(DistanceResultMixin, OracleToleranceMixin, GeoFunc): def as_sqlite(self, compiler, connection): if self.geo_field.geodetic(connection): - raise NotImplementedError("Perimeter cannot use a non-projected field.") + raise NotSupportedError("Perimeter cannot use a non-projected field.") return super().as_sql(compiler, connection) diff --git a/django/db/backends/base/features.py b/django/db/backends/base/features.py index 3a89cc0900..3706e12db1 100644 --- a/django/db/backends/base/features.py +++ b/django/db/backends/base/features.py @@ -1,5 +1,5 @@ from django.db.models.aggregates import StdDev -from django.db.utils import ProgrammingError +from django.db.utils import NotSupportedError, ProgrammingError from django.utils.functional import cached_property @@ -269,9 +269,9 @@ class BaseDatabaseFeatures: """Confirm support for STDDEV and related stats functions.""" try: self.connection.ops.check_expression_support(StdDev(1)) - return True - except NotImplementedError: + except NotSupportedError: return False + return True def introspected_boolean_field_type(self, field=None): """ diff --git a/django/db/backends/base/operations.py b/django/db/backends/base/operations.py index 3517300b50..3d476b77da 100644 --- a/django/db/backends/base/operations.py +++ b/django/db/backends/base/operations.py @@ -579,7 +579,7 @@ class BaseDatabaseOperations: This is used on specific backends to rule out known expressions that have problematic or nonexistent implementations. If the expression has a known problem, the backend should raise - NotImplementedError. + NotSupportedError. """ pass diff --git a/django/db/backends/sqlite3/operations.py b/django/db/backends/sqlite3/operations.py index 408848c9ad..e90cc052d0 100644 --- a/django/db/backends/sqlite3/operations.py +++ b/django/db/backends/sqlite3/operations.py @@ -43,7 +43,7 @@ class DatabaseOperations(BaseDatabaseOperations): pass else: if isinstance(output_field, bad_fields): - raise NotImplementedError( + raise utils.NotSupportedError( 'You cannot use Sum, Avg, StdDev, and Variance ' 'aggregations on date/time fields in sqlite3 ' 'since date/time is saved as text.' diff --git a/django/db/models/lookups.py b/django/db/models/lookups.py index f79f435515..66945c4a1a 100644 --- a/django/db/models/lookups.py +++ b/django/db/models/lookups.py @@ -25,7 +25,7 @@ class Lookup: # a bilateral transformation on a nested QuerySet: that won't work. from django.db.models.sql.query import Query # avoid circular import if isinstance(rhs, Query): - raise NotImplementedError("Bilateral transformations on nested querysets are not supported.") + raise NotImplementedError("Bilateral transformations on nested querysets are not implemented.") self.bilateral_transforms = bilateral_transforms def apply_bilateral_transforms(self, value): diff --git a/docs/releases/2.1.txt b/docs/releases/2.1.txt index 6a451bdd50..516f6163eb 100644 --- a/docs/releases/2.1.txt +++ b/docs/releases/2.1.txt @@ -198,7 +198,9 @@ Backwards incompatible changes in 2.1 Database backend API -------------------- -* ... +* To adhere to :pep:`249`, exceptions where a database doesn't support a + feature are changed from :exc:`NotImplementedError` to + :exc:`django.db.NotSupportedError`. :mod:`django.contrib.gis` ------------------------- diff --git a/tests/backends/sqlite/tests.py b/tests/backends/sqlite/tests.py index 3addcc8c34..0c07f95e6f 100644 --- a/tests/backends/sqlite/tests.py +++ b/tests/backends/sqlite/tests.py @@ -4,6 +4,7 @@ import unittest from django.db import connection from django.db.models import Avg, StdDev, Sum, Variance +from django.db.utils import NotSupportedError from django.test import TestCase, TransactionTestCase, override_settings from ..models import Item, Object, Square @@ -34,13 +35,13 @@ class Tests(TestCase): Raise NotImplementedError when aggregating on date/time fields (#19360). """ for aggregate in (Sum, Avg, Variance, StdDev): - with self.assertRaises(NotImplementedError): + with self.assertRaises(NotSupportedError): Item.objects.all().aggregate(aggregate('time')) - with self.assertRaises(NotImplementedError): + with self.assertRaises(NotSupportedError): Item.objects.all().aggregate(aggregate('date')) - with self.assertRaises(NotImplementedError): + with self.assertRaises(NotSupportedError): Item.objects.all().aggregate(aggregate('last_modified')) - with self.assertRaises(NotImplementedError): + with self.assertRaises(NotSupportedError): Item.objects.all().aggregate( **{'complex': aggregate('last_modified') + aggregate('last_modified')} ) diff --git a/tests/custom_lookups/tests.py b/tests/custom_lookups/tests.py index d39ebe6cdc..bdb27a224a 100644 --- a/tests/custom_lookups/tests.py +++ b/tests/custom_lookups/tests.py @@ -319,7 +319,7 @@ class BilateralTransformTests(TestCase): def test_bilateral_inner_qs(self): with register_lookup(models.CharField, UpperBilateralTransform): - msg = 'Bilateral transformations on nested querysets are not supported.' + msg = 'Bilateral transformations on nested querysets are not implemented.' with self.assertRaisesMessage(NotImplementedError, msg): Author.objects.filter(name__upper__in=Author.objects.values_list('name')) diff --git a/tests/gis_tests/distapp/tests.py b/tests/gis_tests/distapp/tests.py index d162759513..e9735de074 100644 --- a/tests/gis_tests/distapp/tests.py +++ b/tests/gis_tests/distapp/tests.py @@ -5,7 +5,7 @@ from django.contrib.gis.db.models.functions import ( ) from django.contrib.gis.geos import GEOSGeometry, LineString, Point from django.contrib.gis.measure import D # alias for Distance -from django.db import connection +from django.db import NotSupportedError, connection from django.db.models import F, Q from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature @@ -474,7 +474,7 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase): # TODO: test with spheroid argument (True and False) else: # Does not support geodetic coordinate systems. - with self.assertRaises(NotImplementedError): + with self.assertRaises(NotSupportedError): list(Interstate.objects.annotate(length=Length('path'))) # Now doing length on a projected coordinate system. @@ -513,7 +513,7 @@ class DistanceFunctionsTests(FuncTestMixin, TestCase): if connection.features.supports_perimeter_geodetic: self.assertAlmostEqual(qs1[0].perim.m, 18406.3818954314, 3) else: - with self.assertRaises(NotImplementedError): + with self.assertRaises(NotSupportedError): list(qs1) # But should work fine when transformed to projected coordinates qs2 = CensusZipcode.objects.annotate(perim=Perimeter(Transform('poly', 32140))).filter(name='77002') diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py index cdd05d78ff..33fe139fb0 100644 --- a/tests/gis_tests/geoapp/test_functions.py +++ b/tests/gis_tests/geoapp/test_functions.py @@ -8,7 +8,7 @@ from django.contrib.gis.geos import ( GEOSGeometry, LineString, Point, Polygon, fromstr, ) from django.contrib.gis.measure import Area -from django.db import connection +from django.db import NotSupportedError, connection from django.db.models import Sum from django.test import TestCase, skipUnlessDBFeature @@ -28,7 +28,7 @@ class GISFunctionsTests(FuncTestMixin, TestCase): def test_asgeojson(self): # Only PostGIS and SpatiaLite support GeoJSON. if not connection.features.has_AsGeoJSON_function: - with self.assertRaises(NotImplementedError): + with self.assertRaises(NotSupportedError): list(Country.objects.annotate(json=functions.AsGeoJSON('mpoly'))) return diff --git a/tests/gis_tests/geoapp/tests.py b/tests/gis_tests/geoapp/tests.py index f9838b461b..52a172792a 100644 --- a/tests/gis_tests/geoapp/tests.py +++ b/tests/gis_tests/geoapp/tests.py @@ -8,7 +8,7 @@ from django.contrib.gis.geos import ( MultiPoint, MultiPolygon, Point, Polygon, fromstr, ) from django.core.management import call_command -from django.db import connection +from django.db import NotSupportedError, connection from django.test import TestCase, skipUnlessDBFeature from ..utils import ( @@ -516,7 +516,7 @@ class GeoQuerySetTest(TestCase): Testing the `MakeLine` aggregate. """ if not connection.features.supports_make_line_aggr: - with self.assertRaises(NotImplementedError): + with self.assertRaises(NotSupportedError): City.objects.all().aggregate(MakeLine('point')) return diff --git a/tests/gis_tests/geogapp/tests.py b/tests/gis_tests/geogapp/tests.py index c9986fd78b..7f6c441ba5 100644 --- a/tests/gis_tests/geogapp/tests.py +++ b/tests/gis_tests/geogapp/tests.py @@ -7,7 +7,7 @@ from unittest import skipIf, skipUnless from django.contrib.gis.db import models from django.contrib.gis.db.models.functions import Area, Distance from django.contrib.gis.measure import D -from django.db import connection +from django.db import NotSupportedError, connection from django.db.models.functions import Cast from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature @@ -152,5 +152,5 @@ class GeographyFunctionTests(FuncTestMixin, TestCase): @skipUnlessDBFeature("has_Area_function") @skipIfDBFeature("supports_area_geodetic") def test_geodetic_area_raises_if_not_supported(self): - with self.assertRaisesMessage(NotImplementedError, 'Area on geodetic coordinate systems not supported.'): + with self.assertRaisesMessage(NotSupportedError, 'Area on geodetic coordinate systems not supported.'): Zipcode.objects.annotate(area=Area('poly')).get(code='77002') diff --git a/tests/gis_tests/relatedapp/tests.py b/tests/gis_tests/relatedapp/tests.py index 8d6b793ce2..ba812fa9fb 100644 --- a/tests/gis_tests/relatedapp/tests.py +++ b/tests/gis_tests/relatedapp/tests.py @@ -1,6 +1,6 @@ from django.contrib.gis.db.models import Collect, Count, Extent, F, Union from django.contrib.gis.geos import GEOSGeometry, MultiPoint, Point -from django.db import connection +from django.db import NotSupportedError, connection from django.test import TestCase, skipUnlessDBFeature from django.test.utils import override_settings from django.utils import timezone @@ -147,7 +147,7 @@ class RelatedGeoModelTest(TestCase): self.assertEqual('P2', qs.get().name) else: msg = "This backend doesn't support the Transform function." - with self.assertRaisesMessage(NotImplementedError, msg): + with self.assertRaisesMessage(NotSupportedError, msg): list(qs) # Should return the first Parcel, which has the center point equal @@ -162,7 +162,7 @@ class RelatedGeoModelTest(TestCase): self.assertEqual('P1', qs.get().name) else: msg = "This backend doesn't support the Transform function." - with self.assertRaisesMessage(NotImplementedError, msg): + with self.assertRaisesMessage(NotSupportedError, msg): list(qs) def test07_values(self):