demokritos-0.2.0/0000755000076500007650000000000010362327333015017 5ustar jtauberjtauber00000000000000demokritos-0.2.0/basic_server.py0000755000076500007650000000105010362323323020032 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import sys sys.path.append("lib") from store import AtomStore import server atom_store = AtomStore("http://localhost:8000") workspace = atom_store.create_workspace("Demokritos Test Workspace") collection1 = atom_store.create_collection("Blog Entry Collection", "/blog", "entry") collection2 = atom_store.create_collection("Media Collection", "/media", "media") workspace.add_collection(collection1) workspace.add_collection(collection2) server.atom_store = atom_store # @@@ hack for now server.AtomServer().serve_forever()demokritos-0.2.0/lib/0000755000076500007650000000000010362327333015565 5ustar jtauberjtauber00000000000000demokritos-0.2.0/lib/app/0000755000076500007650000000000010362327333016345 5ustar jtauberjtauber00000000000000demokritos-0.2.0/lib/app/__init__.py0000644000076500007650000000030310362270053020446 0ustar jtauberjtauber00000000000000from parse import parse_introspection, parse_entry, AtomProtocolException from generate import generate_introspection from model import Service, Workspace, CollectionReference, Collection, Memberdemokritos-0.2.0/lib/app/generate.py0000644000076500007650000000150510362321362020506 0ustar jtauberjtauber00000000000000from atom import generate_entry def generate_introspection(wfile, service): wfile.write("""\n""") for workspace in service.workspaces: generate_workspace(wfile, workspace) wfile.write("""\n""") def generate_workspace(wfile, workspace): wfile.write("""\n""" % workspace.title) for collection_reference in workspace.collections: generate_collection_reference(wfile, collection_reference) wfile.write("""\n""") def generate_collection_reference(wfile, collection_reference): wfile.write("""\n%s\n\n""" % (collection_reference.title, collection_reference.href, collection_reference.member_type)) demokritos-0.2.0/lib/app/model.py0000644000076500007650000000310510362321362020012 0ustar jtauberjtauber00000000000000from atom import Entry class Service: # workspaces[] def __init__(self): self.workspaces = [] # extension elements self.children = [] def add(self, child): self.children.append(child) class Workspace: # title # collections[] - the first collection is the primary one (APP-07 7.2.2) def __init__(self, title=None): if title: self.title = title self.collections = [] # extension elements self.children = [] def add_collection(self, title, href, member_type): self.collections.append(CollectionReference(title, href, member_type)) def add(self, child): self.children.append(child) class CollectionReference: # title # href # member_type def __init__(self, title=None, href=None, member_type=None): # note that if arguments are not provided here, the attributes must # be set individually. self.title = title self.href = href self.member_type = member_type # extension elements self.children = [] def add(self, child): self.children.append(child) def updated_sort_key(member): return member.updated # @@@ need to normalize timezone class Collection: # members[] def __init__(self): self.members = [] def add_member(self, member): self.members.insert(0, member) self.members.sort(key=updated_sort_key, reverse=True) class Member: passdemokritos-0.2.0/lib/app/parse.py0000644000076500007650000001042410362321362020026 0ustar jtauberjtauber00000000000000from xmlbase import HandlerBase, parse, Generic_ElementHandler, NAMESPACE_SEPARATOR from atom.parse import Atom_EntryHandler, atom_ns from model import * APP_NAMESPACE = "http://purl.org/atom/app#" def app_ns(name): return APP_NAMESPACE + NAMESPACE_SEPARATOR + name class AtomProtocolException(Exception): pass class AtomProtocol_HandlerBase(HandlerBase): def no_children(self, name, attribute): raise AtomProtocolException("%s not allowed here" % name) ## APP-07 7.2.1 class Introspection_RootHandler(AtomProtocol_HandlerBase): def child(self, name, attributes): if name == app_ns("service"): return Introspection_ServiceHandler else: raise AtomProtocolException("%s not allowed here" % name) ## APP-07 7.2.1 - app:service class Introspection_ServiceHandler(AtomProtocol_HandlerBase): def init(self): self.product = Service() def child(self, name, attributes): if name == app_ns("workspace"): return Introspection_WorkspaceHandler else: # @@@ extensionElement return Generic_ElementHandler def end(self, name): if len(self.product.workspaces) == 0: raise AtomProtocolException("service must have at least one workspace") self.parent_handler.product = self.product ## APP-07 7.2.2 - app:workspace class Introspection_WorkspaceHandler(AtomProtocol_HandlerBase): def init(self): self.product = Workspace() if "title" not in self.attributes: raise AtomProtocolException("workspace must have a title") self.product.title = self.attributes["title"] def child(self, name, attributes): if name == app_ns("collection"): return Introspection_CollectionHandler else: # @@@ extensionElement return Generic_ElementHandler def end(self, name): # @@@ APP-07 requires there be at least one collection in a workspace self.parent_handler.product.workspaces.append(self.product) ## APP-07 7.2.3 - app:collection class Introspection_CollectionHandler(AtomProtocol_HandlerBase): def init(self): self.product = CollectionReference() if "title" not in self.attributes: raise AtomProtocolException("collection must have 'title' attribute") if "href" not in self.attributes: raise AtomProtocolException("collection must have 'href' attribute") for name, value in self.attributes.items(): if name == "title": self.product.title = value elif name == "href": self.product.href = value else: raise AtomException("%s not allowed here" % name) def child(self, name, attributes): if name == app_ns("member-type"): return Introspection_MemberTypeHandler else: # @@@ extensionElement return Generic_ElementHandler def end(self, name): if self.product.member_type is None: raise AtomProtocolException("collection must have a member-type") self.parent_handler.product.collections.append(self.product) ## APP-07 7.2.4 - app:member-type class Introspection_MemberTypeHandler(AtomProtocol_HandlerBase): def end(self, name): # @@@ should appTypeValue = "entry" | "media" be enforced? self.parent_handler.product.member_type = self.char_data # this is version of Atom_RootHandler for the more lax entries that can be # POSTed to a collection class LaxEntry_RootHandler(AtomProtocol_HandlerBase): def child(self, name, attributes): if name == atom_ns("entry"): return LaxEntry_EntryHandler else: raise AtomProtocolException("document element must be entry") # this is version of Atom_EntryHandler for the more lax entries that can be # POSTed to a collection class LaxEntry_EntryHandler(Atom_EntryHandler): def validate(self): # unlike superclass, this doesn't have restrictions on id, updated if not self.got_title: raise AtomException("entry must have title element") ## MAIN FUNCTIONS IN THIS MODULE def parse_introspection(xml): return parse(xml, Introspection_RootHandler) def parse_entry(xml): return parse(xml, LaxEntry_RootHandler)demokritos-0.2.0/lib/atom/0000755000076500007650000000000010362327333016525 5ustar jtauberjtauber00000000000000demokritos-0.2.0/lib/atom/__init__.py0000644000076500007650000000020510362270053020627 0ustar jtauberjtauber00000000000000from parse import parse_atom, AtomException from generate import generate_atom, generate_entry, generate_feed from model import Entrydemokritos-0.2.0/lib/atom/generate.py0000644000076500007650000001163010357206762020700 0ustar jtauberjtauber00000000000000# @@@ dates and IRIs must not be surrounded by whitespace import re from model import Feed, Entry, InlineXHTMLContent, OutOfLineContent from xmlbase import generate_xml def generate_atom(wfile, atom_object): if isinstance(atom_object, Feed): generate_feed(wfile, atom_object) elif isinstance(atom_object, Entry): generate_entry(wfile, atom_object, top=True) else: raise AtomException("unknown object '%s'" % atom_object) def generate_feed(wfile, feed, top=True): if top: wfile.write("""\n""") if top: wfile.write("""\n""") else: wfile.write("""\n""") generate_text("title", wfile, feed.title) if hasattr(feed, "subtitle"): generate_text("subtitle", wfile, feed.subtitle) generate_id(wfile, feed.id) generate_date("updated", wfile, feed.updated) generate_links(wfile, feed.links) generate_people("author", wfile, feed.authors) generate_people("contributor", wfile, feed.contributors) if hasattr(feed, "rights"): generate_rights(wfile, feed.rights) if hasattr(feed, "generator"): # @@@ should this actually just be us? generate_generator(wfile, feed.generator) generate_entries(wfile, feed.entries) wfile.write("""\n""") def generate_text(name, wfile, text_construct): if hasattr(text_construct, "type_"): typ = text_construct.type_ if typ in ["text", "html"]: value = re.sub("&", "&", text_construct.value) value = re.sub("<", "<", value) wfile.write("""<%s type="%s">%s\n""" % (name, typ, value, name)) else: pass # @@@ else: value = re.sub("&", "&", text_construct.value) value = re.sub("<", "<", value) wfile.write("""<%s>%s\n""" % (name, value, name)) def generate_id(wfile, id): wfile.write("""%s\n""" % id) def generate_date(name, wfile, date_construct): wfile.write("""<%s>%s\n""" % (name, date_construct.value, name)) def generate_links(wfile, links): for link in links: attrs = " href=\"%s\"" % link.href # note underscore in rel_ to get explicit value if hasattr(link, "rel_"): attrs += " rel=\"%s\"" % link.rel_ if hasattr(link, "type"): attrs += " type=\"%s\"" % link.type if hasattr(link, "hreflang"): attrs += " hreflang=\"%s\"" % link.hreflang if hasattr(link, "title"): attrs += " title=\"%s\"" % link.title if hasattr(link, "length"): attrs += " length=\"%s\"" % link.length wfile.write("""\n""" % attrs) def generate_people(name, wfile, people): for person in people: wfile.write("""<%s>\n""" % name) wfile.write("""%s\n""" % person.name) if hasattr(person, "uri"): wfile.write("""%s\n""" % person.uri) if hasattr(person, "email"): wfile.write("""%s\n""" % person.email) wfile.write("""\n""" % name) def generate_rights(wfile, rights): wfile.write("""%s\n""" % rights) def generate_generator(wfile, generator): attrs = "" if hasattr(generator, "uri"): attrs += " uri=\"%s\"" % generator.uri if hasattr(generator, "version"): attrs += " version=\"%s\"" % generator.version wfile.write("""%s\n""" % (attrs, generator.value)) def generate_entries(wfile, entries): for entry in entries: generate_entry(wfile, entry) def generate_entry(wfile, entry, top=False): if top: wfile.write("""\n""") if top: wfile.write("""\n""") else: wfile.write("""\n""") generate_text("title", wfile, entry.title) generate_id(wfile, entry.id) generate_date("updated", wfile, entry.updated) if hasattr(entry, "published"): generate_date("published", wfile, entry.published) generate_links(wfile, entry.links) generate_people("author", wfile, entry.authors) generate_people("contributor", wfile, entry.contributors) if hasattr(entry, "summary"): generate_text("summary", wfile, entry.summary) if hasattr(entry, "content"): generate_content(wfile, entry.content) wfile.write("""\n""") def generate_content(wfile, content): if isinstance(content, InlineXHTMLContent): wfile.write("""\n""") generate_xml(wfile, content.children[0]) wfile.write("""\n""") elif isinstance(content, OutOfLineContent): wfile.write("""\n""" % content.src) else: pass # @@@ demokritos-0.2.0/lib/atom/model.py0000644000076500007650000000523510357206762020212 0ustar jtauberjtauber00000000000000class Feed: def __init__(self): self.links = [] self.authors = [] self.contributors = [] self.categories = [] self.entries = [] class Entry: def __init__(self): self.links = [] self.authors = [] self.contributors = [] self.categories = [] # extension elements self.children = [] def add(self, child): self.children.append(child) class Category: def __init__(self): # extension elements self.children = [] def add(self, child): self.children.append(child) class Source: def __init__(self): self.links = [] self.authors = [] self.contributors = [] self.categories = [] # extension elements self.children = [] def add(self, child): self.children.append(child) class PlainText(object): def __init__(self, value=None): if value: self.value = value def get_type(self): if hasattr(self, "type_"): return self.type_ else: return "text" def set_type(self, type): self.type_ = type type = property(get_type, set_type) class XHTMLText: def __init__(self): self.children = [] def add(self, child): self.children.append(child) class Link(object): def __init__(self, rel=None, href=None): if rel: self.rel_ = rel if href: self.href = href def get_rel(self): if hasattr(self, "rel_"): return self.rel_ else: return "alternate" def set_rel(self, rel): self.rel_ = rel rel = property(get_rel, set_rel) class Generator: pass class Person: def __init__(self, name=None, uri=None, email=None): if name: self.name = name if uri: self.uri = uri if email: self.email = email # extension elements self.children = [] def add(self, child): self.children.append(child) class Date: def __init__(self, value=None): if value: self.value = value class InlineTextContent: def __init__(self, content=None): if content: self.content = content class InlineXHTMLContent: def __init__(self): self.children = [] def add(self, child): self.children.append(child) class InlineOtherContent: pass class OutOfLineContent: def __init__(self, src=None): if src: self.src = src demokritos-0.2.0/lib/atom/parse.py0000644000076500007650000005541110344405070020212 0ustar jtauberjtauber00000000000000from xmlbase import HandlerBase, parse, Generic_ElementHandler, NAMESPACE_SEPARATOR from model import * ATOM_NAMESPACE = "http://www.w3.org/2005/Atom" def atom_ns(name): return ATOM_NAMESPACE + NAMESPACE_SEPARATOR + name def xhtml_ns(name): return "http://www.w3.org/1999/xhtml" + NAMESPACE_SEPARATOR + name class AtomException(Exception): pass class Atom_HandlerBase(HandlerBase): def no_children(self, name, attribute): raise AtomException("%s not allowed here" % name) # @@@ dsig support (5.1) # @@@ language sensitive # @@@ xml:base class Atom_RootHandler(Atom_HandlerBase): def child(self, name, attributes): if name == atom_ns("feed"): return Atom_FeedHandler elif name == atom_ns("entry"): return Atom_EntryHandler else: raise AtomException("%s not allowed here" % name) class Atom_FeedHandler(Atom_HandlerBase): def init(self): self.product = Feed() self.got_author = False self.got_generator = False self.got_icon = False self.got_id = False self.got_logo = False self.got_rights = False self.got_subtitle = False self.got_title = False self.got_updated = False self.got_entries = False def child(self, name, attributes): if self.got_entries: if name == atom_ns("entry"): return Atom_EntryHandler else: raise AtomException("once entries start in feed, only further entries can follow, not '%s'" % name) else: if name == atom_ns("author"): self.got_author = True return Atom_AuthorHandler elif name == atom_ns("category"): return Atom_CategoryHandler elif name == atom_ns("contributor"): return Atom_ContributorHandler elif name == atom_ns("generator"): if self.got_generator: raise AtomException("feed can only have one generator element") else: self.got_generator = True return Atom_GeneratorHandler elif name == atom_ns("icon"): if self.got_icon: raise AtomException("feed can only have one icon element") else: self.got_icon = True return Atom_IconHandler elif name == atom_ns("id"): if self.got_id: raise AtomException("feed can only have one id element") else: self.got_id = True return Atom_IdHandler elif name == atom_ns("link"): return Atom_LinkHandler elif name == atom_ns("logo"): if self.got_logo: raise AtomException("feed can only have one logo element") else: self.got_logo = True return Atom_LogoHandler elif name == atom_ns("rights"): if self.got_rights: raise AtomException("feed can only have one rights element") else: self.got_rights = True return Atom_RightsHandler elif name == atom_ns("subtitle"): if self.got_subtitle: raise AtomException("feed can only have one subtitle element") else: self.got_subtitle = True return Atom_SubtitleHandler elif name == atom_ns("title"): if self.got_title: raise AtomException("feed can only have one title element") else: self.got_title = True return Atom_TitleHandler elif name == atom_ns("updated"): if self.got_updated: raise AtomException("feed can only have one updated element") else: self.got_updated = True return Atom_UpdatedHandler elif name == atom_ns("entry"): self.got_entries = True return Atom_EntryHandler else: raise AtomException("%s not allowed here" % name) def end(self, name): if not self.got_id: raise AtomException("feed must have id element") elif not self.got_title: raise AtomException("feed must have title element") elif not self.got_updated: raise AtomException("feed must have updated element") else: self.parent_handler.product = self.product class Atom_TextConstructHandlerBase(Atom_HandlerBase): # ABSTRACT BASE def init(self): self.product = None for name, value in self.attributes.items(): if name == "type": if value in ["text", "html"]: # plain text construct self.product = PlainText() self.product.type = value elif value in ["xhtml"]: # xhtml text construct self.product = XHTMLText() self.product.type = value else: raise AtomException("type must be one of 'text', 'html' or 'xhtml', not '%s'" % value) else: raise AtomException("%s not allowed here" % name) if self.product is None: self.product = PlainText() def child(self, name, attributes): if self.product.type in ["text", "html"]: raise AtomException("child elements not allowed when type is '%s'" % self.product.type) elif name != xhtml_ns("div"): raise AtomException("must contain single xhtml:div element, not '%s'" % name) else: return Generic_ElementHandler class Atom_TitleHandler(Atom_TextConstructHandlerBase): def end(self, name): self.product.value = self.char_data.strip() self.parent_handler.product.title = self.product class Atom_SubtitleHandler(Atom_TextConstructHandlerBase): def end(self, name): self.product.value = self.char_data.strip() self.parent_handler.product.subtitle = self.product class Atom_SummaryHandler(Atom_TextConstructHandlerBase): def end(self, name): self.product.value = self.char_data.strip() self.parent_handler.product.summary = self.product class Atom_LinkHandler(Atom_HandlerBase): def init(self): self.product = Link() # @@@ atom:feed elements MUST NOT contain more than one atom:link # element with a rel attribute value of "alternate" that has the # same combination of type and hreflang attribute values. if "href" not in self.attributes: raise AtomException("link must have 'href' attribute") for name, value in self.attributes.items(): if name == "href": self.product.href = value elif name == "rel": self.product.rel = value elif name == "type": self.product.type = value elif name == "hreflang": self.product.hreflang = value elif name == "length": self.product.length = value elif name == "title": self.product.title = value else: raise AtomException("%s not allowed here" % name) child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.links.append(self.product) class Atom_CategoryHandler(Atom_HandlerBase): def init(self): self.product = Category() if "term" not in self.attributes: raise AtomException("category must have term attribute") for name, value in self.attributes.items(): if name == "term": self.product.term = value elif name == "scheme": self.product.scheme = value elif name == "label": self.product.label = value # @@@ language sensitive else: raise AtomException("%s not allowed here" % name) def child(self, name, attributes): return Generic_ElementHandler def end(self, name): self.parent_handler.product.categories.append(self.product) class Atom_DateConstructHandlerBase(Atom_HandlerBase): def init(self): self.product = Date() child = Atom_HandlerBase.no_children # @@@ should validate date (see section 3.3) class Atom_UpdatedHandler(Atom_DateConstructHandlerBase): def end(self, name): self.product.value = self.char_data self.parent_handler.product.updated = self.product class Atom_PublishedHandler(Atom_DateConstructHandlerBase): def end(self, name): self.product.value = self.char_data self.parent_handler.product.published = self.product class Atom_PersonConstructHandlerBase(Atom_HandlerBase): # ABSTRACT BASE def init(self): self.product = Person() self.got_name = False self.got_uri = False self.got_email = False def child(self, name, attributes): if name == atom_ns("name"): if self.got_name: raise AtomException("can only have one name element in a person construct") else: self.got_name = True return Atom_NameHandler elif name == atom_ns("uri"): if self.got_uri: raise AtomException("can only have one uri element in a person construct") else: self.got_uri = True return Atom_UriHandler elif name == atom_ns("email"): if self.got_email: raise AtomException("can only have one email element in a person construct") else: self.got_email = True return Atom_EmailHandler else: return Generic_ElementHandler def end(self, name): if not self.got_name: raise AtomException("person construct must have a name") # @@@ If an atom:entry element does not contain atom:author elements, then # the atom:author elements of the contained atom:source element are # considered to apply. In an Atom Feed Document, the atom:author # elements of the containing atom:feed element are considered to apply # to the entry if there are no atom:author elements in the locations # described above. class Atom_AuthorHandler(Atom_PersonConstructHandlerBase): def end(self, name): self.parent_handler.product.authors.append(self.product) class Atom_ContributorHandler(Atom_PersonConstructHandlerBase): def end(self, name): self.parent_handler.product.contributors.append(self.product) class Atom_NameHandler(Atom_HandlerBase): child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.name = self.char_data class Atom_UriHandler(Atom_HandlerBase): child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.uri = self.char_data class Atom_EmailHandler(Atom_HandlerBase): child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.email = self.char_data class Atom_IdHandler(Atom_HandlerBase): # @@@ 4.2.6 child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.id = self.char_data class Atom_IconHandler(Atom_HandlerBase): child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.icon = self.char_data class Atom_LogoHandler(Atom_HandlerBase): child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.icon = self.char_data class Atom_RightsHandler(Atom_HandlerBase): child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.rights = self.char_data.strip() class Atom_GeneratorHandler(Atom_HandlerBase): def init(self): self.product = Generator() for name, value in self.attributes.items(): if name == "uri": self.product.uri = value elif name == "version": self.product.version = value else: raise AtomException("%s not allowed here" % name) child = Atom_HandlerBase.no_children def end(self, name): self.product.value = self.char_data.strip() self.parent_handler.product.generator = self.product class Atom_EntryHandler(Atom_HandlerBase): def init(self): self.product = Entry() self.got_author = False self.got_content = False self.got_id = False self.got_published = False self.got_rights = False self.got_source = False self.got_summary = False self.got_title = False self.got_updated = False def child(self, name, attributes): if name == atom_ns("author"): self.got_author = True return Atom_AuthorHandler elif name == atom_ns("category"): return Atom_CategoryHandler elif name == atom_ns("content"): if self.got_content: raise AtomException("entry can only have one content element") else: self.got_content = True return Atom_ContentHandler elif name == atom_ns("contributor"): return Atom_ContributorHandler elif name == atom_ns("id"): if self.got_id: raise AtomException("entry can only have one id element") else: self.got_id = True return Atom_IdHandler elif name == atom_ns("link"): return Atom_LinkHandler elif name == atom_ns("published"): if self.got_published: raise AtomException("entry can only have one published element") else: self.got_published = True return Atom_PublishedHandler elif name == atom_ns("rights"): if self.got_rights: raise AtomException("entry can only have one rights element") else: self.got_rights = True return Atom_RightsHandler elif name == atom_ns("source"): if self.got_source: raise AtomException("entry can only have one source element") else: self.got_source = True return Atom_SourceHandler elif name == atom_ns("summary"): if self.got_summary: raise AtomException("entry can only have one summary element") else: self.got_summary = True return Atom_SummaryHandler elif name == atom_ns("title"): if self.got_title: raise AtomException("entry can only have one title element") else: self.got_title = True return Atom_TitleHandler elif name == atom_ns("updated"): if self.got_updated: raise AtomException("entry can only have one updated element") else: self.got_updated = True return Atom_UpdatedHandler else: return Generic_ElementHandler # @@@ atom:entry elements that contain no child atom:content element # MUST contain at least one atom:link element with a rel attribute # value of "alternate". # @@@ atom:entry elements MUST contain an atom:summary element in either # of the following cases: # # * the atom:entry contains an atom:content that has a "src" # attribute (and is thus empty). # # * the atom:entry contains content that is encoded in Base64; i.e. # the "type" attribute of atom:content is a MIME media type # [MIMEREG], but is not an XML media type [RFC3023], does not # begin with "text/", and does not end with "/xml" or "+xml". def end(self, name): self.validate() if isinstance(self.parent_handler, Atom_FeedHandler): self.parent_handler.product.entries.append(self.product) else: self.parent_handler.product = self.product def validate(self): if isinstance(self.parent_handler, Atom_FeedHandler): if not self.got_author and not self.parent_handler.got_author: raise AtomException("entry may not omit author element if feed does") # if not in a feed, ignore the author requirement @@@ if not self.got_id: raise AtomException("entry must have id element") if not self.got_title: raise AtomException("entry must have title element") if not self.got_updated: raise AtomException("entry must have updated element") class Atom_ContentHandler(Atom_HandlerBase): def init(self): self.product = None if "src" in self.attributes: self.product = OutOfLineContent() self.product.src = self.attributes["src"] type_attr = self.attributes.get("type") if type_attr in ["text", "html", "xhtml"]: raise AtomException("content with src cannot have type of '%s'" % type_attr) if type_attr: self.product.type = type_attr else: type_attr = self.attributes.get("type") if type_attr in ["text", "html"]: self.product = InlineTextContent() elif type_attr == "xhtml": self.product = InlineXHTMLContent() elif type_attr == None: self.product = InlineTextContent() else: self.product = InlineOtherContent() if type_attr: self.product.type = type_attr def child(self, name, attributes): if isinstance(self.product, InlineTextContent): raise AtomException("child elements not allowed when type is inline text") elif isinstance(self.product, InlineXHTMLContent): if name != xhtml_ns("div"): raise AtomException("content of type 'xhtml' must contain single xhtml:div element, not '%s'" % name) else: return Generic_ElementHandler elif isinstance(self.product, OutOfLineContent): raise AtomException("child elements not allowed in out of line content") else: # InlineOtherContent pass # @@@ # @@@ # 4. If the value of "type" is an XML media type [RFC3023], or ends # with "+xml" or "/xml" (case-insensitive), the content of # atom:content MAY include child elements, and SHOULD be suitable # for handling as the indicated media type. If the "src" attribute # is not provided, this would normally mean that the "atom:content" # # element would contain a single child element which would serve as # the root element of the XML document of the indicated type. # # 5. If the value of "type" begins with "text/" (case-insensitive), # the content of atom:content MUST NOT contain child elements. # # 6. For all other values of "type", the content of atom:content MUST # be a valid Base64 encoding, as described in [RFC3548], section 3. # When decoded, it SHOULD be suitable for handling as the indicated # media type. In this case, the characters in the Base64 encoding # MAY be preceded and followed in the atom:content element by # white-space, and lines are separated by a single newline (U+000A) # character. # def end(self, name): self.parent_handler.product.content = self.product Atom_HandlerBase.end(self, name) class Atom_SourceHandler(Atom_HandlerBase): def init(self): self.product = Source() self.got_author = False self.got_generator = False self.got_icon = False self.got_id = False self.got_logo = False self.got_rights = False self.got_subtitle = False self.got_title = False self.got_updated = False def child(self, name, attributes): if name == atom_ns("author"): self.got_author = True return Atom_AuthorHandler elif name == atom_ns("category"): return Atom_CategoryHandler elif name == atom_ns("contributor"): return Atom_ContributorHandler elif name == atom_ns("generator"): if self.got_generator: raise AtomException("source can only have one generator element") else: self.got_generator = True return Atom_GeneratorHandler elif name == atom_ns("icon"): if self.got_icon: raise AtomException("source can only have one icon element") else: self.got_icon = True return Atom_IconHandler elif name == atom_ns("id"): if self.got_id: raise AtomException("source can only have one id element") else: self.got_id = True return Atom_IdHandler elif name == atom_ns("link"): return Atom_LinkHandler elif name == atom_ns("logo"): if self.got_logo: raise AtomException("source can only have one logo element") else: self.got_logo = True return Atom_LogoHandler elif name == atom_ns("rights"): if self.got_rights: raise AtomException("source can only have one rights element") else: self.got_rights = True return Atom_RightsHandler elif name == atom_ns("subtitle"): if self.got_subtitle: raise AtomException("source can only have one subtitle element") else: self.got_subtitle = True return Atom_SubtitleHandler elif name == atom_ns("title"): if self.got_title: raise AtomException("source can only have one title element") else: self.got_title = True return Atom_TitleHandler elif name == atom_ns("updated"): if self.got_updated: raise AtomException("source can only have one updated element") else: self.got_updated = True return Atom_UpdatedHandler else: # @@@ extensionElement return Generic_ElementHandler def end(self, name): self.parent_handler.product.source = self.product def parse_atom(xml): return parse(xml, Atom_RootHandler) demokritos-0.2.0/lib/server.py0000755000076500007650000002042710362322515017452 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import traceback from cStringIO import StringIO import BaseHTTPServer import time import urllib, cgi from store import UnsupportedMediaTypeError from app import generate_introspection from atom import generate_feed class IntrospectionHandler: def HEAD(self, handler): handler.send_response(200) handler.send_header("Content-type", "application/atomserv+xml") handler.end_headers() def GET(self, handler): handler.send_response(200) handler.send_header("Content-type", "application/atomserv+xml") handler.end_headers() generate_introspection(handler.wfile, atom_store.service) def POST(self, handler): handler.send_error(405, "Cannot POST to this URI") def PUT(self, handler): handler.send_error(405, "Cannot PUT to this URI") def DELETE(self, handler): handler.send_error(405, "Cannot DELETE this URI") class CollectionHandler: def HEAD(self, handler): # get head of collection document path, query = urllib.splitquery(handler.path) collection = atom_store.get_collection(path) if collection: if query: # @@@ cleanup try: qsl = cgi.parse_qsl(query, strict_parsing=True) if len(qsl) != 1: raise ValueError if qsl[0][0] != "index": raise ValueError index = int(qsl[0][1]) except ValueError: handler.send_error(404, "Resource Not Found") # @@@ else: index = 0 feed = collection.make_feed(index) content = StringIO() generate_feed(content, feed) handler.send_response(200) handler.send_header("Content-type", "application/atom+xml") handler.send_header("Content-Length", str(len(content.getvalue()))) handler.end_headers() else: handler.send_error(404, "Resource Not Found") def GET(self, handler): # get collection document path, query = urllib.splitquery(handler.path) collection = atom_store.get_collection(path) if collection: if query: # @@@ cleanup try: qsl = cgi.parse_qsl(query, strict_parsing=True) if len(qsl) != 1: raise ValueError if qsl[0][0] != "index": raise ValueError index = int(qsl[0][1]) except ValueError: handler.send_error(404, "Resource Not Found") # @@@ else: index = 0 feed = collection.make_feed(index) content = StringIO() generate_feed(content, feed) handler.send_response(200) handler.send_header("Content-type", "application/atom+xml") handler.send_header("Content-Length", str(len(content.getvalue()))) handler.end_headers() handler.wfile.write(content.getvalue()) else: handler.send_error(404, "Collection Not Found") def POST(self, handler): # add new resource to collection and return 201 with location collection = atom_store.get_collection(handler.path) # @@@ the following code really belongs in store.py if collection: suggested_title = handler.headers.get("Title") media_type = handler.headers.get("Content-type") length = handler.headers.get("Content-Length") content = handler.rfile.read(int(length)) # throw away additional data (from CGIHTTPServer) #while select.select([self.rfile._sock], [], [], 0)[0]: # if not self.rfile._sock.recv(1): # break try: new_uri = collection.create_member(content, media_type, suggested_title) handler.send_response(201) handler.send_header("Location", new_uri) handler.end_headers() except UnsupportedMediaTypeError: handler.send_error(415, "Unsupported Media Type") else: handler.send_error(404, "Collection Not Found") def PUT(self, handler): handler.send_error(405, "Cannot PUT to this URI") def DELETE(self, handler): handler.send_error(405, "Cannot DELETE this URI") class MemberHandler: def HEAD(self, handler): # get head of member resource pass # @@@ def GET(self, handler): # get member resource member = atom_store.get_member(handler.path) if member: handler.send_response(200) handler.send_header("Content-type", member.media_type) # @@@ length handler.end_headers() handler.wfile.write(member.get_content()) else: handler.send_error(404, "Resource Not Found") def POST(self, handler): handler.send_error(405, "Cannot POST to this URI") def PUT(self, handler): # replace resource, return 200 with resource media_type = handler.headers.get("Content-type") length = handler.headers.get("Content-Length") suggested_title = handler.headers.get("Title") content = handler.rfile.read(int(length)) # throw away additional data (from CGIHTTPServer) #while select.select([self.rfile._sock], [], [], 0)[0]: # if not self.rfile._sock.recv(1): # break if atom_store.member_exists(handler.path): member = atom_store.get_member(handler.path) else: member = atom_store.create_member(handler.path) # @@@ won't be in any collections atom_store.replace_member(member, content, media_type, suggested_title) handler.send_response(200) handler.send_header("Content-type", media_type) # @@@ length handler.end_headers() handler.wfile.write(content) def DELETE(self, handler): # delete resource, return 204 member = atom_store.get_member(handler.path) if member: atom_store.remove_member(member) else: handler.send_error(404, "Resource Not Found") handler.send_response(204) class AtomRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): server_version = "Demokritos/0.2" def do_HEAD(self): try: resource_handler = self.dispatch(self.path) resource_handler.HEAD(self) except: traceback.print_exc() self.send_error(500, "Server Got Exception") def do_GET(self): try: resource_handler = self.dispatch(self.path) resource_handler.GET(self) except: traceback.print_exc() self.send_error(500, "Server Got Exception") def do_POST(self): try: resource_handler = self.dispatch(self.path) resource_handler.POST(self) except: traceback.print_exc() self.send_error(500, "Server Got Exception") def do_PUT(self): try: resource_handler = self.dispatch(self.path) resource_handler.PUT(self) except: traceback.print_exc() self.send_error(500, "Server Got Exception") def do_DELETE(self): try: resource_handler = self.dispatch(self.path) resource_handler.DELETE(self) except: traceback.print_exc() self.send_error(500, "Server Got Exception") def dispatch(self, path): if path == "/introspection": return IntrospectionHandler() elif atom_store.collection_exists(urllib.splitquery(path)[0]): return CollectionHandler() else: return MemberHandler() class AtomServer: def __init__(self, host="", port=8000): self.httpd = BaseHTTPServer.HTTPServer((host, port), AtomRequestHandler) def serve_forever(self): self.httpd.serve_forever()demokritos-0.2.0/lib/store.py0000644000076500007650000002436010362326232017275 0ustar jtauberjtauber00000000000000import sys sys.path.append("lib") from app import Service, Workspace, Collection, Member # @@@ put in atom.__init__ from atom.model import Feed, Entry, Link, Date, PlainText, Person, InlineTextContent, OutOfLineContent from app import parse_entry from atom import generate_entry from cStringIO import StringIO import re import time class UnsupportedMediaTypeError(Exception): pass class AtomStore: def __init__(self, root_uri): self.root_uri = root_uri self.service = Service() self.collection_manager = CollectionManager() self.member_manager = MemberManager() self.make_uri = UriMint(self.root_uri) def create_workspace(self, name): workspace = ServerWorkspace(name) self.service.workspaces.append(workspace) return workspace def create_collection(self, title, path, member_type): if member_type == "media": collection = ServerMediaCollection(title, path) elif member_type == "entry": collection = ServerEntryCollection(title, path) collection.href = self.root_uri + path collection.id = collection.href collection.store = self # @@@ self.collection_manager.register(collection) return collection def create_media_member(self, path, media_type): member = ServerMediaMember(path, media_type) self.member_manager.register(member) member.href = self.root_uri + path member.id = member.href return member def create_entry_member(self, path): member = ServerEntryMember(path) self.member_manager.register(member) member.href = self.root_uri + path member.id = member.href return member # @@@ where should this go? method on member itself? def replace_member(self, member, content, media_type, suggested_title): member.title = suggested_title or "no title" # @@@ member.updated = Date(time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())) # @@@ member.media_type = media_type # @@@ member.raw_content = content # @@@ def collection_exists(self, path): return self.collection_manager.exists(path) def member_exists(self, path): return self.member_manager.exists(path) def get_collection(self, path): return self.collection_manager.get(path) def get_member(self, path): return self.member_manager.get(path) def remove_member(self, member): self.member_manager.remove(member) class UriMint: """ This example of a URI Mint concatenates the collection path with yyyy/mm/dd with a modified suggested_name. """ def __init__(self, root_uri): self.root_uri = root_uri def __call__(self, collection, suggested_name=None): if suggested_name: base_name = re.sub(" ", "_", suggested_name).lower()[:20] else: base_name = "resource" # @@@ date = "/%04d/%02d/%02d/" % tuple(time.localtime()[:3]) # @@@ doesn't check for duplicates yet # @@@ should support collection-specific prefix path = date + base_name uri = self.root_uri + path return uri, path class ServerWorkspace(Workspace): def add_collection(self, collection): Workspace.add_collection(self, collection.title, collection.href, collection.member_type) class ServerCollection(Collection): def __init__(self, title, path): Collection.__init__(self) self.title = title self.path = path self.updated = Date(time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())) # @@@ self.by_path = {} def add_member(self, member): if not self.supported_media_type(member.media_type): raise UnsupportedMediaTypeError() Collection.add_member(self, member) self.by_path[member.path] = member member.collections.add(self) self.updated = self.members[0].updated def replace(self, member): if member.path in self.by_path: self.members.remove(self.by_path[member.path]) del self.by_path[member.path] self.add_member(member) def remove(self, member): self.members.remove(member) del self.by_path[member.path] def make_feed(self, index=0): max_entries = 10 # @@@ magic number to = min(index + max_entries, len(self.members)) if len(self.members) > to: next = to else: next = 0 if index > 0: prev = min(0, index - max_entries) end = max(0, max_entries * ((len(self.members) - 1) / max_entries)) feed = Feed() feed.id = self.id # @@@ feed.title = PlainText(self.title) # @@@ perhaps Collection.title should be text construct feed.updated = self.updated if index: feed.links.append(Link(rel="start", href=self.href)) if prev: feed.links.append(Link(rel="previous", href="%s?index=%d" % (self.href, prev))) else: feed.links.append(Link(rel="previous", href=self.href)) if next: feed.links.append(Link(rel="next", href="%s?index=%d" % (self.href, next))) if end: feed.links.append(Link(rel="end", href="%s?index=%d" % (self.href, end))) else: # always include end feed.links.append(Link(rel="end", href=self.href)) feed.entries = [member.make_feed_entry() for member in self.members[index:to]] return feed class ServerMediaCollection(ServerCollection): # @@@ add support for restricting media types def __init__(self, title, path): ServerCollection.__init__(self, title, path) self.member_type = "media" def supported_media_type(self, media_type): return True def create_member(self, content, media_type, suggested_title): if not self.supported_media_type(media_type): raise UnsupportedMediaTypeError() new_uri, path = self.store.make_uri(self, suggested_title) member = self.store.create_media_member(path, media_type) member.title = PlainText(suggested_title or "no title") member.updated = Date(time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())) member.authors = [Person(name="Sam Pell")] # @@@ eventually whoever is authenticated member.raw_content = content self.add_member(member) return new_uri class ServerEntryCollection(ServerCollection): def __init__(self, title, path): ServerCollection.__init__(self, title, path) self.member_type = "entry" def supported_media_type(self, media_type): return media_type == "application/atom+xml" def create_member(self, content, media_type, suggested_title): if not self.supported_media_type(media_type): raise UnsupportedMediaTypeError() new_uri, path = self.store.make_uri(self, suggested_title) member = self.store.create_entry_member(path) member.media_type = "application/atom+xml" entry = parse_entry(content) entry.id = member.id entry.updated = Date(time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())) member.title = entry.title member.updated = entry.updated member.authors = entry.authors or [Person(name="Sam Pell")] # @@@ eventually whoever is authenticated out = StringIO() generate_entry(out, entry) member.raw_content = out.getvalue() self.add_member(member) return new_uri class ServerMember(Member): def __init__(self, path): # Member.__init__(self) self.path = path self.collections = set() class ServerMediaMember(ServerMember): def __init__(self, path, media_type): ServerMember.__init__(self, path) self.media_type = media_type def make_feed_entry(self): entry = Entry() entry.title = self.title entry.id = self.id entry.updated = self.updated # @@@ entry.published = self.published entry.links.append(Link(rel="edit", href=self.href)) entry.authors = self.authors # @@@ entry.contributors = self.contributors # @@@ entry.categories = self.categories entry.content = OutOfLineContent(src=self.href) return entry def get_content(self): return self.raw_content class ServerEntryMember(ServerMember): def make_feed_entry(self): entry = Entry() entry.title = self.title entry.id = self.id entry.updated = self.updated # @@@ entry.published = self.published entry.links.append(Link(rel="edit", href=self.href)) entry.authors = self.authors # @@@ entry.contributors = self.contributors # @@@ entry.categories = self.categories # @@@ summary # entry.content = OutOfLineContent(src=self.href) return entry def get_content(self): return self.raw_content # @@@ class CollectionManager: def __init__(self): self.collections = {} def register(self, collection): self.collections[collection.path] = collection def get(self, path): return self.collections.get(path) def exists(self, path): return path in self.collections class MemberManager: def __init__(self): self.members = {} def register(self, member): self.members[member.path] = member def replace(self, member): old_member = self.get(member.path) if old_member: for collection in old_member.collections: collection.replace(member) member.collections.add(collection) self.members[member.path] = member def get(self, path): return self.members.get(path) def exists(self, path): return path in self.members def remove(self, member): for collection in member.collections: collection.remove(member) del self.members[member.path] demokritos-0.2.0/lib/xmlbase.py0000644000076500007650000000673310344405070017576 0ustar jtauberjtauber00000000000000from xml.parsers import expat XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace" NAMESPACE_SEPARATOR = "^" def xml_ns(name): return XML_NAMESPACE + NAMESPACE_SEPARATOR + name class HandlerBase: def __init__(self, parser, parent_handler=None, name=None, attributes=None, inherited={}): self.parser = parser self.parent_handler = parent_handler self.name = name self.attributes = {} self.char_data = "" self.set_handlers() self.inherited = inherited if attributes: for name, value in attributes.items(): if name == xml_ns("lang"): self.inherited["xml:lang"] = value elif name == xml_ns("base"): self.inherited["xml:base"] = value else: self.attributes[name] = value self.init() def init(self): pass def set_handlers(self): self.parser.StartElementHandler = self.process_child self.parser.CharacterDataHandler = self.char self.parser.EndElementHandler = self.process_end def process_child(self, name, attributes): handler = self.child(name, attributes) handler(self.parser, self, name, attributes, self.inherited) def char(self, data): self.char_data += data def process_end(self, name): self.end(name) if self.parent_handler is not None: self.parent_handler.set_handlers() else: # must be root pass def end(self, name): pass class Element: def __init__(self, name, attributes): self.name = name self.attributes = attributes self.children = [] def add(self, child): self.children.append(child) class CharData: def __init__(self, chardata): self.chardata = chardata class Generic_ElementHandler(HandlerBase): def init(self): self.product = Element(self.name, self.attributes) def child(self, name, attributes): return Generic_ElementHandler def char(self, data): self.product.add(CharData(data)) def end(self, name): self.parent_handler.product.add(self.product) def parse(xml, root_handler_class): parser = expat.ParserCreate(namespace_separator=NAMESPACE_SEPARATOR) root_handler = root_handler_class(parser) parser.Parse(xml) return root_handler.product def make_prefix(uri): if uri == "http://www.w3.org/1999/xhtml": return "xhtml" else: pass # @@@ def generate_xml(wfile, element, namespaces = {}): attrs = "" for name, value in element.attributes.items(): # @@@ escaping attrs += " %s=\"%s\"" % (name, value) if NAMESPACE_SEPARATOR in element.name: # @@@ should really be pre-split uri, name = element.name.split(NAMESPACE_SEPARATOR) if uri not in namespaces: prefix = make_prefix(uri) attrs += " xmlns:%s=\"%s\"" % (prefix, uri) namespaces[uri] = prefix element_name = namespaces[uri] + ":" + name else: element_name = element.name wfile.write("<%s%s>" % (element_name, attrs)) for child in element.children: if isinstance(child, Element): generate_xml(wfile, child, namespaces) else: # chardata wfile.write(child.chardata) wfile.write("" % element_name)demokritos-0.2.0/README0000644000076500007650000000045110362323537015701 0ustar jtauberjtauber00000000000000Demokritos Atom Server version 0.2 http://jtauber.com/demokritos ./basic_server.py provides a server set up with an entry collection and media collection in a single workspace. It should be sufficient for basic interoperability testing. There are more detailed tests in the ./test/ directory. demokritos-0.2.0/test/0000755000076500007650000000000010362327333015776 5ustar jtauberjtauber00000000000000demokritos-0.2.0/test/client_test.py0000755000076500007650000002245610362321362020675 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import httplib, socket, urlparse import sys sys.path.append("../lib") from app import parse_introspection from atom import parse_atom SERVER = "localhost:8000" def request(server, method, path, body=None, headers=None): conn = httplib.HTTPConnection(server) try: print "trying %s %s to %s" % (method, path, server) if body is not None and headers is not None: conn.request(method, path, body, headers) else: conn.request(method, path) resp = conn.getresponse() conn.close() print "got %s %s" % (resp.status, resp.reason) return resp except socket.error: print "got socket error" return None ## Retrieving an Introspection Document (APP-07 5.1) print "getting introspection document..." collection_href = None response = request(SERVER, "GET", "/introspection") if response: assert response.status == 200 doc = response.read() service = parse_introspection(doc) print "service:" for workspace in service.workspaces: print "\tworkspace:", workspace.title for collection in workspace.collections: print "\t\tcollection:", collection.title print "\t\t\thref =", collection.href print "\t\t\tmember-type =", collection.member_type collection_href = service.workspaces[0].collections[0].href raw_input("press return") ## Listing Collection Memembers (APP-07 5.4) member_href = None if collection_href: print "getting first collection in first workspace..." server, path = urlparse.urlparse(collection_href)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src member_href = collection.entries[0].links[0].href raw_input("press return") ## if member_href: print "getting first member in that collection..." server, path = urlparse.urlparse(member_href)[1:3] response = request(server, "GET", path) if response and response.status == 200: doc = response.read() print doc raw_input("press return") ## Creating a Resource (APP-07 5.2) location = None if collection_href: print "POSTing to collection..." server, path = urlparse.urlparse(collection_href)[1:3] body = "Hello world!" headers = {"Content-type": "text/plain", "Title": "My Title"} response = request(server, "POST", path, body, headers) if response: assert response.status == 201 location = response.getheader("Location") print "got location:", location raw_input("press return") ## Listing Collection Memembers (APP-07 5.4) if collection_href: print "getting collection we just posted to..." server, path = urlparse.urlparse(collection_href)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src member_href = collection.entries[0].links[0].href raw_input("press return") ## Retrieving a Resource (APP-07 5.3.1) if location: print "GETting new resource..." server, path = urlparse.urlparse(location)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() print doc assert doc == "Hello world!" raw_input("press return") ## Updating a Resource (APP-07 5.3.2) if collection_href: print "PUTing new resource..." server, path = urlparse.urlparse(location)[1:3] body = "Goodbye world!" headers = {"Content-type": "text/plain"} response = request(server, "PUT", path, body, headers) if response: assert response.status == 200 doc = response.read() print doc assert doc == "Goodbye world!" raw_input("press return") ## if location: print "GETting newer resource..." server, path = urlparse.urlparse(location)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() print doc assert doc == "Goodbye world!" raw_input("press return") ## Deleting a Resource (APP-07 5.3.3) if location: print "DELETEing resource..." server, path = urlparse.urlparse(location)[1:3] response = request(server, "DELETE", path) if response: assert response.status == 204 raw_input("press return") ## if location: print "GETting deleted resource..." server, path = urlparse.urlparse(location)[1:3] response = request(server, "GET", path) if response: assert response.status == 404 raw_input("press return") ## Listing Collection Memembers (APP-07 5.4) if collection_href: print "getting collection we just deleted from..." server, path = urlparse.urlparse(collection_href)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src raw_input("press return") ### atom collection tests ## collection_href = None response = request(SERVER, "GET", "/introspection") if response: assert response.status == 200 doc = response.read() service = parse_introspection(doc) collection_href = service.workspaces[0].collections[1].href if collection_href: print "getting second collection in first workspace..." server, path = urlparse.urlparse(collection_href)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src raw_input("press return") ## if collection_href: print "POSTing non-atom to entry collection..." server, path = urlparse.urlparse(collection_href)[1:3] body = "Hello world!" headers = {"Content-type": "text/plain", "Title": "My Title"} response = request(server, "POST", path, body, headers) if response: assert response.status == 415 raw_input("press return") ## Creating resources with POST (APP-07 8.1) location = None if collection_href: print "POSTing atom to entry collection..." server, path = urlparse.urlparse(collection_href)[1:3] # from APP-07 8.1.1 (@@@ externalise) body = """ Atom-Powered Robots Run Amok urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a 2003-12-13T18:30:02Z Some text. """ headers = {"Content-type": "application/atom+xml"} response = request(server, "POST", path, body, headers) if response: assert response.status == 201 location = response.getheader("Location") print "got location:", location raw_input("press return") ## if location: print "GETting new resource..." server, path = urlparse.urlparse(location)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() print doc raw_input("press return") ## Listing Collection Memembers (APP-07 5.4) if collection_href: print "getting collection again" server, path = urlparse.urlparse(collection_href)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) assert collection.entries[0].title.value == "Atom-Powered Robots Run Amok" assert collection.entries[0].links[0].rel == "edit" demokritos-0.2.0/test/coverage.py0000644000076500007650000007324510316664521020160 0ustar jtauberjtauber00000000000000#!/usr/bin/python # # Perforce Defect Tracking Integration Project # # # COVERAGE.PY -- COVERAGE TESTING # # Gareth Rees, Ravenbrook Limited, 2001-12-04 # Ned Batchelder, 2004-12-12 # http://nedbatchelder.com/code/modules/coverage.html # # # 1. INTRODUCTION # # This module provides coverage testing for Python code. # # The intended readership is all Python developers. # # This document is not confidential. # # See [GDR 2001-12-04a] for the command-line interface, programmatic # interface and limitations. See [GDR 2001-12-04b] for requirements and # design. """Usage: coverage.py -x MODULE.py [ARG1 ARG2 ...] Execute module, passing the given command-line arguments, collecting coverage data. coverage.py -e Erase collected coverage data. coverage.py -r [-m] FILE1 FILE2 ... Report on the statement coverage for the given files. With the -m option, show line numbers of the statements that weren't executed. coverage.py -a [-d dir] FILE1 FILE2 ... Make annotated copies of the given files, marking statements that are executed with > and statements that are missed with !. With the -d option, make the copies in that directory. Without the -d option, make each copy in the same directory as the original. Coverage data is saved in the file .coverage by default. Set the COVERAGE_FILE environment variable to save it somewhere else.""" __version__ = "2.2.20041231" # see detailed history at the end of this file. import compiler import compiler.visitor import os import re import string import sys import types # 2. IMPLEMENTATION # # This uses the "singleton" pattern. # # The word "morf" means a module object (from which the source file can # be deduced by suitable manipulation of the __file__ attribute) or a # filename. # # When we generate a coverage report we have to canonicalize every # filename in the coverage dictionary just in case it refers to the # module we are reporting on. It seems a shame to throw away this # information so the data in the coverage dictionary is transferred to # the 'cexecuted' dictionary under the canonical filenames. # # The coverage dictionary is called "c" and the trace function "t". The # reason for these short names is that Python looks up variables by name # at runtime and so execution time depends on the length of variables! # In the bottleneck of this application it's appropriate to abbreviate # names to increase speed. # A dictionary with an entry for (Python source file name, line number # in that file) if that line has been executed. c = {} # t(f, x, y). This method is passed to sys.settrace as a trace # function. See [van Rossum 2001-07-20b, 9.2] for an explanation of # sys.settrace and the arguments and return value of the trace function. # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code # objects. def t(f, w, a): #print w, f.f_code.co_filename, f.f_lineno if w == 'line': c[(f.f_code.co_filename, f.f_lineno)] = 1 return t class StatementFindingAstVisitor(compiler.visitor.ASTVisitor): def __init__(self, statements, excluded, suite_spots): compiler.visitor.ASTVisitor.__init__(self) self.statements = statements self.excluded = excluded self.suite_spots = suite_spots self.excluding_suite = 0 def doRecursive(self, node): self.recordNodeLine(node) for n in node.getChildNodes(): self.dispatch(n) visitStmt = visitModule = doRecursive def doCode(self, node): if hasattr(node, 'decorators') and node.decorators: self.dispatch(node.decorators) self.doSuite(node, node.code) visitFunction = visitClass = doCode def getFirstLine(self, node): # Find the first line in the tree node. lineno = node.lineno for n in node.getChildNodes(): f = self.getFirstLine(n) if lineno and f: lineno = min(lineno, f) else: lineno = lineno or f return lineno def getLastLine(self, node): # Find the first line in the tree node. lineno = node.lineno for n in node.getChildNodes(): lineno = max(lineno, self.getLastLine(n)) return lineno def doStatement(self, node): self.recordLine(self.getFirstLine(node)) visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \ visitPrintnl = visitRaise = visitSubscript = \ visitDecorators = \ doStatement def recordNodeLine(self, node): return self.recordLine(node.lineno) def recordLine(self, lineno): # Returns a bool, whether the line is included or excluded. if lineno: # Multi-line tests introducing suites have to get charged to their # keyword. if lineno in self.suite_spots: lineno = self.suite_spots[lineno][0] # If we're inside an exluded suite, record that this line was # excluded. if self.excluding_suite: self.excluded[lineno] = 1 return 0 # If this line is excluded, or suite_spots maps this line to # another line that is exlcuded, then we're excluded. elif self.excluded.has_key(lineno) or \ self.suite_spots.has_key(lineno) and \ self.excluded.has_key(self.suite_spots[lineno][1]): return 0 # Otherwise, this is an executable line. else: self.statements[lineno] = 1 return 1 return 0 default = recordNodeLine def recordAndDispatch(self, node): self.recordNodeLine(node) self.dispatch(node) def doSuite(self, intro, body, exclude=0): exsuite = self.excluding_suite if exclude or (intro and not self.recordNodeLine(intro)): self.excluding_suite = 1 self.recordAndDispatch(body) self.excluding_suite = exsuite def doPlainWordSuite(self, prevsuite, suite): # Finding the exclude lines for else's is tricky, because they aren't # present in the compiler parse tree. Look at the previous suite, # and find its last line. If any line between there and the else's # first line are excluded, then we exclude the else. lastprev = self.getLastLine(prevsuite) firstelse = self.getFirstLine(suite) for l in range(lastprev+1, firstelse): if self.suite_spots.has_key(l): self.doSuite(None, suite, exclude=self.excluded.has_key(l)) break else: self.doSuite(None, suite) def doElse(self, prevsuite, node): if node.else_: self.doPlainWordSuite(prevsuite, node.else_) def visitFor(self, node): self.doSuite(node, node.body) self.doElse(node.body, node) def visitIf(self, node): # The first test has to be handled separately from the rest. # The first test is credited to the line with the "if", but the others # are credited to the line with the test for the elif. self.doSuite(node, node.tests[0][1]) for t, n in node.tests[1:]: self.doSuite(t, n) self.doElse(node.tests[-1][1], node) def visitWhile(self, node): self.doSuite(node, node.body) self.doElse(node.body, node) def visitTryExcept(self, node): self.doSuite(node, node.body) for i in range(len(node.handlers)): a, b, h = node.handlers[i] if not a: # It's a plain "except:". Find the previous suite. if i > 0: prev = node.handlers[i-1][2] else: prev = node.body self.doPlainWordSuite(prev, h) else: self.doSuite(a, h) self.doElse(node.handlers[-1][2], node) def visitTryFinally(self, node): self.doSuite(node, node.body) self.doPlainWordSuite(node.body, node.final) def visitGlobal(self, node): # "global" statements don't execute like others (they don't call the # trace function), so don't record their line numbers. pass the_coverage = None class coverage: error = "coverage error" # Name of the cache file (unless environment variable is set). cache_default = ".coverage" # Environment variable naming the cache file. cache_env = "COVERAGE_FILE" # A map from canonical Python source file name to a dictionary in # which there's an entry for each line number that has been # executed. cexecuted = {} # Cache of results of calling the analysis2() method, so that you can # specify both -r and -a without doing double work. analysis_cache = {} # Cache of results of calling the canonical_filename() method, to # avoid duplicating work. canonical_filename_cache = {} def __init__(self): global the_coverage if the_coverage: raise self.error, "Only one coverage object allowed." self.usecache = 1 self.cache = None self.exclude_re = '' def help(self, error=None): if error: print error print print __doc__ sys.exit(1) def command_line(self): import getopt settings = {} optmap = { '-a': 'annotate', '-d:': 'directory=', '-e': 'erase', '-h': 'help', '-i': 'ignore-errors', '-m': 'show-missing', '-r': 'report', '-x': 'execute', } short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '') long_opts = optmap.values() options, args = getopt.getopt(sys.argv[1:], short_opts, long_opts) for o, a in options: if optmap.has_key(o): settings[optmap[o]] = 1 elif optmap.has_key(o + ':'): settings[optmap[o + ':']] = a elif o[2:] in long_opts: settings[o[2:]] = 1 elif o[2:] + '=' in long_opts: settings[o[2:]] = a else: self.help("Unknown option: '%s'." % o) if settings.get('help'): self.help() for i in ['erase', 'execute']: for j in ['annotate', 'report']: if settings.get(i) and settings.get(j): self.help("You can't specify the '%s' and '%s' " "options at the same time." % (i, j)) args_needed = (settings.get('execute') or settings.get('annotate') or settings.get('report')) action = settings.get('erase') or args_needed if not action: self.help("You must specify at least one of -e, -x, -r, or -a.") if not args_needed and args: self.help("Unexpected arguments %s." % args) self.get_ready() self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]') if settings.get('erase'): self.erase() if settings.get('execute'): if not args: self.help("Nothing to do.") sys.argv = args self.start() import __main__ sys.path[0] = os.path.dirname(sys.argv[0]) execfile(sys.argv[0], __main__.__dict__) if not args: args = self.cexecuted.keys() ignore_errors = settings.get('ignore-errors') show_missing = settings.get('show-missing') directory = settings.get('directory=') if settings.get('report'): self.report(args, show_missing, ignore_errors) if settings.get('annotate'): self.annotate(args, directory, ignore_errors) def use_cache(self, usecache): self.usecache = usecache def get_ready(self): if self.usecache and not self.cache: self.cache = os.environ.get(self.cache_env, self.cache_default) self.restore() self.analysis_cache = {} def start(self): self.get_ready() sys.settrace(t) def stop(self): sys.settrace(None) def erase(self): global c c = {} self.analysis_cache = {} self.cexecuted = {} if self.cache and os.path.exists(self.cache): os.remove(self.cache) self.exclude_re = '' def exclude(self, re): if self.exclude_re: self.exclude_re += "|" self.exclude_re += "(" + re + ")" # save(). Save coverage data to the coverage cache. def save(self): if self.usecache and self.cache: self.canonicalize_filenames() cache = open(self.cache, 'wb') import marshal marshal.dump(self.cexecuted, cache) cache.close() # restore(). Restore coverage data from the coverage cache (if it # exists). def restore(self): global c c = {} self.cexecuted = {} assert self.usecache if not os.path.exists(self.cache): return try: cache = open(self.cache, 'rb') import marshal cexecuted = marshal.load(cache) cache.close() if isinstance(cexecuted, types.DictType): self.cexecuted = cexecuted except: pass # canonical_filename(filename). Return a canonical filename for the # file (that is, an absolute path with no redundant components and # normalized case). See [GDR 2001-12-04b, 3.3]. def canonical_filename(self, filename): if not self.canonical_filename_cache.has_key(filename): f = filename if os.path.isabs(f) and not os.path.exists(f): f = os.path.basename(f) if not os.path.isabs(f): for path in [os.curdir] + sys.path: g = os.path.join(path, f) if os.path.exists(g): f = g break cf = os.path.normcase(os.path.abspath(f)) self.canonical_filename_cache[filename] = cf return self.canonical_filename_cache[filename] # canonicalize_filenames(). Copy results from "executed" to # "cexecuted", canonicalizing filenames on the way. Clear the # "executed" map. def canonicalize_filenames(self): global c for filename, lineno in c.keys(): f = self.canonical_filename(filename) if not self.cexecuted.has_key(f): self.cexecuted[f] = {} self.cexecuted[f][lineno] = 1 c = {} # morf_filename(morf). Return the filename for a module or file. def morf_filename(self, morf): if isinstance(morf, types.ModuleType): if not hasattr(morf, '__file__'): raise self.error, "Module has no __file__ attribute." file = morf.__file__ else: file = morf return self.canonical_filename(file) # analyze_morf(morf). Analyze the module or filename passed as # the argument. If the source code can't be found, raise an error. # Otherwise, return a tuple of (1) the canonical filename of the # source code for the module, (2) a list of lines of statements # in the source code, and (3) a list of lines of excluded statements. def analyze_morf(self, morf): if self.analysis_cache.has_key(morf): return self.analysis_cache[morf] filename = self.morf_filename(morf) ext = os.path.splitext(filename)[1] if ext == '.pyc': if not os.path.exists(filename[0:-1]): raise self.error, ("No source for compiled code '%s'." % filename) filename = filename[0:-1] elif ext != '.py': raise self.error, "File '%s' not Python source." % filename source = open(filename, 'r') lines, excluded_lines = self.find_executable_statements( source.read(), exclude=self.exclude_re ) source.close() result = filename, lines, excluded_lines self.analysis_cache[morf] = result return result def get_suite_spots(self, tree, spots): import symbol, token for i in range(1, len(tree)): if type(tree[i]) == type(()): if tree[i][0] == symbol.suite: # Found a suite, look back for the colon and keyword. lineno_colon = lineno_word = None for j in range(i-1, 0, -1): if tree[j][0] == token.COLON: lineno_colon = tree[j][2] elif tree[j][0] == token.NAME: if tree[j][1] == 'elif': # Find the line number of the first non-terminal # after the keyword. t = tree[j+1] while t and token.ISNONTERMINAL(t[0]): t = t[1] if t: lineno_word = t[2] else: lineno_word = tree[j][2] break elif tree[j][0] == symbol.except_clause: # "except" clauses look like: # ('except_clause', ('NAME', 'except', lineno), ...) if tree[j][1][0] == token.NAME: lineno_word = tree[j][1][2] break if lineno_colon and lineno_word: # Found colon and keyword, mark all the lines # between the two with the two line numbers. for l in range(lineno_word, lineno_colon+1): spots[l] = (lineno_word, lineno_colon) self.get_suite_spots(tree[i], spots) def find_executable_statements(self, text, exclude=None): # Find lines which match an exclusion pattern. excluded = {} suite_spots = {} if exclude: reExclude = re.compile(exclude) lines = text.split('\n') for i in range(len(lines)): if reExclude.search(lines[i]): excluded[i+1] = 1 import parser tree = parser.suite(text+'\n\n').totuple(1) self.get_suite_spots(tree, suite_spots) # Use the compiler module to parse the text and find the executable # statements. We add newlines to be impervious to final partial lines. statements = {} ast = compiler.parse(text+'\n\n') visitor = StatementFindingAstVisitor(statements, excluded, suite_spots) compiler.walk(ast, visitor, walker=visitor) lines = statements.keys() lines.sort() excluded_lines = excluded.keys() excluded_lines.sort() return lines, excluded_lines # format_lines(statements, lines). Format a list of line numbers # for printing by coalescing groups of lines as long as the lines # represent consecutive statements. This will coalesce even if # there are gaps between statements, so if statements = # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then # format_lines will return "1-2, 5-11, 13-14". def format_lines(self, statements, lines): pairs = [] i = 0 j = 0 start = None pairs = [] while i < len(statements) and j < len(lines): if statements[i] == lines[j]: if start == None: start = lines[j] end = lines[j] j = j + 1 elif start: pairs.append((start, end)) start = None i = i + 1 if start: pairs.append((start, end)) def stringify(pair): start, end = pair if start == end: return "%d" % start else: return "%d-%d" % (start, end) import string return string.join(map(stringify, pairs), ", ") # Backward compatibility with version 1. def analysis(self, morf): f, s, _, m, mf = self.analysis2(morf) return f, s, m, mf def analysis2(self, morf): filename, statements, excluded = self.analyze_morf(morf) self.canonicalize_filenames() if not self.cexecuted.has_key(filename): self.cexecuted[filename] = {} missing = [] for line in statements: if not self.cexecuted[filename].has_key(line): missing.append(line) return (filename, statements, excluded, missing, self.format_lines(statements, missing)) def morf_name(self, morf): if isinstance(morf, types.ModuleType): return morf.__name__ else: return os.path.splitext(os.path.basename(morf))[0] def report(self, morfs, show_missing=1, ignore_errors=0): if not isinstance(morfs, types.ListType): morfs = [morfs] max_name = max([5,] + map(len, map(self.morf_name, morfs))) fmt_name = "%%- %ds " % max_name fmt_err = fmt_name + "%s: %s" header = fmt_name % "Name" + " Stmts Exec Cover" fmt_coverage = fmt_name + "% 6d % 6d % 5d%%" if show_missing: header = header + " Missing" fmt_coverage = fmt_coverage + " %s" print header print "-" * len(header) total_statements = 0 total_executed = 0 for morf in morfs: name = self.morf_name(morf) try: _, statements, _, missing, readable = self.analysis2(morf) n = len(statements) m = n - len(missing) if n > 0: pc = 100.0 * m / n else: pc = 100.0 args = (name, n, m, pc) if show_missing: args = args + (readable,) print fmt_coverage % args total_statements = total_statements + n total_executed = total_executed + m except KeyboardInterrupt: raise except: if not ignore_errors: type, msg = sys.exc_info()[0:2] print fmt_err % (name, type, msg) if len(morfs) > 1: print "-" * len(header) if total_statements > 0: pc = 100.0 * total_executed / total_statements else: pc = 100.0 args = ("TOTAL", total_statements, total_executed, pc) if show_missing: args = args + ("",) print fmt_coverage % args # annotate(morfs, ignore_errors). blank_re = re.compile("\\s*(#|$)") else_re = re.compile("\\s*else\\s*:\\s*(#|$)") def annotate(self, morfs, directory=None, ignore_errors=0): for morf in morfs: try: filename, statements, excluded, missing, _ = self.analysis2(morf) self.annotate_file(filename, statements, excluded, missing, directory) except KeyboardInterrupt: raise except: if not ignore_errors: raise def annotate_file(self, filename, statements, excluded, missing, directory=None): source = open(filename, 'r') if directory: dest_file = os.path.join(directory, os.path.basename(filename) + ',cover') else: dest_file = filename + ',cover' dest = open(dest_file, 'w') lineno = 0 i = 0 j = 0 covered = 1 while 1: line = source.readline() if line == '': break lineno = lineno + 1 while i < len(statements) and statements[i] < lineno: i = i + 1 while j < len(missing) and missing[j] < lineno: j = j + 1 if i < len(statements) and statements[i] == lineno: covered = j >= len(missing) or missing[j] > lineno if self.blank_re.match(line): dest.write(' ') elif self.else_re.match(line): # Special logic for lines containing only # 'else:'. See [GDR 2001-12-04b, 3.2]. if i >= len(statements) and j >= len(missing): dest.write('! ') elif i >= len(statements) or j >= len(missing): dest.write('> ') elif statements[i] == missing[j]: dest.write('! ') else: dest.write('> ') elif lineno in excluded: dest.write('- ') elif covered: dest.write('> ') else: dest.write('! ') dest.write(line) source.close() dest.close() # Singleton object. the_coverage = coverage() # Module functions call methods in the singleton object. def use_cache(*args, **kw): return the_coverage.use_cache(*args, **kw) def start(*args, **kw): return the_coverage.start(*args, **kw) def stop(*args, **kw): return the_coverage.stop(*args, **kw) def erase(*args, **kw): return the_coverage.erase(*args, **kw) def exclude(*args, **kw): return the_coverage.exclude(*args, **kw) def analysis(*args, **kw): return the_coverage.analysis(*args, **kw) def analysis2(*args, **kw): return the_coverage.analysis2(*args, **kw) def report(*args, **kw): return the_coverage.report(*args, **kw) def annotate(*args, **kw): return the_coverage.annotate(*args, **kw) def annotate_file(*args, **kw): return the_coverage.annotate_file(*args, **kw) # Save coverage data when Python exits. (The atexit module wasn't # introduced until Python 2.0, so use sys.exitfunc when it's not # available.) try: import atexit atexit.register(the_coverage.save) except ImportError: sys.exitfunc = the_coverage.save # Command-line interface. if __name__ == '__main__': the_coverage.command_line() # A. REFERENCES # # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees; # Ravenbrook Limited; 2001-12-04; # . # # [GDR 2001-12-04b] "Statement coverage for Python: design and # analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04; # . # # [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)"; # Guide van Rossum; 2001-07-20; # . # # [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum; # 2001-07-20; . # # # B. DOCUMENT HISTORY # # 2001-12-04 GDR Created. # # 2001-12-06 GDR Added command-line interface and source code # annotation. # # 2001-12-09 GDR Moved design and interface to separate documents. # # 2001-12-10 GDR Open cache file as binary on Windows. Allow # simultaneous -e and -x, or -a and -r. # # 2001-12-12 GDR Added command-line help. Cache analysis so that it # only needs to be done once when you specify -a and -r. # # 2001-12-13 GDR Improved speed while recording. Portable between # Python 1.5.2 and 2.1.1. # # 2002-01-03 GDR Module-level functions work correctly. # # 2002-01-07 GDR Update sys.path when running a file with the -x option, # so that it matches the value the program would get if it were run on # its own. # # 2004-12-12 NMB Significant code changes. # - Finding executable statements has been rewritten so that docstrings and # other quirks of Python execution aren't mistakenly identified as missing # lines. # - Lines can be excluded from consideration, even entire suites of lines. # - The filesystem cache of covered lines can be disabled programmatically. # - Modernized the code. # # 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior # and add 'analysis2'. Add a global for 'annotate', and factor it, adding # 'annotate_file'. # # 2004-12-31 NMB Allow for keyword arguments in the module global functions. # # C. COPYRIGHT AND LICENCE # # Copyright 2001 Gareth Rees. All rights reserved. # Copyright 2004 Ned Batchelder. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the # distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. # # $Id: coverage.py 5 2004-12-14 12:08:23Z ned $ demokritos-0.2.0/test/data/0000755000076500007650000000000010362327332016706 5ustar jtauberjtauber00000000000000demokritos-0.2.0/test/data/07-introspection-out.xml0000644000076500007650000000077110362321566023371 0ustar jtauberjtauber00000000000000 entry media entry demokritos-0.2.0/test/data/07-introspection.xml0000644000076500007650000000117110362321566022557 0ustar jtauberjtauber00000000000000 entry media entry demokritos-0.2.0/test/data/category.atom0000644000076500007650000000266110316664521021415 0ustar jtauberjtauber00000000000000 dive into mark A <em>lot</em> of effort went into making this effortless Mark Pilgrim http://example.org/ f8dy@example.com 2005-07-31T12:29:29Z tag:example.org,2003:3 Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello what goes here? demokritos-0.2.0/test/data/content1.atom0000644000076500007650000000517110316664521021332 0ustar jtauberjtauber00000000000000 dive into mark A <em>lot</em> of effort went into making this effortless Mark Pilgrim http://example.org/ f8dy@example.com 2005-07-31T12:29:29Z tag:example.org,2003:3 some icon some logo Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z <b>hello</b> Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z
hello
Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z [hello] Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z
demokritos-0.2.0/test/data/entry1.atom0000644000076500007650000000211510316664521021014 0ustar jtauberjtauber00000000000000 Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z 2003-12-13T08:29:29-04:00 Mark Pilgrim http://example.org/ f8dy@example.com Sam Ruby Joe Gregorio do whatever you want

