metaprogramming examples

This commit is contained in:
Luciano Ramalho 2015-01-01 10:21:37 -02:00
parent 7a268a5b43
commit 8e2b8d90e5
18 changed files with 13902 additions and 11 deletions

View File

@ -60,4 +60,4 @@ class LineItem:
self.__weight = value # <6>
else:
raise ValueError('value must be > 0') # <7>
# END LINEITEM_V2
# END LINEITEM_V2

View File

@ -33,6 +33,7 @@ No change was made::
"""
# BEGIN LINEITEM_V3
class Quantity: # <1>

View File

@ -34,6 +34,7 @@ No change was made::
"""
class Quantity:
def __init__(self, storage_name):

View File

@ -45,23 +45,23 @@ class Quantity:
def __init__(self):
cls = self.__class__ # <2>
prefix = cls.__name__ # <3>
index = cls.__counter # <4>
self.storage_name = '_{}_{}'.format(prefix, index) # <5>
cls.__counter += 1 # <6>
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}_{}'.format(prefix, index) # <3>
cls.__counter += 1 # <4>
def __get__(self, instance, owner): # <7>
return getattr(instance, self.storage_name) # <8>
def __get__(self, instance, owner): # <5>
return getattr(instance, self.storage_name) # <6>
def __set__(self, instance, value): # <9>
def __set__(self, instance, value):
if value > 0:
setattr(instance, self.storage_name, value) # <10>
setattr(instance, self.storage_name, value) # <7>
else:
raise ValueError('value must be > 0')
class LineItem:
weight = Quantity() # <11>
weight = Quantity() # <8>
price = Quantity()
def __init__(self, description, weight, price):
@ -71,4 +71,4 @@ class LineItem:
def subtotal(self):
return self.weight * self.price
# END LINEITEM_V4
# END LINEITEM_V4

View File

@ -0,0 +1,86 @@
"""
A line item for a bulk food order has description, weight and price fields::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.weight, raisins.description, raisins.price
(10, 'Golden raisins', 6.95)
A ``subtotal`` method gives the total price for that line item::
>>> raisins.subtotal()
69.5
The weight of a ``LineItem`` must be greater than 0::
>>> raisins.weight = -20
Traceback (most recent call last):
...
ValueError: value must be > 0
No change was made::
>>> raisins.weight
10
The value of the attributes managed by the descriptors are stored in
alternate attributes, created by the descriptors in each ``LineItem``
instance::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> dir(raisins) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
['_Quantity_0', '_Quantity_1', '__class__', ...
'description', 'price', 'subtotal', 'weight']
>>> raisins._Quantity_0
10
>>> raisins._Quantity_1
6.95
If the descriptor is accessed in the class, the descriptor object is
returned:
>>> LineItem.price # doctest: +ELLIPSIS
<bulkfood_v4b.Quantity object at 0x...>
>>> br_nuts = LineItem('Brazil nuts', 10, 34.95)
>>> br_nuts.price
34.95
"""
# BEGIN LINEITEM_V4B
class Quantity:
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}_{}'.format(prefix, index)
cls.__counter += 1
def __get__(self, instance, owner):
if instance is None:
return self # <1>
else:
return getattr(instance, self.storage_name) # <2>
def __set__(self, instance, value):
if value > 0:
setattr(instance, self.storage_name, value)
else:
raise ValueError('value must be > 0')
# END LINEITEM_V4B
class LineItem:
weight = Quantity()
price = Quantity()
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price

View File

