# nr.types

[![CircleCI](https://circleci.com/gh/NiklasRosenstein/python-nr.types.svg?style=svg)](https://circleci.com/gh/NiklasRosenstein/python-nr.types)

The `nr.types` package provides a number os useful data types for day-to-day
Python programming. It is compatible with Python 2.7 and modern versions of
Python 3.

### Installation

    pip install nr.types

### Run Tests

    pip install -e .[test]
    pytest --cov=./src/nr

### API

#### `nr.types.NotSet`

The `NotSet` singleton is useful in cases where `None` is an acceptable value
for a parameter so there needs to be an additional state that defines the
parameter as "not set".

#### `nr.types.abc`

An alias for `collections.abc` or `collections` (the `six` module does not
provide a move for these modules).

#### `nr.types.functools`

Tools to help with Python function internals such as closures, code
and function objects.

<details doctest name='functools.example'><summary>Example:</summary>

```python
import nr.types.functools as ft
def test(value):
  def x():
    return value
  return x
x = test(42)
assert x() == 42
y = ft.copy_function(x, closure={'value': 99})
assert y() == 99
```
</details>

#### `nr.types.generic`

Allows you to implement generic types, ie. classes with parameters.

<details doctest name='generic.example'><summary>Example:</summary>

```python
from nr.types import generic
class HashDict(generic.Generic['key_hash']):
  def __init__(self):
    generic.assert_initialized(self)
    self.data = {}
  def __getitem__(self, key):
    return self.data[self.key_hash(key)]
  def __setitem__(self, key, value):
    self.data[self.key_hash(key)] = value
UnsafeHashDict = HashDict[hash]
```
</details>

#### `nr.types.interface`

Similar to `zope.interface`, but Python 3 compatible and less magical.

<details doctest name="interface.example"><summary>Example:</summary>

```python
from nr.types.interface import Interface, Implementation, implements, attr

class IFoo(Interface):
  """ The foo interface. """

  x = attr("""Some attribute.""")

  def bar(self, q, r=None):
    """ The bar function. """

assert set(IFoo) == set(['x', 'bar'])
assert not hasattr(IFoo, 'x')
assert not hasattr(IFoo, 'bar')
assert IFoo['x'].name == 'x'
assert IFoo['bar'].name == 'bar'

@implements(IFoo)
class Foo(object):

  def __init__(self, x=None):
    self.x = x

  def bar(self, q, r=None):
    return q, r, self.x

assert issubclass(Foo, Implementation)
assert IFoo.implemented_by(Foo)
assert IFoo.provided_by(Foo())
assert list(IFoo.implementations()) == [Foo]
assert Foo(42).x == 42
```
</details>


#### `nr.types.local`

Vendors the `LocalProxy` class fro,m `pallets/werkzeug@0.15.2`.

<details doctest name="local.proxy"><summary>Example (LocalProxy):</summary>

```python
from nr.types.local import LocalProxy

count = 0

def test():
  global count
  count += 1
  return count

proxy = LocalProxy(test)
assert proxy == 1
assert count == 1
assert proxy == 2
assert count == 2
assert proxy == 3
assert count == 3
assert proxy + 10 == 14
```
</details>


#### `nr.types.maps`

Provides the following mapping (and mapping-related) implementations:

* `OrderedDict`
* `ObjectAsDict`
* `ObjectFromDict`
* `ChainDict` (use `maps.chain()`)
* `HashDict[hash_func]`
* `ValueIterableDict`

#### `nr.types.meta`

Provides useful metaclasses, such as `InlineMetaclass`.

<details doctest name='meta.example'><summary>Example:</summary>

```python
from nr.types.meta import InlineMetaclassBase
class MyClass(InlineMetaclassBase):
  def __metainit__(self, name, bases, attr):
    print('MyClass constructed!')
    self.value = 'foo'
assert MyClass.value == 'foo'
```
</details>

#### `nr.types.moduletools`

Provides some tools for working with modules. Currently only provides the
`make_inheritable()` function which can be used from within your module to
make the module object itself usable as a parent class.

```python
# myclass.py
class MyClass(object): pass
make_inheritable(__name__)

# test.py
import myclass
class MySubclass(myclass): pass
assert issubclass(MySubclass, myclass.MyClass)
```

#### `nr.types.record`

Similar to `namedtuple` but mutable, with support for keyword arguments,
type declarations and default values. Supports multiple forms of declaring
a record, eg. via Python 3.6+ class-level annotations, specifying a class-level
`__fields__` member or declaring attributes by creating `record.Field()`
objects.

<details doctest pymin="3.6" name="record.example"><summary>Example:</summary>

```python
import random
from nr.types import record

class Person(record.Record):
  name: str
  mail: str = None
  age: int = lambda: random.randint(10, 50)

p = Person('John Smith')
assert p.name == 'John Smith'
assert p.mail is None
assert 10 <= p.age <= 50
```
</details>

<details doctest name="record.alternatives"><summary>Alternatives:</summary>

```python
import random
from nr.types import record

class Person(record.Record):
  name = record.Field(str)
  mail = record.Field(str, None)
  age = record.Field(str, lambda: random.randint(10, 50))

class Person(record.Record):
  __fields__ = [
    ('name', str),
    ('mail', str, None),
    ('age', str, lambda: random.randint(10, 50)),
  ]

Person = record.create_record('Person', [
  ('name', str),
  record.Field.with_name('mail', str, None),
  ('age', str, lambda: random.randint(10, 50))
])

Person = record.create_record('Person', {
  'name': record.Field(str),
  'mail': record.Field(str, None),
  'age': record.Field(str, lambda: random.randint(10, 50))
})

assert list(Person.__fields__.keys()) == ['name', 'mail', 'age']
```
</details>

#### `nr.types.sets`

Currently only provides an `OrderedSet` implementation.

#### `nr.types.stream`

<details doctest name='stream.example'><summary>Example:</summary>

```python
from nr.types import stream
stream(range(10)).map(lambda x: x*2)
stream.map(range(10), lambda x: x*2)
```
</details>

#### `nr.types.sumtype`

<details doctest name='sumtype.example'><summary>Example:</summary>

```python
from nr.types import record, sumtype

class Filter(sumtype):
  Date = record.create_record('Date', 'min,max')
  Keyword = sumtype.constructor('text')
  class Duration(record.Record):
    value = record.Field(int, lambda: 3600)

f = Filter.Keyword('building')
assert isinstance(f, Filter)
assert f.is_keyword()
assert f.text == 'building'

f = Filter.Date(10, 42)
assert isinstance(f, Filter)
assert f.is_date()
assert (f.min, f.max) == (10, 42)

f = Filter.Duration()
assert isinstance(f, Filter)
assert f.is_duration()
assert f.value == 3600
```
</details>

### Acknowledgements

* This library vendors a modified version of the `werkzeug.local` module in
  version 0.15.2 as the `nr.types.local` module. Werkzeug is licensed under
  the BSD-3-Clause license and the copyright of the `nr.types.local` module
  lies with Pallets.

---

<p align="center">Copyright &copy; Niklas Rosenstein 2019</p>
