Metadata-Version: 2.1
Name: exprlens
Version: 0.0.4
Summary: Lenses from python expressions.
Home-page: https://github.com/piotrm0/exprlens
License: Artistic License
Author: Piotr Mardziel
Author-email: piotrm@gmail.com
Requires-Python: >=3.9,<4.0
Classifier: License :: OSI Approved :: Artistic License
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Dist: pydantic (>=2.0,<3.0)
Requires-Dist: typeguard (>=4.3,<5.0)
Description-Content-Type: text/markdown

# Python Expression Lenses

Python expressions to/from lenses. This library focuses on constructing lenses
from python expressions, targeting function bindings as the primary container to
get/set from/to. Such lenses can be serialized into strings which are valid python expressions (and thus can be deserialized using the python expression parser).

## Alternatives

This package has very specific goals which are not considerations in the theory of lenses. Consider these alternatives for more general lens usage:

- [lenses](https://python-lenses.readthedocs.io/en/latest/tutorial/intro.html) -
  more general and feature rich lenses implementation based on lenses in
  Haskell.
- [pylens](https://pythonhosted.org/pylens/) - simpler
- [simplelens](https://pypi.org/project/simplelens/) - unknown functionality

## Basic usage


```python
from exprlens import arg

# Lens for getting the first item in the first argument from a call:
first = arg[0]

# Getter can be accessed using the `get` method or by calling (`__call__`):
assert first.get([0, 1, 2, 3]) == first([0, 1, 2, 3]) == 0

# Setter can be accessed using the `set` method.
assert first.set([0, 1, 2, 3], val=42) == [42, 1, 2, 3]
```

## Predefined basic lenses


```python
from exprlens import arg, kwargs, args, argskwargs, ArgsKwargs, ident, lens, arguments, all

# All positional arguments:
assert args.get(42) == (42, )

# All keyword arguments:
assert kwargs.get(hello="there") == {'hello': 'there'}

# All arguments, stored as `ArgsKwargs` object:
assert argskwargs.get(42, hello="there") \
    == ArgsKwargs(args=(42,), kwargs={'hello': 'there'})

# argskwargs, arguments, and all are aliases.
assert argskwargs is arguments is all

# Only positional or keyword argument:
assert arg.get(42) == 42
assert arg.get(hello="there") == 'there'
# arg.get(1,2) # error if more than one argument is given

# lens, argm and ident are aliases.
assert arg is ident is lens
```

## Lense iterators


```python
from exprlens.valid import ValidDict, ValidMapping, ValidTuple, ValidList, ValidSequence, ValidJSON

seq = [1, 2, 3, 4]
# Lenses for each element in a sequence:
value_lenses = list(ValidSequence.elements(seq))
assert value_lenses[0].get(seq) == 1
assert value_lenses[1].get(seq) == 2
assert value_lenses[2].get(seq) == 3

# Elements can be set:
# Set the value at index 1 to 42:
assert value_lenses[1].set(seq, val=42) == [1, 42, 3, 4]

# Indices in a sequence:
seq = [1, 2, 3, 4]
index_lenses = list(ValidSequence.indices(seq))
assert index_lenses[0].get(seq) == 0
assert index_lenses[1].get(seq) == 1
assert index_lenses[2].get(seq) == 2

# Indices can be set:
# Index of element 1 to 3 thus copying the value at index 1 to index 3:
# Items at old index is spliced out.
assert index_lenses[1].set(seq, val=3) == [1, 3, 2]

# Values in a mapping:
mapping = {'a': 1, 'b': 2}
value_lenses = list(ValidMapping.values(mapping))
assert value_lenses[0].get(mapping) == 1
assert value_lenses[1].get(mapping) == 2

# Values can be set:
# Set the value of key 'a' to 42:
assert value_lenses[0].set(mapping, val=42) == {'a': 42, 'b': 2}

# Keys in a mapping:
key_lenses = list(ValidMapping.keys(mapping))
assert key_lenses[0].get(mapping) == 'a'
assert key_lenses[1].get(mapping) == 'b'

# Keys can be set:
# Key 'a' to 'c' thus renaming the key:
assert key_lenses[0].set(mapping, val='c') == {'c': 42, 'b': 2}

# JSON-like objects:
obj = {'baseint': 1, 'seqofints': [1, 2, 3], 'seqofstrs': ['a', 'b', 'c']}

# Get all lenses to JSON values including sequences and dictionaries:
for l in ValidJSON.all_values(obj):
    print(l, l.get(obj))

# Get lenses only to base JSON values (int, str, bool, None):
for l in ValidJSON.base_values(obj):
    print(l, l.get(obj))

# Get lenses only to values of the specified type, here int:
for l in ValidJSON.of_type(obj, int):
    print(l, l.get(obj))
```

## Expressions


```python
from exprlens import arg

# Lens that first grabs the first two items in the first argument and adds them:
plusfirsts = arg[0] + arg[1]

# Note that once expression lenses are constructed, `set` can no longer be used on them.
# plusfirsts.set([1, 2, 3, 4], val=42)  # Raises an exception.

assert plusfirsts([1, 2, 3, 4]) == 3

# Literals/constants: Expression lenses can involve literals/constants.
plusone = arg + 1
assert list(map(plusone, [1, 2, 3])) == [2, 3, 4]
```

## Validation


```python
from exprlens import args, kwargs

# Lenses can be validate on construction:
args[0]  # ok
# args["something"] # error

kwargs["something"]  # ok
# kwargs[0] # error

# Lenses can be validated on use:

# Lens that gets a key from the first argument, thus the first argument must be a mapping:
firstkey = arg["something"]
firstkey.get({"something": 42})  # ok

# Will fail if the first argument is not a mapping:
# firstkey.get([1,2,3]) # error
```

## Boolean expressions

Python does not allow overriding boolean operators (`and`, `or`, `not`) (see
relevant [rejected PEP](https://peps.python.org/pep-0335/)) so lenses
corresponding to expressions with boolean operators cannot be created by writing
python directly, i.e. `lens[0] and lens[1]`. Instead you can make use of
`Lens.conjunction`, `Lens.disjunction`, and `Lens.negation` static methods to
construct these. Alternatively you can use `Lens.of_string` static method
to construct it from python code, e.g. `Lens.of_string("lens[0] and
lens[1]")`.



```python
from exprlens import Lens, lens

# Conjunction:
both = Lens.of_string("lens[0] and lens[1]")

assert Lens.conjunction(lens[0], lens[1]) \
    == both

assert both([1, 2, 3, 4]) == 2

# Disjunction:
either = Lens.of_string("lens[0] or lens[1]")

assert Lens.disjunction(lens[0], lens[1]) \
    == either

assert either([1, 2, 3, 4]) == 1

# Logical negation:
not_second = Lens.of_string("not lens[1]")

assert Lens.negation(lens[1]) \
    == not_second

assert not_second([1, 2, 3, 4]) == False
```

## Serialization


```python
from exprlens import Expr, Lens, arg
from ast import parse

plusone = arg + 1
assert repr(plusone) == "(lens + 1)"

# assert plusone.pyast == parse(str(plusone), mode="eval")
assert plusone == Lens.of_string(str(plusone))
```


```python

```

