Skip to content

Commit 2cf61dc

Browse files
committed
Merge branch 'release/v0.1.0'
2 parents b7aa8fc + a81743d commit 2cf61dc

13 files changed

+315
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.vscode
2+
13
# Byte-compiled / optimized / DLL files
24
__pycache__/
35
*.py[cod]

.travis.yml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
language: python
2+
python:
3+
- "2.7"
4+
- "3.4"
5+
- "3.5"
6+
- "3.6" # current default Python on Travis CI
7+
- "3.7"
8+
- "3.8"
9+
- "3.8-dev" # 3.8 development branch
10+
- "nightly" # nightly build
11+
# command to install dependencies
12+
install:
13+
- pip install -r requirements.txt
14+
# command to run tests
15+
script:
16+
- python tests/units.py

README.md

+48
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,50 @@
11
# validium
22
a Python utility library for performing validations flexibly
3+
4+
## Installation
5+
6+
```bash
7+
pip install validium
8+
```
9+
10+
## Quick Start
11+
12+
Here's an example of how to create and use some very simple validators for some numbers:
13+
14+
```py
15+
import validium as V
16+
17+
PI = 3.14
18+
ONE = 1
19+
20+
is_number = V.Validator(lambda x: isinstance(x, Number), 'must be a number')
21+
22+
is_number.validate(PI) # pass
23+
is_number.validate(ONE) # pass
24+
25+
is_positive = V.Validator(lambda x: x > 0, 'must be positive')
26+
27+
is_positive.validate(PI) # pass
28+
is_positive.validate(ONE) # pass
29+
30+
is_not_one = V.Validator(lambda x: not x == 1, 'must not equal 1')
31+
32+
is_not_one.validate(PI) # pass
33+
is_not_one.validate(ONE) # AssertionError: must not equal 1
34+
35+
```
36+
37+
### Building Up
38+
39+
Here's an example of how to parameterize and reuse a common validator pattern:
40+
41+
```py
42+
is_not = lambda y: V.Validator(lambda x: not x == y, 'must not equal {}'.format(x)) # u
43+
44+
is_not(-1).validate(ONE) # pass
45+
is_not(0).validate(ONE) # pass
46+
is_not(1).validate(ONE) # AssertionError: must not equal 1
47+
48+
```
49+
50+
This approach will help keep your code nice and DRY in the event you need handful of validators that behave mostly the same but slightly different.

docker-compose.shell.yml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
version: '3'
2+
services:
3+
test-runner:
4+
build:
5+
context: .
6+
dockerfile: python3.Dockerfile
7+
command: python
8+
volumes:
9+
- .:/app
10+
stdin_open: true
11+
tty: true

docker-compose.test-runner.yml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
version: '3'
2+
services:
3+
test-runner:
4+
build:
5+
context: .
6+
dockerfile: python3.Dockerfile
7+
command: python tests/units.py
8+
volumes:
9+
- .:/app
10+
stdin_open: true
11+
tty: true
12+
test-runner-2:
13+
build:
14+
context: .
15+
dockerfile: python2.Dockerfile
16+
command: python tests/units.py
17+
volumes:
18+
- .:/app
19+
stdin_open: true
20+
tty: true

python2.Dockerfile

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM python:2.7
2+
3+
WORKDIR /app
4+
5+
COPY requirements.txt ./
6+
RUN pip install --no-cache-dir -r requirements.txt

python3.Dockerfile

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
FROM python:3.8
2+
3+
WORKDIR /app
4+
5+
COPY requirements.txt ./
6+
RUN pip install --no-cache-dir -r requirements.txt

requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pandas==1.0.1
2+
ramda==0.5.5

setup.cfg

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[metadata]
2+
description-file = README.md

setup.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from distutils.core import setup
2+
setup(
3+
name = 'validium', # How you named your package folder (MyLib)
4+
packages = ['validium'], # Chose the same as "name"
5+
version = '0.1.0', # Start with a small number and increase it with every change you make
6+
license= 'MIT', # Chose a license from here: https://help.github.com/articles/licensing-a-repository
7+
description = 'a utility library for generating chunks of time within a datetime range', # Give a short description about your library
8+
author = 'Jason Yung',
9+
author_email = 'json.yung@gmail.com',
10+
url = 'https://github.com/json2d/validium', # Provide either the link to your github or to your website
11+
download_url = 'https://github.com/json2d/validium/archive/v0.1.0.tar.gz',
12+
keywords = ['datetime', 'interval', 'chunks'], # Keywords that define your package best
13+
install_requires= [],
14+
classifiers=[
15+
'Development Status :: 3 - Alpha', # Chose either "3 - Alpha", "4 - Beta" or "5 - Production/Stable" as the current state of your package
16+
'Intended Audience :: Developers', # Define your audience
17+
'Topic :: Utilities',
18+
'License :: OSI Approved :: MIT License', # Again, pick a license
19+
'Programming Language :: Python :: 3', # Specify which pyhton versions that you want to support
20+
'Programming Language :: Python :: 3.4',
21+
'Programming Language :: Python :: 3.5',
22+
'Programming Language :: Python :: 3.6',
23+
'Programming Language :: Python :: 3.7',
24+
'Programming Language :: Python :: 3.8',
25+
],
26+
)

