def test_mixed_array_types(conn, fmt_out): conn.execute("create table testmix (a daterange[], b tstzrange[])") r1 = Range(dt.date(2000, 1, 1), dt.date(2001, 1, 1), "[)") r2 = Range( dt.datetime(2000, 1, 1, tzinfo=dt.timezone.utc), dt.datetime(2001, 1, 1, tzinfo=dt.timezone.utc), "[)", ) conn.execute("insert into testmix values (%s, %s)", [[r1], [r2]]) got = conn.execute("select * from testmix").fetchone() assert got == ([r1], [r2])
def test_copy_in_empty_set_type(conn, bounds, pgtype, format): cur = conn.cursor() cur.execute(f"create table copyrange (id serial primary key, r {pgtype})") r = Range(empty=True) if bounds == "empty" else Range(None, None, bounds) with cur.copy( f"copy copyrange (r) from stdin (format {format.name})" ) as copy: copy.set_types([pgtype]) copy.write_row([r]) rec = cur.execute("select r from copyrange order by id").fetchone() assert rec[0] == r
def test_dump_custom_empty(conn, testrange): info = RangeInfo.fetch(conn, "testrange") register_range(info, conn) r = Range(empty=True) cur = conn.execute("select 'empty'::testrange = %s", (r,)) assert cur.fetchone()[0] is True
def test_dump_builtin_array_wrapper(conn, wrapper, fmt_in): wrapper = getattr(multirange, wrapper) mr1 = Multirange() # type: ignore[var-annotated] mr2 = Multirange([Range(bounds="()")]) # type: ignore[var-annotated] cur = conn.execute(f"""select '{{"{{}}","{{(,)}}"}}' = %{fmt_in}""", ([mr1, mr2], )) assert cur.fetchone()[0] is True
def test_copy_in(conn, min, max, bounds, format): cur = conn.cursor() cur.execute( "create table copymr (id serial primary key, mr datemultirange)") if bounds != "empty": min = dt.date(*map(int, min.split(","))) if min else None max = dt.date(*map(int, max.split(","))) if max else None r = Range[dt.date](min, max, bounds) else: r = Range(empty=True) mr = Multirange([r]) try: with cur.copy( f"copy copymr (mr) from stdin (format {format.name})") as copy: copy.write_row([mr]) except e.InternalError_: if not min and not max and format == pq.Format.BINARY: pytest.xfail( "TODO: add annotation to dump multirange with no type info") else: raise rec = cur.execute("select mr from copymr order by id").fetchone() if not r.isempty: assert rec[0] == mr else: assert rec[0] == Multirange()
def test_load_builtin_array(conn, pgtype, fmt_out): mr1 = Multirange() # type: ignore[var-annotated] mr2 = Multirange([Range(bounds="()")]) # type: ignore[var-annotated] cur = conn.cursor(binary=fmt_out) (got, ) = cur.execute( f"select array['{{}}'::{pgtype}, '{{(,)}}'::{pgtype}]").fetchone() assert got == [mr1, mr2]
def test_exclude_inf_bounds(self): r = Range(None, 10, "[]") assert r.lower is None assert not r.lower_inc assert r.bounds == "(]" r = Range(10, None, "[]") assert r.upper is None assert not r.upper_inc assert r.bounds == "[)" r = Range(None, None, "[]") assert r.lower is None assert not r.lower_inc assert r.upper is None assert not r.upper_inc assert r.bounds == "()"
def test_dump_builtin_array(conn, pgtype, fmt_in): mr1 = Multirange() # type: ignore[var-annotated] mr2 = Multirange([Range(bounds="()")]) # type: ignore[var-annotated] cur = conn.execute( f"select array['{{}}'::{pgtype}, '{{(,)}}'::{pgtype}] = %{fmt_in}", ([mr1, mr2], ), ) assert cur.fetchone()[0] is True
def test_load_builtin_empty(conn, pgtype, fmt_out): r = Range(empty=True) # type: ignore[var-annotated] cur = conn.cursor(binary=fmt_out) (got, ) = cur.execute(f"select 'empty'::{pgtype}").fetchone() assert type(got) is Range assert got == r assert not got assert got.isempty
def test_dump_builtin_range(conn, pgtype, min, max, bounds, fmt_in): r = Range(min, max, bounds) # type: ignore[var-annotated] sub = type2sub[pgtype] cur = conn.execute( f"select {pgtype}(%s::{sub}, %s::{sub}, %s) = %{fmt_in}", (min, max, bounds, r), ) assert cur.fetchone()[0] is True
def test_in(self): r = Range(empty=True) assert 10 not in r r = Range() assert 10 in r r = Range(lower=10, bounds="[)") assert 9 not in r assert 10 in r assert 11 in r r = Range(lower=10, bounds="()") assert 9 not in r assert 10 not in r assert 11 in r r = Range(upper=20, bounds="()") assert 19 in r assert 20 not in r assert 21 not in r r = Range(upper=20, bounds="(]") assert 19 in r assert 20 in r assert 21 not in r r = Range(10, 20) assert 9 not in r assert 10 in r assert 11 in r assert 19 in r assert 20 not in r assert 21 not in r r = Range(10, 20, "(]") assert 9 not in r assert 10 not in r assert 11 in r assert 19 in r assert 20 in r assert 21 not in r r = Range(20, 10) assert 9 not in r assert 10 not in r assert 11 not in r assert 19 not in r assert 20 not in r assert 21 not in r
def test_nobounds(self): r = Range(10, 20) assert r.lower == 10 assert r.upper == 20 assert not r.isempty assert not r.lower_inf assert not r.upper_inf assert r.lower_inc assert not r.upper_inc
def test_keywords(self): r = Range(upper=20) r.lower is None r.upper == 20 assert not r.isempty assert r.lower_inf assert not r.upper_inf assert not r.lower_inc assert not r.upper_inc r = Range(lower=10, bounds="(]") r.lower == 10 r.upper is None assert not r.isempty assert not r.lower_inf assert r.upper_inf assert not r.lower_inc assert not r.upper_inc
def test_empty(self): r = Range(empty=True) assert r.isempty assert r.lower is None assert r.upper is None assert not r.lower_inf assert not r.upper_inf assert not r.lower_inc assert not r.upper_inc
def test_noparam(self): r = Range() assert not r.isempty assert r.lower is None assert r.upper is None assert r.lower_inf assert r.upper_inf assert not r.lower_inc assert not r.upper_inc
def test_load_builtin_inf(conn, pgtype, fmt_out): r = Range(bounds="()") # type: ignore[var-annotated] cur = conn.cursor(binary=fmt_out) (got, ) = cur.execute(f"select '(,)'::{pgtype}").fetchone() assert type(got) is Range assert got == r assert got assert not got.isempty assert got.lower_inf assert got.upper_inf
def test_noparam(self): r = Range() # type: ignore[var-annotated] assert not r.isempty assert r.lower is None assert r.upper is None assert r.lower_inf assert r.upper_inf assert not r.lower_inc assert not r.upper_inc
def test_empty(self): r = Range(empty=True) # type: ignore[var-annotated] assert r.isempty assert r.lower is None assert r.upper is None assert not r.lower_inf assert not r.upper_inf assert not r.lower_inc assert not r.upper_inc
def test_str(self): """ Range types should have a short and readable ``str`` implementation. """ expected = [ "(0, 4)", "[0, 4]", "(0, 4]", "[0, 4)", "empty", ] results = [] for bounds in ("()", "[]", "(]", "[)"): r = Range(0, 4, bounds=bounds) results.append(str(r)) r = Range(empty=True) results.append(str(r)) assert results == expected
def test_load_builtin_range(conn, pgtype, min, max, bounds, fmt_out): r = Range(min, max, bounds) # type: ignore[var-annotated] sub = type2sub[pgtype] cur = conn.cursor(binary=fmt_out) cur.execute(f"select {pgtype}(%s::{sub}, %s::{sub}, %s)", (min, max, bounds)) # normalise discrete ranges if r.upper_inc and isinstance(r.upper, int): bounds = "[)" if r.lower_inc else "()" r = type(r)(r.lower, r.upper + 1, bounds) assert cur.fetchone()[0] == r
def test_bad_type(self): with pytest.raises(TypeError): Multirange(Range(10, 20)) # type: ignore[arg-type] with pytest.raises(TypeError): Multirange([10]) # type: ignore[arg-type] mr = Multirange([Range(10, 20), Range(30, 40), Range(50, 60)]) with pytest.raises(TypeError): mr[0] = "foo" # type: ignore[call-overload] with pytest.raises(TypeError): mr[0:1] = "foo" # type: ignore[assignment] with pytest.raises(TypeError): mr[0:1] = ["foo"] # type: ignore[list-item] with pytest.raises(TypeError): mr.insert(0, "foo") # type: ignore[arg-type]
def test_in(self): r = Range[int](empty=True) assert 10 not in r assert "x" not in r # type: ignore[operator] r = Range() assert 10 in r r = Range(lower=10, bounds="[)") assert 9 not in r assert 10 in r assert 11 in r r = Range(lower=10, bounds="()") assert 9 not in r assert 10 not in r assert 11 in r r = Range(upper=20, bounds="()") assert 19 in r assert 20 not in r assert 21 not in r r = Range(upper=20, bounds="(]") assert 19 in r assert 20 in r assert 21 not in r r = Range(10, 20) assert 9 not in r assert 10 in r assert 11 in r assert 19 in r assert 20 not in r assert 21 not in r r = Range(10, 20, "(]") assert 9 not in r assert 10 not in r assert 11 in r assert 19 in r assert 20 in r assert 21 not in r r = Range(20, 10) assert 9 not in r assert 10 not in r assert 11 not in r assert 19 not in r assert 20 not in r assert 21 not in r
def to_range(value): if isinstance(value, (str, NoneType)): return value return Multirange([ Range( element.lower, element.upper, element.bounds, element.empty, ) for element in cast("Iterable[ranges.Range]", value) ])
def test_delitem(self): mr = Multirange([Range(10, 20), Range(30, 40), Range(50, 60)]) del mr[1] assert mr == Multirange([Range(10, 20), Range(50, 60)]) del mr[-2] assert mr == Multirange([Range(50, 60)])
def test_dump_builtin_empty_range(conn, fmt_in): conn.execute(""" drop type if exists tmptype; create type tmptype as (num integer, range daterange, nums integer[]) """) info = CompositeInfo.fetch(conn, "tmptype") register_composite(info, conn) cur = conn.execute( f"select pg_typeof(%{fmt_in})", [info.python_type(10, Range(empty=True), [])], ) assert cur.fetchone()[0] == "tmptype"
def test_str_datetime(self): """ Date-Time ranges should return a human-readable string as well on string conversion. """ tz = dt.timezone(dt.timedelta(hours=-5)) r = Range( dt.datetime(2010, 1, 1, tzinfo=tz), dt.datetime(2011, 1, 1, tzinfo=tz), ) expected = "[2010-01-01 00:00:00-05:00, 2011-01-01 00:00:00-05:00)" result = str(r) assert result == expected
def test_dump_quoting(conn, testrange): info = RangeInfo.fetch(conn, "testrange") register_range(info, conn) cur = conn.cursor() for i in range(1, 254): cur.execute( """ select ascii(lower(%(r)s)) = %(low)s and ascii(upper(%(r)s)) = %(up)s """, {"r": Range(chr(i), chr(i + 1)), "low": i, "up": i + 1}, ) assert cur.fetchone()[0] is True
def test_copy_in_empty(conn, min, max, bounds, format): cur = conn.cursor() cur.execute("create table copyrange (id serial primary key, r daterange)") if bounds != "empty": min = dt.date(*map(int, min.split(","))) if min else None max = dt.date(*map(int, max.split(","))) if max else None r = Range(min, max, bounds) else: r = Range(empty=True) try: with cur.copy(f"copy copyrange (r) from stdin (format {format.name})" ) as copy: copy.write_row([r]) except psycopg.errors.ProtocolViolation: if not min and not max and format == pq.Format.BINARY: pytest.xfail( "TODO: add annotation to dump array with no type info") else: raise rec = cur.execute("select r from copyrange order by id").fetchone() assert rec[0] == r
def test_bounds(self): for bounds, lower_inc, upper_inc in [ ("[)", True, False), ("(]", False, True), ("()", False, False), ("[]", True, True), ]: r = Range(10, 20, bounds) assert r.bounds == bounds assert r.lower == 10 assert r.upper == 20 assert not r.isempty assert not r.lower_inf assert not r.upper_inf assert r.lower_inc == lower_inc assert r.upper_inc == upper_inc
def test_relations(self): mr1 = Multirange([Range(10, 20), Range(30, 40)]) mr2 = Multirange([Range(11, 20), Range(30, 40)]) mr3 = Multirange([Range(9, 20), Range(30, 40)]) assert mr1 <= mr1 assert not mr1 < mr1 assert mr1 >= mr1 assert not mr1 > mr1 assert mr1 < mr2 assert mr1 <= mr2 assert mr1 > mr3 assert mr1 >= mr3 assert mr1 != mr2 assert not mr1 == mr2