Skip to content

Commit

Permalink
feat(selectors): Add All and None_ to complete selector group (#46)
Browse files Browse the repository at this point in the history
* feat(selectors): Add All and None_ selectors

* Import All and None_ from __init__

* Update CHANGELOG [skip ci]

* Re-flow CHANGELOG
  • Loading branch information
coroa authored Dec 12, 2023
1 parent 82b57d9 commit c2bdaef
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ v0.4.0 (2023-12-12)
------------------------------------------------------------
* **BREAKING** :mod:`accessors` is imported implicitly. User code does not need to
import it any longer :pull:`45`
* Add :attr:`~selectors.All` and :attr:`~selectors.None_` completing selector group
:pull:`46`
* Fix type hints of function :func:`~core.aggregatelevel` :pull:`44`
* Switch from black to ruff for formatting and update pre-commit versions :pull:`43`

Expand Down
2 changes: 1 addition & 1 deletion src/pandas_indexing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
to_tidy,
uniquelevel,
)
from .selectors import isin, ismatch
from .selectors import All, None_, isin, ismatch
from .units import convert_unit, dequantify, quantify, set_openscm_registry_as_default


Expand Down
46 changes: 46 additions & 0 deletions src/pandas_indexing/selectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ def __invert__(self):
return Not(self)

def __and__(self, other):
if isinstance(other, Special):
return other & self
return And(self, maybe_const(other))

def __or__(self, other):
if isinstance(other, Special):
return other | self
return Or(self, maybe_const(other))

__rand__ = __and__
Expand Down Expand Up @@ -69,6 +73,48 @@ def __call__(self, df):
return ~self.a.__call__(df)


class Special(Selector):
pass


@define
class AllSelector(Special):
def __invert__(self):
return NoneSelector()

def __and__(self, other):
return other

def __or__(self, other):
return self

def __call__(self, df):
return Series(True, df.index)


@define
class NoneSelector(Special):
def __invert__(self):
return AllSelector()

def __and__(self, other):
return self

def __or__(self, other):
return other

__rand__ = __and__
__ror__ = __or__

def __call__(self, df):
return Series(False, df.index)


# Singletons for easy access
All = AllSelector()
None_ = NoneSelector()


@define
class Isin(Selector):
filters: Mapping[str, Any]
Expand Down
31 changes: 30 additions & 1 deletion tests/test_selectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,16 @@
from pandas.testing import assert_frame_equal, assert_series_equal

from pandas_indexing import isin, ismatch
from pandas_indexing.selectors import And, Const, Isin, Ismatch, Not, Or
from pandas_indexing.selectors import (
AllSelector,
And,
Const,
Isin,
Ismatch,
NoneSelector,
Not,
Or,
)


def test_isin_mseries(mseries: Series):
Expand Down Expand Up @@ -57,3 +66,23 @@ def test_ismatch_explicitly_given(sdf):
assert_series_equal(
ismatch(sdf.columns, ["o*"]), Series([True, False], sdf.columns)
)


def test_all_none(sdf):
assert isinstance(~AllSelector(), NoneSelector)
assert isinstance(~NoneSelector(), AllSelector)

sel = isin(str="bar")

assert isinstance(sel | AllSelector(), AllSelector)
assert isinstance(AllSelector() | sel, AllSelector)
assert sel & AllSelector() == sel
assert AllSelector() & sel == sel

assert sel | NoneSelector() == sel
assert NoneSelector() | sel == sel
assert isinstance(sel & NoneSelector(), NoneSelector)
assert isinstance(NoneSelector() & sel, NoneSelector)

assert_frame_equal(sdf.loc[AllSelector()], sdf)
assert_frame_equal(sdf.loc[NoneSelector()], sdf.iloc[[]])

0 comments on commit c2bdaef

Please sign in to comment.