Fixed #27830 -- Used distutils.version.LooseVersion for version parsing.

This commit is contained in:
chillaranand 2017-02-15 21:44:21 +05:30 committed by Tim Graham
parent e7dc39fb65
commit 08bda82c23
10 changed files with 52 additions and 45 deletions

View File

@ -11,6 +11,7 @@ from django.core.exceptions import ImproperlyConfigured
from django.db.backends.postgresql.operations import DatabaseOperations from django.db.backends.postgresql.operations import DatabaseOperations
from django.db.utils import ProgrammingError from django.db.utils import ProgrammingError
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.version import get_version_tuple
from .adapter import PostGISAdapter from .adapter import PostGISAdapter
from .models import PostGISGeometryColumns, PostGISSpatialRefSys from .models import PostGISGeometryColumns, PostGISSpatialRefSys
@ -109,7 +110,6 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations):
postgis = True postgis = True
geography = True geography = True
geom_func_prefix = 'ST_' geom_func_prefix = 'ST_'
version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
Adapter = PostGISAdapter Adapter = PostGISAdapter
@ -353,18 +353,8 @@ class PostGISOperations(BaseSpatialOperations, DatabaseOperations):
Return the PostGIS version as a tuple (version string, major, Return the PostGIS version as a tuple (version string, major,
minor, subminor). minor, subminor).
""" """
# Getting the PostGIS version
version = self.postgis_lib_version() version = self.postgis_lib_version()
m = self.version_regex.match(version) return (version,) + get_version_tuple(version)
if m:
major = int(m.group('major'))
minor1 = int(m.group('minor1'))
minor2 = int(m.group('minor2'))
else:
raise Exception('Could not parse PostGIS version string: %s' % version)
return (version, major, minor1, minor2)
def proj_version_tuple(self): def proj_version_tuple(self):
""" """

View File

@ -3,8 +3,6 @@ SQL functions reference lists:
https://web.archive.org/web/20130407175746/https://www.gaia-gis.it/gaia-sins/spatialite-sql-4.0.0.html https://web.archive.org/web/20130407175746/https://www.gaia-gis.it/gaia-sins/spatialite-sql-4.0.0.html
https://www.gaia-gis.it/gaia-sins/spatialite-sql-4.2.1.html https://www.gaia-gis.it/gaia-sins/spatialite-sql-4.2.1.html
""" """
import re
from django.contrib.gis.db.backends.base.operations import ( from django.contrib.gis.db.backends.base.operations import (
BaseSpatialOperations, BaseSpatialOperations,
) )
@ -16,6 +14,7 @@ from django.contrib.gis.measure import Distance
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db.backends.sqlite3.operations import DatabaseOperations from django.db.backends.sqlite3.operations import DatabaseOperations
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.version import get_version_tuple
class SpatiaLiteDistanceOperator(SpatialOperator): class SpatiaLiteDistanceOperator(SpatialOperator):
@ -35,7 +34,6 @@ class SpatiaLiteDistanceOperator(SpatialOperator):
class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations): class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
name = 'spatialite' name = 'spatialite'
spatialite = True spatialite = True
version_regex = re.compile(r'^(?P<major>\d)\.(?P<minor1>\d)\.(?P<minor2>\d+)')
Adapter = SpatiaLiteAdapter Adapter = SpatiaLiteAdapter
@ -189,16 +187,7 @@ class SpatiaLiteOperations(BaseSpatialOperations, DatabaseOperations):
minor, subminor). minor, subminor).
""" """
version = self.spatialite_version() version = self.spatialite_version()
return (version,) + get_version_tuple(version)
m = self.version_regex.match(version)
if m:
major = int(m.group('major'))
minor1 = int(m.group('minor1'))
minor2 = int(m.group('minor2'))
else:
raise Exception('Could not parse SpatiaLite version string: %s' % version)
return (version, major, minor1, minor2)
def spatial_aggregate_name(self, agg_name): def spatial_aggregate_name(self, agg_name):
""" """

View File

