beset 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
beset-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: beset
3
+ Version: 0.1.0
4
+ Summary: Immutable, typed intervals as sets
5
+ Author: Maarten Oosten
6
+ Author-email: Maarten Oosten <oosten@gmail.com>
7
+ License-Expression: MIT
8
+ Requires-Dist: typing-extensions>=4.16.0 ; python_full_version < '3.11'
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+
12
+ # `⟨ beset ⟩`
13
+
14
+ _immutable, typed intervals with the interface of Python sets_
15
+
16
+ - Intervals are typed as generics and pass type checks by all common Python type checkers:
17
+ - `mypy --strict`
18
+ - `ty`
19
+ - `pyright`
20
+ - `pyrefly`
21
+ - Intervals are immutable and hashable
22
+ - Intervals behave like sets
23
+ - If you know Python set operations, you know how to use this library
24
+ - Intervals can be used on any data type that supports `<`, the less-than operator, for linear ordering
25
+ - Typical: `int`, `float`, `datetime`
26
+ - Even a class like `str` with no notion of distance between values, can be used for intervals (e.g. dictionary ranges)
27
+
28
+ ## Examples
29
+
30
+ Intervals are sets that contain all possible values between their lower and upper bounds.
31
+ The `Closed` interval also includes the bounds themselves.
32
+
33
+ ```python
34
+ >>> from beset import Closed
35
+
36
+ >>> x = Closed(1, 3)
37
+
38
+ >>> print(x)
39
+ [1 ; 3]
40
+
41
+ >>> 0 in x, 1 in x, 2 in x, 3 in x, 4 in x
42
+ (False, True, True, True, False)
43
+
44
+ >>> x.start, x.stop
45
+ (1, 3)
46
+ ```
47
+
48
+ The half-open `ClosedOpen` interval includes its lower bound but not its upper one.
49
+
50
+ ```python
51
+ >>> from beset import ClosedOpen
52
+
53
+ >>> y = ClosedOpen(1, 3)
54
+
55
+ >>> print(y)
56
+ [1 ; 3)
57
+
58
+ >>> 2 in y, 3 in y, 4 in y
59
+ (True, False, False)
60
+ ```
61
+
62
+ The `Open` interval excludes both its bounds and the `OpenClosed` interval includes its upper bound, but not its lower one.
63
+
64
+ You can also create the required interval dynamically using their `Interval` base class.
65
+
66
+ ```python
67
+ >>> from beset import Interval
68
+
69
+ >>> Interval(9, 99, start_closed=False, stop_closed=True)
70
+ OpenClosed(9, 99)
71
+ ```
72
+
73
+ Intervals support all Python `set` operations. Some examples:
74
+
75
+ ```python
76
+ >>> ClosedOpen(10, 20) & ClosedOpen(15, 25) # intersection
77
+ ClosedOpen(15, 20)
78
+
79
+ >>> ClosedOpen(3, 9) < Open(0, 10) # is proper subset
80
+ True
81
+ ```
82
+
83
+ Set subtraction can lead to disjoint sets. The `beset` library represents these using the class `IntervalSet`.
84
+
85
+ ```python
86
+ >>> s = Open(0, 10) - Open(3, 5)
87
+
88
+ >>> s
89
+ IntervalSet([OpenClosed(0, 3), ClosedOpen(5, 10)])
90
+
91
+ >>> print(s)
92
+ (0 ; 3] | [5 ; 10)
93
+ ```
94
+
95
+ You can also create an `IntervalSet` explicitly, but it's often easier to use the union operator on simple intervals.
96
+ The results are equal.
97
+
98
+ ```python
99
+ >>> IntervalSet([Open(10, 20), Open(30, 40)]) == Open(10, 20) | Open(30, 40)
100
+ True
101
+ ```
102
+
103
+ The `beset` library supports unbounded intervals without upper or lower bound.
104
+ Create such intervals by using `None` as a bound.
105
+
106
+ ```python
107
+ >>> x = Closed(10, None)
108
+ >>> print(x)
109
+ [10 ; +inf⟩
110
+ >>> 100 in x
111
+ True
112
+ ```
113
+
114
+ Unbounded intervals allow for the introduction of the _complement_ operation that returns the complementary interval, containing everything not in the original interval.
115
+
116
+ ```python
117
+ >>> Closed(-3, 7).complement()
118
+ IntervalSet([Open(None, -3), Open(7, None)])
119
+
120
+ >>> print(~ClosedOpen(0, 100)) # the ~-operator returns the complement
121
+ ⟨-inf ; 0) | [100 ; +inf⟩
122
+ ```
123
+
124
+ ## Typing
125
+
126
+ The `Interval` and `IntervalSet` class are generics.
127
+ Type checkers automatically infer the correct type.
128
+
129
+ ```python
130
+ >>> reveal_type(ClosedOpen(2.718, 6.283)) # Revealed type is beset.ClosedOpen[float]
131
+ ```
132
+
133
+ Taking the complement of an interval can introduce `None` values.
134
+
135
+ ```python
136
+ >>> print(x := ~Closed(0, 10))
137
+ ⟨-inf ; 0) | (10 ; +inf⟩
138
+
139
+ >>> reveal_type(x) # Revealed type is beset.IntervalSet[int | None]
140
+ ```
141
+
142
+ Getting rid of the union with `None` can be accomplished using intersection.
143
+
144
+ ```python
145
+ >>> domain = Closed(-100, 100)
146
+ >>> y = domain & x
147
+ >>> print(y)
148
+ [-100 ; 0) | (10 ; 100]
149
+
150
+ >>> reveal_type(y) # Revealed type is beset.IntervalSet[int]
151
+ ```
@@ -0,0 +1,69 @@
1
+ [project]
2
+ name = "beset"
3
+ version = "0.1.0"
4
+ description = "Immutable, typed intervals as sets"
5
+ readme = "readme.md"
6
+ license = "MIT"
7
+ requires-python = ">=3.10"
8
+ dependencies = ["typing-extensions>=4.16.0 ; python_full_version < '3.11'"]
9
+
10
+ [[project.authors]]
11
+ name = "Maarten Oosten"
12
+ email = "oosten@gmail.com"
13
+
14
+ [dependency-groups]
15
+ lint = ["ruff>=0.16.5"]
16
+ type-check = [
17
+ "mypy>=2.3.1",
18
+ "pyrefly>=1.2.0",
19
+ "pyright>=1.1.411",
20
+ "ty>=0.0.75",
21
+ ]
22
+ test = ["pytest>=9.1.1"]
23
+ coverage = [
24
+ "pytest-cov>=7.1.0",
25
+ { include-group = "test" },
26
+ ]
27
+ slotscheck = ["slotscheck==0.21.0b1"]
28
+
29
+ [[dependency-groups.dev]]
30
+ include-group = "lint"
31
+
32
+ [[dependency-groups.dev]]
33
+ include-group = "type-check"
34
+
35
+ [[dependency-groups.dev]]
36
+ include-group = "test"
37
+
38
+ [[dependency-groups.dev]]
39
+ include-group = "coverage"
40
+
41
+ [[dependency-groups.dev]]
42
+ include-group = "slotscheck"
43
+
44
+ [build-system]
45
+ requires = ["uv_build>=0.12.7,<0.13.0"]
46
+ build-backend = "uv_build"
47
+
48
+ [tool.ruff]
49
+ line-length = 120
50
+
51
+ [tool.ruff.lint]
52
+ select = [
53
+ "E",
54
+ "F",
55
+ "I",
56
+ ]
57
+
58
+ [tool.mypy]
59
+ strict = true
60
+
61
+ [tool.pyrefly]
62
+ preset = "strict"
63
+
64
+ [tool.pyrefly.errors]
65
+ unused-ignore = false
66
+ missing-override-decorator = false
67
+
68
+ [tool.coverage.run]
69
+ omit = ["test/*"]
@@ -0,0 +1,64 @@
1
+ [project]
2
+ name = "beset"
3
+ version = "0.1.0"
4
+ description = "Immutable, typed intervals as sets"
5
+ readme = "readme.md"
6
+ authors = [
7
+ { name = "Maarten Oosten", email = "oosten@gmail.com" }
8
+ ]
9
+ license = "MIT"
10
+ requires-python = ">=3.10"
11
+ dependencies = [
12
+ "typing-extensions>=4.16.0 ; python_full_version < '3.11'",
13
+ ]
14
+
15
+ [dependency-groups]
16
+ lint = [
17
+ "ruff>=0.16.5",
18
+ ]
19
+ type-check = [
20
+ "mypy>=2.3.1",
21
+ "pyrefly>=1.2.0",
22
+ "pyright>=1.1.411",
23
+ "ty>=0.0.75",
24
+ ]
25
+ test = [
26
+ "pytest>=9.1.1",
27
+ ]
28
+ coverage = [
29
+ "pytest-cov>=7.1.0",
30
+ {include-group = "test"},
31
+ ]
32
+ slotscheck = [
33
+ "slotscheck==0.21.0b1",
34
+ ]
35
+ dev = [
36
+ {include-group = "lint"},
37
+ {include-group = "type-check"},
38
+ {include-group = "test"},
39
+ {include-group = "coverage"},
40
+ {include-group = "slotscheck"},
41
+ ]
42
+
43
+ [build-system]
44
+ requires = ["uv_build>=0.12.7,<0.13.0"]
45
+ build-backend = "uv_build"
46
+
47
+ [tool.ruff]
48
+ line-length = 120
49
+
50
+ [tool.ruff.lint]
51
+ select = ["E", "F", "I"]
52
+
53
+ [tool.mypy]
54
+ strict = true
55
+
56
+ [tool.pyrefly]
57
+ preset = "strict"
58
+
59
+ [tool.pyrefly.errors]
60
+ unused-ignore = false
61
+ missing-override-decorator = false
62
+
63
+ [tool.coverage.run]
64
+ omit = ["test/*"]
beset-0.1.0/readme.md ADDED
@@ -0,0 +1,140 @@
1
+ # `⟨ beset ⟩`
2
+
3
+ _immutable, typed intervals with the interface of Python sets_
4
+
5
+ - Intervals are typed as generics and pass type checks by all common Python type checkers:
6
+ - `mypy --strict`
7
+ - `ty`
8
+ - `pyright`
9
+ - `pyrefly`
10
+ - Intervals are immutable and hashable
11
+ - Intervals behave like sets
12
+ - If you know Python set operations, you know how to use this library
13
+ - Intervals can be used on any data type that supports `<`, the less-than operator, for linear ordering
14
+ - Typical: `int`, `float`, `datetime`
15
+ - Even a class like `str` with no notion of distance between values, can be used for intervals (e.g. dictionary ranges)
16
+
17
+ ## Examples
18
+
19
+ Intervals are sets that contain all possible values between their lower and upper bounds.
20
+ The `Closed` interval also includes the bounds themselves.
21
+
22
+ ```python
23
+ >>> from beset import Closed
24
+
25
+ >>> x = Closed(1, 3)
26
+
27
+ >>> print(x)
28
+ [1 ; 3]
29
+
30
+ >>> 0 in x, 1 in x, 2 in x, 3 in x, 4 in x
31
+ (False, True, True, True, False)
32
+
33
+ >>> x.start, x.stop
34
+ (1, 3)
35
+ ```
36
+
37
+ The half-open `ClosedOpen` interval includes its lower bound but not its upper one.
38
+
39
+ ```python
40
+ >>> from beset import ClosedOpen
41
+
42
+ >>> y = ClosedOpen(1, 3)
43
+
44
+ >>> print(y)
45
+ [1 ; 3)
46
+
47
+ >>> 2 in y, 3 in y, 4 in y
48
+ (True, False, False)
49
+ ```
50
+
51
+ The `Open` interval excludes both its bounds and the `OpenClosed` interval includes its upper bound, but not its lower one.
52
+
53
+ You can also create the required interval dynamically using their `Interval` base class.
54
+
55
+ ```python
56
+ >>> from beset import Interval
57
+
58
+ >>> Interval(9, 99, start_closed=False, stop_closed=True)
59
+ OpenClosed(9, 99)
60
+ ```
61
+
62
+ Intervals support all Python `set` operations. Some examples:
63
+
64
+ ```python
65
+ >>> ClosedOpen(10, 20) & ClosedOpen(15, 25) # intersection
66
+ ClosedOpen(15, 20)
67
+
68
+ >>> ClosedOpen(3, 9) < Open(0, 10) # is proper subset
69
+ True
70
+ ```
71
+
72
+ Set subtraction can lead to disjoint sets. The `beset` library represents these using the class `IntervalSet`.
73
+
74
+ ```python
75
+ >>> s = Open(0, 10) - Open(3, 5)
76
+
77
+ >>> s
78
+ IntervalSet([OpenClosed(0, 3), ClosedOpen(5, 10)])
79
+
80
+ >>> print(s)
81
+ (0 ; 3] | [5 ; 10)
82
+ ```
83
+
84
+ You can also create an `IntervalSet` explicitly, but it's often easier to use the union operator on simple intervals.
85
+ The results are equal.
86
+
87
+ ```python
88
+ >>> IntervalSet([Open(10, 20), Open(30, 40)]) == Open(10, 20) | Open(30, 40)
89
+ True
90
+ ```
91
+
92
+ The `beset` library supports unbounded intervals without upper or lower bound.
93
+ Create such intervals by using `None` as a bound.
94
+
95
+ ```python
96
+ >>> x = Closed(10, None)
97
+ >>> print(x)
98
+ [10 ; +inf⟩
99
+ >>> 100 in x
100
+ True
101
+ ```
102
+
103
+ Unbounded intervals allow for the introduction of the _complement_ operation that returns the complementary interval, containing everything not in the original interval.
104
+
105
+ ```python
106
+ >>> Closed(-3, 7).complement()
107
+ IntervalSet([Open(None, -3), Open(7, None)])
108
+
109
+ >>> print(~ClosedOpen(0, 100)) # the ~-operator returns the complement
110
+ ⟨-inf ; 0) | [100 ; +inf⟩
111
+ ```
112
+
113
+ ## Typing
114
+
115
+ The `Interval` and `IntervalSet` class are generics.
116
+ Type checkers automatically infer the correct type.
117
+
118
+ ```python
119
+ >>> reveal_type(ClosedOpen(2.718, 6.283)) # Revealed type is beset.ClosedOpen[float]
120
+ ```
121
+
122
+ Taking the complement of an interval can introduce `None` values.
123
+
124
+ ```python
125
+ >>> print(x := ~Closed(0, 10))
126
+ ⟨-inf ; 0) | (10 ; +inf⟩
127
+
128
+ >>> reveal_type(x) # Revealed type is beset.IntervalSet[int | None]
129
+ ```
130
+
131
+ Getting rid of the union with `None` can be accomplished using intersection.
132
+
133
+ ```python
134
+ >>> domain = Closed(-100, 100)
135
+ >>> y = domain & x
136
+ >>> print(y)
137
+ [-100 ; 0) | (10 ; 100]
138
+
139
+ >>> reveal_type(y) # Revealed type is beset.IntervalSet[int]
140
+ ```
@@ -0,0 +1,43 @@
1
+ from beset._interval import (
2
+ EMPTY,
3
+ UNBOUNDED,
4
+ Closed,
5
+ ClosedOpen,
6
+ ClosedOpenSet,
7
+ ClosedSet,
8
+ Empty,
9
+ Interval,
10
+ IntervalSet,
11
+ LeftClosed,
12
+ LeftOpen,
13
+ Open,
14
+ OpenClosed,
15
+ OpenClosedSet,
16
+ OpenSet,
17
+ RightClosed,
18
+ RightOpen,
19
+ Unbounded,
20
+ )
21
+ from beset._protocol import Sortable
22
+
23
+ __all__ = [
24
+ "Sortable",
25
+ "IntervalSet",
26
+ "OpenSet",
27
+ "ClosedSet",
28
+ "OpenClosedSet",
29
+ "ClosedOpenSet",
30
+ "Interval",
31
+ "Open",
32
+ "Closed",
33
+ "OpenClosed",
34
+ "ClosedOpen",
35
+ "LeftOpen",
36
+ "RightOpen",
37
+ "LeftClosed",
38
+ "RightClosed",
39
+ "Unbounded",
40
+ "Empty",
41
+ "UNBOUNDED",
42
+ "EMPTY",
43
+ ]