[Update: The Atom draft is finished.]

demokritos-0.2.0/test/data/example1.atom0000644000076500007650000000115110316664521021305 0ustar jtauberjtauber00000000000000 Example Feed 2003-12-13T18:30:02Z John Doe urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 Atom-Powered Robots Run Amok urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a 2003-12-13T18:30:02Z Some text. demokritos-0.2.0/test/data/example2.atom0000644000076500007650000000321410316664521021310 0ustar jtauberjtauber00000000000000 dive into mark A <em>lot</em> of effort went into making this effortless 2005-07-31T12:29:29Z tag:example.org,2003:3 Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z 2003-12-13T08:29:29-04:00 Mark Pilgrim http://example.org/ f8dy@example.com Sam Ruby Joe Gregorio

[Update: The Atom draft is finished.]

demokritos-0.2.0/test/data/link.atom0000644000076500007650000000206110316664521020527 0ustar jtauberjtauber00000000000000 dive into mark A <em>lot</em> of effort went into making this effortless Mark Pilgrim http://example.org/ f8dy@example.com 2005-07-31T12:29:29Z tag:example.org,2003:3 Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello demokritos-0.2.0/test/data/nochildren.atom0000644000076500007650000000033410316664521021720 0ustar jtauberjtauber00000000000000 test hello hello ooops demokritos-0.2.0/test/data/source.atom0000644000076500007650000000311510316664521021073 0ustar jtauberjtauber00000000000000 dive into mark A <em>lot</em> of effort went into making this effortless Mark Pilgrim http://example.org/ f8dy@example.com 2005-07-31T12:29:29Z tag:example.org,2003:3 Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello original id Foo Bar... something http://whatever logo attribution subtitle title 2005-07-31T12:29:29Z demokritos-0.2.0/test/data/summary.atom0000644000076500007650000000331610316664521021273 0ustar jtauberjtauber00000000000000 dive into mark A <em>lot</em> of effort went into making this effortless Mark Pilgrim http://example.org/ f8dy@example.com 2005-07-31T12:29:29Z tag:example.org,2003:3 Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z <b>hello</b> Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z
hello
demokritos-0.2.0/test/data/test31a.atom0000644000076500007650000000030510316664521021055 0ustar jtauberjtauber00000000000000 Example Feed some-id 2005-07-31T12:29:29Z demokritos-0.2.0/test/data/test31b.atom0000644000076500007650000000031410316664521021056 0ustar jtauberjtauber00000000000000 <b>Example</b> Feed some-id 2005-07-31T12:29:29Z demokritos-0.2.0/test/data/test31c.atom0000644000076500007650000000033010316664521021055 0ustar jtauberjtauber00000000000000 <b>Example</b> Feed some-id 2005-07-31T12:29:29Z demokritos-0.2.0/test/data/test31d.atom0000644000076500007650000000033010316664521021056 0ustar jtauberjtauber00000000000000 <b>Example</b> Feed some-id 2005-07-31T12:29:29Z demokritos-0.2.0/test/data/test31e.atom0000644000076500007650000000032710316664521021065 0ustar jtauberjtauber00000000000000 <b>Example</b> Feed some-id 2005-07-31T12:29:29Z demokritos-0.2.0/test/data/test31f.atom0000644000076500007650000000033210316664521021062 0ustar jtauberjtauber00000000000000 <b>Example</b> Feed some-id 2005-07-31T12:29:29Z demokritos-0.2.0/test/data/test31g.atom0000644000076500007650000000051410316664521021065 0ustar jtauberjtauber00000000000000 <xhtml:div> Less: <xhtml:em> < </xhtml:em> </xhtml:div> some-id 2005-07-31T12:29:29Z demokritos-0.2.0/test/data/test31i.atom0000644000076500007650000000103310316664521021064 0ustar jtauberjtauber00000000000000 Title some-id 2005-07-31T12:29:29Z me some-id some-title 2005-07-31T12:29:29Z This is XHTML content. demokritos-0.2.0/test/test_collection_paging_client.py0000755000076500007650000001753310362321362024435 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import httplib, socket, urllib import sys sys.path.append("../lib") from app import parse_introspection from atom import parse_atom SERVER = "localhost:8000" def urlsplit(url): return urllib.splithost(urllib.splittype(url)[1]) def request(server, method, path, body=None, headers=None): conn = httplib.HTTPConnection(server) try: print "trying %s %s to %s" % (method, path, server) if body is not None and headers is not None: conn.request(method, path, body, headers) else: conn.request(method, path) resp = conn.getresponse() conn.close() print "got %s %s" % (resp.status, resp.reason) return resp except socket.error: print "got socket error" return None ## Retrieving an Introspection Document (APP-07 5.1) print "getting introspection document..." collection_href = None response = request(SERVER, "GET", "/introspection") if response: assert response.status == 200 doc = response.read() service = parse_introspection(doc) print "service:" for workspace in service.workspaces: print "\tworkspace:", workspace.title for collection in workspace.collections: print "\t\tcollection:", collection.title print "\t\t\thref =", collection.href print "\t\t\tmember-type =", collection.member_type collection1_href = service.workspaces[0].collections[0].href collection2_href = service.workspaces[0].collections[1].href collection3_href = service.workspaces[0].collections[2].href raw_input("press return") ## List First Collection if collection1_href: print "getting first collection..." server, path = urlsplit(collection1_href) response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for link in collection.links: print "\t\tlink %s: %s" % (link.rel, link.href) for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src assert collection.links[0].rel == "end" assert collection.links[0].href == collection1_href, "got %s" % collection.links[0].href raw_input("press return") ## List Second Collection if collection2_href: print "getting second collection..." server, path = urlsplit(collection2_href) response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for link in collection.links: print "\t\tlink %s: %s" % (link.rel, link.href) for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src assert len(collection.links) == 1 assert collection.links[0].rel == "end" assert collection.links[0].href == collection2_href, "got %s" % collection.links[0].href raw_input("press return") ## Creating a Resource location = None if collection2_href: print "POSTing to collection..." server, path = urlsplit(collection2_href) body = "Hello world!" headers = {"Content-type": "text/plain", "Title": "My Title"} response = request(server, "POST", path, body, headers) if response: assert response.status == 201 location = response.getheader("Location") print "got location:", location raw_input("press return") ## List Second Collection Again next_href = None if collection2_href: print "getting second collection again..." server, path = urlsplit(collection2_href) response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for link in collection.links: print "\t\tlink %s: %s" % (link.rel, link.href) for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src assert len(collection.links) == 2 assert collection.links[0].rel == "next" assert collection.links[1].rel == "end" assert collection.links[0].href == "%s?index=10" % collection2_href, "got %s" % collection.links[0].href assert collection.links[1].href == "%s?index=10" % collection2_href, "got %s" % collection.links[0].href next_href = collection.links[0].href raw_input("press return") ## Get second page if next_href: print "getting second page of second collection..." server, path = urlsplit(next_href) response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for link in collection.links: print "\t\tlink %s: %s" % (link.rel, link.href) for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src assert len(collection.links) == 3 assert collection.links[0].rel == "start" assert collection.links[1].rel == "previous" assert collection.links[2].rel == "end" assert collection.links[0].href == collection2_href assert collection.links[1].href == collection2_href assert collection.links[2].href == "%s?index=10" % collection2_href raw_input("press return") ## List third collection next_href = None if collection1_href: print "getting third collection..." server, path = urlsplit(collection3_href) response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for link in collection.links: print "\t\tlink %s: %s" % (link.rel, link.href) for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src next_href = collection.links[0].href raw_input("press return") ## Get next page of third collection if next_href: print "getting next page of third collection..." server, path = urlsplit(next_href) response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for link in collection.links: print "\t\tlink %s: %s" % (link.rel, link.href) for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src raw_input("press return") demokritos-0.2.0/test/test_collection_paging_server.py0000755000076500007650000000236210362321362024457 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import sys sys.path.append("../lib") from store import AtomStore import server from atom.model import PlainText, Date, Person atom_store = AtomStore("http://localhost:8000") workspace = atom_store.create_workspace("Colleciton Paging Test Workspace") collection1 = atom_store.create_collection("Collection 1", "/collection/1", "media") collection2 = atom_store.create_collection("Collection 2", "/collection/2", "media") collection3 = atom_store.create_collection("Collection 3", "/collection/3", "media") workspace.add_collection(collection1) workspace.add_collection(collection2) workspace.add_collection(collection3) def add_entries(collection, count): for i in range(count): path = "/%d" % i member = atom_store.create_media_member(path, "text/plain") member.raw_content = "Hello from %d" % i member.title = PlainText("Sample %d" % i) member.updated = Date("20%02d-%02d-13T00:00:00Z" % (i / 10, i % 10)) member.authors = [Person(name="Sam Pell")] collection.add_member(member) add_entries(collection1, 5) add_entries(collection2, 10) add_entries(collection3, 95) server.atom_store = atom_store # @@@ hack for now server.AtomServer().serve_forever()demokritos-0.2.0/test/test_parse.py0000755000076500007650000001251610337065376020541 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import sys sys.path.append("../lib") from atom import parse_atom, AtomException print "example1" ex1 = file("data/example1.atom").read() feed = parse_atom(ex1) assert feed.title.value == "Example Feed" assert len(feed.links) == 1 assert feed.links[0].href == "http://example.org/" assert feed.updated.value == "2003-12-13T18:30:02Z" assert feed.authors[0].name == "John Doe" assert feed.id == "urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6" assert len(feed.entries) == 1 entry = feed.entries[0] assert entry.title.value == "Atom-Powered Robots Run Amok" assert len(entry.links) == 1 assert entry.links[0].href == "http://example.org/2003/12/13/atom03" assert entry.id == "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a" assert entry.updated.value == "2003-12-13T18:30:02Z" assert entry.summary.value == "Some text." ex2 = file("data/example2.atom").read() print "example2" feed = parse_atom(ex2) assert feed.title.value == "dive into mark" assert feed.title.type == "text" assert feed.subtitle.type == "html" assert feed.subtitle.value == """A lot of effort went into making this effortless""" assert feed.updated.value == "2005-07-31T12:29:29Z" assert feed.id == "tag:example.org,2003:3" assert len(feed.links) == 2 assert feed.links[0].href == "http://example.org/" assert feed.links[0].type == "text/html" assert feed.links[0].rel == "alternate" assert feed.links[0].hreflang == "en" assert feed.links[1].href == "http://example.org/feed.atom" assert feed.links[1].type == "application/atom+xml" assert feed.links[1].rel == "self" assert feed.rights == "Copyright (c) 2003, Mark Pilgrim" assert feed.generator.uri == "http://www.example.com/" assert feed.generator.version == "1.0" assert feed.generator.value == """Example Toolkit""" assert len(feed.entries) == 1 entry = feed.entries[0] assert entry.title.value == "Atom draft-07 snapshot" assert len(entry.links) == 2 assert entry.links[0].rel == "alternate" assert entry.links[0].type == "text/html" assert entry.links[0].href == "http://example.org/2005/04/02/atom" assert entry.links[1].rel == "enclosure" assert entry.links[1].type == "audio/mpeg" assert entry.links[1].length == "1337" assert entry.links[1].href == "http://example.org/audio/ph34r_my_podcast.mp3" assert entry.id == "tag:example.org,2003:3.2397" assert entry.updated.value == "2005-07-31T12:29:29Z" assert entry.published.value == "2003-12-13T08:29:29-04:00" assert entry.authors[0].name =="Mark Pilgrim" assert entry.authors[0].uri =="http://example.org/" assert entry.authors[0].email =="f8dy@example.com" assert len(entry.contributors) == 2 assert entry.contributors[0].name == "Sam Ruby" assert entry.contributors[1].name == "Joe Gregorio" assert entry.content.type == "xhtml" assert entry.content.children[0].name == "http://www.w3.org/1999/xhtml^div" assert entry.content.children[0].attributes == {} assert entry.content.children[0].children[2].name == "http://www.w3.org/1999/xhtml^p" assert entry.content.children[0].children[2].children[0].name == "http://www.w3.org/1999/xhtml^i" assert entry.content.children[0].children[2].children[0].children[0].chardata == "[Update: The Atom draft is finished.]" print "3.1 test a" feed = parse_atom(file("data/test31a.atom").read()) assert feed.title.type == "text" assert feed.title.value == "Example Feed" print "3.1 test b" success = False try: feed = parse_atom(file("data/test31b.atom").read()) except AtomException: success = True assert success print "3.1 test c" success = False try: feed = parse_atom(file("data/test31c.atom").read()) except AtomException: success = True assert success print "3.1 test d" success = False try: feed = parse_atom(file("data/test31d.atom").read()) except AtomException: success = True assert success print "3.1 test e" success = False try: feed = parse_atom(file("data/test31e.atom").read()) except AtomException: success = True assert success print "3.1 test f" success = False try: feed = parse_atom(file("data/test31f.atom").read()) except AtomException: success = True assert success print "3.1 test g" feed = parse_atom(file("data/test31g.atom").read()) assert feed.title.children[0].name == "http://www.w3.org/1999/xhtml^div" print "3.1 test i" feed = parse_atom(file("data/test31i.atom").read()) assert feed.entries[0].summary.children[0].name == "http://www.w3.org/1999/xhtml^div" print "no children" success = False try: feed = parse_atom(file("data/nochildren.atom").read()) except AtomException: success = True assert success print "link" link = file("data/link.atom").read() feed = parse_atom(link) assert feed.entries[0].links[0].rel == "alternate" print "summary" summary = file("data/summary.atom").read() feed = parse_atom(summary) assert feed.entries[0].summary.type == "text" assert feed.entries[1].summary.type == "text" assert feed.entries[2].summary.type == "html" assert feed.entries[3].summary.type == "xhtml" print "content1" content1 = file("data/content1.atom").read() feed = parse_atom(content1) print "entry" e = file("data/entry1.atom").read() entry = parse_atom(e) print "category" cat = file("data/category.atom").read() feed = parse_atom(cat) print "non-Atom" success = False try: parse_atom("") except AtomException: success = True assert success print "source" feed = parse_atom(file("data/source.atom").read()) demokritos-0.2.0/test/test_protocol_parse.py0000755000076500007650000000313710362321362022445 0ustar jtauberjtauber00000000000000#!/usr/bin/env python from cStringIO import StringIO import sys sys.path.append("../lib") from app import parse_introspection, generate_introspection, AtomProtocolException empty_service = """""" try: service = parse_introspection(empty_service) assert False except AtomProtocolException: pass f = file("data/07-introspection.xml").read() service = parse_introspection(f) assert len(service.workspaces) == 2 assert service.workspaces[0].title == "Main Site" assert service.workspaces[1].title == "Side Bar Blog" assert len(service.workspaces[0].collections) == 2 assert len(service.workspaces[1].collections) == 1 assert service.workspaces[0].collections[0].title == "My Blog Entries" assert service.workspaces[0].collections[0].href == "http://example.org/reilly/main" assert service.workspaces[0].collections[0].member_type == "entry" assert service.workspaces[0].collections[1].title == "Pictures" assert service.workspaces[0].collections[1].href == "http://example.org/reilly/pic" assert service.workspaces[0].collections[1].member_type == "media" assert service.workspaces[1].collections[0].title == "Remaindered Links" assert service.workspaces[1].collections[0].href == "http://example.org/reilly/list" assert service.workspaces[1].collections[0].member_type == "entry" out = StringIO() generate_introspection(out, service) def compare(a, b): print len(a), len(b) for i in range(min(len(a), len(b))): print ord(a[i]), ord(b[i]) if a[i] != b[i]: break assert out.getvalue() == file("data/07-introspection-out.xml").read() demokritos-0.2.0/test/test_roundtrip.py0000755000076500007650000000461110316664521021444 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import sys sys.path.append("../lib") import atom from cStringIO import StringIO inp = file("data/example1.atom").read() feed = atom.parse_atom(inp) out = StringIO() atom.generate_atom(out, feed) assert out.getvalue() == """ Example Feed urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 2003-12-13T18:30:02Z John Doe Atom-Powered Robots Run Amok urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a 2003-12-13T18:30:02Z Some text. """ # make sure we can re-parse atom.parse_atom(out.getvalue()) inp = file("data/example2.atom").read() feed = atom.parse_atom(inp) out = StringIO() atom.generate_atom(out, feed) assert out.getvalue() == """ dive into mark A <em>lot</em> of effort went into making this effortless tag:example.org,2003:3 2005-07-31T12:29:29Z Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z 2003-12-13T08:29:29-04:00 Mark Pilgrim http://example.org/ f8dy@example.com Sam Ruby Joe Gregorio [Update: The Atom draft is finished.] """ # make sure we can re-parse atom.parse_atom(out.getvalue()) demokritos-0.2.0/test/test_server.py0000755000076500007650000000170710362321362020721 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import sys sys.path.append("../lib") from store import AtomStore import server from atom.model import PlainText, Date, Person, InlineTextContent atom_store = AtomStore("http://localhost:8000") workspace = atom_store.create_workspace("My Workspace") collection1 = atom_store.create_collection("Collection 1", "/collection/1", "media") collection2 = atom_store.create_collection("Collection 2", "/collection/2", "entry") workspace.add_collection(collection1) workspace.add_collection(collection2) for i in range(10): path = "/%d" % i member = atom_store.create_media_member(path, "text/plain") member.raw_content = "Hello from %d" % i member.title = PlainText("Sample %d" % i) member.updated = Date("20%02d-%02d-13T00:00:00Z" % (i / 10, i % 10)) member.authors = [Person(name="Sam Pell")] collection1.add_member(member) server.atom_store = atom_store # @@@ hack for now server.AtomServer().serve_forever()