Skip to content

Commit

Permalink
Merge 6c99fb0 into 464762c
Browse files Browse the repository at this point in the history
  • Loading branch information
matyama committed Apr 11, 2019
2 parents 464762c + 6c99fb0 commit 10c48e4
Show file tree
Hide file tree
Showing 21 changed files with 1,101 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .coveragerc
@@ -1,7 +1,7 @@
[run]
branch = True
source =
pytoolz/
ftoolz/
omit =

[report]
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -14,6 +14,7 @@ __pycache__/
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
Expand Down
5 changes: 3 additions & 2 deletions .pylintrc
Expand Up @@ -29,8 +29,9 @@ method-rgx=[a-z_][a-z0-9_]{2,60}$
# Good variable names which should always be accepted, separated by a comma
# defaults were i,j,k,ex,Run,_
good-names=
i,j,k,ex,Run,_,e,op,m,t,it,id,fn,f,g,h,n,v,ds,xs,ys,zs,x,y,z,
ff,fa,fb,fc,a,b,c,A,B,C,E,F,G,K,R,T,R,V,Z
a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,K,R,T,R,V,Z,
ex,fa,fb,fc,ff,fn,id,it,op,xs,ys,zs,_,,applyN,fmap,fmapN,
A_in,B_in,C_in,D_in,A_out,A_out,B_out,C_out,D_out

const-naming-style=any

Expand Down
16 changes: 16 additions & 0 deletions .travis.yml
@@ -0,0 +1,16 @@
sudo: required
dist: xenial
language: python
python:
- "3.6"
- "3.7"

install:
- make install
- pip install coveralls

script:
- make release-check

after_success:
- coveralls
31 changes: 23 additions & 8 deletions Makefile
@@ -1,9 +1,13 @@
.PHONY: help clean setup setup-dev install release-check type-check flake8-check lint tests
.PHONY: help build clean clean-build setup setup-dev install release-check type-check flake8-check lint tests twine-release-test

.DEFAULT: help
help:
@echo "make build"
@echo " build distribution directories"
@echo "make clean"
@echo " clean virtual environment"
@echo " clean virtual environment and distribution"
@echo "make clean-build"
@echo " clean distribution directories"
@echo "make setup"
@echo " setup development environment"
@echo "make setup-dev"
Expand All @@ -20,9 +24,17 @@ help:
@echo " run unit and doc tests"
@echo "make release-check"
@echo " run type-check, flake8 check, linting and tests"
@echo "make twine-release-test"
@echo " release ftoolz to test pypi using twine"

clean:
build: clean-build
@echo ">>> building ftoolz distribution"
python setup.py sdist

clean: clean-build
rm -rf venv

clean-build:
rm -rf dist
rm -rf build
rm -rf *.egg-info
Expand All @@ -40,20 +52,23 @@ install: clean
python setup.py install

type-check:
@echo ">>> checking types in pytoolz and tests"
MYPYPATH=./stubs mypy pytoolz tests || ( echo ">>> type check failed"; exit 1; )
@echo ">>> checking types in ftoolz and tests"
MYPYPATH=./stubs mypy ftoolz tests || ( echo ">>> type check failed"; exit 1; )

flake8-check:
@echo ">>> enforcing PEP 8 style with flake8 in pytoolz and tests"
flake8 --config=.flake8 pytoolz/ tests/ || ( echo ">>> flake8 check failed"; exit 1; )
@echo ">>> enforcing PEP 8 style with flake8 in ftoolz and tests"
flake8 --config=.flake8 ftoolz/ tests/ || ( echo ">>> flake8 check failed"; exit 1; )

lint:
@echo ">>> linting code"
pylint -j 0 --rcfile .pylintrc pytoolz tests || ( echo ">>> linting failed"; exit 1; )
pylint -j 0 --rcfile .pylintrc ftoolz tests || ( echo ">>> linting failed"; exit 1; )

tests:
@echo ">>> running tests"
python tests/run.py || ( echo ">>> tests failed"; exit 1; )
# python setup.py test

release-check: type-check flake8-check lint tests

