Initial commit
This commit is contained in:
76
Solutions/8_6/asyncserver.py
Normal file
76
Solutions/8_6/asyncserver.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# server.py
|
||||
|
||||
from socket import *
|
||||
from select import select
|
||||
from collections import deque
|
||||
from types import coroutine
|
||||
|
||||
tasks = deque()
|
||||
recv_wait = {} # sock -> task
|
||||
send_wait = {} # sock -> task
|
||||
|
||||
def run():
|
||||
while any([tasks, recv_wait, send_wait]):
|
||||
while not tasks:
|
||||
can_recv, can_send, _ = select(recv_wait, send_wait, [])
|
||||
for s in can_recv:
|
||||
tasks.append(recv_wait.pop(s))
|
||||
for s in can_send:
|
||||
tasks.append(send_wait.pop(s))
|
||||
task = tasks.popleft()
|
||||
try:
|
||||
reason, resource = task.send(None)
|
||||
if reason == 'recv':
|
||||
recv_wait[resource] = task
|
||||
elif reason == 'send':
|
||||
send_wait[resource] = task
|
||||
else:
|
||||
raise RuntimeError('Unknown reason %r' % reason)
|
||||
except StopIteration:
|
||||
print('Task done')
|
||||
|
||||
class GenSocket:
|
||||
def __init__(self, sock):
|
||||
self.sock = sock
|
||||
|
||||
@coroutine
|
||||
def accept(self):
|
||||
yield 'recv', self.sock
|
||||
client, addr = self.sock.accept()
|
||||
return GenSocket(client), addr
|
||||
|
||||
@coroutine
|
||||
def recv(self, maxsize):
|
||||
yield 'recv', self.sock
|
||||
return self.sock.recv(maxsize)
|
||||
|
||||
@coroutine
|
||||
def send(self, data):
|
||||
yield 'send', self.sock
|
||||
return self.sock.send(data)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.sock, name)
|
||||
|
||||
async def tcp_server(address, handler):
|
||||
sock = GenSocket(socket(AF_INET, SOCK_STREAM))
|
||||
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
||||
sock.bind(address)
|
||||
sock.listen(5)
|
||||
while True:
|
||||
client, addr = await sock.accept()
|
||||
tasks.append(handler(client, addr))
|
||||
|
||||
async def echo_handler(client, address):
|
||||
print('Connection from', address)
|
||||
while True:
|
||||
data = await client.recv(1000)
|
||||
if not data:
|
||||
break
|
||||
await client.send(b'GOT:' + data)
|
||||
print('Connection closed')
|
||||
|
||||
if __name__ == '__main__':
|
||||
tasks.append(tcp_server(('',25000), echo_handler))
|
||||
run()
|
||||
|
||||
41
Solutions/8_6/cofollow.py
Normal file
41
Solutions/8_6/cofollow.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# cofollow.py
|
||||
import os
|
||||
import time
|
||||
import csv
|
||||
|
||||
def follow(filename,target):
|
||||
with open(filename,"r") as f:
|
||||
f.seek(0,os.SEEK_END)
|
||||
while True:
|
||||
line = f.readline()
|
||||
if line != '':
|
||||
target.send(line)
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
|
||||
def receive(expected_type):
|
||||
msg = yield
|
||||
assert isinstance(msg, expected_type), 'Expected type %s' % (expected_type)
|
||||
return msg
|
||||
|
||||
# Decorator for coroutines
|
||||
from functools import wraps
|
||||
|
||||
def consumer(func):
|
||||
@wraps(func)
|
||||
def start(*args,**kwargs):
|
||||
f = func(*args,**kwargs)
|
||||
f.send(None)
|
||||
return f
|
||||
return start
|
||||
|
||||
# Sample coroutine
|
||||
@consumer
|
||||
def printer():
|
||||
while True:
|
||||
item = yield from receive(object)
|
||||
print(item)
|
||||
|
||||
# Example use.
|
||||
if __name__ == '__main__':
|
||||
follow('../../Data/stocklog.csv', printer())
|
||||
58
Solutions/8_6/coticker.py
Normal file
58
Solutions/8_6/coticker.py
Normal file
@@ -0,0 +1,58 @@
|
||||
# coticker.py
|
||||
from structure import Structure
|
||||
from validate import String, Integer, Float
|
||||
|
||||
class Ticker(Structure):
|
||||
name = String()
|
||||
price = Float()
|
||||
date = String()
|
||||
time = String()
|
||||
change = Float()
|
||||
open = Float()
|
||||
high = Float()
|
||||
low = Float()
|
||||
volume = Integer()
|
||||
|
||||
from cofollow import consumer, follow, receive
|
||||
from tableformat import create_formatter
|
||||
import csv
|
||||
|
||||
@consumer
|
||||
def to_csv(target):
|
||||
def producer():
|
||||
while True:
|
||||
yield line
|
||||
|
||||
reader = csv.reader(producer())
|
||||
while True:
|
||||
line = yield from receive(str)
|
||||
target.send(next(reader))
|
||||
|
||||
@consumer
|
||||
def create_ticker(target):
|
||||
while True:
|
||||
row = yield from receive(list)
|
||||
target.send(Ticker.from_row(row))
|
||||
|
||||
@consumer
|
||||
def negchange(target):
|
||||
while True:
|
||||
record = yield from receive(Ticker)
|
||||
if record.change < 0:
|
||||
target.send(record)
|
||||
|
||||
@consumer
|
||||
def ticker(fmt, fields):
|
||||
formatter = create_formatter('text')
|
||||
formatter.headings(fields)
|
||||
while True:
|
||||
rec = yield from receive(Ticker)
|
||||
row = [getattr(rec, name) for name in fields]
|
||||
formatter.row(row)
|
||||
|
||||
if __name__ == '__main__':
|
||||
follow('../../Data/stocklog.csv',
|
||||
to_csv(
|
||||
create_ticker(
|
||||
negchange(
|
||||
ticker('text', ['name','price','change'])))))
|
||||
72
Solutions/8_6/server.py
Normal file
72
Solutions/8_6/server.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# server.py
|
||||
|
||||
from socket import *
|
||||
from select import select
|
||||
from collections import deque
|
||||
|
||||
tasks = deque()
|
||||
recv_wait = {} # sock -> task
|
||||
send_wait = {} # sock -> task
|
||||
|
||||
def run():
|
||||
while any([tasks, recv_wait, send_wait]):
|
||||
while not tasks:
|
||||
can_recv, can_send, _ = select(recv_wait, send_wait, [])
|
||||
for s in can_recv:
|
||||
tasks.append(recv_wait.pop(s))
|
||||
for s in can_send:
|
||||
tasks.append(send_wait.pop(s))
|
||||
task = tasks.popleft()
|
||||
try:
|
||||
reason, resource = task.send(None)
|
||||
if reason == 'recv':
|
||||
recv_wait[resource] = task
|
||||
elif reason == 'send':
|
||||
send_wait[resource] = task
|
||||
else:
|
||||
raise RuntimeError('Unknown reason %r' % reason)
|
||||
except StopIteration:
|
||||
print('Task done')
|
||||
|
||||
class GenSocket:
|
||||
def __init__(self, sock):
|
||||
self.sock = sock
|
||||
|
||||
def accept(self):
|
||||
yield 'recv', self.sock
|
||||
client, addr = self.sock.accept()
|
||||
return GenSocket(client), addr
|
||||
|
||||
def recv(self, maxsize):
|
||||
yield 'recv', self.sock
|
||||
return self.sock.recv(maxsize)
|
||||
|
||||
def send(self, data):
|
||||
yield 'send', self.sock
|
||||
return self.sock.send(data)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.sock, name)
|
||||
|
||||
def tcp_server(address, handler):
|
||||
sock = GenSocket(socket(AF_INET, SOCK_STREAM))
|
||||
sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
|
||||
sock.bind(address)
|
||||
sock.listen(5)
|
||||
while True:
|
||||
client, addr = yield from sock.accept()
|
||||
tasks.append(handler(client, addr))
|
||||
|
||||
def echo_handler(client, address):
|
||||
print('Connection from', address)
|
||||
while True:
|
||||
data = yield from client.recv(1000)
|
||||
if not data:
|
||||
break
|
||||
yield from client.send(b'GOT:' + data)
|
||||
print('Connection closed')
|
||||
|
||||
if __name__ == '__main__':
|
||||
tasks.append(tcp_server(('',25000), echo_handler))
|
||||
run()
|
||||
|
||||
91
Solutions/8_6/structure.py
Normal file
91
Solutions/8_6/structure.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# structure.py
|
||||
|
||||
from validate import Validator, validated
|
||||
from collections import ChainMap
|
||||
|
||||
class StructureMeta(type):
|
||||
@classmethod
|
||||
def __prepare__(meta, clsname, bases):
|
||||
return ChainMap({}, Validator.validators)
|
||||
|
||||
@staticmethod
|
||||
def __new__(meta, name, bases, methods):
|
||||
methods = methods.maps[0]
|
||||
return super().__new__(meta, name, bases, methods)
|
||||
|
||||
class Structure(metaclass=StructureMeta):
|
||||
_fields = ()
|
||||
_types = ()
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name.startswith('_') or name in self._fields:
|
||||
super().__setattr__(name, value)
|
||||
else:
|
||||
raise AttributeError('No attribute %s' % name)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (type(self).__name__,
|
||||
', '.join(repr(getattr(self, name)) for name in self._fields))
|
||||
|
||||
def __iter__(self):
|
||||
for name in self._fields:
|
||||
yield getattr(self, name)
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, type(self)) and tuple(self) == tuple(other)
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row):
|
||||
rowdata = [ func(val) for func, val in zip(cls._types, row) ]
|
||||
return cls(*rowdata)
|
||||
|
||||
@classmethod
|
||||
def create_init(cls):
|
||||
'''
|
||||
Create an __init__ method from _fields
|
||||
'''
|
||||
args = ','.join(cls._fields)
|
||||
code = f'def __init__(self, {args}):\n'
|
||||
for name in cls._fields:
|
||||
code += f' self.{name} = {name}\n'
|
||||
locs = { }
|
||||
exec(code, locs)
|
||||
cls.__init__ = locs['__init__']
|
||||
|
||||
@classmethod
|
||||
def __init_subclass__(cls):
|
||||
# Apply the validated decorator to subclasses
|
||||
validate_attributes(cls)
|
||||
|
||||
def validate_attributes(cls):
|
||||
'''
|
||||
Class decorator that scans a class definition for Validators
|
||||
and builds a _fields variable that captures their definition order.
|
||||
'''
|
||||
validators = []
|
||||
for name, val in vars(cls).items():
|
||||
if isinstance(val, Validator):
|
||||
validators.append(val)
|
||||
|
||||
# Apply validated decorator to any callable with annotations
|
||||
elif callable(val) and val.__annotations__:
|
||||
setattr(cls, name, validated(val))
|
||||
|
||||
# Collect all of the field names
|
||||
cls._fields = tuple([v.name for v in validators])
|
||||
|
||||
# Collect type conversions. The lambda x:x is an identity
|
||||
# function that's used in case no expected_type is found.
|
||||
cls._types = tuple([ getattr(v, 'expected_type', lambda x: x)
|
||||
for v in validators ])
|
||||
|
||||
# Create the __init__ method
|
||||
if cls._fields:
|
||||
cls.create_init()
|
||||
|
||||
|
||||
return cls
|
||||
|
||||
def typed_structure(clsname, **validators):
|
||||
cls = type(clsname, (Structure,), validators)
|
||||
return cls
|
||||
82
Solutions/8_6/tableformat.py
Normal file
82
Solutions/8_6/tableformat.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# tableformat.py
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
def print_table(records, fields, formatter):
|
||||
if not isinstance(formatter, TableFormatter):
|
||||
raise RuntimeError('Expected a TableFormatter')
|
||||
|
||||
formatter.headings(fields)
|
||||
for r in records:
|
||||
rowdata = [getattr(r, fieldname) for fieldname in fields]
|
||||
formatter.row(rowdata)
|
||||
|
||||
class TableFormatter(ABC):
|
||||
@abstractmethod
|
||||
def headings(self, headers):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def row(self, rowdata):
|
||||
pass
|
||||
|
||||
class TextTableFormatter(TableFormatter):
|
||||
def headings(self, headers):
|
||||
print(' '.join('%10s' % h for h in headers))
|
||||
print(('-'*10 + ' ')*len(headers))
|
||||
|
||||
def row(self, rowdata):
|
||||
print(' '.join('%10s' % d for d in rowdata))
|
||||
|
||||
class CSVTableFormatter(TableFormatter):
|
||||
def headings(self, headers):
|
||||
print(','.join(headers))
|
||||
|
||||
def row(self, rowdata):
|
||||
print(','.join(str(d) for d in rowdata))
|
||||
|
||||
class HTMLTableFormatter(TableFormatter):
|
||||
def headings(self, headers):
|
||||
print('<tr>', end=' ')
|
||||
for h in headers:
|
||||
print('<th>%s</th>' % h, end=' ')
|
||||
print('</tr>')
|
||||
|
||||
def row(self, rowdata):
|
||||
print('<tr>', end=' ')
|
||||
for d in rowdata:
|
||||
print('<td>%s</td>' % d, end=' ')
|
||||
print('</tr>')
|
||||
|
||||
class ColumnFormatMixin:
|
||||
formats = []
|
||||
def row(self, rowdata):
|
||||
rowdata = [ (fmt % item) for fmt, item in zip(self.formats, rowdata)]
|
||||
super().row(rowdata)
|
||||
|
||||
class UpperHeadersMixin:
|
||||
def headings(self, headers):
|
||||
super().headings([h.upper() for h in headers])
|
||||
|
||||
def create_formatter(name, column_formats=None, upper_headers=False):
|
||||
if name == 'text':
|
||||
formatter_cls = TextTableFormatter
|
||||
elif name == 'csv':
|
||||
formatter_cls = CSVTableFormatter
|
||||
elif name == 'html':
|
||||
formatter_cls = HTMLTableFormatter
|
||||
else:
|
||||
raise RuntimeError('Unknown format %s' % name)
|
||||
|
||||
if column_formats:
|
||||
class formatter_cls(ColumnFormatMixin, formatter_cls):
|
||||
formats = column_formats
|
||||
|
||||
if upper_headers:
|
||||
class formatter_cls(UpperHeadersMixin, formatter_cls):
|
||||
pass
|
||||
|
||||
return formatter_cls()
|
||||
|
||||
|
||||
|
||||
|
||||
173
Solutions/8_6/validate.py
Normal file
173
Solutions/8_6/validate.py
Normal file
@@ -0,0 +1,173 @@
|
||||
# validate.py
|
||||
|
||||
class Validator:
|
||||
def __init__(self, name=None):
|
||||
self.name = name
|
||||
|
||||
def __set_name__(self, cls, name):
|
||||
self.name = name
|
||||
|
||||
@classmethod
|
||||
def check(cls, value):
|
||||
return value
|
||||
|
||||
def __set__(self, instance, value):
|
||||
instance.__dict__[self.name] = self.check(value)
|
||||
|
||||
# Collect all derived classes into a dict
|
||||
validators = { }
|
||||
@classmethod
|
||||
def __init_subclass__(cls):
|
||||
cls.validators[cls.__name__] = cls
|
||||
|
||||
class Typed(Validator):
|
||||
expected_type = object
|
||||
@classmethod
|
||||
def check(cls, value):
|
||||
if not isinstance(value, cls.expected_type):
|
||||
raise TypeError(f'expected {cls.expected_type}')
|
||||
return super().check(value)
|
||||
|
||||
_typed_classes = [
|
||||
('Integer', int),
|
||||
('Float', float),
|
||||
('String', str) ]
|
||||
|
||||
globals().update((name, type(name, (Typed,), {'expected_type':ty}))
|
||||
for name, ty in _typed_classes)
|
||||
|
||||
class Positive(Validator):
|
||||
@classmethod
|
||||
def check(cls, value):
|
||||
if value < 0:
|
||||
raise ValueError('must be >= 0')
|
||||
return super().check(value)
|
||||
|
||||
class NonEmpty(Validator):
|
||||
@classmethod
|
||||
def check(cls, value):
|
||||
if len(value) == 0:
|
||||
raise ValueError('must be non-empty')
|
||||
return super().check(value)
|
||||
|
||||
class PositiveInteger(Integer, Positive):
|
||||
pass
|
||||
|
||||
class PositiveFloat(Float, Positive):
|
||||
pass
|
||||
|
||||
class NonEmptyString(String, NonEmpty):
|
||||
pass
|
||||
|
||||
from inspect import signature
|
||||
from functools import wraps
|
||||
|
||||
def isvalidator(item):
|
||||
return isinstance(item, type) and issubclass(item, Validator)
|
||||
|
||||
def validated(func):
|
||||
sig = signature(func)
|
||||
|
||||
# Gather the function annotations
|
||||
annotations = { name:val for name, val in func.__annotations__.items()
|
||||
if isvalidator(val) }
|
||||
|
||||
# Get the return annotation (if any)
|
||||
retcheck = annotations.pop('return', None)
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
bound = sig.bind(*args, **kwargs)
|
||||
errors = []
|
||||
|
||||
# Enforce argument checks
|
||||
for name, validator in annotations.items():
|
||||
try:
|
||||
validator.check(bound.arguments[name])
|
||||
except Exception as e:
|
||||
errors.append(f' {name}: {e}')
|
||||
|
||||
if errors:
|
||||
raise TypeError('Bad Arguments\n' + '\n'.join(errors))
|
||||
|
||||
result = func(*args, **kwargs)
|
||||
|
||||
# Enforce return check (if any)
|
||||
if retcheck:
|
||||
try:
|
||||
retcheck.check(result)
|
||||
except Exception as e:
|
||||
raise TypeError(f'Bad return: {e}') from None
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
def enforce(**annotations):
|
||||
retcheck = annotations.pop('return_', None)
|
||||
|
||||
def decorate(func):
|
||||
sig = signature(func)
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
bound = sig.bind(*args, **kwargs)
|
||||
errors = []
|
||||
|
||||
# Enforce argument checks
|
||||
for name, validator in annotations.items():
|
||||
try:
|
||||
validator.check(bound.arguments[name])
|
||||
except Exception as e:
|
||||
errors.append(f' {name}: {e}')
|
||||
|
||||
if errors:
|
||||
raise TypeError('Bad Arguments\n' + '\n'.join(errors))
|
||||
|
||||
result = func(*args, **kwargs)
|
||||
|
||||
if retcheck:
|
||||
try:
|
||||
retcheck.check(result)
|
||||
except Exception as e:
|
||||
raise TypeError(f'Bad return: {e}') from None
|
||||
return result
|
||||
return wrapper
|
||||
return decorate
|
||||
|
||||
# Examples
|
||||
if __name__ == '__main__':
|
||||
@validated
|
||||
def add(x:Integer, y:Integer) -> Integer:
|
||||
return x + y
|
||||
|
||||
@validated
|
||||
def div(x:Integer, y:Integer) -> Integer:
|
||||
return x / y
|
||||
|
||||
@enforce(x=Integer, y=Integer)
|
||||
def sub(x, y):
|
||||
return x - y
|
||||
|
||||
class Stock:
|
||||
name = NonEmptyString()
|
||||
shares = PositiveInteger()
|
||||
price = PositiveFloat()
|
||||
def __init__(self, name, shares, price):
|
||||
self.name = name
|
||||
self.shares = shares
|
||||
self.price = price
|
||||
|
||||
def __repr__(self):
|
||||
return f'Stock({self.name!r}, {self.shares!r}, {self.price!r})'
|
||||
|
||||
@property
|
||||
def cost(self):
|
||||
return self.shares * self.price
|
||||
|
||||
@validated
|
||||
def sell(self, nshares:PositiveInteger):
|
||||
self.shares -= nshares
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user