Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 2 additions & 103 deletions pyproject.toml

Large diffs are not rendered by default.

41 changes: 24 additions & 17 deletions src/bitfield/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from collections.abc import Mapping, Sequence
from typing import Any, TypeVar, cast
from collections.abc import Iterator, Mapping, Sequence
from typing import Any, SupportsIndex, SupportsInt, TypeVar, cast

from django.db.models import Model
from django.db.models.fields import BigIntegerField
Expand All @@ -13,42 +13,42 @@


class BitFieldFlags:
def __init__(self, flags):
def __init__(self, flags: Sequence[str]) -> None:
if len(flags) > MAX_FLAG_COUNT:
raise ValueError("Too many flags")
self._flags = flags

def __repr__(self) -> str:
return repr(self._flags)

def __iter__(self):
def __iter__(self) -> Iterator[str]:
yield from self._flags

def __getattr__(self, key):
def __getattr__(self, key: str) -> Bit:
if key not in self._flags:
raise AttributeError
return Bit(self._flags.index(key))

__getitem__ = __getattr__

def iteritems(self):
def iteritems(self) -> Iterator[tuple[str, Bit]]:
for flag in self._flags:
yield flag, Bit(self._flags.index(flag))

def iterkeys(self):
def iterkeys(self) -> Iterator[str]:
yield from self._flags

def itervalues(self):
def itervalues(self) -> Iterator[Bit]:
for flag in self._flags:
yield Bit(self._flags.index(flag))

def items(self):
def items(self) -> list[tuple[str, Bit]]:
return list(self.iteritems())

def keys(self):
def keys(self) -> list[str]:
return list(self.iterkeys())

def values(self):
def values(self) -> list[Bit]:
return list(self.itervalues())


Expand All @@ -61,10 +61,10 @@ class BitFieldCreator:
available (usually during deploys).
"""

def __init__(self, field):
def __init__(self, field: "BitField") -> None:
self.field = field

def __set__(self, obj, value):
def __set__(self, obj: Model, value: object) -> None:
obj.__dict__[self.field.name] = self.field.to_python(value)

def __get__(self, obj, type=None):
Expand All @@ -78,6 +78,9 @@ def __get__(self, obj, type=None):


class BitField(BigIntegerField):
flags: list[str]
labels: list[str]

def contribute_to_class(self, cls: type[Model], name: str, private_only: bool = False) -> None:
super().contribute_to_class(cls, name, private_only=private_only)
setattr(cls, self.name, BitFieldCreator(self))
Expand All @@ -98,6 +101,7 @@ def __init__(self, flags, default=None, *args, **kwargs):

self._arg_flags = flags
flags = list(flags)

labels = []
for num, flag in enumerate(flags):
if isinstance(flag, (tuple, list)):
Expand All @@ -117,11 +121,14 @@ def __init__(self, flags, default=None, *args, **kwargs):
self.flags = flags
self.labels = labels

def pre_save(self, instance, add):
def pre_save(self, instance: Model, add: bool) -> object:
value = getattr(instance, self.attname)
return value

def get_prep_value(self, value):
def get_prep_value(
self,
value: SupportsInt | SupportsIndex | str | bytes | bytearray | Bit | BitHandler | None,
) -> int | None:
if value is None:
return None
if isinstance(value, (BitHandler, Bit)):
Expand Down Expand Up @@ -153,7 +160,7 @@ def deconstruct(self):
return name, path, args, kwargs


def flags_from_annotations(annotations: Mapping[str, type]) -> Sequence[str]:
def flags_from_annotations(annotations: Mapping[str, object]) -> list[str]:
flags = []
for attr, ty in annotations.items():
assert ty in ("bool", bool), f"bitfields can only hold bools, {attr} is {ty!r}"
Expand Down Expand Up @@ -203,7 +210,7 @@ class TypedClassBitField(metaclass=TypedBitfieldMeta):
T = TypeVar("T")


def typed_dict_bitfield(definition: type[T], default=None, null=False) -> T:
def typed_dict_bitfield(definition: type[T], default: object = None, null: bool = False) -> T:
"""
A wrapper around BitField that allows you to access its fields as
dictionary keys attributes in a type-safe way.
Expand Down
2 changes: 1 addition & 1 deletion src/bitfield/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def get_db_prep_lookup(self, value, connection, prepared=False):
raise NotImplementedError("get_db_prep_lookup not supported for Bit, BitHandler")
return super().get_db_prep_lookup(value, connection)

def get_prep_lookup(self):
def get_prep_lookup(self) -> object:
if isinstance(self.rhs, (Bit,)):
raise NotImplementedError("get_db_prep_lookup not supported for Bit")
return self.rhs
105 changes: 70 additions & 35 deletions src/bitfield/types.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,43 @@
def cmp(a, b):
from __future__ import annotations

from collections.abc import Iterator, Sequence
from typing import Protocol, SupportsIndex, SupportsInt, TypeVar

_IntConvertible = SupportsInt | SupportsIndex | str | bytes | bytearray | float


def cmp(a: int, b: int) -> int:
return (a > b) - (a < b)


_TPrepared = TypeVar("_TPrepared", covariant=True)


class _BitEvaluator(Protocol[_TPrepared]):
def prepare_node(self, node: Bit, query: object, allow_joins: bool) -> _TPrepared: ...


class Bit:
def __init__(self, number, is_set=True):
number: int
is_set: bool
mask: int
children: list[Bit]

def __init__(self, number: int, is_set: object = True) -> None:
self.number = number
self.is_set = bool(is_set)
self.mask = 2 ** int(number)
self.children = []
self.children: list[Bit] = []
if not self.is_set:
self.mask = ~self.mask

