Skip to content

Commit 0a388ce

Browse files
committed
add initial tests and project boilerplate
+ docker containers for running tests
1 parent 422b6b3 commit 0a388ce

File tree

10 files changed

+288
-24
lines changed

10 files changed

+288
-24
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
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]

README.md

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,41 +8,53 @@ import validframe as vf
88

99
df = pd.DataFrame([
1010
['a','b'], # headers
11-
[1,2], # row 1
12-
[1, None] # row 2
13-
[1, 'hello'] # row 3
14-
[1, 3.14] # row 4
11+
[1, 1], # row 10
12+
[1, None] # row 1
13+
[1, 'hello'] # row 2
14+
[1, 3.14] # row 3
1515
])
1616

17-
err_msg = "you wont see this error"
18-
err_msg = "you wont see this error"
17+
vf.validator(lambda x: , col='a')(df)
1918

20-
assert vf.not_negative()(df), "you wont see this error"
21-
assert vf.not_empty()(df), "you will see this error"
19+
# validate that all cells that are numbers are also positive
20+
vf.validator(lambda x: x>0, filter=lambda x: isinstance(x, Number) )(df)
2221

23-
assert vf.not_empty(row=0)(df), "you wont see this error"
24-
assert vf.not_empty(row=1)(df), "you will see this error"
25-
assert vf.not_empty(col='a')(df), "you wont see this error"
26-
assert vf.not_empty(col='b')(df), "you will see this error"
2722

28-
assert vf.totals(4, col='a')(df), "you won't see this error"
29-
assert vf.min(0, col='a')(df), "you won't see this error"
30-
assert vf.max(2, col='a')(df), "you won't see this error"
23+
vf.positive()(df) # AssertionError
24+
vf.not_empty()(df) # AssertionError
25+
vf.empty()(df) # AssertionError
3126

32-
assert vf.ints(col='a')(df), "you won't see this error"
33-
assert vf.ints(col='b')(df), "you will see this error"
27+
vf.not_empty(row=0)(df)
28+
vf.not_empty(row=1)(df) # AssertionError
29+
vf.not_empty(col='a')(df)
30+
vf.not_empty(col='b')(df) # AssertionError
3431

35-
assert vf.strs(col='b', row=3)(df), "you won't see this error"
36-
assert vf.floats(col='b', row=4)(df), "you won't see this error"
32+
vf.empty(col='b', row=1)(df)
33+
vf.empty(col='b')(df) # AssertionError
3734

38-
assert vf.validate(lambda x: x.isnumeric, col='a')(df), "you won't see this error"
35+
vf.min(0, col='a')(df)
36+
vf.max(2, col='a')(df)
37+
38+
vf.ints(col='a')(df)
39+
vf.ints(row=0)(df)
40+
vf.ints(col='b')(df) # AssertionError
41+
42+
vf.strs(col='b', row=3)(df)
43+
vf.floats(col='b', row=4)(df)
44+
45+
vf.totals(4, col='a')(df)
3946

40-
# validate that all cells that are numbers are positive
41-
assert vf.validate(lambda x: x>0, filter=lambda x: x.isnumeric() )(df), "you won't see this error"
4247
```
4348

4449
### boycotting lambdas?
4550
use functions instead
46-
```
47-
def
51+
```py
52+
def is_cell_foo(x):
53+
foo = (None, str, int, float)
54+
return isinstance(x, foo)
55+
56+
validate_is_cells_foo = vf.validator(is_cell_foo)
57+
58+
validate_is_cells_foo(df)
4859

60+
```

docker-compose.shell.yml

Lines changed: 11 additions & 0 deletions
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

Lines changed: 20 additions & 0 deletions
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

Lines changed: 6 additions & 0 deletions
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

Lines changed: 6 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pandas==1.0.1