@ -8,7 +8,7 @@ from ctypes import byref, c_int, c_uint
from django.contrib.gis.geos import prototypes as capi from django.contrib.gis.geos import prototypes as capi
from django.contrib.gis.geos.error import GEOSException from django.contrib.gis.geos.error import GEOSException
from django.contrib.gis.geos.geometry import GEOSGeometry, LinearGeometryMixin from django.contrib.gis.geos.geometry import GEOSGeometry, LinearGeometryMixin
from django.contrib.gis.geos.libgeos import geos_version_info, get_pointer_arr from django.contrib.gis.geos.libgeos import geos_version_tuple, get_pointer_arr
from django.contrib.gis.geos.linestring import LinearRing, LineString from django.contrib.gis.geos.linestring import LinearRing, LineString
from django.contrib.gis.geos.point import Point from django.contrib.gis.geos.point import Point
from django.contrib.gis.geos.polygon import Polygon from django.contrib.gis.geos.polygon import Polygon
@ -115,7 +115,7 @@ class MultiLineString(LinearGeometryMixin, GeometryCollection):
@property @property
def closed(self): def closed(self):
if geos_version_info()['version'] < '3.5': if geos_version_tuple() < (3, 5):
raise GEOSException("MultiLineString.closed requires GEOS >= 3.5.0.") raise GEOSException("MultiLineString.closed requires GEOS >= 3.5.0.")
return super().closed return super().closed

View File

@ -15,6 +15,7 @@ from ctypes.util import find_library
from django.contrib.gis.geos.error import GEOSException from django.contrib.gis.geos.error import GEOSException
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
from django.utils.version import get_version_tuple
logger = logging.getLogger('django.contrib.gis') logger = logging.getLogger('django.contrib.gis')
@ -199,3 +200,8 @@ def geos_version_info():
raise GEOSException('Could not parse version info string "%s"' % ver) raise GEOSException('Could not parse version info string "%s"' % ver)
return {key: m.group(key) for key in ( return {key: m.group(key) for key in (
'version', 'release_candidate', 'capi_version', 'major', 'minor', 'subminor')} 'version', 'release_candidate', 'capi_version', 'major', 'minor', 'subminor')}
def geos_version_tuple():
"""Return the GEOS version as a tuple (major, minor, subminor)."""
return get_version_tuple(geos_version_info()['version'])

View File

@ -14,6 +14,7 @@ from django.db.backends.base.base import BaseDatabaseWrapper
from django.db.utils import DatabaseError as WrappedDatabaseError from django.db.utils import DatabaseError as WrappedDatabaseError
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.safestring import SafeText from django.utils.safestring import SafeText
from django.utils.version import get_version_tuple
try: try:
import psycopg2 as Database import psycopg2 as Database
@ -25,7 +26,7 @@ except ImportError as e:
def psycopg2_version(): def psycopg2_version():
version = psycopg2.__version__.split(' ', 1)[0] version = psycopg2.__version__.split(' ', 1)[0]
return tuple(int(v) for v in version.split('.') if v.isdigit()) return get_version_tuple(version)
PSYCOPG2_VERSION = psycopg2_version() PSYCOPG2_VERSION = psycopg2_version()

View File

@ -2,6 +2,7 @@ import datetime
import functools import functools
import os import os
import subprocess import subprocess
from distutils.version import LooseVersion
def get_version(version=None): def get_version(version=None):
@ -77,3 +78,17 @@ def get_git_changeset():
except ValueError: except ValueError:
return None return None
return timestamp.strftime('%Y%m%d%H%M%S') return timestamp.strftime('%Y%m%d%H%M%S')
def get_version_tuple(version):
"""
Return a tuple of version numbers (e.g. (1, 2, 3)) from the version
string (e.g. '1.2.3').
"""
loose_version = LooseVersion(version)
version_numbers = []
for item in loose_version.version:
if not isinstance(item, int):
break
version_numbers.append(item)
return tuple(version_numbers)

View File

@ -323,13 +323,12 @@ class PostgreSQLTests(TestCase):
def test_correct_extraction_psycopg2_version(self): def test_correct_extraction_psycopg2_version(self):
from django.db.backends.postgresql.base import psycopg2_version from django.db.backends.postgresql.base import psycopg2_version
version_path = 'django.db.backends.postgresql.base.Database.__version__'
with mock.patch(version_path, '2.6.9'): with mock.patch('psycopg2.__version__', '4.2.1 (dt dec pq3 ext lo64)'):
self.assertEqual(psycopg2_version(), (2, 6, 9)) self.assertEqual(psycopg2_version(), (4, 2, 1))
with mock.patch(version_path, '2.5.dev0'): with mock.patch('psycopg2.__version__', '4.2b0.dev1 (dt dec pq3 ext lo64)'):
self.assertEqual(psycopg2_version(), (2, 5)) self.assertEqual(psycopg2_version(), (4, 2))
class DateQuotingTest(TestCase): class DateQuotingTest(TestCase):

View File