tests/units.py

+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import sys
2+
from os import path
3+
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
4+
5+
from numbers import Number
6+
7+
# from helpers import *
8+
9+
# from functools import reduce
10+
11+
import unittest
12+
import ramda as R
13+
import pandas as pd
14+
15+
import validium as V
16+
17+
# extensions for some missing ramda functions
18+
R.isinstance = lambda x: lambda y: isinstance(y,x)
19+
20+
class TestEverything(unittest.TestCase):
21+
22+
# use `_test` prefix isntead of `test` (w/o leading underscore) so test runner doesn't use it
23+
def _test_should_fail(self, fail_validators, foo):
24+
for validator in fail_validators:
25+
with self.assertRaises(AssertionError):
26+
validator.validate(foo)
27+
28+
def _test_should_pass(self, pass_validators, foo):
29+
try:
30+
for validator in pass_validators:
31+
validator.validate(foo)
32+
except:
33+
self.fail('validation should have passed but exception was raised')
34+
35+
def test_base(self):
36+
37+
foo = 3.14
38+
39+
pass_validators = [
40+
V.Validator(
41+
lambda x: isinstance(x, Number),
42+
'must be a number'
43+
),
44+
45+
V.Validator(
46+
lambda x: isinstance(x, float),
47+
'must be a float'
48+
),
49+
50+
V.Validator(
51+
lambda x: x > 0 and x < 100,
52+
'must be greater than 0 and less than 100'
53+
),
54+
55+
V.Validator(
56+
lambda x: x == 3.14,
57+
'must equal 3.14'
58+
),
59+
]
60+
61+
self._test_should_pass(pass_validators, foo)
62+
63+
fail_validators = [
64+
65+
V.Validator(
66+
R.isinstance(str),
67+
'must be a string'
68+
),
69+
70+
V.Validator(R.equals(42), 'must equal 42'),
71+
72+
V.Validator(
73+
lambda x: x < 0,
74+
'must be less than 0',
75+
)
76+
]
77+
78+
self._test_should_fail(fail_validators, foo)
79+
80+
def test_list(self):
81+
82+
class Mystery:
83+
pass
84+
85+
bars = [1, 2, .14, None, 'hello', 'world']
86+
87+
pass_validators = [
88+
V.Validator(
89+
lambda xs: isinstance(xs, list),
90+
'must be a list'
91+
),
92+
93+
V.Validator(
94+
lambda xs: len(xs) == 6,
95+
'must be of length 6'
96+
),
97+
98+
V.Validator(
99+
lambda xs: all([not isinstance(x, Mystery) for x in xs]),
100+
'all must not be Mystery'
101+
),
102+
103+
V.Validator(
104+
lambda xs: all([x > 0 for x in filter(lambda x: isinstance(x, Number), xs)]),
105+
'all numbers must be greater than 0'
106+
),
107+
108+
V.Validator(
109+
lambda xs: ' '.join(filter(lambda x: isinstance(x, str), xs)) == 'hello world',
110+
'all strings joined with a space must equal "hello world"'
111+
),
112+
113+
V.Validator(
114+
R.any(R.equals('hello')),
115+
'any must equal "hello"'
116+
),
117+
118+
V.Validator(
119+
R.all(R.isinstance((Number, type(None), str))),
120+
'all must be number, None or str'
121+
),
122+
123+
V.Validator(
124+
R.pipe(R.filter(R.isinstance(Number)), R.sum, R.equals(3.14)),
125+
'all numbers summed must equal 3.14'
126+
),
127+
128+
V.Validator(
129+
R.pipe(R.filter(R.isinstance(str)), R.length, R.equals(2)),
130+
'the count of str must be 2'
131+
),
132+
]
133+
134+
self._test_should_pass(pass_validators, bars)
135+
136+
fail_validators = [
137+
V.Validator(
138+
R.all(R.pipe(R.isinstance(type(None)), R.negate)),
139+
'all must not be None'
140+
),
141+
142+
V.Validator(
143+
R.any(R.isinstance(dict)),
144+
'any must be dict'
145+
),
146+
147+
V.Validator(
148+
R.pipe(R.filter(R.isinstance(Number)), R.sum, R.equals(42)),
149+
'all numbers summed must equal 42'
150+
),
151+
152+
V.Validator(
153+
R.pipe(R.filter(R.isinstance(str)), R.length, R.equals(4)),
154+
'the count of str must be 4'
155+
),
156+
157+
]
158+
159+
self._test_should_fail(fail_validators, bars)
160+
161+
unittest.main()

validium/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .core import Validator

validium/core.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class Validator:
2+
3+
def __init__(self, predicate, fail_msg=None):
4+
5+
assert callable(predicate), "the argument 'predicate' must be callable"
6+
assert fail_msg is None or isinstance(fail_msg, str), "the argument 'fail_msg' must be None or an instance of str"
7+
8+
self.__dict__ = dict(
9+
predicate=predicate,
10+
fail_msg=fail_msg,
11+
)
12+
13+
def validate(self, target):
14+
assert self.predicate(target), self.fail_msg

0 commit comments

Comments
 (0)