@ -0,0 +1,65 @@
"""
A line item for a bulk food order has description, weight and price fields::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> raisins.weight, raisins.description, raisins.price
(10, 'Golden raisins', 6.95)
A ``subtotal`` method gives the total price for that line item::
>>> raisins.subtotal()
69.5
The weight of a ``LineItem`` must be greater than 0::
>>> raisins.weight = -20
Traceback (most recent call last):
...
ValueError: value must be > 0
No change was made::
>>> raisins.weight
10
The value of the attributes managed by the descriptors are stored in
alternate attributes, created by the descriptors in each ``LineItem``
instance::
>>> raisins = LineItem('Golden raisins', 10, 6.95)
>>> dir(raisins) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
['_Quantity_0', '_Quantity_1', '__class__', ...
'description', 'price', 'subtotal', 'weight']
>>> raisins._Quantity_0
10
>>> raisins._Quantity_1
6.95
If the descriptor is accessed in the class, the descriptor object is
returned:
>>> LineItem.price # doctest: +ELLIPSIS
<model_v4c.Quantity object at 0x...>
>>> br_nuts = LineItem('Brazil nuts', 10, 34.95)
>>> br_nuts.price
34.95
"""
# BEGIN LINEITEM_V4C
import model_v4c as model # <1>
class LineItem:
weight = model.Quantity() # <2>
price = model.Quantity()
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
# END LINEITEM_V4C

23
descriptors/model_v4c.py Normal file
View File

@ -0,0 +1,23 @@
# BEGIN MODEL_V4
class Quantity:
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = '_{}_{}'.format(prefix, index)
cls.__counter += 1
def __get__(self, instance, owner):
if instance is None:
return self
else:
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
if value > 0:
setattr(instance, self.storage_name, value)
else:
raise ValueError('value must be > 0')
# END MODEL_V4

View File

@ -0,0 +1,43 @@
"""
explore0.py: Script to download and explore the OSCON schedule feed
>>> feed = load_json()
>>> sorted(feed['Schedule'].keys())
['conferences', 'events', 'speakers', 'venues']
>>> for key, value in sorted(feed['Schedule'].items()):
... print('{:3} {}'.format(len(value), key))
...
1 conferences
484 events
357 speakers
53 venues
>>> feed['Schedule']['speakers'][-1]['name']
'Carina C. Zona'
>>> carina = feed['Schedule']['speakers'][-1]
>>> carina['twitter']
'cczona'
>>> feed['Schedule']['events'][40]['name']
'There *Will* Be Bugs'
>>> feed['Schedule']['events'][40]['speakers']
[3471, 5199]
"""
from urllib.request import urlopen
import warnings
import os
import json
URL = 'http://www.oreilly.com/pub/sc/osconfeed'
JSON_NAME = 'osconfeed.json'
def load_json():
if not os.path.exists(JSON_NAME):
msg = 'downloading {} to {}'.format(URL, JSON_NAME)
warnings.warn(msg)
with urlopen(URL) as remote, open(JSON_NAME, 'wb') as local:
local.write(remote.read())
with open(JSON_NAME) as fp:
return json.load(fp)

View File

@ -0,0 +1,69 @@
"""
explore.py: Script to download and explore the OSCON schedule feed
>>> raw_feed = load_json()
>>> feed = FrozenJSON(raw_feed)
>>> sorted(feed.Schedule.keys())
['conferences', 'events', 'speakers', 'venues']
>>> for key, value in sorted(feed.Schedule.items()):
... print('{:3} {}'.format(len(value), key))
...
1 conferences
484 events
357 speakers
53 venues
>>> feed.Schedule.speakers[-1].name
'Carina C. Zona'
>>> carina = feed.Schedule.speakers[-1]
>>> carina.twitter
'cczona'
>>> feed.Schedule.events[40].name
'There *Will* Be Bugs'
>>> feed.Schedule.events[40].speakers
[3471, 5199]
"""
from urllib.request import urlopen
import warnings
import os
import json
from collections import abc
URL = 'http://www.oreilly.com/pub/sc/osconfeed'
JSON_NAME = 'osconfeed.json'
def load_json():
if not os.path.exists(JSON_NAME):
msg = 'downloading {} to {}'.format(URL, JSON_NAME)
warnings.warn(msg)
with urlopen(URL) as remote, open(JSON_NAME, 'wb') as local:
local.write(remote.read())
with open(JSON_NAME) as fp:
return json.load(fp)
class FrozenJSON:
"""A read-only façade for navigating a JSON-like object
using attribute notation
"""
def __init__(self, mapping):
self._data = dict(mapping)
def __getattr__(self, name):
if hasattr(self._data, name):
return getattr(self._data, name)
else:
return FrozenJSON.build(self._data[name])
@classmethod
def build(cls, obj):
if isinstance(obj, abc.Mapping):
return cls(obj)
elif isinstance(obj, abc.MutableSequence):
return [cls.build(item) for item in obj]
else:
return obj

