diff --git a/construction/index.html b/construction/index.html index 529ed5a..0f160b5 100644 --- a/construction/index.html +++ b/construction/index.html @@ -512,98 +512,94 @@
range
or slice
from arranges import Ranges
-
-# Create ranges from a string. Like a slice but without the square brackets.
-
-the_full_range = Ranges(":") # an endless sequence starting at 0
-the_empty_range = Ranges("") # start, stop and len() are all 0
-from_0_to_99 = Ranges("0:100")
-skip_10_items = Ranges("10:") # boundless, goes to infinity
-access_by_index = Ranges("0") # just the first item
-
-# Decimal, hexadecimal, octal and binary are supported. Note that octal uses
-# Python's 0o prefix, not 0 like in C.
-gbc_palette = Ranges("0xff68:0xff88")
-skip_bmp_header = Ranges("0o66:end") # skip "54", "0x36" or "0b1100110" bytes
-
-# "end" and "inf" are the same. You can also use "start" instead of 0.
-the_full_range_again = Ranges("start:inf")
-
-# whitespace is ignored during construction
-first_kilobyte = Ranges("start : 0x400")
-
-# They are simplified when converted to str, which they can be compared with.
-assert first_kilobyte == ":1024"
-
-# Comparisons work in both directions
-assert "000001:000002" == Ranges("1")
-
-# and the hash is the same as the string
-w = Ranges("6 : 8")
-assert hash(w) == hash("6:8")
-
-# so you can use them as dict keys
-d = {"6:8": "width"}
-d[w] = "GIF file width"
-assert d == {"6:8": "GIF file width"}
-
from arranges import Ranges
+
+# Create ranges from a string. Like a slice but without the square brackets.
+
+the_full_range = Ranges(":") # an endless sequence starting at 0
+the_empty_range = Ranges("") # start, stop and len() are all 0
+from_0_to_99 = Ranges("0:100")
+skip_10_items = Ranges("10:") # boundless, goes to infinity
+access_by_index = Ranges("0") # just the first item
+
+# Decimal, hexadecimal, octal and binary are supported. Note that octal uses
+# Python's 0o prefix, not 0 like in C.
+gbc_palette = Ranges("0xff68:0xff88")
+skip_bmp_header = Ranges("0o66:end") # skip "54", "0x36" or "0b1100110" bytes
+
+# "end" and "inf" are the same. You can also use "start" instead of 0.
+the_full_range_again = Ranges("start:inf")
+
+# whitespace is ignored during construction
+first_kilobyte = Ranges("start : 0x400")
+
+# They are simplified when converted to str, which they can be compared with.
+assert first_kilobyte == ":1024"
+
+# Comparisons work in both directions
+assert "000001:000002" == Ranges("1")
+
+# and the hash is the same as the string
+w = Ranges("6 : 8")
+assert hash(w) == hash("6:8")
+
+# so you can use them as dict keys
+d = {"6:8": "width"}
+d[w] = "GIF file width"
+assert d == {"6:8": "GIF file width"}
+
from arranges import Ranges
-
-# You can put gaps in your range using commas.
-pages_2_to_5_and_20 = Ranges("2:6, 20")
-
-# The ranges are sorted and combined as they are added
-overlapping = Ranges("start:10, 14, 1:3")
-assert overlapping == ":10,14"
-
-# But you can't hash Ranges because they're mutable
-import pytest
-
-with pytest.raises(ValueError):
- a = {overlapping: "whatever"}
-
from arranges import Ranges
+
+# You can put gaps in your range using commas.
+pages_2_to_5_and_20 = Ranges("2:6, 20")
+
+# The ranges are sorted and combined as they are added
+overlapping = Ranges("start:10, 14, 1:3")
+assert overlapping == ":10,14"
+
+# But you can't hash Ranges because they're mutable
+import pytest
+
+with pytest.raises(ValueError):
+ a = {overlapping: "whatever"}
+
If it has a start, stop and step attribute then it quacks like a range so you
can pass it in as the value. If the step
value is something other than None
or 1 then it'll raise ValueError
.
from arranges import Ranges
-
-from_slice = Ranges(slice(10, 20))
-from_range = Ranges(range(10, 20))
-from_other = Ranges(Ranges("10:20"))
-
-assert from_slice == from_range == from_other == "10:20"
-
from arranges import Ranges
+
+from_slice = Ranges(slice(10, 20))
+from_range = Ranges(range(10, 20))
+from_other = Ranges(Ranges("10:20"))
+
+assert from_slice == from_range == from_other == "10:20"
+
from arranges import Ranges
-
-l = list(range(100))
-t = tuple(range(100))
-i = (i for i in range(100))
-
-assert Ranges(l) == Ranges(t) == Ranges(i) == ":100"
-
-jumble = Ranges([0, [2, 4], ["1:3"], 3, 4, range(10, 20)])
-
-assert jumble == "5,10:20"
-
from arranges import Ranges
+
+l = list(range(100))
+t = tuple(range(100))
+i = (i for i in range(100))
+
+assert Ranges(l) == Ranges(t) == Ranges(i) == ":100"
+
+jumble = Ranges([0, [2, 4], ["1:3"], 3, 4, range(10, 20)])
+
+assert jumble == "5,10:20"
+
You can create them in the same way as range
or slice
objects which have a
slightly different syntax to slice notation.
from arranges import Ranges, inf
-
-assert Ranges(10, inf) == "10:"
-
-# Ranges("10") is not the same as Ranges(10)
-assert Ranges(10) == ":10"
-assert Ranges("10") == 10 == Ranges(10, 11)
-
from arranges import Ranges, inf
+
+assert Ranges(10, inf) == "10:"
+
+# Ranges("10") is not the same as Ranges(10)
+assert Ranges(10) == ":10"
+assert Ranges("10") == 10 == Ranges(10, 11)
+
Ranges
objects are sequences of integers that you can iterate over.
from arranges import Ranges
-
-assert list(Ranges("start:3, 10")) == [0, 1, 2, 10]
-
The length of a range is the number of elements it contains. But because ranges
can be boundless, we have a special inf
value that represents an infinite int
@@ -475,36 +474,35 @@
This value is math.inf
, but when returned from len
it becomes sys.maxsize
as it only supports ints that are this size or smaller. When comparing inf
to
sys.maxsize
it'll return True
, but this doesn't work in all directions:
import math
-import sys
-from arranges import Ranges, inf
-
-assert len(Ranges("1,2,3,4,10:20")) == 14
-
-full = Ranges(":")
-
-assert len(full) == sys.maxsize
-assert len(full) == inf
-assert sys.maxsize == inf
-assert full.stop == inf
-assert inf - 100 == sys.maxsize
-
-# Careful though, this does not hold
-assert len(full) != math.inf
-
-# because it's an int
-assert type(len(full)) is int
-
import math
+import sys
+from arranges import Ranges, inf
+
+assert len(Ranges("1,2,3,4,10:20")) == 14
+
+full = Ranges(":")
+
+assert len(full) == sys.maxsize
+assert len(full) == inf
+assert sys.maxsize == inf
+assert full.stop == inf
+assert inf - 100 == sys.maxsize
+
+# Careful though, this does not hold
+assert len(full) != math.inf
+
+# because it's an int
+assert type(len(full)) is int
+
Empty ranges are, of course, Falsey.
-from arranges import Ranges
-
-assert Ranges(":")
-assert Range(10)
-
-assert not Ranges("")
-
You'll need Pydantic >=2.3:
-from pydantic import BaseModel, Field
-
-
-class BinaryPatch(BaseModel):
- """
-
- """
- regions: Ranges
-
-
-model = BinaryPatch.model_validate_json("""
-{
- "regions": "0:10, 20:30, 15:, 0x0b"
-}
-""")
-
-assert model.regions == ":10,15:"
-
from pydantic import BaseModel, Field
+
+
+class BinaryPatch(BaseModel):
+ """
+
+ """
+ regions: Ranges
+
+
+model = BinaryPatch.model_validate_json("""
+{
+ "regions": "0:10, 20:30, 15:, 0x0b"
+}
+""")
+
+assert model.regions == ":10,15:"
+
class Ranges(str)
-
A range set that can be hashed and converted to a string.
def __init__(value: Any, stop: range_idx | None = None)
-
Construct a new string with the canonical form of the range.
def __new__(cls, value: Any, stop: range_idx | None = None) -> str
-
Construct a new string with the canonical form of the range.
This becomes "self" in init, so we're always a string
@classmethod
-def construct_str(cls, value, stop) -> str
-
Create a string representation of a range series
@classmethod
-def from_str(cls, value: str) -> tuple[Segment]
-
Construct from a string.
@classmethod
-def iterable_to_str(cls, iterable: Iterable) -> str
-
Convert an iterable of ranges to a string
@classmethod
-@lru_cache
-def from_hashable_iterable(cls, value: tuple[Any]) -> tuple[Segment]
-
Cache the result of from_iterable
@classmethod
-def from_iterable(cls, iterable: Iterable) -> tuple[Segment]
-
Sort and merge a list of ranges.
def __eq__(other: Any) -> bool
-
Compare the two lists based on their string representations
def __contains__(other: Any) -> bool
-
Are all of the other ranges in our ranges?
def __iter__()
-
Iterate over the values in our ranges.
Note that this could be boundless.
def intersects(other: Any) -> bool
-
True if this range overlaps with the other range
def union(other) -> "Ranges"
-
Return the union of this range and the other
def __or__(other: "Ranges") -> "Ranges"
-
Return the union of this range and the other
def __and__(other: "Ranges") -> "Ranges"
-
Return the intersection of this range and the other
def __invert__()
-
The inverse of this range
@classmethod
-def validate(cls, value: Any) -> "Ranges"
-
Validate a value and convert it to a Range
@classmethod
-def __get_pydantic_core_schema__(cls, source_type: Any,
- handler: GetCoreSchemaHandler) -> CoreSchema
-
@classmethod
+def __get_pydantic_core_schema__(cls, source_type: Any,
+ handler: GetCoreSchemaHandler) -> CoreSchema
+
For automatic validation in pydantic
Some quacky type helpers so we don't have to use isinstance everywhere
class _Boundless(float)
-
A class that represents a boundless end of a range
An enormous number that's used as a stand-in for infinity
def __eq__(other) -> bool
-
Is this boundless?
def __index__() -> int
-
When used as an index, return a huge integer rather than infinity.
This is necessary because CPython doesn't allow lengths larger than sys.maxsize, and Python has no way to represent infinity as an integer.
@@ -548,150 +527,127 @@def to_int(value: str, default: int) -> int
-
Convert a string to an integer. If the string is empty, return the default
def is_rangelike(obj: Any) -> bool
-
Check if a value is a range-like object
def is_intlike(value: Any) -> bool
-
Can this object be converted to an integer?
def is_iterable(value: Any) -> bool
-
Is this object iterable?
def as_type(cls: Type[T], value: Any) -> T
-
Convert a value to a type, if necessary.
Saves a bit of construction time if the value is already the right type.
def try_hash(obj: Any) -> int | None
-
Try to hash an object. If it can't be hashed, return None
def start_stop_to_str(start: range_idx, stop: range_idx) -> str
-
Returns a string representation of a range from start to stop.
class Segment(str)
-
A single range segment that's a string and can be hashed.
def __init__(start: range_idx, stop: range_idx = None)
-
Construct a new string with the canonical form of the range.
def __new__(cls, start: range_idx, stop: range_idx = None) -> str
-
Construct a new string with the canonical form of the range.
def __or__(other: "Segment") -> "Segment"
-
Return the union of this range and the other range
def __iter__()
-
Iterate over the values in this range
def __len__() -> int
-
Get the length of this range
def __bool__() -> bool
-
True if this range has a length
@property
-def last() -> int
-
Gets the last value in this range. Will return inf if the range has no end, and -1 if it has no contents,
@classmethod
-@lru_cache
-def from_str(cls, value: str) -> "Segment"
-
Construct from a string.
@staticmethod
-def sort_key(value: "Segment") -> tuple[int, int]
-
Sort key function for sorting ranges
def isdisjoint(other: Any) -> bool
-
Return True if this range is disjoint from the other range
def __eq__(other: Any) -> bool
-
Compare two segments
def isconnected(other: "Segment") -> bool
-
True if this range is adjacent to or overlaps the other range, and so they can be joined together.
def isadjacent(other: "Segment") -> bool
-
True if this range is adjacent to the other range
def intersects(other: "Segment") -> bool
-
True if this range intersects the other range.
def __contains__(other: Any) -> bool
-
Membership test. Supports integers, strings, ranges and iterables.
diff --git a/search/search_index.json b/search/search_index.json index 29f009c..fe76173 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Arranges","text":""},{"location":"#range-string-fields-for-pydantic-basemodels","title":"Range string fields for Pydantic BaseModels","text":"I needed a way to parse batches of byte, row and line and other object ranges in my merge-files
app, in a way that I can just drop it in as a string field type. The reason for this is so the machine-generated command line help is flat and readable by humans.
It it kinda grew into a monster so I've split it out into this separate package. The main feature is a pair of classes that can represent ranges:
Segment
is a class that can be treated like a set
and its constructor is compatible with range
and slice
. It is derived from str
so is easy to compare and serializes nicely. It is immutable, hashable and has a stable string representation.Ranges
is an ordered tuple
of Segment
s. It is also immutable and derived from str
like the above. It can be constructed from comma-separated Python-style slice notation strings (e.g. \"1:10, 20:\"
, \"0x00:0xff
and \":\"
), integers, slice
s, range
s, integers and (nested) iterables of the above.inf
singleton that is a float
with a value of math.inf
but has an __index__
that returns sys.maxsize
and compares equal to infinity and maxsize
, and its string representation is \"inf\"
.The range class is designed to be used as fields in Pydantic BaseModel
s, but can be used anywhere you need a range. They are not designed with speed in mind, and comparisons usually use the canonical string form by converting other things into Ranges
objects. Their preferred pronoun is they/them.
I made them to select lines or bytes in a stream of data, so they:
int
s;range
and slice
, but step
is fixed to 1
. If you pass something with a step into its constructor it'll be converted to a list of int
s (range(0, 10, 2)
becomes \"0,2,4,6,8\"
);Ranges
object;pip install arranges
if you want to use them. You'll need Python 3.10 or above.
To add features etc you'll ideally need git
, make
, bash
and something with a debugger. Config for Visual Studio Code is included.
Clone the repo and make dev
to make the venv, install dependencies, then code .
to open the project up in the venv with tests and debugging and all that jazz.
Type make help
to see the other options, or run the one-liner scripts in the ./build
dir if you want to run steps without all that fancy caching nonsense.
Free as in freedom from legalese; the WTFPL with a warranty clause.
Political note: I don't want to live in a world where lawyers tell me how to speak. If you don't trust me enough to use the WTFPL then you shouldn't be running my code in the first place.
"},{"location":"LICENSE/","title":"License","text":"Licensed under the WTFPL with one additional clause:
Do whatever the fuck you want, just don't blame me.
"},{"location":"construction/","title":"Constructing Range and Ranges","text":"You can create them from:
range
or slice
from arranges import Ranges\n\n# Create ranges from a string. Like a slice but without the square brackets.\n\nthe_full_range = Ranges(\":\") # an endless sequence starting at 0\nthe_empty_range = Ranges(\"\") # start, stop and len() are all 0\nfrom_0_to_99 = Ranges(\"0:100\")\nskip_10_items = Ranges(\"10:\") # boundless, goes to infinity\naccess_by_index = Ranges(\"0\") # just the first item\n\n# Decimal, hexadecimal, octal and binary are supported. Note that octal uses\n# Python's 0o prefix, not 0 like in C.\ngbc_palette = Ranges(\"0xff68:0xff88\")\nskip_bmp_header = Ranges(\"0o66:end\") # skip \"54\", \"0x36\" or \"0b1100110\" bytes\n\n# \"end\" and \"inf\" are the same. You can also use \"start\" instead of 0.\nthe_full_range_again = Ranges(\"start:inf\")\n\n# whitespace is ignored during construction\nfirst_kilobyte = Ranges(\"start : 0x400\")\n\n# They are simplified when converted to str, which they can be compared with.\nassert first_kilobyte == \":1024\"\n\n# Comparisons work in both directions\nassert \"000001:000002\" == Ranges(\"1\")\n\n# and the hash is the same as the string\nw = Ranges(\"6 : 8\")\nassert hash(w) == hash(\"6:8\")\n\n# so you can use them as dict keys\nd = {\"6:8\": \"width\"}\nd[w] = \"GIF file width\"\nassert d == {\"6:8\": \"GIF file width\"}\n
"},{"location":"construction/#ranges-from-a-string","title":"Ranges from a string","text":"from arranges import Ranges\n\n# You can put gaps in your range using commas.\npages_2_to_5_and_20 = Ranges(\"2:6, 20\")\n\n# The ranges are sorted and combined as they are added\noverlapping = Ranges(\"start:10, 14, 1:3\")\nassert overlapping == \":10,14\"\n\n# But you can't hash Ranges because they're mutable\nimport pytest\n\nwith pytest.raises(ValueError):\n a = {overlapping: \"whatever\"}\n
"},{"location":"construction/#ranges-from-range-like-objects","title":"Ranges from range-like objects","text":"If it has a start, stop and step attribute then it quacks like a range so you can pass it in as the value. If the step
value is something other than None or 1 then it'll raise ValueError
.
from arranges import Ranges\n\nfrom_slice = Ranges(slice(10, 20))\nfrom_range = Ranges(range(10, 20))\nfrom_other = Ranges(Ranges(\"10:20\"))\n\nassert from_slice == from_range == from_other == \"10:20\"\n
"},{"location":"construction/#ranges-from-sequences","title":"Ranges from sequences","text":"from arranges import Ranges\n\nl = list(range(100))\nt = tuple(range(100))\ni = (i for i in range(100))\n\nassert Ranges(l) == Ranges(t) == Ranges(i) == \":100\"\n\njumble = Ranges([0, [2, 4], [\"1:3\"], 3, 4, range(10, 20)])\n\nassert jumble == \"5,10:20\"\n
"},{"location":"construction/#explicitly-creating-ranges","title":"Explicitly creating Ranges","text":"You can create them in the same way as range
or slice
objects which have a slightly different syntax to slice notation.
from arranges import Ranges, inf\n\nassert Ranges(10, inf) == \"10:\"\n\n# Ranges(\"10\") is not the same as Ranges(10)\nassert Ranges(10) == \":10\"\nassert Ranges(\"10\") == 10 == Ranges(10, 11)\n
"},{"location":"iteration/","title":"Iteration","text":"Ranges
objects are sequences of integers that you can iterate over.
from arranges import Ranges\n\nassert list(Ranges(\"start:3, 10\")) == [0, 1, 2, 10]\n
"},{"location":"iteration/#cardinality-of-the-address-range","title":"Cardinality of the address range","text":"The length of a range is the number of elements it contains. But because ranges can be boundless, we have a special inf
value that represents an infinite int as Python doesn't have or support one.
This value is math.inf
, but when returned from len
it becomes sys.maxsize
as it only supports ints that are this size or smaller. When comparing inf
to sys.maxsize
it'll return True
, but this doesn't work in all directions:
import math\nimport sys\nfrom arranges import Ranges, inf\n\nassert len(Ranges(\"1,2,3,4,10:20\")) == 14\n\nfull = Ranges(\":\")\n\nassert len(full) == sys.maxsize\nassert len(full) == inf\nassert sys.maxsize == inf\nassert full.stop == inf\nassert inf - 100 == sys.maxsize\n\n# Careful though, this does not hold\nassert len(full) != math.inf\n\n# because it's an int\nassert type(len(full)) is int\n
"},{"location":"iteration/#truthyness","title":"Truthyness","text":"Empty ranges are, of course, Falsey.
from arranges import Ranges\n\nassert Ranges(\":\")\nassert Range(10)\n\nassert not Ranges(\"\")\n
"},{"location":"models/","title":"Use in Pydantic models","text":"You'll need Pydantic >=2.3:
from pydantic import BaseModel, Field\n\n\nclass BinaryPatch(BaseModel):\n\"\"\"\n\n \"\"\"\n regions: Ranges\n\n\nmodel = BinaryPatch.model_validate_json(\"\"\"\n{\n \"regions\": \"0:10, 20:30, 15:, 0x0b\"\n}\n\"\"\")\n\nassert model.regions == \":10,15:\"\n
"},{"location":"operators/","title":"Set operations","text":"They try to be set-like, supporting most of the operations that the set
object does.
Actually, dunno how much of this I'm missing. Raise a ticket if there are gaps.
Symbol Name CodeA \u22c2 B
intersection A & B
A \u22c3 B
union A | B
A \u2286 B
subset A <= B
A \u2282 B
proper subset A < B
A \u2287 B
superset A >= B
A \u2283 B
proper superset A > B
A = B
equality A == B
A'
complement ~A
A \\ B
relative complement A - B
A \u2206 B
symmetric difference (A | B) - (A & B)
a \u2208 A
membership a in A
|A|
cardinality len(A)
\u00d8
empty set Ranges(\"\")
\u21150
natural numbers Ranges(\":\")
"},{"location":"pydoc/","title":"Pydoc","text":""},{"location":"pydoc/#arranges","title":"arranges","text":""},{"location":"pydoc/#arrangesranges","title":"arranges.ranges","text":""},{"location":"pydoc/#ranges-objects","title":"Ranges Objects","text":"class Ranges(str)\n
A range set that can be hashed and converted to a string.
"},{"location":"pydoc/#__init__","title":"__init__","text":"def __init__(value: Any, stop: range_idx | None = None)\n
Construct a new string with the canonical form of the range.
"},{"location":"pydoc/#__new__","title":"__new__","text":"def __new__(cls, value: Any, stop: range_idx | None = None) -> str\n
Construct a new string with the canonical form of the range.
This becomes \"self\" in init, so we're always a string
"},{"location":"pydoc/#construct_str","title":"construct_str","text":"@classmethod\ndef construct_str(cls, value, stop) -> str\n
Create a string representation of a range series
"},{"location":"pydoc/#from_str","title":"from_str","text":"@classmethod\ndef from_str(cls, value: str) -> tuple[Segment]\n
Construct from a string.
"},{"location":"pydoc/#iterable_to_str","title":"iterable_to_str","text":"@classmethod\ndef iterable_to_str(cls, iterable: Iterable) -> str\n
Convert an iterable of ranges to a string
"},{"location":"pydoc/#from_hashable_iterable","title":"from_hashable_iterable","text":"@classmethod\n@lru_cache\ndef from_hashable_iterable(cls, value: tuple[Any]) -> tuple[Segment]\n
Cache the result of from_iterable
"},{"location":"pydoc/#from_iterable","title":"from_iterable","text":"@classmethod\ndef from_iterable(cls, iterable: Iterable) -> tuple[Segment]\n
Sort and merge a list of ranges.
"},{"location":"pydoc/#__eq__","title":"__eq__","text":"def __eq__(other: Any) -> bool\n
Compare the two lists based on their string representations
"},{"location":"pydoc/#__contains__","title":"__contains__","text":"def __contains__(other: Any) -> bool\n
Are all of the other ranges in our ranges?
"},{"location":"pydoc/#__iter__","title":"__iter__","text":"def __iter__()\n
Iterate over the values in our ranges.
Note that this could be boundless.
"},{"location":"pydoc/#intersects","title":"intersects","text":"def intersects(other: Any) -> bool\n
True if this range overlaps with the other range
"},{"location":"pydoc/#union","title":"union","text":"def union(other) -> \"Ranges\"\n
Return the union of this range and the other
"},{"location":"pydoc/#__or__","title":"__or__","text":"def __or__(other: \"Ranges\") -> \"Ranges\"\n
Return the union of this range and the other
"},{"location":"pydoc/#__and__","title":"__and__","text":"def __and__(other: \"Ranges\") -> \"Ranges\"\n
Return the intersection of this range and the other
"},{"location":"pydoc/#__invert__","title":"__invert__","text":"def __invert__()\n
The inverse of this range
"},{"location":"pydoc/#validate","title":"validate","text":"@classmethod\ndef validate(cls, value: Any) -> \"Ranges\"\n
Validate a value and convert it to a Range
"},{"location":"pydoc/#__get_pydantic_core_schema__","title":"__get_pydantic_core_schema__","text":"@classmethod\ndef __get_pydantic_core_schema__(cls, source_type: Any,\n handler: GetCoreSchemaHandler) -> CoreSchema\n
For automatic validation in pydantic
"},{"location":"pydoc/#arrangesutils","title":"arranges.utils","text":"Some quacky type helpers so we don't have to use isinstance everywhere
"},{"location":"pydoc/#_boundless-objects","title":"_Boundless Objects","text":"class _Boundless(float)\n
A class that represents a boundless end of a range
"},{"location":"pydoc/#huge","title":"huge","text":"An enormous number that's used as a stand-in for infinity
"},{"location":"pydoc/#__eq___1","title":"__eq__","text":"def __eq__(other) -> bool\n
Is this boundless?
"},{"location":"pydoc/#__index__","title":"__index__","text":"def __index__() -> int\n
When used as an index, return a huge integer rather than infinity.
This is necessary because CPython doesn't allow lengths larger than sys.maxsize, and Python has no way to represent infinity as an integer.
"},{"location":"pydoc/#inf","title":"inf","text":"A boundless end of a range. When used as a stop value it's infinite, but when used as a length it's the largest index integer possible in cpython.
"},{"location":"pydoc/#to_int","title":"to_int","text":"def to_int(value: str, default: int) -> int\n
Convert a string to an integer. If the string is empty, return the default
"},{"location":"pydoc/#is_rangelike","title":"is_rangelike","text":"def is_rangelike(obj: Any) -> bool\n
Check if a value is a range-like object
"},{"location":"pydoc/#is_intlike","title":"is_intlike","text":"def is_intlike(value: Any) -> bool\n
Can this object be converted to an integer?
"},{"location":"pydoc/#is_iterable","title":"is_iterable","text":"def is_iterable(value: Any) -> bool\n
Is this object iterable?
"},{"location":"pydoc/#as_type","title":"as_type","text":"def as_type(cls: Type[T], value: Any) -> T\n
Convert a value to a type, if necessary.
Saves a bit of construction time if the value is already the right type.
"},{"location":"pydoc/#try_hash","title":"try_hash","text":"def try_hash(obj: Any) -> int | None\n
Try to hash an object. If it can't be hashed, return None
"},{"location":"pydoc/#arrangessegment","title":"arranges.segment","text":""},{"location":"pydoc/#start_stop_to_str","title":"start_stop_to_str","text":"def start_stop_to_str(start: range_idx, stop: range_idx) -> str\n
Returns a string representation of a range from start to stop.
"},{"location":"pydoc/#segment-objects","title":"Segment Objects","text":"class Segment(str)\n
A single range segment that's a string and can be hashed.
"},{"location":"pydoc/#__init___1","title":"__init__","text":"def __init__(start: range_idx, stop: range_idx = None)\n
Construct a new string with the canonical form of the range.
"},{"location":"pydoc/#__new___1","title":"__new__","text":"def __new__(cls, start: range_idx, stop: range_idx = None) -> str\n
Construct a new string with the canonical form of the range.
"},{"location":"pydoc/#__or___1","title":"__or__","text":"def __or__(other: \"Segment\") -> \"Segment\"\n
Return the union of this range and the other range
"},{"location":"pydoc/#__iter___1","title":"__iter__","text":"def __iter__()\n
Iterate over the values in this range
"},{"location":"pydoc/#__len__","title":"__len__","text":"def __len__() -> int\n
Get the length of this range
"},{"location":"pydoc/#__bool__","title":"__bool__","text":"def __bool__() -> bool\n
True if this range has a length
"},{"location":"pydoc/#last","title":"last","text":"@property\ndef last() -> int\n
Gets the last value in this range. Will return inf if the range has no end, and -1 if it has no contents,
"},{"location":"pydoc/#from_str_1","title":"from_str","text":"@classmethod\n@lru_cache\ndef from_str(cls, value: str) -> \"Segment\"\n
Construct from a string.
"},{"location":"pydoc/#sort_key","title":"sort_key","text":"@staticmethod\ndef sort_key(value: \"Segment\") -> tuple[int, int]\n
Sort key function for sorting ranges
"},{"location":"pydoc/#isdisjoint","title":"isdisjoint","text":"def isdisjoint(other: Any) -> bool\n
Return True if this range is disjoint from the other range
"},{"location":"pydoc/#__eq___2","title":"__eq__","text":"def __eq__(other: Any) -> bool\n
Compare two segments
"},{"location":"pydoc/#isconnected","title":"isconnected","text":"def isconnected(other: \"Segment\") -> bool\n
True if this range is adjacent to or overlaps the other range, and so they can be joined together.
"},{"location":"pydoc/#isadjacent","title":"isadjacent","text":"def isadjacent(other: \"Segment\") -> bool\n
True if this range is adjacent to the other range
"},{"location":"pydoc/#intersects_1","title":"intersects","text":"def intersects(other: \"Segment\") -> bool\n
True if this range intersects the other range.
"},{"location":"pydoc/#__contains___1","title":"__contains__","text":"def __contains__(other: Any) -> bool\n
Membership test. Supports integers, strings, ranges and iterables.
"}]} \ No newline at end of file +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"Arranges","text":""},{"location":"#range-string-fields-for-pydantic-basemodels","title":"Range string fields for Pydantic BaseModels","text":"I needed a way to parse batches of byte, row and line and other object ranges in my merge-files
app, in a way that I can just drop it in as a string field type. The reason for this is so the machine-generated command line help is flat and readable by humans.
It it kinda grew into a monster so I've split it out into this separate package. The main feature is a pair of classes that can represent ranges:
Segment
is a class that can be treated like a set
and its constructor is compatible with range
and slice
. It is derived from str
so is easy to compare and serializes nicely. It is immutable, hashable and has a stable string representation.Ranges
is an ordered tuple
of Segment
s. It is also immutable and derived from str
like the above. It can be constructed from comma-separated Python-style slice notation strings (e.g. \"1:10, 20:\"
, \"0x00:0xff
and \":\"
), integers, slice
s, range
s, integers and (nested) iterables of the above.inf
singleton that is a float
with a value of math.inf
but has an __index__
that returns sys.maxsize
and compares equal to infinity and maxsize
, and its string representation is \"inf\"
.The range class is designed to be used as fields in Pydantic BaseModel
s, but can be used anywhere you need a range. They are not designed with speed in mind, and comparisons usually use the canonical string form by converting other things into Ranges
objects. Their preferred pronoun is they/them.
I made them to select lines or bytes in a stream of data, so they:
int
s;range
and slice
, but step
is fixed to 1
. If you pass something with a step into its constructor it'll be converted to a list of int
s (range(0, 10, 2)
becomes \"0,2,4,6,8\"
);Ranges
object;pip install arranges
if you want to use them. You'll need Python 3.10 or above.
To add features etc you'll ideally need git
, make
, bash
and something with a debugger. Config for Visual Studio Code is included.
Clone the repo and make dev
to make the venv, install dependencies, then code .
to open the project up in the venv with tests and debugging and all that jazz.
Type make help
to see the other options, or run the one-liner scripts in the ./build
dir if you want to run steps without all that fancy caching nonsense.
Free as in freedom from legalese; the WTFPL with a warranty clause.
Political note: I don't want to live in a world where lawyers tell me how to speak. If you don't trust me enough to use the WTFPL then you shouldn't be running my code in the first place.
"},{"location":"LICENSE/","title":"License","text":"Licensed under the WTFPL with one additional clause:
Do whatever the fuck you want, just don't blame me.
"},{"location":"construction/","title":"Constructing Range and Ranges","text":"You can create them from:
range
or slice
from arranges import Ranges\n# Create ranges from a string. Like a slice but without the square brackets.\nthe_full_range = Ranges(\":\") # an endless sequence starting at 0\nthe_empty_range = Ranges(\"\") # start, stop and len() are all 0\nfrom_0_to_99 = Ranges(\"0:100\")\nskip_10_items = Ranges(\"10:\") # boundless, goes to infinity\naccess_by_index = Ranges(\"0\") # just the first item\n# Decimal, hexadecimal, octal and binary are supported. Note that octal uses\n# Python's 0o prefix, not 0 like in C.\ngbc_palette = Ranges(\"0xff68:0xff88\")\nskip_bmp_header = Ranges(\"0o66:end\") # skip \"54\", \"0x36\" or \"0b1100110\" bytes\n# \"end\" and \"inf\" are the same. You can also use \"start\" instead of 0.\nthe_full_range_again = Ranges(\"start:inf\")\n# whitespace is ignored during construction\nfirst_kilobyte = Ranges(\"start : 0x400\")\n# They are simplified when converted to str, which they can be compared with.\nassert first_kilobyte == \":1024\"\n# Comparisons work in both directions\nassert \"000001:000002\" == Ranges(\"1\")\n# and the hash is the same as the string\nw = Ranges(\"6 : 8\")\nassert hash(w) == hash(\"6:8\")\n# so you can use them as dict keys\nd = {\"6:8\": \"width\"}\nd[w] = \"GIF file width\"\nassert d == {\"6:8\": \"GIF file width\"}\n
"},{"location":"construction/#ranges-from-a-string","title":"Ranges from a string","text":"from arranges import Ranges\n# You can put gaps in your range using commas.\npages_2_to_5_and_20 = Ranges(\"2:6, 20\")\n# The ranges are sorted and combined as they are added\noverlapping = Ranges(\"start:10, 14, 1:3\")\nassert overlapping == \":10,14\"\n# But you can't hash Ranges because they're mutable\nimport pytest\nwith pytest.raises(ValueError):\na = {overlapping: \"whatever\"}\n
"},{"location":"construction/#ranges-from-range-like-objects","title":"Ranges from range-like objects","text":"If it has a start, stop and step attribute then it quacks like a range so you can pass it in as the value. If the step
value is something other than None or 1 then it'll raise ValueError
.
from arranges import Ranges\nfrom_slice = Ranges(slice(10, 20))\nfrom_range = Ranges(range(10, 20))\nfrom_other = Ranges(Ranges(\"10:20\"))\nassert from_slice == from_range == from_other == \"10:20\"\n
"},{"location":"construction/#ranges-from-sequences","title":"Ranges from sequences","text":"from arranges import Ranges\nl = list(range(100))\nt = tuple(range(100))\ni = (i for i in range(100))\nassert Ranges(l) == Ranges(t) == Ranges(i) == \":100\"\njumble = Ranges([0, [2, 4], [\"1:3\"], 3, 4, range(10, 20)])\nassert jumble == \"5,10:20\"\n
"},{"location":"construction/#explicitly-creating-ranges","title":"Explicitly creating Ranges","text":"You can create them in the same way as range
or slice
objects which have a slightly different syntax to slice notation.
from arranges import Ranges, inf\nassert Ranges(10, inf) == \"10:\"\n# Ranges(\"10\") is not the same as Ranges(10)\nassert Ranges(10) == \":10\"\nassert Ranges(\"10\") == 10 == Ranges(10, 11)\n
"},{"location":"iteration/","title":"Iteration","text":"Ranges
objects are sequences of integers that you can iterate over.
from arranges import Ranges\nassert list(Ranges(\"start:3, 10\")) == [0, 1, 2, 10]\n
"},{"location":"iteration/#cardinality-of-the-address-range","title":"Cardinality of the address range","text":"The length of a range is the number of elements it contains. But because ranges can be boundless, we have a special inf
value that represents an infinite int as Python doesn't have or support one.
This value is math.inf
, but when returned from len
it becomes sys.maxsize
as it only supports ints that are this size or smaller. When comparing inf
to sys.maxsize
it'll return True
, but this doesn't work in all directions:
import math\nimport sys\nfrom arranges import Ranges, inf\nassert len(Ranges(\"1,2,3,4,10:20\")) == 14\nfull = Ranges(\":\")\nassert len(full) == sys.maxsize\nassert len(full) == inf\nassert sys.maxsize == inf\nassert full.stop == inf\nassert inf - 100 == sys.maxsize\n# Careful though, this does not hold\nassert len(full) != math.inf\n# because it's an int\nassert type(len(full)) is int\n
"},{"location":"iteration/#truthyness","title":"Truthyness","text":"Empty ranges are, of course, Falsey.
from arranges import Ranges\nassert Ranges(\":\")\nassert Range(10)\nassert not Ranges(\"\")\n
"},{"location":"models/","title":"Use in Pydantic models","text":"You'll need Pydantic >=2.3:
from pydantic import BaseModel, Field\nclass BinaryPatch(BaseModel):\n\"\"\"\n \"\"\"\nregions: Ranges\nmodel = BinaryPatch.model_validate_json(\"\"\"\n{\n \"regions\": \"0:10, 20:30, 15:, 0x0b\"\n}\n\"\"\")\nassert model.regions == \":10,15:\"\n
"},{"location":"operators/","title":"Set operations","text":"They try to be set-like, supporting most of the operations that the set
object does.
Actually, dunno how much of this I'm missing. Raise a ticket if there are gaps.
Symbol Name CodeA \u22c2 B
intersection A & B
A \u22c3 B
union A | B
A \u2286 B
subset A <= B
A \u2282 B
proper subset A < B
A \u2287 B
superset A >= B
A \u2283 B
proper superset A > B
A = B
equality A == B
A'
complement ~A
A \\ B
relative complement A - B
A \u2206 B
symmetric difference (A | B) - (A & B)
a \u2208 A
membership a in A
|A|
cardinality len(A)
\u00d8
empty set Ranges(\"\")
\u21150
natural numbers Ranges(\":\")
"},{"location":"pydoc/","title":"Pydoc","text":""},{"location":"pydoc/#arranges","title":"arranges","text":""},{"location":"pydoc/#arrangesranges","title":"arranges.ranges","text":""},{"location":"pydoc/#ranges-objects","title":"Ranges Objects","text":"class Ranges(str)\n
A range set that can be hashed and converted to a string.
"},{"location":"pydoc/#__init__","title":"__init__","text":"def __init__(value: Any, stop: range_idx | None = None)\n
Construct a new string with the canonical form of the range.
"},{"location":"pydoc/#__new__","title":"__new__","text":"def __new__(cls, value: Any, stop: range_idx | None = None) -> str\n
Construct a new string with the canonical form of the range.
This becomes \"self\" in init, so we're always a string
"},{"location":"pydoc/#construct_str","title":"construct_str","text":"@classmethod\ndef construct_str(cls, value, stop) -> str\n
Create a string representation of a range series
"},{"location":"pydoc/#from_str","title":"from_str","text":"@classmethod\ndef from_str(cls, value: str) -> tuple[Segment]\n
Construct from a string.
"},{"location":"pydoc/#iterable_to_str","title":"iterable_to_str","text":"@classmethod\ndef iterable_to_str(cls, iterable: Iterable) -> str\n
Convert an iterable of ranges to a string
"},{"location":"pydoc/#from_hashable_iterable","title":"from_hashable_iterable","text":"@classmethod\n@lru_cache\ndef from_hashable_iterable(cls, value: tuple[Any]) -> tuple[Segment]\n
Cache the result of from_iterable
"},{"location":"pydoc/#from_iterable","title":"from_iterable","text":"@classmethod\ndef from_iterable(cls, iterable: Iterable) -> tuple[Segment]\n
Sort and merge a list of ranges.
"},{"location":"pydoc/#__eq__","title":"__eq__","text":"def __eq__(other: Any) -> bool\n
Compare the two lists based on their string representations
"},{"location":"pydoc/#__contains__","title":"__contains__","text":"def __contains__(other: Any) -> bool\n
Are all of the other ranges in our ranges?
"},{"location":"pydoc/#__iter__","title":"__iter__","text":"def __iter__()\n
Iterate over the values in our ranges.
Note that this could be boundless.
"},{"location":"pydoc/#intersects","title":"intersects","text":"def intersects(other: Any) -> bool\n
True if this range overlaps with the other range
"},{"location":"pydoc/#union","title":"union","text":"def union(other) -> \"Ranges\"\n
Return the union of this range and the other
"},{"location":"pydoc/#__or__","title":"__or__","text":"def __or__(other: \"Ranges\") -> \"Ranges\"\n
Return the union of this range and the other
"},{"location":"pydoc/#__and__","title":"__and__","text":"def __and__(other: \"Ranges\") -> \"Ranges\"\n
Return the intersection of this range and the other
"},{"location":"pydoc/#__invert__","title":"__invert__","text":"def __invert__()\n
The inverse of this range
"},{"location":"pydoc/#validate","title":"validate","text":"@classmethod\ndef validate(cls, value: Any) -> \"Ranges\"\n
Validate a value and convert it to a Range
"},{"location":"pydoc/#__get_pydantic_core_schema__","title":"__get_pydantic_core_schema__","text":"@classmethod\ndef __get_pydantic_core_schema__(cls, source_type: Any,\nhandler: GetCoreSchemaHandler) -> CoreSchema\n
For automatic validation in pydantic
"},{"location":"pydoc/#arrangesutils","title":"arranges.utils","text":"Some quacky type helpers so we don't have to use isinstance everywhere
"},{"location":"pydoc/#_boundless-objects","title":"_Boundless Objects","text":"class _Boundless(float)\n
A class that represents a boundless end of a range
"},{"location":"pydoc/#huge","title":"huge","text":"An enormous number that's used as a stand-in for infinity
"},{"location":"pydoc/#__eq___1","title":"__eq__","text":"def __eq__(other) -> bool\n
Is this boundless?
"},{"location":"pydoc/#__index__","title":"__index__","text":"def __index__() -> int\n
When used as an index, return a huge integer rather than infinity.
This is necessary because CPython doesn't allow lengths larger than sys.maxsize, and Python has no way to represent infinity as an integer.
"},{"location":"pydoc/#inf","title":"inf","text":"A boundless end of a range. When used as a stop value it's infinite, but when used as a length it's the largest index integer possible in cpython.
"},{"location":"pydoc/#to_int","title":"to_int","text":"def to_int(value: str, default: int) -> int\n
Convert a string to an integer. If the string is empty, return the default
"},{"location":"pydoc/#is_rangelike","title":"is_rangelike","text":"def is_rangelike(obj: Any) -> bool\n
Check if a value is a range-like object
"},{"location":"pydoc/#is_intlike","title":"is_intlike","text":"def is_intlike(value: Any) -> bool\n
Can this object be converted to an integer?
"},{"location":"pydoc/#is_iterable","title":"is_iterable","text":"def is_iterable(value: Any) -> bool\n
Is this object iterable?
"},{"location":"pydoc/#as_type","title":"as_type","text":"def as_type(cls: Type[T], value: Any) -> T\n
Convert a value to a type, if necessary.
Saves a bit of construction time if the value is already the right type.
"},{"location":"pydoc/#try_hash","title":"try_hash","text":"def try_hash(obj: Any) -> int | None\n
Try to hash an object. If it can't be hashed, return None
"},{"location":"pydoc/#arrangessegment","title":"arranges.segment","text":""},{"location":"pydoc/#start_stop_to_str","title":"start_stop_to_str","text":"def start_stop_to_str(start: range_idx, stop: range_idx) -> str\n
Returns a string representation of a range from start to stop.
"},{"location":"pydoc/#segment-objects","title":"Segment Objects","text":"class Segment(str)\n
A single range segment that's a string and can be hashed.
"},{"location":"pydoc/#__init___1","title":"__init__","text":"def __init__(start: range_idx, stop: range_idx = None)\n
Construct a new string with the canonical form of the range.
"},{"location":"pydoc/#__new___1","title":"__new__","text":"def __new__(cls, start: range_idx, stop: range_idx = None) -> str\n
Construct a new string with the canonical form of the range.
"},{"location":"pydoc/#__or___1","title":"__or__","text":"def __or__(other: \"Segment\") -> \"Segment\"\n
Return the union of this range and the other range
"},{"location":"pydoc/#__iter___1","title":"__iter__","text":"def __iter__()\n
Iterate over the values in this range
"},{"location":"pydoc/#__len__","title":"__len__","text":"def __len__() -> int\n
Get the length of this range
"},{"location":"pydoc/#__bool__","title":"__bool__","text":"def __bool__() -> bool\n
True if this range has a length
"},{"location":"pydoc/#last","title":"last","text":"@property\ndef last() -> int\n
Gets the last value in this range. Will return inf if the range has no end, and -1 if it has no contents,
"},{"location":"pydoc/#from_str_1","title":"from_str","text":"@classmethod\n@lru_cache\ndef from_str(cls, value: str) -> \"Segment\"\n
Construct from a string.
"},{"location":"pydoc/#sort_key","title":"sort_key","text":"@staticmethod\ndef sort_key(value: \"Segment\") -> tuple[int, int]\n
Sort key function for sorting ranges
"},{"location":"pydoc/#isdisjoint","title":"isdisjoint","text":"def isdisjoint(other: Any) -> bool\n
Return True if this range is disjoint from the other range
"},{"location":"pydoc/#__eq___2","title":"__eq__","text":"def __eq__(other: Any) -> bool\n
Compare two segments
"},{"location":"pydoc/#isconnected","title":"isconnected","text":"def isconnected(other: \"Segment\") -> bool\n
True if this range is adjacent to or overlaps the other range, and so they can be joined together.
"},{"location":"pydoc/#isadjacent","title":"isadjacent","text":"def isadjacent(other: \"Segment\") -> bool\n
True if this range is adjacent to the other range
"},{"location":"pydoc/#intersects_1","title":"intersects","text":"def intersects(other: \"Segment\") -> bool\n
True if this range intersects the other range.
"},{"location":"pydoc/#__contains___1","title":"__contains__","text":"def __contains__(other: Any) -> bool\n
Membership test. Supports integers, strings, ranges and iterables.
"}]} \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 416c5aa..dd28624 100644 Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