-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathBikeStock.py
executable file
·292 lines (242 loc) · 9.72 KB
/
BikeStock.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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
#!/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.
"""
>>> import os
>>> import tempfile
>>> bike_file = os.path.join(tempfile.gettempdir(), "bikes.dat")
>>> if os.path.exists(bike_file): os.remove(bike_file)
>>> bike_data = []
>>> bike_data.append(('REFK2', 'Reflex Kalahari', 5, 200.97))
>>> bike_data.append(('REFT1', 'Reflex Tempus', 4, 200.97))
>>> bike_data.append(('UNISTOW', 'Universal Stowaway', 1, 203.00))
>>> bike_data.append(('REFONA', "Reflex Out 'n' About", 0, 213.15))
>>> bike_data.append(('B4U16RS', 'Bicycles4U 16/6/RS ', 0, 223.30))
>>> bike_data.append(('B4U20', 'Bicycles4U 20/6', 9, 223.30))
>>> bike_data.append(('B4U20MTB', 'Bicycles4U 20/6/MTB', 5, 223.30))
>>> bike_data.append(('REFA3', 'Reflex Axiom 3', 15, 223.30))
>>> bike_data.append(('ASBC', 'AS Bikes Compact', 22, 243.60))
>>> bike_data.append(('AMMC', 'Ammaco Commuter', 4, 259.84))
>>> bike_data.append(('AMMP5', 'Ammaco Pakka Mk 5', 7, 259.84))
>>> bike_data.append(('B4U20RS', 'Bicycles4U 20/6/RS', 3, 263.90))
>>> bike_data.append(('COM16', 'Compass 16"', 2, 263.90))
>>> bike_data.append(('ASBC+', 'AS Bikes Compact Plus', 11, 284.20))
>>> bike_data.append(('TIGB', 'Tiger Bikes', 0, 284.20))
>>> bike_data.append(('ASBEX', 'AS Bikes Explorer', 2, 304.50))
>>> bike_data.append(('GEKKO', 'Gekko', 6, 304.50))
>>> bike_data.append(('PROBE', 'Probike Enfold', 4, 304.50))
>>> bike_data.append(('SAMHDXM', 'Samchuly Haro DX MTB', 5, 304.50))
>>> bike_data.append(('SINAB', 'Sinclair A-Bike', 3, 304.50))
>>> bike_data.append(('FALOM', 'Falcon Optima Mayfair', 2, 324.80))
>>> bike_data.append(('RALPARK', 'Raleigh Parkway', 1, 324.80))
>>> bike_data.append(('SAXS', 'Saxon Safari', 2, 324.80))
>>> bike_data.append(('CLACHA', 'Classic Chatsworth', 0, 328.86))
>>> bike_data.append(('ASBE+', 'AS Bikes Explorer Plus', 3, 345.10))
>>> bike_data.append(('RALPROM', 'Raleigh Promenade', 2, 345.10))
>>> bike_data.append(('VIKBS', 'Viking Bikes Safari', 1, 345.10))
>>> bike_data.append(('AMMT+C', 'Ammaco Town & Country', 0, 353.22))
>>> bike_data.append(('AMMCT', 'Ammaco Cruiser Tandem 16"', 0, 355.25))
>>> bike_data.append(('AMMMON', 'Ammaco Montreal', 4, 355.25))
>>> bike_data.append(('MSGENIE', 'Mission Space Genie', 4, 355.25))
>>> bike_data.append(('TRANSS', 'Transair Sea Sure', 3, 355.25))
>>> bike_data.append(('DAHSP', 'Dahon Sweet Pea', 1, 363.37))
>>> bike_data.append(('SALEASY', 'Salcano Easy', 0, 365.40))
>>> bike_data.append(('CLAMEL', 'Classic Melbourne', 1, 379.61))
>>> bike_data.append(('VENTGL', 'Ventura Go Lite', 1, 383.67))
>>> bicycles = BikeStock(bike_file)
>>> for bike in bike_data:
... bicycles.append(Bike(*bike))
>>> bicycles["VIKBS"].name
'Viking Bikes Safari'
>>> ok = []
>>> value = 0.0
>>> for i, bike in enumerate(bicycles):
... ok.append(bike.quantity == bike_data[i][2])
... value += bike.value
>>> all(ok), "{0:.2f}".format(round(value, 2))
(True, '35969.57')
>>> bicycles["SALEASY"].name
'Salcano Easy'
>>> bicycles.change_name("SALEASY", "Salcano EZ")
True
>>> bicycles["SALEASY"].name
'Salcano EZ'
>>> total = 0
>>> for bike in bicycles:
... if bike.identity.startswith("B4U"):
... total += bike.quantity
>>> total
17
>>> total = 0
>>> for bike in bicycles:
... if bike.identity.startswith("B4U"):
... if bicycles.increase_stock(bike.identity, 2):
... total += bicycles[bike.identity].quantity
... else:
... print("error", bike)
>>> total
25
>>> value = 0.0
>>> count = 0
>>> for bike in bicycles:
... value += bike.value
... count += 1
>>> "{0:.2f}".format(round(value, 2)), count, count == len(bike_data)
('37837.17', 36, True)
>>> bicycles["CLAMEL"].name
'Classic Melbourne'
>>> del bicycles["UNISTOW"]
>>> value = 0.0
>>> count = 0
>>> for bike in bicycles:
... value += bike.value
... count += 1
>>> "{0:.2f}".format(round(value, 2)), count
('37634.17', 35)
>>> bicycles["CLAMEL"].name
'Classic Melbourne'
>>> bicycles.append(Bike('UNISTOW', 'Universal Stowaway', 1, 203.00))
>>> bicycles.close()
>>> bicycles = BikeStock(bike_file)
>>> value = 0.0
>>> for bike in bicycles:
... value += bike.value
>>> "{0:.2f}".format(round(value, 2))
'37837.17'
>>> bicycles.close()
>>> os.path.getsize(bike_file)
1836
>>> if os.path.exists(bike_file): os.remove(bike_file)
"""
import struct
import BinaryRecordFile
class Bike:
def __init__(self, identity, name, quantity, price):
assert len(identity) > 3, ("invalid bike identity '{0}'"
.format(identity))
self.__identity = identity
self.name = name
self.quantity = quantity
self.price = price
@property
def identity(self):
"The bike's identity"
return self.__identity
@property
def name(self):
"The bike's name"
return self.__name
@name.setter
def name(self, name):
assert len(name), "bike name must not be empty"
self.__name = name
@property
def quantity(self):
"How many of this bike are in stock"
return self.__quantity
@quantity.setter
def quantity(self, quantity):
assert 0 <= quantity, "quantity must not be negative"
self.__quantity = quantity
@property
def price(self):
"The bike's price"
return self.__price
@price.setter
def price(self, price):
assert 0.0 <= price, "price must not be negative"
self.__price = price
@property
def value(self):
"The value of these bikes in stock"
return self.quantity * self.price
_BIKE_STRUCT = struct.Struct("<8s30sid")
def _bike_from_record(record):
ID, NAME, QUANTITY, PRICE = range(4)
parts = list(_BIKE_STRUCT.unpack(record))
parts[ID] = parts[ID].decode("utf8").rstrip("\x00")
parts[NAME] = parts[NAME].decode("utf8").rstrip("\x00")
return Bike(*parts)
def _record_from_bike(bike):
return _BIKE_STRUCT.pack(bike.identity.encode("utf8"),
bike.name.encode("utf8"),
bike.quantity, bike.price)
class BikeStock:
def __init__(self, filename):
self.__file = BinaryRecordFile.BinaryRecordFile(filename,
_BIKE_STRUCT.size)
self.__index_from_identity = {}
for index in range(len(self.__file)):
record = self.__file[index]
if record is not None:
bike = _bike_from_record(record)
self.__index_from_identity[bike.identity] = index
def close(self):
"Compacts and closes the file"
self.__file.inplace_compact()
self.__file.close()
def append(self, bike):
"Adds a new bike to the stock"
index = len(self.__file)
self.__file[index] = _record_from_bike(bike)
self.__index_from_identity[bike.identity] = index
def __delitem__(self, identity):
"Deletes the stock record for the specified bike"
del self.__file[self.__index_from_identity[identity]]
del self.__index_from_identity[identity]
def __getitem__(self, identity):
"Retrieves the stock record for the specified bike"
record = self.__file[self.__index_from_identity[identity]]
return None if record is None else _bike_from_record(record)
def __change_bike(self, identity, what, value):
index = self.__index_from_identity[identity]
record = self.__file[index]
if record is None:
return False
bike = _bike_from_record(record)
if what == "price" and value is not None and value >= 0.0:
bike.price = value
elif what == "name" and value is not None:
bike.name = value
else:
return False
self.__file[index] = _record_from_bike(bike)
return True
change_name = lambda self, identity, name: self.__change_bike(
identity, "name", name)
change_name.__doc__ = "Changes the bike's name"
change_price = lambda self, identity, price: self.__change_bike(
identity, "price", name)
change_price.__doc__ = "Changes the bike's price"
def __change_stock(self, identity, amount):
index = self.__index_from_identity[identity]
record = self.__file[index]
if record is None:
return False
bike = _bike_from_record(record)
bike.quantity += amount
self.__file[index] = _record_from_bike(bike)
return True
increase_stock = (lambda self, identity, amount:
self.__change_stock(identity, amount))
increase_stock.__doc__ = ("Increases the stock held for the "
"specified bike by by the given amount")
decrease_stock = (lambda self, identity, amount:
self.__change_stock(identity, -amount))
decrease_stock.__doc__ = ("Decreases the stock held for the "
"specified bike by by the given amount")
def __iter__(self):
for index in range(len(self.__file)):
record = self.__file[index]
if record is not None:
yield _bike_from_record(record)
if __name__ == "__main__":
import doctest
doctest.testmod()