twine-release-test: build
python3 -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*
74 changes: 68 additions & 6 deletions README.md
@@ -1,9 +1,70 @@
# pytoolz
# ftoolz
[![Build Status](https://travis-ci.com/blindspot-ai/ftoolz.svg?branch=master)](https://travis-ci.com/blindspot-ai/ftoolz) [![Coverage Status](https://coveralls.io/repos/github/blindspot-ai/ftoolz/badge.svg?branch=master)](https://coveralls.io/github/blindspot-ai/ftoolz?branch=master) [![PyPI version](https://badge.fury.io/py/ftoolz.svg)](https://badge.fury.io/py/ftoolz)

Collection of higher-order and utility functions built on top of `cytoolz`.

## Module overview
Pytoolz are split into few generic modules.
Ftoolz are split into few generic modules.

### functoolz package
Package that provides higher-order functions commonly associated with Functor, Applicative and Monad.

Moreover, there are general package-level functions implemented in `__init__.py`.

| Function | Description |
|----------|-------------|
| `try_except(ex, f, g, args, kwargs)` | `f(args, kwargs)` and on exception(s) `ex` fallback to `g(args, kwargs)` |

The package content is organized into modules by individual type class:
1. `iter.py` for class `Iterable`. **Warn** some functions might not be pure because input iterable is consumed.
1. `opt.py` for class `Optional`
1. `seq.py` for class `Seq` (`Sequece`). Methods typically return `tuple` instances to preserve immutability.

#### Module function overview
| def / .py | iter | opt | seq |
|-----------|------|-----|-----|
|`apply`| x | x | x |
|`apply2`| - | x | - |
|`applyN`| - | + | - |
|`flatmap`| x | x | x |
|`flatten`| x | x | x |
|`fmap`| x | x | x |
|`fmap2`| x | x | x |
|`fmap3`| - | x | - |
|`fmapN`| - | + | - |
|`fproduct`| x | x | x |
|`lift`| x | x | x |
|`product`| x | x | x |
|`unit`| x | * | x |
|`zip_map`| + | - | + |

* `x` - implemented, statically type checked
* `+` - implemented, possible runtime type errors
* `*` - not implemented, supported natively
* `-` - not implemented

#### traverse package
Each module contains traversable-related functions for traversables `Iterable` and `Seq`.
Individual modules are named and reserved for single functor that wraps elements of the traversable sequence.

List of currently implemented functions in modules (functors):

| def / .py | opt |
|-----------|-----|
|`sequence_iter`| x |
|`sequence_seq`| x |
|`traverse_iter`| x |
|`traverse_iter`| x |

### dicttoolz
This module contains functions that work with `Map` (`Mapping`) instances.

Table of contents

| Function | Description |
|----------|-------------|
| `swap(dict, key1, key2)` | swap arbitrary values for `key1` and `key2` in given mapping |
| `swap_values(dict, key1, key2)` | same as `swap` but preserving concrete value type `V` |

### itertoolz
This module contains functions that work with `Iterable` instances.
Expand All @@ -21,6 +82,7 @@ selected by `key_fn` |
| `filter_not_none(iterable)` | filter out `None` elements from iterable |
| `find(predicate, iterable)` | find first element of iterable satisfying predicate |
| `first(sequence)` | return first element of a sequence or `None` |
| `fold_right(op, iterable, z)` | fold iterable by applying binary operator `op` from the *right* |
| `head_tail(iterable)` | split iterable into head element and tail iterable |
| `head_tail_list(iterable)` | same as `head_tail` but materialized tail into list |
| `iter_with_final(iterable)` | creates iterable of tuples of original element and final flag |
Expand Down Expand Up @@ -55,14 +117,14 @@ Cytoolz is a cython implementation of a python library supporting functional sty

We highly recommend reading the API docs and using it in your project.

Pytoolz does not fork but rather extends cytoolz and provides typed stubs for it's API.
Ftoolz does not fork but rather extends cytoolz and provides typed stubs for it's API.
Please note that the typed stubs do not cover all the functions from cytoolz.

Also some valid cases might not be covered due to Python's restricted typing capabilities.

## Setup development environment
It is highly recommended to use virtual environment to develop and test `pytoolz`. For making things easy there are
two make targets to setup `pytoolz`:
It is highly recommended to use virtual environment to develop and test `ftoolz`. For making things easy there are
two make targets to setup `ftoolz`:
* `make setup-dev` which creates new virtual environment in `./venv`
* `make setup` that just installs dependencies for development

Expand All @@ -83,7 +145,7 @@ make type-check
```

### Code style checking
Pytoolz uses [Flake8](http://flake8.pycqa.org/en/latest/index.html) for enforcing PEP 8 and other code smells.
Ftoolz uses [Flake8](http://flake8.pycqa.org/en/latest/index.html) for enforcing PEP 8 and other code smells.
```bash
make flake8-check
```
Expand Down
File renamed without changes.
42 changes: 42 additions & 0 deletions ftoolz/dicttoolz.py
@@ -0,0 +1,42 @@
from typing import Any, TypeVar

from ftoolz.typing import Map

K = TypeVar('K')
V = TypeVar('V')


def swap(d: Map[K, Any], key1: K, key2: K) -> Map[K, Any]:
"""
Swap arbitrary values for given keys creating new mapping.
>>> swap({'k1': [1, 2, 3], 'k2': {1, 2, 3}}, 'k1', 'k2')
{'k1': {1, 2, 3}, 'k2': [1, 2, 3]}
Original mapping is returned if at least one key does not exist in `d`.
>>> swap({'k1': 1}, 'k1', 'k2')
{'k1': 1}
>>> swap({'k2': 2}, 'k1', 'k2')
{'k2': 2}
"""
return swap_values(d, key1, key2)


def swap_values(d: Map[K, V], key1: K, key2: K) -> Map[K, V]:
"""
Swap values for given keys creating new mapping.
>>> swap_values({'k1': 1, 'k2': 2}, 'k1', 'k2')
{'k1': 2, 'k2': 1}
Original mapping is returned if at least one key does not exist in `d`.
>>> swap_values({'k1': 1}, 'k1', 'k2')
{'k1': 1}
>>> swap_values({'k2': 2}, 'k1', 'k2')
{'k2': 2}
"""
return {**d, key1: d[key2], key2: d[key1]} \
if key1 in d and key2 in d \
else d
62 changes: 62 additions & 0 deletions ftoolz/functoolz/__init__.py
@@ -0,0 +1,62 @@
from typing import Any, Callable, Tuple, Type, TypeVar, Union

# Invariant
A = TypeVar('A')
B = TypeVar('B')
C = TypeVar('C')
D = TypeVar('D')

# Contravariant
A_in = TypeVar('A_in', contravariant=True)
B_in = TypeVar('B_in', contravariant=True)
C_in = TypeVar('C_in', contravariant=True)
D_in = TypeVar('D_in', contravariant=True)

# Covariant
A_out = TypeVar('A_out', covariant=True)
B_out = TypeVar('B_out', covariant=True)
C_out = TypeVar('C_out', covariant=True)
D_out = TypeVar('D_out', covariant=True)


def try_except(
e: Union[Type[Exception], Tuple[Type[Exception], ...]],
f: Callable[..., A],
g: Callable[..., A],
*args: Any,
**kwargs: Any
) -> A:
"""
Call `f(args, kwargs)` and on exception `e` fallback to `g(args, kwargs)`.
>>> try_except(ValueError, int, lambda s: s.upper(), '1')
1
>>> try_except(ValueError, int, lambda s: s.upper(), 'a')
'A'
One can pass both args and kwargs to `try_except`.
>>> def f(x: int, y: str):
... return x + int(y)
>>> def g(x: int, y: str):
... return f'error: {x} + {y}'
>>> try_except(ValueError, f, g, 1, y='2')
3
>>> try_except(ValueError, f, g, 1, y='x')
'error: 1 + x'
Errors that are not mentioned in `e` are propagated to outer scope.
>>> try_except(ValueError, lambda d: d['k'], str, {'a': 1})
Traceback (most recent call last):
...
KeyError: 'k'
"""
# noinspection PyBroadException
try:
return f(*args, **kwargs)
except e:
return g(*args, **kwargs)

0 comments on commit 10c48e4

Please sign in to comment.