def __repr__(self) -> str:
return "<%s: number=%d, is_set=%s>" % (self.__class__.__name__, self.number, self.is_set)

def __int__(self):
def __int__(self) -> int:
return self.mask

def __bool__(self):
def __bool__(self) -> bool:
return self.is_set

def __eq__(self, value):
Expand All @@ -29,13 +49,13 @@ def __eq__(self, value):
return value == self.mask
return value == self.is_set

def __ne__(self, value):
def __ne__(self, value: object) -> bool:
return not self == value

def __coerce__(self, value):
def __coerce__(self, value: object) -> tuple[bool, bool]:
return (self.is_set, bool(value))

def __invert__(self):
def __invert__(self) -> Bit:
return self.__class__(self.number, not self.is_set)

def __and__(self, value):
Expand Down Expand Up @@ -88,13 +108,17 @@ def __rxor__(self, value):
value = value.mask
return self.mask ^ value

def __sentry__(self):
def __sentry__(self) -> str:
return repr(self)

def evaluate(self, evaluator, qn, connection):
def evaluate(
self, evaluator: object, qn: object, connection: object
) -> tuple[int, list[object]]:
return self.mask, []

def prepare(self, evaluator, query, allow_joins):
def prepare(
self, evaluator: _BitEvaluator[_TPrepared], query: object, allow_joins: bool
) -> _TPrepared:
return evaluator.prepare_node(self, query, allow_joins)


Expand All @@ -103,7 +127,16 @@ class BitHandler:
Represents an array of bits, each as a ``Bit`` object.
"""

def __init__(self, value, keys, labels=None):
_value: int
_keys: Sequence[str]
_labels: Sequence[object]

def __init__(
self,
value: _IntConvertible | None,
keys: Sequence[str],
labels: Sequence[object] | None = None,
) -> None:
# TODO: change to bitarray?
if value:
self._value = int(value)
Expand All @@ -112,7 +145,7 @@ def __init__(self, value, keys, labels=None):
self._keys = keys
self._labels = labels is not None and labels or keys

def __eq__(self, other):
def __eq__(self, other: object) -> bool:
if not isinstance(other, BitHandler):
return False
return self._value == other._value
Expand Down Expand Up @@ -141,34 +174,34 @@ def __repr__(self) -> str:
def __str__(self) -> str:
return str(self._value)

def __int__(self):
def __int__(self) -> int:
return self._value

def __bool__(self):
def __bool__(self) -> bool:
return bool(self._value)

def __and__(self, value):
def __and__(self, value: _IntConvertible) -> BitHandler:
return BitHandler(self._value & int(value), self._keys)

def __or__(self, value):
def __or__(self, value: _IntConvertible) -> BitHandler:
return BitHandler(self._value | int(value), self._keys)

def __add__(self, value):
def __add__(self, value: _IntConvertible) -> BitHandler:
return BitHandler(self._value + int(value), self._keys)

def __sub__(self, value):
def __sub__(self, value: _IntConvertible) -> BitHandler:
return BitHandler(self._value - int(value), self._keys)

def __lshift__(self, value):
def __lshift__(self, value: _IntConvertible) -> BitHandler:
return BitHandler(self._value << int(value), self._keys)

def __rshift__(self, value):
def __rshift__(self, value: _IntConvertible) -> BitHandler:
return BitHandler(self._value >> int(value), self._keys)

def __xor__(self, value):
def __xor__(self, value: _IntConvertible) -> BitHandler:
return BitHandler(self._value ^ int(value), self._keys)

def __contains__(self, key):
def __contains__(self, key: str) -> bool:
bit_number = self._keys.index(key)
return bool(self.get_bit(bit_number))

Expand All @@ -181,7 +214,7 @@ def __getattr__(self, key):

__getitem__ = __getattr__

def __setattr__(self, key, value) -> None:
def __setattr__(self, key: str, value: object) -> None:
if key.startswith("_"):
return object.__setattr__(self, key, value)
if key not in self._keys:
Expand All @@ -190,46 +223,48 @@ def __setattr__(self, key, value) -> None:

__setitem__ = __setattr__

def __iter__(self):
def __iter__(self) -> Iterator[tuple[str, bool]]:
return self.iteritems()

def __sentry__(self):
def __sentry__(self) -> str:
return repr(self)

def _get_mask(self):
def _get_mask(self) -> int:
return self._value

mask = property(_get_mask)

def evaluate(self, evaluator, qn, connection):
def evaluate(
self, evaluator: object, qn: object, connection: object
) -> tuple[int, list[object]]:
return self.mask, []

def get_bit(self, bit_number):
def get_bit(self, bit_number: int) -> Bit:
mask = 2 ** int(bit_number)
return Bit(bit_number, self._value & mask != 0)

def set_bit(self, bit_number, true_or_false):
def set_bit(self, bit_number: int, true_or_false: object) -> Bit:
mask = 2 ** int(bit_number)
if true_or_false:
self._value |= mask
else:
self._value &= ~mask
return Bit(bit_number, self._value & mask != 0)

def keys(self):
def keys(self) -> Sequence[str]:
return self._keys

def iterkeys(self):
def iterkeys(self) -> Iterator[str]:
return iter(self._keys)

def items(self):
def items(self) -> list[tuple[str, bool]]:
return list(self.iteritems())

def iteritems(self):
def iteritems(self) -> Iterator[tuple[str, bool]]:
for k in self._keys:
yield (k, getattr(self, k).is_set)

def get_label(self, flag):
def get_label(self, flag: str | Bit | int) -> object:
if isinstance(flag, str):
flag = self._keys.index(flag)
if isinstance(flag, Bit):
Expand Down
Loading
Loading