renumbering chapters >= 19
This commit is contained in:
4
22-dyn-attr-prop/README.rst
Normal file
4
22-dyn-attr-prop/README.rst
Normal file
@@ -0,0 +1,4 @@
|
||||
Sample code for Chapter 19 - "Dynamic attributes and properties"
|
||||
|
||||
From the book "Fluent Python" by Luciano Ramalho (O'Reilly, 2015)
|
||||
http://shop.oreilly.com/product/0636920032519.do
|
||||
44
22-dyn-attr-prop/blackknight.py
Normal file
44
22-dyn-attr-prop/blackknight.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
This class is inspired by the Black Knight scene in the movie
|
||||
"Monty Python and the Holy Grail", where King Arthur fights the
|
||||
Black Knight, slicing off his arms and legs, but the knight
|
||||
refuses to concede defeat.
|
||||
|
||||
# tag::BLACK_KNIGHT_DEMO[]
|
||||
>>> knight = BlackKnight()
|
||||
>>> knight.member
|
||||
next member is:
|
||||
'an arm'
|
||||
>>> del knight.member
|
||||
BLACK KNIGHT (loses an arm) -- 'Tis but a scratch.
|
||||
>>> del knight.member
|
||||
BLACK KNIGHT (loses another arm) -- It's just a flesh wound.
|
||||
>>> del knight.member
|
||||
BLACK KNIGHT (loses a leg) -- I'm invincible!
|
||||
>>> del knight.member
|
||||
BLACK KNIGHT (loses another leg) -- All right, we'll call it a draw.
|
||||
|
||||
# end::BLACK_KNIGHT_DEMO[]
|
||||
"""
|
||||
|
||||
# tag::BLACK_KNIGHT[]
|
||||
class BlackKnight:
|
||||
|
||||
def __init__(self):
|
||||
self.phrases = [
|
||||
('an arm', "'Tis but a scratch."),
|
||||
('another arm', "It's just a flesh wound."),
|
||||
('a leg', "I'm invincible!"),
|
||||
('another leg', "All right, we'll call it a draw.")
|
||||
]
|
||||
|
||||
@property
|
||||
def member(self):
|
||||
print('next member is:')
|
||||
return self.phrases[0][0]
|
||||
|
||||
@member.deleter
|
||||
def member(self):
|
||||
member, text = self.phrases.pop(0)
|
||||
print(f'BLACK KNIGHT (loses {member}) -- {text}')
|
||||
# end::BLACK_KNIGHT[]
|
||||
37
22-dyn-attr-prop/bulkfood/bulkfood_v1.py
Normal file
37
22-dyn-attr-prop/bulkfood/bulkfood_v1.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
A line item for a bulk food order has description, weight and price fields.
|
||||
A ``subtotal`` method gives the total price for that line item::
|
||||
|
||||
>>> raisins = LineItem('Golden raisins', 10, 6.95)
|
||||
>>> raisins.weight, raisins.description, raisins.price
|
||||
(10, 'Golden raisins', 6.95)
|
||||
>>> raisins.subtotal()
|
||||
69.5
|
||||
|
||||
But, without validation, these public attributes can cause trouble::
|
||||
|
||||
# tag::LINEITEM_PROBLEM_V1[]
|
||||
|
||||
>>> raisins = LineItem('Golden raisins', 10, 6.95)
|
||||
>>> raisins.subtotal()
|
||||
69.5
|
||||
>>> raisins.weight = -20 # garbage in...
|
||||
>>> raisins.subtotal() # garbage out...
|
||||
-139.0
|
||||
|
||||
# end::LINEITEM_PROBLEM_V1[]
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# tag::LINEITEM_V1[]
|
||||
class LineItem:
|
||||
|
||||
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_V1[]
|
||||
63
22-dyn-attr-prop/bulkfood/bulkfood_v2.py
Normal file
63
22-dyn-attr-prop/bulkfood/bulkfood_v2.py
Normal file
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
|
||||
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 check is also performed on instantiation::
|
||||
|
||||
>>> walnuts = LineItem('walnuts', 0, 10.00)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: value must be > 0
|
||||
|
||||
The proteced attribute can still be accessed if needed for some reason, such as
|
||||
white box testing)::
|
||||
|
||||
>>> raisins._LineItem__weight
|
||||
10
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# tag::LINEITEM_V2[]
|
||||
class LineItem:
|
||||
|
||||
def __init__(self, description, weight, price):
|
||||
self.description = description
|
||||
self.weight = weight # <1>
|
||||
self.price = price
|
||||
|
||||
def subtotal(self):
|
||||
return self.weight * self.price
|
||||
|
||||
@property # <2>
|
||||
def weight(self): # <3>
|
||||
return self.__weight # <4>
|
||||
|
||||
@weight.setter # <5>
|
||||
def weight(self, value):
|
||||
if value > 0:
|
||||
self.__weight = value # <6>
|
||||
else:
|
||||
raise ValueError('value must be > 0') # <7>
|
||||
# end::LINEITEM_V2[]
|
||||
64
22-dyn-attr-prop/bulkfood/bulkfood_v2b.py
Normal file
64
22-dyn-attr-prop/bulkfood/bulkfood_v2b.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""
|
||||
|
||||
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 check is also performed on instantiation::
|
||||
|
||||
>>> walnuts = LineItem('walnuts', 0, 10.00)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: value must be > 0
|
||||
|
||||
The proteced attribute can still be accessed if needed for some reason, such as
|
||||
white box testing)::
|
||||
|
||||
>>> raisins._LineItem__weight
|
||||
10
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# tag::LINEITEM_V2B[]
|
||||
class LineItem:
|
||||
|
||||
def __init__(self, description, weight, price):
|
||||
self.description = description
|
||||
self.weight = weight
|
||||
self.price = price
|
||||
|
||||
def subtotal(self):
|
||||
return self.weight * self.price
|
||||
|
||||
def get_weight(self): # <1>
|
||||
return self.__weight
|
||||
|
||||
def set_weight(self, value): # <2>
|
||||
if value > 0:
|
||||
self.__weight = value
|
||||
else:
|
||||
raise ValueError('value must be > 0')
|
||||
|
||||
weight = property(get_weight, set_weight) # <3>
|
||||
|
||||
# end::LINEITEM_V2B[]
|
||||
69
22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py
Normal file
69
22-dyn-attr-prop/bulkfood/bulkfood_v2prop.py
Normal file
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
|
||||
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 properties are stored in
|
||||
instance attributes, created in each ``LineItem`` instance::
|
||||
|
||||
# tag::LINEITEM_V2_PROP_DEMO[]
|
||||
>>> nutmeg = LineItem('Moluccan nutmeg', 8, 13.95)
|
||||
>>> nutmeg.weight, nutmeg.price # <1>
|
||||
(8, 13.95)
|
||||
>>> sorted(vars(nutmeg).items()) # <2>
|
||||
[('description', 'Moluccan nutmeg'), ('price', 13.95), ('weight', 8)]
|
||||
|
||||
# end::LINEITEM_V2_PROP_DEMO[]
|
||||
|
||||
"""
|
||||
|
||||
|
||||
# tag::LINEITEM_V2_PROP_FACTORY_FUNCTION[]
|
||||
def quantity(storage_name): # <1>
|
||||
|
||||
def qty_getter(instance): # <2>
|
||||
return instance.__dict__[storage_name] # <3>
|
||||
|
||||
def qty_setter(instance, value): # <4>
|
||||
if value > 0:
|
||||
instance.__dict__[storage_name] = value # <5>
|
||||
else:
|
||||
raise ValueError('value must be > 0')
|
||||
|
||||
return property(qty_getter, qty_setter) # <6>
|
||||
# end::LINEITEM_V2_PROP_FACTORY_FUNCTION[]
|
||||
|
||||
|
||||
# tag::LINEITEM_V2_PROP_CLASS[]
|
||||
class LineItem:
|
||||
weight = quantity('weight') # <1>
|
||||
price = quantity('price') # <2>
|
||||
|
||||
def __init__(self, description, weight, price):
|
||||
self.description = description
|
||||
self.weight = weight # <3>
|
||||
self.price = price
|
||||
|
||||
def subtotal(self):
|
||||
return self.weight * self.price # <4>
|
||||
# end::LINEITEM_V2_PROP_CLASS[]
|
||||
23
22-dyn-attr-prop/doc_property.py
Normal file
23
22-dyn-attr-prop/doc_property.py
Normal file
@@ -0,0 +1,23 @@
|
||||
"""
|
||||
Example of property documentation
|
||||
|
||||
>>> f = Foo()
|
||||
>>> f.bar = 77
|
||||
>>> f.bar
|
||||
77
|
||||
>>> Foo.bar.__doc__
|
||||
'The bar attribute'
|
||||
"""
|
||||
|
||||
# tag::DOC_PROPERTY[]
|
||||
class Foo:
|
||||
|
||||
@property
|
||||
def bar(self):
|
||||
'''The bar attribute'''
|
||||
return self.__dict__['bar']
|
||||
|
||||
@bar.setter
|
||||
def bar(self, value):
|
||||
self.__dict__['bar'] = value
|
||||
# end::DOC_PROPERTY[]
|
||||
13307
22-dyn-attr-prop/oscon/data/osconfeed.json
Normal file
13307
22-dyn-attr-prop/oscon/data/osconfeed.json
Normal file
File diff suppressed because it is too large
Load Diff
25
22-dyn-attr-prop/oscon/demo_schedule2.py
Executable file
25
22-dyn-attr-prop/oscon/demo_schedule2.py
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import shelve
|
||||
|
||||
from schedule_v2 import DB_NAME, CONFERENCE, load_db
|
||||
from schedule_v2 import DbRecord, Event
|
||||
|
||||
with shelve.open(DB_NAME) as db:
|
||||
if CONFERENCE not in db:
|
||||
load_db(db)
|
||||
|
||||
DbRecord.set_db(db)
|
||||
event = DbRecord.fetch('event.33950')
|
||||
print(event)
|
||||
print(event.venue)
|
||||
print(event.venue.name)
|
||||
for spkr in event.speakers:
|
||||
print(f'{spkr.serial}:', spkr.name)
|
||||
|
||||
print(repr(Event.venue))
|
||||
|
||||
event2 = DbRecord.fetch('event.33451')
|
||||
print(event2)
|
||||
print(event2.fetch)
|
||||
print(event2.venue)
|
||||
65
22-dyn-attr-prop/oscon/explore0.py
Normal file
65
22-dyn-attr-prop/oscon/explore0.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""
|
||||
explore0.py: Script to explore the OSCON schedule feed
|
||||
|
||||
# tag::EXPLORE0_DEMO[]
|
||||
>>> import json
|
||||
>>> raw_feed = json.load(open('data/osconfeed.json'))
|
||||
>>> feed = FrozenJSON(raw_feed) # <1>
|
||||
>>> len(feed.Schedule.speakers) # <2>
|
||||
357
|
||||
>>> feed.keys()
|
||||
dict_keys(['Schedule'])
|
||||
>>> sorted(feed.Schedule.keys()) # <3>
|
||||
['conferences', 'events', 'speakers', 'venues']
|
||||
>>> for key, value in sorted(feed.Schedule.items()): # <4>
|
||||
... print(f'{len(value):3} {key}')
|
||||
...
|
||||
1 conferences
|
||||
484 events
|
||||
357 speakers
|
||||
53 venues
|
||||
>>> feed.Schedule.speakers[-1].name # <5>
|
||||
'Carina C. Zona'
|
||||
>>> talk = feed.Schedule.events[40]
|
||||
>>> type(talk) # <6>
|
||||
<class 'explore0.FrozenJSON'>
|
||||
>>> talk.name
|
||||
'There *Will* Be Bugs'
|
||||
>>> talk.speakers # <7>
|
||||
[3471, 5199]
|
||||
>>> talk.flavor # <8>
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'flavor'
|
||||
|
||||
# end::EXPLORE0_DEMO[]
|
||||
|
||||
"""
|
||||
|
||||
# tag::EXPLORE0[]
|
||||
from collections import abc
|
||||
|
||||
|
||||
class FrozenJSON:
|
||||
"""A read-only façade for navigating a JSON-like object
|
||||
using attribute notation
|
||||
"""
|
||||
|
||||
def __init__(self, mapping):
|
||||
self.__data = dict(mapping) # <1>
|
||||
|
||||
def __getattr__(self, name): # <2>
|
||||
try:
|
||||
return getattr(self.__data, name) # <3>
|
||||
except AttributeError:
|
||||
return FrozenJSON.build(self.__data[name]) # <4>
|
||||
|
||||
@classmethod
|
||||
def build(cls, obj): # <5>
|
||||
if isinstance(obj, abc.Mapping): # <6>
|
||||
return cls(obj)
|
||||
elif isinstance(obj, abc.MutableSequence): # <7>
|
||||
return [cls.build(item) for item in obj]
|
||||
else: # <8>
|
||||
return obj
|
||||
# end::EXPLORE0[]
|
||||
78
22-dyn-attr-prop/oscon/explore1.py
Normal file
78
22-dyn-attr-prop/oscon/explore1.py
Normal file
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
explore1.py: Script to explore the OSCON schedule feed
|
||||
|
||||
>>> import json
|
||||
>>> raw_feed = json.load(open('data/osconfeed.json'))
|
||||
>>> feed = FrozenJSON(raw_feed)
|
||||
>>> len(feed.Schedule.speakers)
|
||||
357
|
||||
>>> sorted(feed.Schedule.keys())
|
||||
['conferences', 'events', 'speakers', 'venues']
|
||||
>>> for key, value in sorted(feed.Schedule.items()):
|
||||
... print(f'{len(value):3} {key}')
|
||||
...
|
||||
1 conferences
|
||||
484 events
|
||||
357 speakers
|
||||
53 venues
|
||||
>>> feed.Schedule.speakers[-1].name
|
||||
'Carina C. Zona'
|
||||
>>> talk = feed.Schedule.events[40]
|
||||
>>> type(talk)
|
||||
<class 'explore1.FrozenJSON'>
|
||||
>>> talk.name
|
||||
'There *Will* Be Bugs'
|
||||
>>> talk.speakers
|
||||
[3471, 5199]
|
||||
>>> talk.flavor
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'flavor'
|
||||
|
||||
Handle keywords by appending a `_`.
|
||||
|
||||
# tag::EXPLORE1_DEMO[]
|
||||
|
||||
>>> grad = FrozenJSON({'name': 'Jim Bo', 'class': 1982})
|
||||
>>> grad.name
|
||||
'Jim Bo'
|
||||
>>> grad.class_
|
||||
1982
|
||||
|
||||
# end::EXPLORE1_DEMO[]
|
||||
|
||||
"""
|
||||
|
||||
from collections import abc
|
||||
import keyword
|
||||
|
||||
|
||||
class FrozenJSON:
|
||||
"""A read-only façade for navigating a JSON-like object
|
||||
using attribute notation
|
||||
"""
|
||||
|
||||
# tag::EXPLORE1[]
|
||||
def __init__(self, mapping):
|
||||
self.__data = {}
|
||||
for key, value in mapping.items():
|
||||
if keyword.iskeyword(key): # <1>
|
||||
key += '_'
|
||||
self.__data[key] = value
|
||||
# end::EXPLORE1[]
|
||||
|
||||
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: # <8>
|
||||
return obj
|
||||
|
||||
54
22-dyn-attr-prop/oscon/explore2.py
Normal file
54
22-dyn-attr-prop/oscon/explore2.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
explore2.py: Script to explore the OSCON schedule feed
|
||||
|
||||
>>> import json
|
||||
>>> raw_feed = json.load(open('data/osconfeed.json'))
|
||||
>>> feed = FrozenJSON(raw_feed)
|
||||
>>> len(feed.Schedule.speakers)
|
||||
357
|
||||
>>> sorted(feed.Schedule.keys())
|
||||
['conferences', 'events', 'speakers', 'venues']
|
||||
>>> feed.Schedule.speakers[-1].name
|
||||
'Carina C. Zona'
|
||||
>>> talk = feed.Schedule.events[40]
|
||||
>>> talk.name
|
||||
'There *Will* Be Bugs'
|
||||
>>> talk.speakers
|
||||
[3471, 5199]
|
||||
>>> talk.flavor
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'flavor'
|
||||
|
||||
"""
|
||||
|
||||
# tag::EXPLORE2[]
|
||||
from collections import abc
|
||||
import keyword
|
||||
|
||||
class FrozenJSON:
|
||||
"""A read-only façade for navigating a JSON-like object
|
||||
using attribute notation
|
||||
"""
|
||||
|
||||
def __new__(cls, arg): # <1>
|
||||
if isinstance(arg, abc.Mapping):
|
||||
return super().__new__(cls) # <2>
|
||||
elif isinstance(arg, abc.MutableSequence): # <3>
|
||||
return [cls(item) for item in arg]
|
||||
else:
|
||||
return arg
|
||||
|
||||
def __init__(self, mapping):
|
||||
self.__data = {}
|
||||
for key, value in mapping.items():
|
||||
if keyword.iskeyword(key):
|
||||
key += '_'
|
||||
self.__data[key] = value
|
||||
|
||||
def __getattr__(self, name):
|
||||
if hasattr(self.__data, name):
|
||||
return getattr(self.__data, name)
|
||||
else:
|
||||
return FrozenJSON(self.__data[name]) # <4>
|
||||
# end::EXPLORE2[]
|
||||
32
22-dyn-attr-prop/oscon/osconfeed-sample.json
Normal file
32
22-dyn-attr-prop/oscon/osconfeed-sample.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{ "Schedule":
|
||||
{ "conferences": [{"serial": 115 }],
|
||||
"events": [
|
||||
{ "serial": 34505,
|
||||
"name": "Why Schools Don´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...",
|
||||
"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..." }
|
||||
],
|
||||
"venues": [
|
||||
{ "serial": 1462,
|
||||
"name": "F151",
|
||||
"category": "Conference Venues" }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
20
22-dyn-attr-prop/oscon/osconfeed_explore.rst
Normal file
20
22-dyn-attr-prop/oscon/osconfeed_explore.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
>>> import json
|
||||
>>> with open('data/osconfeed.json') as fp:
|
||||
... feed = json.load(fp) # <1>
|
||||
>>> sorted(feed['Schedule'].keys()) # <2>
|
||||
['conferences', 'events', 'speakers', 'venues']
|
||||
>>> for key, value in sorted(feed['Schedule'].items()):
|
||||
... print(f'{len(value):3} {key}') # <3>
|
||||
...
|
||||
1 conferences
|
||||
484 events
|
||||
357 speakers
|
||||
53 venues
|
||||
>>> feed['Schedule']['speakers'][-1]['name'] # <4>
|
||||
'Carina C. Zona'
|
||||
>>> feed['Schedule']['speakers'][-1]['serial'] # <5>
|
||||
141590
|
||||
>>> feed['Schedule']['events'][40]['name']
|
||||
'There *Will* Be Bugs'
|
||||
>>> feed['Schedule']['events'][40]['speakers'] # <6>
|
||||
[3471, 5199]
|
||||
2
22-dyn-attr-prop/oscon/runtests.sh
Executable file
2
22-dyn-attr-prop/oscon/runtests.sh
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
pytest --doctest-modules $2 $1 test_$1
|
||||
39
22-dyn-attr-prop/oscon/schedule_v1.py
Normal file
39
22-dyn-attr-prop/oscon/schedule_v1.py
Normal file
@@ -0,0 +1,39 @@
|
||||
"""
|
||||
schedule_v1.py: traversing OSCON schedule data
|
||||
|
||||
# tag::SCHEDULE1_DEMO[]
|
||||
>>> records = load(JSON_PATH) # <1>
|
||||
>>> speaker = records['speaker.3471'] # <2>
|
||||
>>> speaker # <3>
|
||||
<Record serial=3471>
|
||||
>>> speaker.name, speaker.twitter # <4>
|
||||
('Anna Martelli Ravenscroft', 'annaraven')
|
||||
|
||||
# end::SCHEDULE1_DEMO[]
|
||||
|
||||
"""
|
||||
|
||||
# tag::SCHEDULE1[]
|
||||
import json
|
||||
|
||||
JSON_PATH = 'data/osconfeed.json'
|
||||
|
||||
class Record:
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs) # <1>
|
||||
|
||||
def __repr__(self):
|
||||
cls_name = self.__class__.__name__
|
||||
return f'<{cls_name} serial={self.serial!r}>' # <2>
|
||||
|
||||
def load(path=JSON_PATH):
|
||||
records = {} # <3>
|
||||
with open(path) as fp:
|
||||
raw_data = json.load(fp) # <4>
|
||||
for collection, raw_records in raw_data['Schedule'].items(): # <5>
|
||||
record_type = collection[:-1] # <6>
|
||||
for raw_record in raw_records:
|
||||
key = f'{record_type}.{raw_record["serial"]}' # <7>
|
||||
records[key] = Record(**raw_record) # <8>
|
||||
return records
|
||||
# end::SCHEDULE1[]
|
||||
76
22-dyn-attr-prop/oscon/schedule_v2.py
Normal file
76
22-dyn-attr-prop/oscon/schedule_v2.py
Normal file
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
schedule_v2.py: property to get venue linked to an event
|
||||
|
||||
# tag::SCHEDULE2_DEMO[]
|
||||
>>> event = Record.fetch('event.33950') # <1>
|
||||
>>> event # <2>
|
||||
<Event 'There *Will* Be Bugs'>
|
||||
>>> event.venue # <3>
|
||||
<Record serial=1449>
|
||||
>>> event.venue.name # <4>
|
||||
'Portland 251'
|
||||
>>> event.venue_serial # <5>
|
||||
1449
|
||||
|
||||
# end::SCHEDULE2_DEMO[]
|
||||
"""
|
||||
|
||||
# tag::SCHEDULE2_RECORD[]
|
||||
import inspect # <1>
|
||||
import json
|
||||
|
||||
JSON_PATH = 'data/osconfeed.json'
|
||||
|
||||
class Record:
|
||||
|
||||
__index = None # <2>
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
cls_name = self.__class__.__name__
|
||||
return f'<{cls_name} serial={self.serial!r}>'
|
||||
|
||||
@staticmethod # <3>
|
||||
def fetch(key):
|
||||
if Record.__index is None: # <4>
|
||||
Record.__index = load()
|
||||
return Record.__index[key] # <5>
|
||||
# end::SCHEDULE2_RECORD[]
|
||||
|
||||
|
||||
# tag::SCHEDULE2_EVENT[]
|
||||
class Event(Record): # <1>
|
||||
|
||||
def __repr__(self):
|
||||
if hasattr(self, 'name'): # <2>
|
||||
cls_name = self.__class__.__name__
|
||||
return f'<{cls_name} {self.name!r}>'
|
||||
else:
|
||||
return super().__repr__()
|
||||
|
||||
@property
|
||||
def venue(self):
|
||||
key = f'venue.{self.venue_serial}'
|
||||
return self.__class__.fetch(key) # <3>
|
||||
# end::SCHEDULE2_EVENT[]
|
||||
|
||||
# tag::SCHEDULE2_LOAD[]
|
||||
def load(path=JSON_PATH):
|
||||
records = {}
|
||||
with open(path) as fp:
|
||||
raw_data = json.load(fp)
|
||||
for collection, raw_records in raw_data['Schedule'].items():
|
||||
record_type = collection[:-1] # <1>
|
||||
cls_name = record_type.capitalize() # <2>
|
||||
cls = globals().get(cls_name, Record) # <3>
|
||||
if inspect.isclass(cls) and issubclass(cls, Record): # <4>
|
||||
factory = cls # <5>
|
||||
else:
|
||||
factory = Record # <6>
|
||||
for raw_record in raw_records: # <7>
|
||||
key = f'{record_type}.{raw_record["serial"]}'
|
||||
records[key] = factory(**raw_record) # <8>
|
||||
return records
|
||||
# end::SCHEDULE2_LOAD[]
|
||||
86
22-dyn-attr-prop/oscon/schedule_v3.py
Normal file
86
22-dyn-attr-prop/oscon/schedule_v3.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
schedule_v3.py: property to get list of event speakers
|
||||
|
||||
>>> event = Record.fetch('event.33950')
|
||||
>>> event
|
||||
<Event 'There *Will* Be Bugs'>
|
||||
>>> event.venue
|
||||
<Record serial=1449>
|
||||
>>> event.venue_serial
|
||||
1449
|
||||
>>> event.venue.name
|
||||
'Portland 251'
|
||||
|
||||
# tag::SCHEDULE3_DEMO[]
|
||||
>>> for spkr in event.speakers:
|
||||
... print(f'{spkr.serial}: {spkr.name}')
|
||||
3471: Anna Martelli Ravenscroft
|
||||
5199: Alex Martelli
|
||||
|
||||
# end::SCHEDULE3_DEMO[]
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import json
|
||||
|
||||
JSON_PATH = 'data/osconfeed.json'
|
||||
|
||||
class Record:
|
||||
|
||||
__index = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
cls_name = self.__class__.__name__
|
||||
return f'<{cls_name} serial={self.serial!r}>'
|
||||
|
||||
@staticmethod
|
||||
def fetch(key):
|
||||
if Record.__index is None:
|
||||
Record.__index = load()
|
||||
return Record.__index[key]
|
||||
|
||||
|
||||
class Event(Record):
|
||||
|
||||
def __repr__(self):
|
||||
if hasattr(self, 'name'): # <3>
|
||||
cls_name = self.__class__.__name__
|
||||
return f'<{cls_name} {self.name!r}>'
|
||||
else:
|
||||
return super().__repr__() # <4>
|
||||
|
||||
@property
|
||||
def venue(self):
|
||||
key = f'venue.{self.venue_serial}'
|
||||
return self.__class__.fetch(key)
|
||||
|
||||
# tag::SCHEDULE3_SPEAKERS[]
|
||||
@property
|
||||
def speakers(self):
|
||||
spkr_serials = self.__dict__['speakers'] # <1>
|
||||
fetch = self.__class__.fetch
|
||||
return [fetch(f'speaker.{key}')
|
||||
for key in spkr_serials] # <2>
|
||||
|
||||
# end::SCHEDULE3_SPEAKERS[]
|
||||
|
||||
|
||||
def load(path=JSON_PATH):
|
||||
records = {}
|
||||
with open(path) as fp:
|
||||
raw_data = json.load(fp)
|
||||
for collection, raw_records in raw_data['Schedule'].items():
|
||||
record_type = collection[:-1]
|
||||
cls_name = record_type.capitalize()
|
||||
cls = globals().get(cls_name, Record)
|
||||
if inspect.isclass(cls) and issubclass(cls, Record):
|
||||
factory = cls
|
||||
else:
|
||||
factory = Record
|
||||
for raw_record in raw_records:
|
||||
key = f'{record_type}.{raw_record["serial"]}'
|
||||
records[key] = factory(**raw_record)
|
||||
return records
|
||||
94
22-dyn-attr-prop/oscon/schedule_v4.py
Normal file
94
22-dyn-attr-prop/oscon/schedule_v4.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
schedule_v4.py: homegrown cached property for speakers
|
||||
|
||||
>>> event = Record.fetch('event.33950')
|
||||
|
||||
# tag::SCHEDULE4_DEMO[]
|
||||
>>> event # <1>
|
||||
<Event 'There *Will* Be Bugs'>
|
||||
>>> event.venue # <2>
|
||||
<Record serial=1449>
|
||||
>>> event.venue.name # <3>
|
||||
'Portland 251'
|
||||
>>> for spkr in event.speakers: # <4>
|
||||
... print(f'{spkr.serial}: {spkr.name}')
|
||||
...
|
||||
3471: Anna Martelli Ravenscroft
|
||||
5199: Alex Martelli
|
||||
|
||||
# end::SCHEDULE4_DEMO[]
|
||||
"""
|
||||
|
||||
import json
|
||||
import inspect
|
||||
|
||||
JSON_PATH = 'data/osconfeed.json'
|
||||
|
||||
class Record:
|
||||
|
||||
__index = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
cls_name = self.__class__.__name__
|
||||
return f'<{cls_name} serial={self.serial!r}>'
|
||||
|
||||
@staticmethod
|
||||
def fetch(key):
|
||||
if Record.__index is None:
|
||||
Record.__index = load()
|
||||
return Record.__index[key]
|
||||
|
||||
|
||||
# tag::SCHEDULE4_INIT[]
|
||||
class Event(Record):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__speaker_objs = None
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# end::SCHEDULE4_INIT[]
|
||||
|
||||
def __repr__(self):
|
||||
if hasattr(self, 'name'):
|
||||
cls_name = self.__class__.__name__
|
||||
return f'<{cls_name} {self.name!r}>'
|
||||
else:
|
||||
return super().__repr__() # <4>
|
||||
|
||||
@property
|
||||
def venue(self):
|
||||
key = f'venue.{self.venue_serial}'
|
||||
return self.__class__.fetch(key)
|
||||
|
||||
# tag::SCHEDULE4_CACHE[]
|
||||
@property
|
||||
def speakers(self):
|
||||
if self.__speaker_objs is None:
|
||||
spkr_serials = self.__dict__['speakers']
|
||||
fetch = self.__class__.fetch
|
||||
self.__speaker_objs = [fetch(f'speaker.{key}')
|
||||
for key in spkr_serials]
|
||||
return self.__speaker_objs
|
||||
|
||||
# end::SCHEDULE4_CACHE[]
|
||||
|
||||
|
||||
def load(path=JSON_PATH):
|
||||
records = {}
|
||||
with open(path) as fp:
|
||||
raw_data = json.load(fp)
|
||||
for collection, raw_records in raw_data['Schedule'].items():
|
||||
record_type = collection[:-1]
|
||||
cls_name = record_type.capitalize()
|
||||
cls = globals().get(cls_name, Record)
|
||||
if inspect.isclass(cls) and issubclass(cls, Record):
|
||||
factory = cls
|
||||
else:
|
||||
factory = Record
|
||||
for raw_record in raw_records:
|
||||
key = f'{record_type}.{raw_record["serial"]}'
|
||||
records[key] = factory(**raw_record)
|
||||
return records
|
||||
86
22-dyn-attr-prop/oscon/schedule_v4_hasattr.py
Normal file
86
22-dyn-attr-prop/oscon/schedule_v4_hasattr.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""
|
||||
schedule_v4.py: homegrown cached property for speakers
|
||||
|
||||
>>> event = Record.fetch('event.33950')
|
||||
|
||||
# tag::SCHEDULE4_DEMO[]
|
||||
>>> event # <1>
|
||||
<Event 'There *Will* Be Bugs'>
|
||||
>>> event.venue # <2>
|
||||
<Record serial=1449>
|
||||
>>> event.venue.name # <3>
|
||||
'Portland 251'
|
||||
>>> for spkr in event.speakers: # <4>
|
||||
... print(f'{spkr.serial}: {spkr.name}')
|
||||
3471: Anna Martelli Ravenscroft
|
||||
5199: Alex Martelli
|
||||
|
||||
# end::SCHEDULE4_DEMO[]
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import json
|
||||
|
||||
JSON_PATH = 'data/osconfeed.json'
|
||||
|
||||
class Record:
|
||||
|
||||
__index = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
cls_name = self.__class__.__name__
|
||||
return f'<{cls_name} serial={self.serial!r}>'
|
||||
|
||||
@staticmethod
|
||||
def fetch(key):
|
||||
if Record.__index is None:
|
||||
Record.__index = load()
|
||||
return Record.__index[key]
|
||||
|
||||
|
||||
class Event(Record):
|
||||
|
||||
def __repr__(self):
|
||||
if hasattr(self, 'name'):
|
||||
cls_name = self.__class__.__name__
|
||||
return f'<{cls_name} {self.name!r}>'
|
||||
else:
|
||||
return super().__repr__() # <4>
|
||||
|
||||
@property
|
||||
def venue(self):
|
||||
key = f'venue.{self.venue_serial}'
|
||||
return self.__class__.fetch(key)
|
||||
|
||||
# tag::SCHEDULE4_HASATTR_CACHE[]
|
||||
@property
|
||||
def speakers(self):
|
||||
if not hasattr(self, '__speaker_objs'): # <1>
|
||||
spkr_serials = self.__dict__['speakers']
|
||||
fetch = self.__class__.fetch
|
||||
self.__speaker_objs = [fetch(f'speaker.{key}')
|
||||
for key in spkr_serials]
|
||||
return self.__speaker_objs # <2>
|
||||
|
||||
# end::SCHEDULE4_HASATTR_CACHE[]
|
||||
|
||||
|
||||
def load(path=JSON_PATH):
|
||||
records = {}
|
||||
with open(path) as fp:
|
||||
raw_data = json.load(fp)
|
||||
for collection, raw_records in raw_data['Schedule'].items():
|
||||
record_type = collection[:-1]
|
||||
cls_name = record_type.capitalize()
|
||||
cls = globals().get(cls_name, Record)
|
||||
if inspect.isclass(cls) and issubclass(cls, Record):
|
||||
factory = cls
|
||||
else:
|
||||
factory = Record
|
||||
for raw_record in raw_records:
|
||||
key = f'{record_type}.{raw_record["serial"]}'
|
||||
records[key] = factory(**raw_record)
|
||||
return records
|
||||
89
22-dyn-attr-prop/oscon/schedule_v5.py
Normal file
89
22-dyn-attr-prop/oscon/schedule_v5.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
schedule_v5.py: cached properties using functools
|
||||
|
||||
>>> event = Record.fetch('event.33950')
|
||||
>>> event
|
||||
<Event 'There *Will* Be Bugs'>
|
||||
>>> event.venue
|
||||
<Record serial=1449>
|
||||
>>> event.venue_serial
|
||||
1449
|
||||
>>> event.venue.name
|
||||
'Portland 251'
|
||||
|
||||
# tag::SCHEDULE3_DEMO[]
|
||||
>>> for spkr in event.speakers: # <3>
|
||||
... print(f'{spkr.serial}: {spkr.name}')
|
||||
...
|
||||
3471: Anna Martelli Ravenscroft
|
||||
5199: Alex Martelli
|
||||
|
||||
# end::SCHEDULE3_DEMO[]
|
||||
"""
|
||||
|
||||
import json
|
||||
import inspect
|
||||
|
||||
from functools import cached_property, cache
|
||||
|
||||
JSON_PATH = 'data/osconfeed.json'
|
||||
|
||||
class Record:
|
||||
|
||||
__index = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
cls_name = self.__class__.__name__
|
||||
return f'<{cls_name} serial={self.serial!r}>'
|
||||
|
||||
@staticmethod
|
||||
def fetch(key):
|
||||
if Record.__index is None:
|
||||
Record.__index = load()
|
||||
return Record.__index[key]
|
||||
|
||||
|
||||
class Event(Record):
|
||||
|
||||
def __repr__(self):
|
||||
if hasattr(self, 'name'):
|
||||
cls_name = self.__class__.__name__
|
||||
return f'<{cls_name} {self.name!r}>'
|
||||
else:
|
||||
return super().__repr__()
|
||||
|
||||
# tag::SCHEDULE5_CACHED_PROPERTY[]
|
||||
@cached_property
|
||||
def venue(self):
|
||||
key = f'venue.{self.venue_serial}'
|
||||
return self.__class__.fetch(key)
|
||||
# end::SCHEDULE5_CACHED_PROPERTY[]
|
||||
# tag::SCHEDULE5_PROPERTY_OVER_CACHE[]
|
||||
@property # <1>
|
||||
@cache # <2>
|
||||
def speakers(self):
|
||||
spkr_serials = self.__dict__['speakers']
|
||||
fetch = self.__class__.fetch
|
||||
return [fetch(f'speaker.{key}')
|
||||
for key in spkr_serials]
|
||||
# end::SCHEDULE5_PROPERTY_OVER_CACHE[]
|
||||
|
||||
def load(path=JSON_PATH):
|
||||
records = {}
|
||||
with open(path) as fp:
|
||||
raw_data = json.load(fp)
|
||||
for collection, raw_records in raw_data['Schedule'].items():
|
||||
record_type = collection[:-1]
|
||||
cls_name = record_type.capitalize()
|
||||
cls = globals().get(cls_name, Record)
|
||||
if inspect.isclass(cls) and issubclass(cls, Record):
|
||||
factory = cls
|
||||
else:
|
||||
factory = Record
|
||||
for raw_record in raw_records:
|
||||
key = f'{record_type}.{raw_record["serial"]}'
|
||||
records[key] = factory(**raw_record)
|
||||
return records
|
||||
24
22-dyn-attr-prop/oscon/test_schedule_v1.py
Normal file
24
22-dyn-attr-prop/oscon/test_schedule_v1.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import pytest
|
||||
|
||||
import schedule_v1 as schedule
|
||||
|
||||
|
||||
@pytest.yield_fixture
|
||||
def records():
|
||||
yield schedule.load(schedule.JSON_PATH)
|
||||
|
||||
|
||||
def test_load(records):
|
||||
assert len(records) == 895
|
||||
|
||||
|
||||
def test_record_attr_access():
|
||||
rec = schedule.Record(spam=99, eggs=12)
|
||||
assert rec.spam == 99
|
||||
assert rec.eggs == 12
|
||||
|
||||
|
||||
def test_venue_record(records):
|
||||
venue = records['venue.1469']
|
||||
assert venue.serial == 1469
|
||||
assert venue.name == 'Exhibit Hall C'
|
||||
48
22-dyn-attr-prop/oscon/test_schedule_v2.py
Normal file
48
22-dyn-attr-prop/oscon/test_schedule_v2.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import pytest
|
||||
|
||||
import schedule_v2 as schedule
|
||||
|
||||
@pytest.yield_fixture
|
||||
def records():
|
||||
yield schedule.load(schedule.JSON_PATH)
|
||||
|
||||
|
||||
def test_load(records):
|
||||
assert len(records) == 895
|
||||
|
||||
|
||||
def test_record_attr_access():
|
||||
rec = schedule.Record(spam=99, eggs=12)
|
||||
assert rec.spam == 99
|
||||
assert rec.eggs == 12
|
||||
|
||||
|
||||
def test_venue_record(records):
|
||||
venue = records['venue.1469']
|
||||
assert venue.serial == 1469
|
||||
assert venue.name == 'Exhibit Hall C'
|
||||
|
||||
|
||||
def test_fetch_speaker_record():
|
||||
speaker = schedule.Record.fetch('speaker.3471')
|
||||
assert speaker.name == 'Anna Martelli Ravenscroft'
|
||||
|
||||
|
||||
def test_event_type():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert type(event) is schedule.Event
|
||||
assert repr(event) == "<Event 'There *Will* Be Bugs'>"
|
||||
|
||||
|
||||
def test_event_repr():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert repr(event) == "<Event 'There *Will* Be Bugs'>"
|
||||
event2 = schedule.Event(serial=77, kind='show')
|
||||
assert repr(event2) == '<Event serial=77>'
|
||||
|
||||
|
||||
def test_event_venue():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert event.venue_serial == 1449
|
||||
assert event.venue == schedule.Record.fetch('venue.1449')
|
||||
assert event.venue.name == 'Portland 251'
|
||||
59
22-dyn-attr-prop/oscon/test_schedule_v3.py
Normal file
59
22-dyn-attr-prop/oscon/test_schedule_v3.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import pytest
|
||||
|
||||
import schedule_v3 as schedule
|
||||
|
||||
@pytest.yield_fixture
|
||||
def records():
|
||||
yield schedule.load(schedule.JSON_PATH)
|
||||
|
||||
|
||||
def test_load(records):
|
||||
assert len(records) == 895
|
||||
|
||||
|
||||
def test_record_attr_access():
|
||||
rec = schedule.Record(spam=99, eggs=12)
|
||||
assert rec.spam == 99
|
||||
assert rec.eggs == 12
|
||||
|
||||
|
||||
def test_venue_record(records):
|
||||
venue = records['venue.1469']
|
||||
assert venue.serial == 1469
|
||||
assert venue.name == 'Exhibit Hall C'
|
||||
|
||||
|
||||
def test_fetch_speaker_record():
|
||||
speaker = schedule.Record.fetch('speaker.3471')
|
||||
assert speaker.name == 'Anna Martelli Ravenscroft'
|
||||
|
||||
|
||||
def test_event_type():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert type(event) is schedule.Event
|
||||
assert repr(event) == "<Event 'There *Will* Be Bugs'>"
|
||||
|
||||
|
||||
def test_event_repr():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert repr(event) == "<Event 'There *Will* Be Bugs'>"
|
||||
event2 = schedule.Event(serial=77, kind='show')
|
||||
assert repr(event2) == '<Event serial=77>'
|
||||
|
||||
|
||||
def test_event_venue():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert event.venue_serial == 1449
|
||||
assert event.venue == schedule.Record.fetch('venue.1449')
|
||||
assert event.venue.name == 'Portland 251'
|
||||
|
||||
|
||||
def test_event_speakers():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert len(event.speakers) == 2
|
||||
anna, alex = [schedule.Record.fetch(f'speaker.{s}') for s in (3471, 5199)]
|
||||
assert event.speakers == [anna, alex]
|
||||
|
||||
def test_event_no_speakers():
|
||||
event = schedule.Record.fetch('event.36848')
|
||||
assert event.speakers == []
|
||||
59
22-dyn-attr-prop/oscon/test_schedule_v4.py
Normal file
59
22-dyn-attr-prop/oscon/test_schedule_v4.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import pytest
|
||||
|
||||
import schedule_v4 as schedule
|
||||
|
||||
@pytest.yield_fixture
|
||||
def records():
|
||||
yield schedule.load(schedule.JSON_PATH)
|
||||
|
||||
|
||||
def test_load(records):
|
||||
assert len(records) == 895
|
||||
|
||||
|
||||
def test_record_attr_access():
|
||||
rec = schedule.Record(spam=99, eggs=12)
|
||||
assert rec.spam == 99
|
||||
assert rec.eggs == 12
|
||||
|
||||
|
||||
def test_venue_record(records):
|
||||
venue = records['venue.1469']
|
||||
assert venue.serial == 1469
|
||||
assert venue.name == 'Exhibit Hall C'
|
||||
|
||||
|
||||
def test_fetch_speaker_record():
|
||||
speaker = schedule.Record.fetch('speaker.3471')
|
||||
assert speaker.name == 'Anna Martelli Ravenscroft'
|
||||
|
||||
|
||||
def test_event_type():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert type(event) is schedule.Event
|
||||
assert repr(event) == "<Event 'There *Will* Be Bugs'>"
|
||||
|
||||
|
||||
def test_event_repr():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert repr(event) == "<Event 'There *Will* Be Bugs'>"
|
||||
event2 = schedule.Event(serial=77, kind='show')
|
||||
assert repr(event2) == '<Event serial=77>'
|
||||
|
||||
|
||||
def test_event_venue():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert event.venue_serial == 1449
|
||||
assert event.venue == schedule.Record.fetch('venue.1449')
|
||||
assert event.venue.name == 'Portland 251'
|
||||
|
||||
|
||||
def test_event_speakers():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert len(event.speakers) == 2
|
||||
anna, alex = [schedule.Record.fetch(f'speaker.{s}') for s in (3471, 5199)]
|
||||
assert event.speakers == [anna, alex]
|
||||
|
||||
def test_event_no_speakers():
|
||||
event = schedule.Record.fetch('event.36848')
|
||||
assert event.speakers == []
|
||||
59
22-dyn-attr-prop/oscon/test_schedule_v5.py
Normal file
59
22-dyn-attr-prop/oscon/test_schedule_v5.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import pytest
|
||||
|
||||
import schedule_v5 as schedule
|
||||
|
||||
@pytest.yield_fixture
|
||||
def records():
|
||||
yield schedule.load(schedule.JSON_PATH)
|
||||
|
||||
|
||||
def test_load(records):
|
||||
assert len(records) == 895
|
||||
|
||||
|
||||
def test_record_attr_access():
|
||||
rec = schedule.Record(spam=99, eggs=12)
|
||||
assert rec.spam == 99
|
||||
assert rec.eggs == 12
|
||||
|
||||
|
||||
def test_venue_record(records):
|
||||
venue = records['venue.1469']
|
||||
assert venue.serial == 1469
|
||||
assert venue.name == 'Exhibit Hall C'
|
||||
|
||||
|
||||
def test_fetch_speaker_record():
|
||||
speaker = schedule.Record.fetch('speaker.3471')
|
||||
assert speaker.name == 'Anna Martelli Ravenscroft'
|
||||
|
||||
|
||||
def test_event_type():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert type(event) is schedule.Event
|
||||
assert repr(event) == "<Event 'There *Will* Be Bugs'>"
|
||||
|
||||
|
||||
def test_event_repr():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert repr(event) == "<Event 'There *Will* Be Bugs'>"
|
||||
event2 = schedule.Event(serial=77, kind='show')
|
||||
assert repr(event2) == '<Event serial=77>'
|
||||
|
||||
|
||||
def test_event_venue():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert event.venue_serial == 1449
|
||||
assert event.venue == schedule.Record.fetch('venue.1449')
|
||||
assert event.venue.name == 'Portland 251'
|
||||
|
||||
|
||||
def test_event_speakers():
|
||||
event = schedule.Record.fetch('event.33950')
|
||||
assert len(event.speakers) == 2
|
||||
anna, alex = [schedule.Record.fetch(f'speaker.{s}') for s in (3471, 5199)]
|
||||
assert event.speakers == [anna, alex]
|
||||
|
||||
def test_event_no_speakers():
|
||||
event = schedule.Record.fetch('event.36848')
|
||||
assert event.speakers == []
|
||||
10
22-dyn-attr-prop/pseudo_construction.py
Normal file
10
22-dyn-attr-prop/pseudo_construction.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# pseudo-code for object construction
|
||||
def make(the_class, some_arg):
|
||||
new_object = the_class.__new__(some_arg)
|
||||
if isinstance(new_object, the_class):
|
||||
the_class.__init__(new_object, some_arg)
|
||||
return new_object
|
||||
|
||||
# the following statements are roughly equivalent
|
||||
x = Foo('bar')
|
||||
x = make(Foo, 'bar')
|
||||
Reference in New Issue
Block a user