@ -12,7 +12,7 @@ from django.contrib.gis.geos import (
MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, fromfile, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, fromfile,
fromstr, fromstr,
) )
from django.contrib.gis.geos.libgeos import geos_version_info from django.contrib.gis.geos.libgeos import geos_version_tuple
from django.contrib.gis.shortcuts import numpy from django.contrib.gis.shortcuts import numpy
from django.template import Context from django.template import Context
from django.template.engine import Engine from django.template.engine import Engine
@ -670,11 +670,11 @@ class GEOSTest(SimpleTestCase, TestDataMixin):
self.assertFalse(ls_not_closed.closed) self.assertFalse(ls_not_closed.closed)
self.assertTrue(ls_closed.closed) self.assertTrue(ls_closed.closed)
if geos_version_info()['version'] >= '3.5': if geos_version_tuple() >= (3, 5):
self.assertFalse(MultiLineString(ls_closed, ls_not_closed).closed) self.assertFalse(MultiLineString(ls_closed, ls_not_closed).closed)
self.assertTrue(MultiLineString(ls_closed, ls_closed).closed) self.assertTrue(MultiLineString(ls_closed, ls_closed).closed)
with mock.patch('django.contrib.gis.geos.collections.geos_version_info', lambda: {'version': '3.4.9'}): with mock.patch('django.contrib.gis.geos.libgeos.geos_version_info', lambda: {'version': '3.4.9'}):
with self.assertRaisesMessage(GEOSException, "MultiLineString.closed requires GEOS >= 3.5.0."): with self.assertRaisesMessage(GEOSException, "MultiLineString.closed requires GEOS >= 3.5.0."):
MultiLineString().closed MultiLineString().closed
@ -1305,6 +1305,10 @@ class GEOSTest(SimpleTestCase, TestDataMixin):
self.assertEqual(m.group('version'), v_geos) self.assertEqual(m.group('version'), v_geos)
self.assertEqual(m.group('capi_version'), v_capi) self.assertEqual(m.group('capi_version'), v_capi)
def test_geos_version_tuple(self):
with mock.patch('django.contrib.gis.geos.libgeos.geos_version_info', lambda: {'version': '3.4.9'}):
self.assertEqual(geos_version_tuple(), (3, 4, 9))
def test_from_gml(self): def test_from_gml(self):
self.assertEqual( self.assertEqual(
GEOSGeometry('POINT(0 0)'), GEOSGeometry('POINT(0 0)'),

View File

@ -68,6 +68,12 @@ class TestPostGISVersionCheck(unittest.TestCase):
actual = ops.postgis_version_tuple() actual = ops.postgis_version_tuple()
self.assertEqual(expect, actual) self.assertEqual(expect, actual)
def test_version_loose_tuple(self):
expect = ('1.2.3b1.dev0', 1, 2, 3)
ops = FakePostGISOperations(expect[0])
actual = ops.postgis_version_tuple()
self.assertEqual(expect, actual)
def test_valid_version_numbers(self): def test_valid_version_numbers(self):
versions = [ versions = [
('1.3.0', 1, 3, 0), ('1.3.0', 1, 3, 0),
@ -81,15 +87,6 @@ class TestPostGISVersionCheck(unittest.TestCase):
actual = ops.spatial_version actual = ops.spatial_version
self.assertEqual(version[1:], actual) self.assertEqual(version[1:], actual)
def test_invalid_version_numbers(self):
versions = ['nope', '123']
for version in versions:
with self.subTest(version=version):
ops = FakePostGISOperations(version)
with self.assertRaises(Exception):
ops.spatial_version
def test_no_version_number(self): def test_no_version_number(self):
ops = FakePostGISOperations() ops = FakePostGISOperations()
with self.assertRaises(ImproperlyConfigured): with self.assertRaises(ImproperlyConfigured):

View File

@ -1,5 +1,6 @@
from django import get_version from django import get_version
from django.test import SimpleTestCase from django.test import SimpleTestCase
from django.utils.version import get_version_tuple
class VersionTests(SimpleTestCase): class VersionTests(SimpleTestCase):
@ -22,3 +23,8 @@ class VersionTests(SimpleTestCase):
) )
for ver_tuple, ver_string in tuples_to_strings: for ver_tuple, ver_string in tuples_to_strings:
self.assertEqual(get_version(ver_tuple), ver_string) self.assertEqual(get_version(ver_tuple), ver_string)
def test_get_version_tuple(self):
self.assertEqual(get_version_tuple('1.2.3'), (1, 2, 3))
self.assertEqual(get_version_tuple('1.2.3b2'), (1, 2, 3))
self.assertEqual(get_version_tuple('1.2.3b2.dev0'), (1, 2, 3))