-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathValid.py
executable file
·164 lines (149 loc) · 5.35 KB
/
Valid.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#!/usr/bin/env python3
# Copyright (c) 2008-11 Qtrac Ltd. All rights reserved.
# This program or module is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. It is provided for educational
# purposes and is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
"""
>>> @valid_string("name", empty_allowed=False)
... @valid_string("productid", empty_allowed=False,
... regex=re.compile(r"[A-Z]{3}\d{4}"))
... @valid_string("category", empty_allowed=False, acceptable=
... frozenset(["Consumables", "Hardware", "Software", "Media"]))
... @valid_number("price", minimum=0, maximum=1e6)
... @valid_number("quantity", minimum=1, maximum=1000)
... class StockItem:
...
... def __init__(self, name, productid, category, price, quantity):
... self.name = name
... self.productid = productid
... self.category = category
... self.price = price
... self.quantity = quantity
...
... @property
... def value(self):
... return self.price * self.quantity
>>>
>>> pc = StockItem("Computer", "EAA5000", "Hardware", 599, 3)
>>> pc.name == "Computer" and pc.category == "Hardware"
True
>>> pc.productid == "EAA5000"
True
>>> pc.price == 599 and pc.quantity == 3 and pc.value == 1797
True
>>> error = None
>>> try:
... StockItem("", "ABC1000", "Software", 129, 2)
... except ValueError as e:
... error = str(e)
>>> error == "name may not be empty"
True
>>> try:
... StockItem("Printer", "KXV5500", "Vaporware", 129, 2)
... except ValueError as e:
... error = str(e)
>>> error == "category cannot be set to Vaporware"
True
>>> try:
... StockItem("Cable", "KXB5001", "Media", -12, 2)
... except ValueError as e:
... error = str(e)
>>> error == "price -12 is too small"
True
>>> try:
... StockItem("Socket", "KXY520", "Media", 1e7, 2)
... except ValueError as e:
... error = str(e)
>>> error == "productid cannot be set to KXY520"
True
>>> try:
... StockItem("Socket", "KXY5020", "Media", 1e7, 2)
... except ValueError as e:
... error = str(e)
>>> error == "price 10000000.0 is too big"
True
>>> try:
... StockItem("Paper", "KXJ5003", "Media", 10, 0)
... except ValueError as e:
... error = str(e)
>>> error == "quantity 0 is too small"
True
>>> try:
... StockItem("Ink", "AKX5005", "Media", 10, 1001)
... except ValueError as e:
... error = str(e)
>>> error == "quantity 1001 is too big"
True
>>> item = StockItem("Toner", "KXV5500", "Media", 10, 100)
>>> item.quantity += 5
>>> item.quantity == 105 and item.value == 1050
True
>>> try:
... item.quantity = "one"
... except AssertionError as e:
... error = str(e)
>>> error == "quantity must be a number"
True
"""
import numbers
import re
class GenericDescriptor:
def __init__(self, getter, setter):
self.getter = getter
self.setter = setter
def __get__(self, instance, owner=None):
if instance is None:
return self
return self.getter(instance)
def __set__(self, instance, value):
return self.setter(instance, value)
def valid_string(attr_name, empty_allowed=True, regex=None,
acceptable=None):
def decorator(cls):
name = "__" + attr_name
def getter(self):
return getattr(self, name)
def setter(self, value):
assert isinstance(value, str), (attr_name +
" must be a string")
if not empty_allowed and not value:
raise ValueError("{0} may not be empty".format(
attr_name))
if ((acceptable is not None and value not in acceptable) or
(regex is not None and not regex.match(value))):
raise ValueError("{attr_name} cannot be set to "
"{value}".format(**locals()))
setattr(self, name, value)
setattr(cls, attr_name, GenericDescriptor(getter, setter))
return cls
return decorator
def valid_number(attr_name, minimum=None, maximum=None,
acceptable=None):
def decorator(cls):
name = "__" + attr_name
def getter(self):
return getattr(self, name)
def setter(self, value):
assert isinstance(value, numbers.Number), (
attr_name + " must be a number")
if minimum is not None and value < minimum:
raise ValueError("{attr_name} {value} is too small"
.format(**locals()))
if maximum is not None and value > maximum:
raise ValueError("{attr_name} {value} is too big"
.format(**locals()))
if acceptable is not None and value not in acceptable:
raise ValueError("{attr_name} {value} is unacceptable"
.format(**locals()))
setattr(self, name, value)
setattr(cls, attr_name, GenericDescriptor(getter, setter))
return cls
return decorator
if __name__ == "__main__":
import doctest
doctest.testmod()