Skip to content

Commit

Permalink
Merge b64907c into 7f93f6a
Browse files Browse the repository at this point in the history
  • Loading branch information
gracinet committed Nov 25, 2018
2 parents 7f93f6a + b64907c commit f084388
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 9 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ addons:
postgresql: "9.6"

python:
- "3.3"
- "3.4"
- "3.5"
- "3.6"
Expand Down
125 changes: 120 additions & 5 deletions anyblok_postgres/column.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# This file is a part of the AnyBlok / Postgres api project
#
# Copyright (C) 2018 Jean-Sebastien SUZANNE <jssuzanne@anybox.fr>
# Copyright (C) 2018 Georges RACINET <gracinet@anybox.fr>
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file,You can
# obtain one at http://mozilla.org/MPL/2.0/.
from sqlalchemy.dialects.postgresql import JSONB, OID
from sqlalchemy.dialects import postgresql as pg
from sqlalchemy import select, and_
from anyblok.column import Column
from anyblok.common import anyblok_column_prefix
Expand All @@ -14,7 +15,7 @@


class Jsonb(Column):
"""Postgres JSONB column
"""PostgreSQL JSONB column
::
Expand All @@ -28,11 +29,125 @@ class Test:
x = Jsonb()
"""
sqlalchemy_type = JSONB(none_as_null=True)
sqlalchemy_type = pg.JSONB(none_as_null=True)


class Int4Range(Column):
"""PostgreSQL int4range column.
Example usage, with this declaration::
from anyblok.declarations import Declarations
from anyblok_postgres.column import Jsonb
@Declarations.register(Declarations.Model)
class Test:
col = Int4Range()
one can perfom these::
Test.insert(col="[1,3)")
Test.insert(col="(4,8)")
Test.query().filter(Test.col.contains(2))
Test.query().filter(Test.col.contains([5, 6))
"""
sqlalchemy_type = pg.INT4RANGE


class Int8Range(Column):
"""PostgreSQL int8range column.
Usage is similar to see :class:`Int4Range`.
See also https://www.postgresql.org/docs/current/rangetypes.html
Caveat (at least with psycopg2):
In containment queries, passing integers that are within
PostgreSQL's regular 'integer' type doesn't work because it lacks the
``::bigint`` cast. One workaround is to pass it as an inclusive 0 length
range in string form such as::
Test.query(Test.col.contains('[1, 1]')
"""
sqlalchemy_type = pg.INT8RANGE


class NumRange(Column):
"""PostgreSQL numrange column.
Usage is similar to see :class:`Int4Range`, with
:class:`decimal.Decimal` instances instead of integers.
Caveat (at least with psycopg2):
In containment queries, passing values that are integers or
:class:`Decimal` instances equal to integers, such as ``Decimal('1')``
doesn't work because they end up as litteral SQL integers, without the
``::numeric`` cast.
One workaround is to pass them as inclusive 0 length ranges in string
representation, such as::
Test.query(Test.col.contains('[1, 1]')
"""
sqlalchemy_type = pg.NUMRANGE


class DateRange(Column):
"""PostgreSQL daterange column.
This range column can be used with Python :class:`date` instances.
Example usage, with this declaration::
from anyblok.declarations import Declarations
from anyblok_postgres.column import Jsonb
@Declarations.register(Declarations.Model)
class Test:
col = DateRange()
one can perform these::
Test.insert(col="['2001-03-12', '2002-01-01']")
Test.insert(col="['2018-01-01', '2019-01-01')")
Test.query().filter(Test.col.contains(date(2001, 4, 7)))
Test.query().filter(Test.col.contains("['2018-02-01', '2018-03-01']")
"""
sqlalchemy_type = pg.DATERANGE


class TsRange(Column):
"""PostgreSQL tsrange column (timestamps without time zones).
This range column can be used with "naive" Python :class:`datetime`
instances. Apart from that, usage is similar to :class:`DateRange`
"""
sqlalchemy_type = pg.TSRANGE


class TsTzRange(Column):
"""PostgreSQL tstzrange column (timestamps with time zones).
See also https://www.postgresql.org/docs/current/rangetypes.html
This range column can be used with "non-naive" (i.e., with explicit
``tzinfo``) Python :class:`datetime`instances.
Apart from taht, usage is similar to :class:`DateRange`
"""
sqlalchemy_type = pg.TSTZRANGE


class LargeObject(Column):
"""Postgres JSONB column
"""PostgreSQL JSONB column
::
Expand All @@ -52,7 +167,7 @@ class Test:
test.x # get the huge file
"""
sqlalchemy_type = OID
sqlalchemy_type = pg.OID

def __init__(self, *args, **kwargs):
self.keep_blob = kwargs.pop('keep_blob', False)
Expand Down
2 changes: 1 addition & 1 deletion anyblok_postgres/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file,You can
# obtain one at http://mozilla.org/MPL/2.0/.
version = '0.1.0'
version = '0.2.0' # pragma: no cover
88 changes: 88 additions & 0 deletions anyblok_postgres/tests/test_column.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file,You can
# obtain one at http://mozilla.org/MPL/2.0/.
from decimal import Decimal
from datetime import date, datetime, timezone
from anyblok.tests.testcase import DBTestCase
from anyblok.tests.test_column import simple_column
from anyblok_postgres.column import Jsonb, LargeObject
from anyblok_postgres import column as pgcol

from os import urandom


Expand Down Expand Up @@ -43,6 +47,90 @@ def test_jsonb_null(self):
self.assertEqual(Test.query().filter(Test.col.is_(None)).count(), 2)
self.assertEqual(Test.query().filter(Test.col.isnot(None)).count(), 1)

def assert_query_contains(self, Model, c, expected):
"""Useful factorisation for range types."""
self.assertEqual(
set(Model.query().filter(Model.col.contains(c)).all()),
set(expected))

def test_int4range(self):
registry = self.init_registry(simple_column,
ColumnType=pgcol.Int4Range)
Test = registry.Test
t1 = Test.insert(col="[1,3)")
t2 = Test.insert(col="(4,8)")
self.assert_query_contains(Test, 2, [t1])
self.assert_query_contains(Test, 4, ())
self.assert_query_contains(Test, "[5,6]", [t2])

def test_int8_range(self):
registry = self.init_registry(simple_column,
ColumnType=pgcol.Int8Range)
Test = registry.Test
t1 = Test.insert(col="[1,3)")
bigint = 1 << 32 # PG's max for plain integer is 2**31-1
t2 = Test.insert(col="({},{})".format(bigint, bigint*2))
self.assert_query_contains(Test, bigint, ())
self.assert_query_contains(Test, bigint + 10, [t2])
self.assert_query_contains(Test,
"({}, {})".format(bigint, bigint + 10),
[t2])

# just plain '2' literal wouldn't work as-is because it's
# not passed as 2::bigint to PG
self.assert_query_contains(Test, "[2, 2]", [t1])

def test_numrange(self):
registry = self.init_registry(simple_column,
ColumnType=pgcol.NumRange)
Test = registry.Test
t1 = Test.insert(col="[1.5, 3)")
t2 = Test.insert(col="(4, 7.5)")
self.assert_query_contains(Test, Decimal(2.1), [t1])
self.assert_query_contains(Test, Decimal('7.5'), ())
self.assert_query_contains(Test, "[5,6]", [t2])

# unfortunately, even Decimal(2) doesn't work, it gets passed as just
# '2', so we need an explict range here too
self.assert_query_contains(Test, "[2, 2]", [t1])

def test_daterange(self):
registry = self.init_registry(simple_column,
ColumnType=pgcol.DateRange)
Test = registry.Test
t1 = Test.insert(col="['2001-03-12', '2002-01-01']")
t2 = Test.insert(col="['2018-01-01', '2019-01-01')")
self.assert_query_contains(Test, date(2001, 4, 7), [t1])
self.assert_query_contains(Test, date(2019, 1, 1), ())
self.assert_query_contains(Test, "['2018-02-01', '2018-03-01']", [t2])

def test_tsrange(self):
registry = self.init_registry(simple_column,
ColumnType=pgcol.TsRange)
Test = registry.Test
t1 = Test.insert(col="['2001-03-12', '2002-01-01 00:00:00']")
t2 = Test.insert(col="['2018-01-01', '2019-01-01')")
self.assert_query_contains(Test, datetime(2001, 4, 7, 11, 0, 6), [t1])
self.assert_query_contains(Test, datetime(2019, 1, 1), ())
self.assert_query_contains(Test, "['2018-02-01', '2018-03-01']", [t2])

def test_tstzrange(self):
registry = self.init_registry(simple_column,
ColumnType=pgcol.TsTzRange)
Test = registry.Test
utc = timezone.utc
t1 = Test.insert(
col="['2001-03-12 00:00:00+01', '2002-01-01 00:00:00+01']")
t2 = Test.insert(
col="['2018-01-01 00:00:00-03', '2019-01-01 00:00:00-03')")
self.assert_query_contains(Test,
datetime(2001, 4, 7, 11, 0, 6, tzinfo=utc),
[t1])
self.assert_query_contains(Test,
datetime(2018, 1, 1, tzinfo=utc),
())
self.assert_query_contains(Test, "['2018-02-01', '2018-03-01']", [t2])

def test_large_object(self):
registry = self.init_registry(simple_column, ColumnType=LargeObject)
hugefile = urandom(1000)
Expand Down
5 changes: 5 additions & 0 deletions doc/CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
CHANGELOG
=========

0.2.0
-----

* [ADD] columns for built-in range types

0.1.1
-----

Expand Down
35 changes: 34 additions & 1 deletion doc/FIELDS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
Fields
======

Add some field only for postgres
This package adds some fields that are specific to PostgreSQL.

Columns
-------
Expand All @@ -33,3 +33,36 @@ Columns
:noindex:
:members:
:show-inheritance:

**Ranges**
``````````

Since version 9.2, PostgreSQL supports a flexible range types system,
with a few predefined ones, that can be used within AnyBlok.

.. seealso:: Range Types in `PostgreSQL documentation
<https://www.postgresql.org/docs/current/rangetypes.html>`_

.. autoclass:: Int4Range
:noindex:
:show-inheritance:

.. autoclass:: Int8Range
:noindex:
:show-inheritance:

.. autoclass:: NumRange
:noindex:
:show-inheritance:

.. autoclass:: DateRange
:noindex:
:show-inheritance:

.. autoclass:: TsRange
:noindex:
:show-inheritance:

.. autoclass:: TsTzRange
:noindex:
:show-inheritance:
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from setuptools import setup, find_packages
import os

version = '0.1.0'
from anyblok_postgres.release import version

here = os.path.abspath(os.path.dirname(__file__))

Expand Down

0 comments on commit f084388

Please sign in to comment.