update from Atlas with major reorg
This commit is contained in:
1 parent
57902d31b5
commit
a786180239
134 files changed
+369
-520
No files matched your search
@@ -4,6 +4,7 @@ concurrency/charfinder/charfinder_index.pickle
|
|||||||
metaprog/oscon-schedule/data/schedule?_db
|
metaprog/oscon-schedule/data/schedule?_db
|
||||||
concurrency/wikipedia/fixture/docroot/
|
concurrency/wikipedia/fixture/docroot/
|
||||||
17-futures/countries/flags/
|
17-futures/countries/flags/
|
||||||
|
attic/futures/countries/flags/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -2,12 +2,13 @@
|
|||||||
# BEGIN BINGO_DEMO
|
# BEGIN BINGO_DEMO
|
||||||
|
|
||||||
>>> bingo = BingoCage(range(3))
|
>>> bingo = BingoCage(range(3))
|
||||||
>>> bingo()
|
>>> bingo.pick()
|
||||||
2
|
1
|
||||||
>>> bingo()
|
>>> bingo()
|
||||||
0
|
0
|
||||||
>>> callable(bingo)
|
>>> callable(bingo)
|
||||||
True
|
True
|
||||||
|
|
||||||
# END BINGO_DEMO
|
# END BINGO_DEMO
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -22,9 +23,13 @@ class BingoCage:
|
|||||||
self._items = list(items) # <1>
|
self._items = list(items) # <1>
|
||||||
random.shuffle(self._items) # <2>
|
random.shuffle(self._items) # <2>
|
||||||
|
|
||||||
def __call__(self):
|
def pick(self): # <3>
|
||||||
if not self._items: # <3>
|
try:
|
||||||
raise IndexError('pop from empty BingoCage')
|
return self._items.pop()
|
||||||
return self._items.pop()
|
except IndexError:
|
||||||
|
raise LookupError('pick from empty BingoCage') # <4>
|
||||||
|
|
||||||
|
def __call__(self): # <5>
|
||||||
|
return self.pick()
|
||||||
|
|
||||||
# END BINGO
|
# END BINGO
|
||||||
File renamed without changes.
@@ -0,0 +1,43 @@
|
|||||||
|
# clockdeco_param.py
|
||||||
|
|
||||||
|
"""
|
||||||
|
>>> snooze(.1) # doctest: +ELLIPSIS
|
||||||
|
[0.101...s] snooze(0.1) -> None
|
||||||
|
>>> clock('{name}: {elapsed}')(time.sleep)(.2) # doctest: +ELLIPSIS
|
||||||
|
sleep: 0.20...
|
||||||
|
>>> clock('{name}({args}) dt={elapsed:0.3f}s')(time.sleep)(.2)
|
||||||
|
sleep(0.2) dt=0.201s
|
||||||
|
"""
|
||||||
|
|
||||||
|
# BEGIN CLOCKDECO_CLS
|
||||||
|
import time
|
||||||
|
|
||||||
|
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'
|
||||||
|
|
||||||
|
class clock:
|
||||||
|
|
||||||
|
def __init__(self, fmt=DEFAULT_FMT):
|
||||||
|
self.fmt = fmt
|
||||||
|
|
||||||
|
def __call__(self, func):
|
||||||
|
def clocked(*_args):
|
||||||
|
t0 = time.time()
|
||||||
|
_result = func(*_args)
|
||||||
|
elapsed = time.time() - t0
|
||||||
|
name = func.__name__
|
||||||
|
args = ', '.join(repr(arg) for arg in _args)
|
||||||
|
result = repr(_result)
|
||||||
|
print(self.fmt.format(**locals()))
|
||||||
|
return _result
|
||||||
|
return clocked
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
@clock()
|
||||||
|
def snooze(seconds):
|
||||||
|
time.sleep(seconds)
|
||||||
|
|
||||||
|
for i in range(3):
|
||||||
|
snooze(.123)
|
||||||
|
|
||||||
|
# END CLOCKDECO_CLS
|
||||||
+16
-7
@@ -1,3 +1,5 @@
|
|||||||
|
# BEGIN TOMBOLA_BINGO
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from tombola import Tombola
|
from tombola import Tombola
|
||||||
@@ -6,14 +8,21 @@ from tombola import Tombola
|
|||||||
class BingoCage(Tombola): # <1>
|
class BingoCage(Tombola): # <1>
|
||||||
|
|
||||||
def __init__(self, items):
|
def __init__(self, items):
|
||||||
self._balls = list(items) # <2>
|
self._randomizer = random.SystemRandom() # <2>
|
||||||
|
self._items = []
|
||||||
|
self.load(items) # <3>
|
||||||
|
|
||||||
def load(self, items):
|
def load(self, items):
|
||||||
self._balls.extend(items)
|
self._items.extend(items)
|
||||||
|
self._randomizer.shuffle(self._items) # <4>
|
||||||
|
|
||||||
def pick(self):
|
def pick(self): # <5>
|
||||||
try:
|
try:
|
||||||
position = random.randrange(len(self._balls)) # <3>
|
return self._items.pop()
|
||||||
except ValueError:
|
except IndexError:
|
||||||
raise LookupError('pop from empty BingoCage')
|
raise LookupError('pick from empty BingoCage')
|
||||||
return self._balls.pop(position) # <4>
|
|
||||||
|
def __call__(self): # <7>
|
||||||
|
self.pick()
|
||||||
|
|
||||||
|
# END TOMBOLA_BINGO
|
||||||
File renamed without changes.
+15
-9
@@ -1,3 +1,5 @@
|
|||||||
|
# BEGIN LOTTERY_BLOWER
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from tombola import Tombola
|
from tombola import Tombola
|
||||||
@@ -6,19 +8,23 @@ from tombola import Tombola
|
|||||||
class LotteryBlower(Tombola):
|
class LotteryBlower(Tombola):
|
||||||
|
|
||||||
def __init__(self, iterable):
|
def __init__(self, iterable):
|
||||||
self.randomizer = random.SystemRandom() # <1>
|
self._balls = list(iterable) # <1>
|
||||||
self.clear()
|
|
||||||
self.load(iterable)
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
self._balls = []
|
|
||||||
|
|
||||||
def load(self, iterable):
|
def load(self, iterable):
|
||||||
self._balls.extend(iterable)
|
self._balls.extend(iterable)
|
||||||
self.randomizer.shuffle(self._balls) # <2>
|
|
||||||
|
|
||||||
def pick(self):
|
def pick(self):
|
||||||
return self._balls.pop() # <3>
|
try:
|
||||||
|
position = random.randrange(len(self._balls)) # <2>
|
||||||
|
except ValueError:
|
||||||
|
raise LookupError('pick from empty BingoCage')
|
||||||
|
return self._balls.pop(position) # <3>
|
||||||
|
|
||||||
def loaded(self): # <4>
|
def loaded(self): # <4>
|
||||||
return len(self._balls) > 0
|
return bool(self._balls)
|
||||||
|
|
||||||
|
def inspect(self): # <5>
|
||||||
|
return tuple(sorted(self._balls))
|
||||||
|
|
||||||
|
|
||||||
|
# END LOTTERY_BLOWER
|
||||||
+19
-7
@@ -1,3 +1,5 @@
|
|||||||
|
# BEGIN TOMBOLA_ABC
|
||||||
|
|
||||||
import abc
|
import abc
|
||||||
|
|
||||||
class Tombola(abc.ABC): # <1>
|
class Tombola(abc.ABC): # <1>
|
||||||
@@ -14,10 +16,20 @@ class Tombola(abc.ABC): # <1>
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def loaded(self): # <4>
|
def loaded(self): # <4>
|
||||||
try:
|
"""Return `True` if there's at least 1 item, `False` otherwise."""
|
||||||
item = self.pick()
|
return bool(self.inspect()) # <5>
|
||||||
except LookupError:
|
|
||||||
return False
|
|
||||||
else:
|
def inspect(self):
|
||||||
self.load([item]) # put it back
|
"""Return a sorted tuple with the items currently inside."""
|
||||||
return True
|
items = []
|
||||||
|
while True: # <6>
|
||||||
|
try:
|
||||||
|
items.append(self.pick())
|
||||||
|
except LookupError:
|
||||||
|
break
|
||||||
|
self.load(items) # <7>
|
||||||
|
return tuple(sorted(items))
|
||||||
|
|
||||||
|
|
||||||
|
# END TOMBOLA_ABC
|
||||||
@@ -11,6 +11,8 @@ Create and load instance from iterable::
|
|||||||
>>> globe = ConcreteTombola(balls)
|
>>> globe = ConcreteTombola(balls)
|
||||||
>>> globe.loaded()
|
>>> globe.loaded()
|
||||||
True
|
True
|
||||||
|
>>> globe.inspect()
|
||||||
|
(0, 1, 2)
|
||||||
|
|
||||||
|
|
||||||
Pick and collect balls::
|
Pick and collect balls::
|
||||||
@@ -55,7 +57,7 @@ Load and pick 100 balls to verify that they all come out::
|
|||||||
>>> balls = list(range(100))
|
>>> balls = list(range(100))
|
||||||
>>> globe = ConcreteTombola(balls)
|
>>> globe = ConcreteTombola(balls)
|
||||||
>>> picks = []
|
>>> picks = []
|
||||||
>>> while globe.loaded():
|
>>> while globe.inspect():
|
||||||
... picks.append(globe.pick())
|
... picks.append(globe.pick())
|
||||||
>>> len(picks) == len(balls)
|
>>> len(picks) == len(balls)
|
||||||
True
|
True
|
||||||
@@ -72,7 +74,7 @@ Check that the order has changed and is not simply reversed::
|
|||||||
|
|
||||||
Note: the previous 2 tests have a *very* small chance of failing
|
Note: the previous 2 tests have a *very* small chance of failing
|
||||||
even if the implementation is OK. The probability of the 100
|
even if the implementation is OK. The probability of the 100
|
||||||
balls coming out, by chance, in the order they were loaded is
|
balls coming out, by chance, in the order they were inspect is
|
||||||
1/100!, or approximately 1.07e-158. It's much easier to win the
|
1/100!, or approximately 1.07e-158. It's much easier to win the
|
||||||
Lotto or to become a billionaire working as a programmer.
|
Lotto or to become a billionaire working as a programmer.
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,12 @@ class TomboList(list): # <2>
|
|||||||
else:
|
else:
|
||||||
raise LookupError('pop from empty TomboList')
|
raise LookupError('pop from empty TomboList')
|
||||||
|
|
||||||
def load(self, iterable): self.extend(iterable) # <5>
|
load = list.extend # <5>
|
||||||
|
|
||||||
def loaded(self): return bool(self) # <6>
|
def loaded(self):
|
||||||
|
return bool(self) # <6>
|
||||||
|
|
||||||
# Tombola.register(TomboList) # <- Python 3.2 or earlier
|
def inspect(self):
|
||||||
|
return tuple(sorted(self))
|
||||||
|
|
||||||
|
# Tombola.register(TomboList) # <7>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# BEGIN TOMBOLA_BINGO
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
from tombola import Tombola
|
||||||
|
|
||||||
|
|
||||||
|
class BingoCage(Tombola): # <1>
|
||||||
|
|
||||||
|
def __init__(self, items):
|
||||||
|
self._randomizer = random.SystemRandom() # <2>
|
||||||
|
self._items = []
|
||||||
|
self.load(items) # <3>
|
||||||
|
|
||||||
|
def load(self, items):
|
||||||
|
self._items.extend(items)
|
||||||
|
self._randomizer.shuffle(self._items) # <4>
|
||||||
|
|
||||||
|
def pick(self): # <5>
|
||||||
|
try:
|
||||||
|
return self._items.pop()
|
||||||
|
except IndexError:
|
||||||
|
raise LookupError('pick from empty BingoCage')
|
||||||
|
|
||||||
|
def __call__(self): # <7>
|
||||||
|
self.pick()
|
||||||
|
|
||||||
|
# END TOMBOLA_BINGO
|
||||||
@@ -4,56 +4,83 @@ AddableBingoCage tests
|
|||||||
======================
|
======================
|
||||||
|
|
||||||
|
|
||||||
Tests for __add__ and __iadd__:
|
Tests for __add__:
|
||||||
|
|
||||||
|
# BEGIN ADDABLE_BINGO_ADD_DEMO
|
||||||
|
|
||||||
>>> vowels = 'AEIOU'
|
>>> vowels = 'AEIOU'
|
||||||
>>> globe = AddableBingoCage(vowels)
|
>>> globe = AddableBingoCage(vowels) # <1>
|
||||||
>>> len(globe)
|
>>> globe.inspect()
|
||||||
5
|
('A', 'E', 'I', 'O', 'U')
|
||||||
>>> globe.pop() in vowels
|
>>> globe.pick() in vowels # <2>
|
||||||
True
|
True
|
||||||
>>> len(globe)
|
>>> len(globe.inspect()) # <3>
|
||||||
4
|
4
|
||||||
>>> globe2 = AddableBingoCage('XYZ')
|
>>> globe2 = AddableBingoCage('XYZ') # <4>
|
||||||
>>> globe3 = globe + globe2
|
>>> globe3 = globe + globe2
|
||||||
>>> len(globe3)
|
>>> len(globe3.inspect()) # <5>
|
||||||
7
|
7
|
||||||
>>> void = globe + [10, 20]
|
>>> void = globe + [10, 20] # <6>
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
TypeError: unsupported operand type(s) for +: 'AddableBingoCage' and 'list'
|
TypeError: unsupported operand type(s) for +: 'AddableBingoCage' and 'list'
|
||||||
|
|
||||||
|
|
||||||
Tests for __add__ and __iadd__:
|
# END ADDABLE_BINGO_ADD_DEMO
|
||||||
|
|
||||||
>>> globe_orig = globe
|
Tests for __iadd__:
|
||||||
>>> len(globe)
|
|
||||||
|
# BEGIN ADDABLE_BINGO_IADD_DEMO
|
||||||
|
|
||||||
|
>>> globe_orig = globe # <1>
|
||||||
|
>>> len(globe.inspect()) # <2>
|
||||||
4
|
4
|
||||||
>>> globe += globe2
|
>>> globe += globe2 # <3>
|
||||||
>>> len(globe)
|
>>> len(globe.inspect())
|
||||||
7
|
7
|
||||||
>>> globe += [10, 20]
|
>>> globe += ['M', 'N'] # <4>
|
||||||
>>> len(globe)
|
>>> len(globe.inspect())
|
||||||
9
|
9
|
||||||
>>> globe is globe_orig
|
>>> globe is globe_orig # <5>
|
||||||
True
|
True
|
||||||
|
>>> globe += 1 # <6>
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
TypeError: right operand in += must be 'AddableBingoCage' or an iterable
|
||||||
|
|
||||||
|
# END ADDABLE_BINGO_IADD_DEMO
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# BEGIN ADDABLE_BINGO
|
# BEGIN ADDABLE_BINGO
|
||||||
import itertools # <1>
|
import itertools # <1>
|
||||||
from bingobase import BingoCage
|
|
||||||
|
from tombola import Tombola
|
||||||
|
from bingo import BingoCage
|
||||||
|
|
||||||
|
|
||||||
class AddableBingoCage(BingoCage): # <2>
|
class AddableBingoCage(BingoCage): # <2>
|
||||||
|
|
||||||
def __add__(self, other):
|
def __add__(self, other):
|
||||||
if isinstance(other, AddableBingoCage): # <3>
|
if isinstance(other, Tombola): # <3>
|
||||||
return AddableBingoCage(itertools.chain(self, other)) # <4>
|
return AddableBingoCage(self.inspect() + other.inspect()) # <6>
|
||||||
else:
|
else:
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __iadd__(self, other):
|
def __iadd__(self, other):
|
||||||
self.load(other) # <5>
|
if isinstance(other, Tombola):
|
||||||
return self # <6>
|
other_iterable = other.inspect() # <4>
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
other_iterable = iter(other) # <5>
|
||||||
|
except TypeError: # <6>
|
||||||
|
self_cls = type(self).__name__
|
||||||
|
msg = "right operand in += must be {!r} or an iterable"
|
||||||
|
raise TypeError(msg.format(self_cls))
|
||||||
|
self.load(other_iterable) # <7>
|
||||||
|
return self # <8>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# END ADDABLE_BINGO
|
# END ADDABLE_BINGO
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
"""
|
|
||||||
===============
|
|
||||||
BingoCage tests
|
|
||||||
===============
|
|
||||||
|
|
||||||
|
|
||||||
Create and load instance from iterable::
|
|
||||||
|
|
||||||
>>> balls = list(range(3))
|
|
||||||
>>> globe = BingoCage(balls)
|
|
||||||
>>> len(globe)
|
|
||||||
3
|
|
||||||
|
|
||||||
|
|
||||||
Pop and collect balls::
|
|
||||||
|
|
||||||
>>> picks = []
|
|
||||||
>>> picks.append(globe.pop())
|
|
||||||
>>> picks.append(globe.pop())
|
|
||||||
>>> picks.append(globe.pop())
|
|
||||||
|
|
||||||
|
|
||||||
Check state and results::
|
|
||||||
|
|
||||||
>>> len(globe)
|
|
||||||
0
|
|
||||||
>>> sorted(picks) == balls
|
|
||||||
True
|
|
||||||
|
|
||||||
|
|
||||||
Reload::
|
|
||||||
|
|
||||||
>>> globe.load(balls)
|
|
||||||
>>> len(globe)
|
|
||||||
3
|
|
||||||
>>> picks = [globe.pop() for i in balls]
|
|
||||||
>>> len(globe)
|
|
||||||
0
|
|
||||||
|
|
||||||
|
|
||||||
Load and pop 20 balls to verify that the order has changed::
|
|
||||||
|
|
||||||
>>> balls = list(range(20))
|
|
||||||
>>> globe = BingoCage(balls)
|
|
||||||
>>> picks = []
|
|
||||||
>>> while globe:
|
|
||||||
... picks.append(globe.pop())
|
|
||||||
>>> len(picks) == len(balls)
|
|
||||||
True
|
|
||||||
>>> picks != balls
|
|
||||||
True
|
|
||||||
|
|
||||||
|
|
||||||
Also check that the order is not simply reversed either::
|
|
||||||
|
|
||||||
>>> picks[::-1] != balls
|
|
||||||
True
|
|
||||||
|
|
||||||
Note: last 2 tests above each have 1 chance in 20! (factorial) of
|
|
||||||
failing even if the implementation is OK. 1/20!, or approximately
|
|
||||||
4.11e-19, is the probability of the 20 balls coming out, by chance,
|
|
||||||
in the exact order the were loaded.
|
|
||||||
|
|
||||||
Check that `LookupError` (or a subclass) is the exception thrown
|
|
||||||
when the device is empty::
|
|
||||||
|
|
||||||
>>> globe = BingoCage([])
|
|
||||||
>>> try:
|
|
||||||
... globe.pop()
|
|
||||||
... except LookupError as exc:
|
|
||||||
... print('OK')
|
|
||||||
OK
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
import random
|
|
||||||
|
|
||||||
|
|
||||||
class BingoCage():
|
|
||||||
|
|
||||||
def __init__(self, iterable):
|
|
||||||
self._balls = []
|
|
||||||
self.load(iterable)
|
|
||||||
|
|
||||||
def load(self, iterable):
|
|
||||||
self._balls.extend(iterable)
|
|
||||||
random.shuffle(self._balls)
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self._balls)
|
|
||||||
|
|
||||||
def pop(self):
|
|
||||||
return self._balls.pop()
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
return reversed(self._balls)
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# BEGIN TOMBOLA_ABC
|
||||||
|
|
||||||
|
import abc
|
||||||
|
|
||||||
|
class Tombola(abc.ABC): # <1>
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def load(self, iterable): # <2>
|
||||||
|
"""Add items from an iterable."""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def pick(self): # <3>
|
||||||
|
"""Remove item at random, returning it.
|
||||||
|
|
||||||
|
This method should raise `LookupError` when the instance is empty.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def loaded(self): # <4>
|
||||||
|
"""Return `True` if there's at least 1 item, `False` otherwise."""
|
||||||
|
return bool(self.inspect()) # <5>
|
||||||
|
|
||||||
|
|
||||||
|
def inspect(self):
|
||||||
|
"""Return a sorted tuple with the items currently inside."""
|
||||||
|
items = []
|
||||||
|
while True: # <6>
|
||||||
|
try:
|
||||||
|
items.append(self.pick())
|
||||||
|
except LookupError:
|
||||||
|
break
|
||||||
|
self.load(items) # <7>
|
||||||
|
return tuple(sorted(items))
|
||||||
|
|
||||||
|
|
||||||
|
# END TOMBOLA_ABC
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
isis2json.py
|
||||||
|
============
|
||||||
|
|
||||||
|
This directory contains a copy of the ``isis2json.py`` script, with
|
||||||
|
minimal dependencies, just to allow the O'Reilly Atlas toolchain to
|
||||||
|
render the listing of the script in appendix A of the book.
|
||||||
|
|
||||||
|
If you want to use or contribute to this script, please get the full
|
||||||
|
source code with all dependencies from the main ``isis2json``
|
||||||
|
repository:
|
||||||
|
|
||||||
|
https://github.com/fluentpython/isis2json
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -32,7 +32,7 @@ MAX_CONCUR_REQ = 1000 # <5>
|
|||||||
|
|
||||||
def download_many(cc_list, base_url, verbose, concur_req):
|
def download_many(cc_list, base_url, verbose, concur_req):
|
||||||
counter = collections.Counter()
|
counter = collections.Counter()
|
||||||
with futures.ThreadPoolExecutor(concur_req) as executor: # <6>
|
with futures.ThreadPoolExecutor(max_workers=concur_req) as executor: # <6>
|
||||||
to_do_map = {} # <7>
|
to_do_map = {} # <7>
|
||||||
for cc in sorted(cc_list): # <8>
|
for cc in sorted(cc_list): # <8>
|
||||||
future = executor.submit(download_one,
|
future = executor.submit(download_one,
|
||||||
|
|||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -54,7 +54,7 @@ class NonBlank(Validated):
|
|||||||
class EntityMeta(type):
|
class EntityMeta(type):
|
||||||
"""Metaclass for business entities with validated fields"""
|
"""Metaclass for business entities with validated fields"""
|
||||||
|
|
||||||
def __init__(self, name, bases, attr_dict):
|
def __init__(cls, name, bases, attr_dict):
|
||||||
super().__init__(name, bases, attr_dict) # <1>
|
super().__init__(name, bases, attr_dict) # <1>
|
||||||
for key, attr in attr_dict.items(): # <2>
|
for key, attr in attr_dict.items(): # <2>
|
||||||
if isinstance(attr, Validated):
|
if isinstance(attr, Validated):
|
||||||
@@ -59,14 +59,14 @@ class EntityMeta(type):
|
|||||||
def __prepare__(cls, name, bases):
|
def __prepare__(cls, name, bases):
|
||||||
return collections.OrderedDict() # <1>
|
return collections.OrderedDict() # <1>
|
||||||
|
|
||||||
def __init__(self, name, bases, attr_dict):
|
def __init__(cls, name, bases, attr_dict):
|
||||||
super().__init__(name, bases, attr_dict)
|
super().__init__(name, bases, attr_dict)
|
||||||
self._field_names = [] # <2>
|
cls._field_names = [] # <2>
|
||||||
for key, attr in attr_dict.items(): # <3>
|
for key, attr in attr_dict.items(): # <3>
|
||||||
if isinstance(attr, Validated):
|
if isinstance(attr, Validated):
|
||||||
type_name = type(attr).__name__
|
type_name = type(attr).__name__
|
||||||
attr.storage_name = '_{}#{}'.format(type_name, key)
|
attr.storage_name = '_{}#{}'.format(type_name, key)
|
||||||
self._field_names.append(key) # <4>
|
cls._field_names.append(key) # <4>
|
||||||
|
|
||||||
|
|
||||||
class Entity(metaclass=EntityMeta):
|
class Entity(metaclass=EntityMeta):
|
||||||
@@ -13,13 +13,13 @@ def deco_alpha(cls):
|
|||||||
class MetaAleph(type):
|
class MetaAleph(type):
|
||||||
print('<[400]> MetaAleph body')
|
print('<[400]> MetaAleph body')
|
||||||
|
|
||||||
def __init__(self, name, bases, dic):
|
def __init__(cls, name, bases, dic):
|
||||||
print('<[500]> MetaAleph.__init__')
|
print('<[500]> MetaAleph.__init__')
|
||||||
|
|
||||||
def inner_2(self):
|
def inner_2(self):
|
||||||
print('<[600]> MetaAleph.__init__:inner_2')
|
print('<[600]> MetaAleph.__init__:inner_2')
|
||||||
|
|
||||||
self.method_z = inner_2
|
cls.method_z = inner_2
|
||||||
# END META_ALEPH
|
# END META_ALEPH
|
||||||
|
|
||||||
print('<[700]> evalsupport module end')
|
print('<[700]> evalsupport module end')
|
||||||
@@ -9,8 +9,8 @@ record_factory: create simple classes just for holding data fields
|
|||||||
>>> name, weight, _ = rex # <3>
|
>>> name, weight, _ = rex # <3>
|
||||||
>>> name, weight
|
>>> name, weight
|
||||||
('Rex', 30)
|
('Rex', 30)
|
||||||
>>> "{2}'s dog weights {1}kg".format(*rex) # <4>
|
>>> "{2}'s dog weighs {1}kg".format(*rex) # <4>
|
||||||
"Bob's dog weights 30kg"
|
"Bob's dog weighs 30kg"
|
||||||
>>> rex.weight = 32 # <5>
|
>>> rex.weight = 32 # <5>
|
||||||
>>> rex
|
>>> rex
|
||||||
Dog(name='Rex', weight=32, owner='Bob')
|
Dog(name='Rex', weight=32, owner='Bob')
|
||||||
@@ -18,33 +18,42 @@ record_factory: create simple classes just for holding data fields
|
|||||||
(<class 'factories.Dog'>, <class 'object'>)
|
(<class 'factories.Dog'>, <class 'object'>)
|
||||||
|
|
||||||
# END RECORD_FACTORY_DEMO
|
# END RECORD_FACTORY_DEMO
|
||||||
|
|
||||||
|
The factory also accepts a list or tuple of identifiers:
|
||||||
|
|
||||||
|
>>> Dog = record_factory('Dog', ['name', 'weight', 'owner'])
|
||||||
|
>>> Dog.__slots__
|
||||||
|
('name', 'weight', 'owner')
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# BEGIN RECORD_FACTORY
|
# BEGIN RECORD_FACTORY
|
||||||
def record_factory(cls_name, field_names):
|
def record_factory(cls_name, field_names):
|
||||||
if isinstance(field_names, str):
|
try:
|
||||||
field_names = field_names.replace(',', ' ').split()
|
field_names = field_names.replace(',', ' ').split() # <1>
|
||||||
field_names = tuple(field_names) # <1>
|
except AttributeError: # no .replace or .split
|
||||||
|
pass # assume it's already a sequence of identifiers
|
||||||
|
field_names = tuple(field_names) # <2>
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs): # <2>
|
def __init__(self, *args, **kwargs): # <3>
|
||||||
attrs = dict(zip(self.__slots__, args))
|
attrs = dict(zip(self.__slots__, args))
|
||||||
attrs.update(kwargs)
|
attrs.update(kwargs)
|
||||||
for name, value in attrs.items():
|
for name, value in attrs.items():
|
||||||
setattr(self, name, value)
|
setattr(self, name, value)
|
||||||
|
|
||||||
def __iter__(self): # <3>
|
def __iter__(self): # <4>
|
||||||
for name in self.__slots__:
|
for name in self.__slots__:
|
||||||
yield getattr(self, name)
|
yield getattr(self, name)
|
||||||
|
|
||||||
def __repr__(self): # <4>
|
def __repr__(self): # <5>
|
||||||
values = ', '.join('{}={!r}'.format(*i) for i
|
values = ', '.join('{}={!r}'.format(*i) for i
|
||||||
in zip(self.__slots__, self))
|
in zip(self.__slots__, self))
|
||||||
return '{}({})'.format(self.__class__.__name__, values)
|
return '{}({})'.format(self.__class__.__name__, values)
|
||||||
|
|
||||||
cls_attrs = dict(__slots__ = field_names, # <5>
|
cls_attrs = dict(__slots__ = field_names, # <6>
|
||||||
__init__ = __init__,
|
__init__ = __init__,
|
||||||
__iter__ = __iter__,
|
__iter__ = __iter__,
|
||||||
__repr__ = __repr__)
|
__repr__ = __repr__)
|
||||||
|
|
||||||
return type(cls_name, (object,), cls_attrs) # <6>
|
return type(cls_name, (object,), cls_attrs) # <7>
|
||||||
# END RECORD_FACTORY
|
# END RECORD_FACTORY
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 134 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user