View File

@ -0,0 +1,68 @@
"""
explore2.py: Script to download and explore the OSCON schedule feed
>>> raw_feed = load_json()
>>> feed = FrozenJSON(raw_feed)
>>> sorted(feed.Schedule.keys())
['conferences', 'events', 'speakers', 'venues']
>>> for key, value in sorted(feed.Schedule.items()):
... print('{:3} {}'.format(len(value), key))
...
1 conferences
484 events
357 speakers
53 venues
>>> feed.Schedule.speakers[-1].name
'Carina C. Zona'
>>> carina = feed.Schedule.speakers[-1]
>>> carina.twitter
'cczona'
>>> feed.Schedule.events[40].name
'There *Will* Be Bugs'
>>> feed.Schedule.events[40].speakers
[3471, 5199]
"""
from urllib.request import urlopen
import warnings
import os
import json
from collections import abc
URL = 'http://www.oreilly.com/pub/sc/osconfeed'
JSON_NAME = 'osconfeed.json'
def load_json():
if not os.path.exists(JSON_NAME):
msg = 'downloading {} to {}'.format(URL, JSON_NAME)
warnings.warn(msg)
with urlopen(URL) as remote, open(JSON_NAME, 'wb') as local:
local.write(remote.read())
with open(JSON_NAME) as fp:
return json.load(fp)
class FrozenJSON:
"""A read-only façade for navigating a JSON-like object
using attribute notation
"""
def __new__(cls, arg):
if isinstance(arg, abc.Mapping):
return super().__new__(cls)
elif isinstance(arg, abc.MutableSequence):
return [FrozenJSON(item) for item in arg]
else:
return arg
def __init__(self, mapping):
self._data = dict(mapping)
def __getattr__(self, name):
if hasattr(self._data, name):
return getattr(self._data, name)
else:
return FrozenJSON(self._data[name])

View File

