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) # 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_dump_builtin_array_with_cast(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}::{pgtype}[] """, ([mr1, mr2], ), ) assert cur.fetchone()[0] is True
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_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_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_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_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_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_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_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_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_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
def to_range(value): if not isinstance(value, (str, NoneType)): value = Range(value.lower, value.upper, value.bounds, value.empty) return value
def test_pickling(self): r = Range(0, 4) assert pickle.loads(pickle.dumps(r)) == r
def test_dump_builtin_empty(conn, pgtype, fmt_in): r = Range(empty=True) # type: ignore[var-annotated] cur = conn.execute(f"select 'empty'::{pgtype} = %{fmt_in}", (r, )) assert cur.fetchone()[0] is True
def test_ge_ordering(self): assert not Range(empty=True) >= Range(0, 4) assert Range(1, 2) >= Range(0, 4) assert not Range(0, 4) >= Range(1, 2) assert Range(1, 2) >= Range() assert not Range() >= Range(1, 2) assert Range(1) >= Range(upper=1) assert Range() >= Range() assert Range(empty=True) >= Range(empty=True) assert Range(1, 2) >= Range(1, 2) with pytest.raises(TypeError): assert not 1 >= Range(1, 2) with pytest.raises(TypeError): (Range(1, 2) >= 1)
def test_le_ordering(self): assert Range(empty=True) <= Range(0, 4) assert not Range(1, 2) <= Range(0, 4) assert Range(0, 4) <= Range(1, 2) assert not Range(1, 2) <= Range() assert Range() <= Range(1, 2) assert not Range(1) <= Range(upper=1) assert Range() <= Range() assert Range(empty=True) <= Range(empty=True) assert Range(1, 2) <= Range(1, 2) with pytest.raises(TypeError): assert 1 <= Range(1, 2) with pytest.raises(TypeError): assert not Range(1, 2) <= 1
def test_eq_wrong_type(self): assert Range(10, 20) != ()
def test_eq_hash(self): def assert_equal(r1, r2): assert r1 == r2 assert hash(r1) == hash(r2) assert_equal(Range(empty=True), Range(empty=True)) assert_equal(Range(), Range()) assert_equal(Range(10, None), Range(10, None)) assert_equal(Range(10, 20), Range(10, 20)) assert_equal(Range(10, 20), Range(10, 20, "[)")) assert_equal(Range(10, 20, "[]"), Range(10, 20, "[]")) def assert_not_equal(r1, r2): assert r1 != r2 assert hash(r1) != hash(r2) assert_not_equal(Range(10, 20), Range(10, 21)) assert_not_equal(Range(10, 20), Range(11, 20)) assert_not_equal(Range(10, 20, "[)"), Range(10, 20, "[]"))
def test_nonzero(self): assert Range() assert Range(10, 20) assert not Range(empty=True)
def test_bad_bounds(self): with pytest.raises(ValueError): Range(bounds="(") with pytest.raises(ValueError): Range(bounds="[}")