tests/units.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import sys
2+
from os import path
3+
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
4+
5+
from datetime import timedelta, datetime
6+
7+
from numbers import Number
8+
9+
# from helpers import *
10+
11+
# from functools import reduce
12+
13+
import unittest
14+
15+
import pandas as pd
16+
17+
import validframe as vf
18+
19+
class TestEverything(unittest.TestCase):
20+
21+
# use `_test` prefix isntead of `test` (w/o leading underscore) so test runner doesn't use it
22+
def _test_should_fail(self, fail_validators, df):
23+
for validate in fail_validators:
24+
with self.assertRaises(AssertionError):
25+
validate(df)
26+
27+
def _test_should_pass(self, pass_validators, df):
28+
try:
29+
for validate in pass_validators:
30+
validate(df)
31+
except:
32+
self.fail('validation should have passed but exception was raised')
33+
34+
def test_base(self):
35+
36+
df = pd.DataFrame(
37+
columns = ['a','b'],
38+
data = [
39+
[1, -42], # row 1
40+
[1, None], # row 2
41+
[1, None], # row 3
42+
[1, 3.14], # row 4
43+
])
44+
45+
class Mystery():
46+
pass
47+
48+
pass_validators = [
49+
vf.validator(lambda x: not isinstance(x, Mystery)),
50+
vf.validator(lambda x: x is None or x >= -42),
51+
vf.validator(lambda x: x == 1, col='a'),
52+
vf.validator(lambda x: x == 1, row=[0, 3]),
53+
54+
vf.validator(lambda x: x == -42 or x == 3.14, col='b', row=[0, 3]),
55+
vf.validator(lambda x: x == 1, col=['a'], row=[0, 3]),
56+
57+
vf.validator(lambda x: x == 1 or x == -42, filter=lambda x: isinstance(x, int)),
58+
vf.validator(lambda x: x is None, filter=lambda x: not isinstance(x, Number)),
59+
60+
vf.validator(lambda x: x == -42, col='b', filter=lambda x: isinstance(x, int)),
61+
vf.validator(lambda x: x == -42, col='b', row=[0,1,2], filter=lambda x: isinstance(x, int))
62+
]
63+
64+
self._test_should_pass(pass_validators, df)
65+
66+
fail_validators = [
67+
vf.validator(lambda x: isinstance(x, Number)), # all cells are numbers
68+
vf.validator(lambda x: isinstance(x, Number), col='b'), # all cells in col 'a' are numbers
69+
vf.validator(lambda x: isinstance(x, Number), col=['b']), # all cells in col 'a' are numbers
70+
71+
vf.validator(lambda x: x < 0, row=0), # all cells in row 0 and 3 are negative (and numbers)
72+
vf.validator(lambda x: x < 0, row=[0, 3]), # all cells in row 0 and 3 are negative (and numbers)
73+
vf.validator(lambda x: x < 0, col='b', row=[0, 3]),
74+
vf.validator(lambda x: x < 0, col=['a'], row=[0, 3]),
75+
76+
vf.validator(lambda x: x < 0, filter=lambda x: isinstance(x, Number)),
77+
vf.validator(lambda x: x < 0, col=['b'], row=[4,3,1], filter=lambda x: isinstance(x, float))
78+
]
79+
80+
self._test_should_fail(fail_validators, df)
81+
82+
83+
def test_mappers(self):
84+
85+
df = pd.DataFrame(
86+
columns = ['a', 'b', 'c'], # headers
87+
data = [
88+
['a', 'b', 'c'], # headers
89+
[1, -42, 'hello'], # row 0
90+
[1, None, 'world'], # row 1
91+
[1, None, 'ciao'], # row 2
92+
[1, 3.14, 'mondo'], # row 3
93+
])
94+
95+
pass_validators = [
96+
vf.positive(col='a'),
97+
vf.negative(col='b', row=0),
98+
vf.empty(col='b', row=[1,2]),
99+
vf.not_empty(col=['a','c'], row=[0,3]),
100+
vf.min(0, col=['a','b'], row=3),
101+
vf.max(3,14, col=['a','b'], row=3),
102+
vf.minmax(-42, 3.14, filter=lambda x : isinstance(x, Number)),
103+
vf.ints(col='a'),
104+
vf.floats(col='b', row=3),
105+
vf.strs(col='c'),
106+
]
107+
108+
self._test_should_pass(pass_validators, df)
109+
110+
111+
fail_validators = [
112+
vf.positive(col='b', row=0),
113+
vf.negative(col='a'),
114+
vf.empty(col=['a','c'], row=[0,3]),
115+
vf.not_empty(col='b', row=[1,2]),
116+
vf.min(3.14, col=['a','b'], row=3),
117+
vf.max(0, col=['a','b'], row=3),
118+
vf.minmax(0, 2, filter=lambda x : isinstance(x, Number)),
119+
vf.ints(col='b'),
120+
vf.floats(col='a'),
121+
vf.strs(row=3),
122+
]
123+
124+
125+
self._test_should_fail(fail_validators, df)
126+
127+
def test_reducers(self):
128+
129+
df = pd.DataFrame(
130+
columns = ['a', 'b', 'c'], # headers
131+
data = [
132+
[1, -42, 'hello'], # row 0
133+
[1, None, 'world'], # row 1
134+
[1, None, 'ciao'], # row 2
135+
[1, 3.14, 'mondo'] # row 3
136+
])
137+
138+
139+
pass_validators = [
140+
vf.totals(4, col='a'),
141+
vf.totals(-41, col=['a','b'], row=0),
142+
vf.totals(3.14, col=['a','b'], row=[0,3]),
143+
vf.totals(-38, filter=lambda x : isinstance(x, int)),
144+
vf.totals(7.14, filter=lambda x : isinstance(x, Number) and x > 0),
145+
]
146+
147+
self._test_should_pass(pass_validators, df)
148+
149+
150+
fail_validators = [
151+
vf.totals(100, col='a'),
152+
vf.totals(1, row=1), # theres a None in this row
153+
vf.totals('gg', col='c'),
154+
]
155+
156+
self._test_should_fail(fail_validators, df)
157+
158+
159+
def test_with_datetime(self):
160+
161+
some_day = datetime(2020, 1, 6)
162+
163+
df = pd.DataFrame(
164+
columns = ['net_amount', 'product_name', 'trn_date'], # headers
165+
data = [
166+
[5.50, 'canoli', some_day], # row 0
167+
[9.50, 'tiramisu', some_day + timedelta(hours=1)], # row 1
168+
[10, 'salad', some_day + timedelta(hours=2)], # row 2
169+
[10, 'bread', some_day + timedelta(days=1)], # row 3
170+
])
171+
172+
pass_validators = [
173+
vf.minmax(some_day, some_day + timedelta(days=1), col='trn_date'),
174+
vf.min(some_day, col='trn_date'),
175+
vf.max(some_day + timedelta(days=2), col='trn_date'),
176+
]
177+
178+
self._test_should_pass(pass_validators, df)
179+
180+
181+
fail_validators = [
182+
vf.minmax(some_day - timedelta(days=1), some_day, col='trn_date'),
183+
vf.min(some_day + timedelta(hours=2), col='trn_date'),
184+
vf.max(some_day + timedelta(hours=2), col='trn_date'),
185+
]
186+
187+
self._test_should_fail(fail_validators, df)
188+
189+
190+
unittest.main()

validframe/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .core import *

validframe/core.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
def validator(validate_cell, col=None, row=None, filter=None):
2+
3+
assert callable(validate_cell), 'validate_cell arg must be callable'
4+
5+
def validate_df(df, **kwargs):
6+
# assert any(df.applymap( ... )
7+
raise NotImplementedError
8+
9+
return validate_df
10+
11+
def positive(**kwargs):
12+
return validator(lambda x : x > 0, **kwargs)
13+
14+
# or with just lambdas
15+
negative = lambda **kwargs : validator(lambda x: x < 0, **kwargs)

0 commit comments

Comments
 (0)