@ -0,0 +1,32 @@
{ "Schedule":
{ "conferences": [{"serial": 115 }],
"events": [
{ "serial": 34505,
"name": "Why Schools Don&#x27;t Use Open Source to Teach Programming",
"event_type": "40-minute conference session",
"time_start": "2014-07-23 11:30:00",
"time_stop": "2014-07-23 12:10:00",
"venue_serial": 1462,
"description": "Aside from the fact that high school programming curricula often require proprietary IDEs, they also don&#x27;t involve examining any source code from Open Source software projects. What changes would be required in programming curricula to incorporate Open Source? And is that a desirable objective?",
"website_url": "http://oscon.com/oscon2014/public/schedule/detail/34505",
"speakers": [157509],
"categories": ["Education"] }
],
"speakers": [
{ "serial": 157509,
"name": "Robert Lefkowitz",
"photo": null,
"url": "http://sharewave.com/",
"position": "CTO",
"affiliation": "Sharewave",
"twitter": "sharewaveteam",
"bio": "Robert 'r0ml' Lefkowitz is the CTO at Sharewave, a startup building an investor management portal. This year, he was a resident at the winter session of Hacker School. He is a Distinguished Engineer of the ACM." }
],
"venues": [
{ "serial": 1462,
"name": "F151",
"category": "Conference Venues" }
]
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
"""
>>> db = shelve.open(DB_NAME)
>>> if CONFERENCE not in db: load_db(db)
>>> event = db['event.33950']
>>> record = db['speaker.3471']
>>> record.name
'Anna Martelli Ravenscroft'
>>> record.twitter
'annaraven'
>>> db.close()
"""
import warnings
import shelve
from explore import load_json
DB_NAME = 'schedule_db'
CONFERENCE = 'conference.115'
class Record:
def __init__(self, mapping):
self.__dict__.update(mapping)
def load_db(db):
raw_data = load_json()
warnings.warn('loading ' + DB_NAME)
for collection, rec_list in raw_data['Schedule'].items():
rec_type = collection[:-1]
for fields in rec_list:
key = '{}.{}'.format(rec_type, fields['serial'])
db[key] = Record(fields)

View File

@ -0,0 +1,62 @@
import warnings
import inspect
from explore import load_json
DB_NAME = 'schedule2_db'
CONFERENCE = 'conference.115'
class Record:
def __init__(self, mapping):
self.__dict__.update(mapping)
def __eq__(self, other):
if isinstance(other, Record):
return self.__dict__ == other.__dict__
else:
return NotImplemented
def __repr__(self):
if hasattr(self, 'name'):
ident = repr(self.name)
else:
ident = 'object at ' + hex(id(self))
cls_name = self.__class__.__name__
return '<{} {}>'.format(cls_name, ident)
class Event(Record):
@classmethod
def set_db(cls, db):
cls._db = db
@property
def venue(self):
key = self.venue_serial
return self._db['venue.{}'.format(key)]
@property
def speakers(self):
spkr_serials = self.__dict__['speakers']
if not hasattr(self, '_speaker_refs'):
self._speaker_refs = [self._db['speaker.{}'.format(key)]
for key in spkr_serials]
return self._speaker_refs
def load_db(db):
raw_data = load_json()
warnings.warn('loading ' + DB_NAME)
for collection, rec_list in raw_data['Schedule'].items():
rec_type = collection[:-1]
for fields in rec_list:
cls_name = rec_type.capitalize()
cls = globals().get(cls_name, Record)
if inspect.isclass(cls) and issubclass(cls, Record):
record = cls(fields)
else:
Record(fields)
key = '{}.{}'.format(rec_type, fields['serial'])
db[key] = record

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,37 @@
import shelve
import pytest
import schedule
@pytest.yield_fixture
def db():
with shelve.open(schedule.DB_NAME) as the_db:
if schedule.CONFERENCE not in the_db:
schedule.load_db(the_db)
yield the_db
def test_record_class():
rec = schedule.Record({'spam': 99, 'eggs': 12})
assert rec.spam == 99
assert rec.eggs == 12
def test_conference_record(db):
assert schedule.CONFERENCE in db
def test_speaker_record(db):
speaker = db['speaker.3471']
assert speaker.name == 'Anna Martelli Ravenscroft'
def test_event_record(db):
event = db['event.33950']
assert event.name == 'There *Will* Be Bugs'
def test_event_venue(db):
event = db['event.33950']
assert event.venue_serial == 1449

View File

@ -0,0 +1,61 @@
import shelve
import pytest
import schedule2 as schedule
@pytest.yield_fixture
def db():
with shelve.open(schedule.DB_NAME) as the_db:
if schedule.CONFERENCE not in the_db:
schedule.load_db(the_db)
yield the_db
def test_record_attr_access():
rec = schedule.Record({'spam': 99, 'eggs': 12})
assert rec.spam == 99
assert rec.eggs == 12
def test_record_repr():
rec = schedule.Record({'spam': 99, 'eggs': 12})
assert repr(rec).startswith('<Record object at 0x')
rec2 = schedule.Record({'name': 'Fido'})
assert repr(rec2) == "<Record 'Fido'>"
def test_conference_record(db):
assert schedule.CONFERENCE in db
def test_speaker_record(db):
speaker = db['speaker.3471']
assert speaker.name == 'Anna Martelli Ravenscroft'
def test_event_record(db):
event = db['event.33950']
assert event.name == 'There *Will* Be Bugs'
def test_event_venue(db):
schedule.Event.set_db(db)
event = db['event.33950']
assert event.venue_serial == 1449
assert event.venue == db['venue.1449']
assert event.venue.name == 'Portland 251'
def test_event_speakers(db):
schedule.Event.set_db(db)
event = db['event.33950']
assert len(event.speakers) == 2
anna_and_alex = [db['speaker.3471'], db['speaker.5199']]
assert event.speakers == anna_and_alex
def test_event_no_speakers(db):
schedule.Event.set_db(db)
event = db['event.36848']
assert len(event.speakers) == 0