Defining classes to support persistence

In order to work with persistence, we need some objects that we want to save. We'll look at a simple microblog and the posts on that blog. Here's a class definition for Post:

from dataclasses import dataclass
import datetime

@dataclass
class Post:
date: datetime.datetime
title: str
rst_text: str
tags: List[str]

def as_dict(self) -> Dict[str, Any]:
return dict(
date=str(self.date),
title=self.title,
underline="-" * len(self.title),
rst_text=self.rst_text,
tag_text=" ".join(self.tags),
)

The instance variables are the attributes of each microblog post  a date, a title, some text, and some tags. Our attribute name provides us with a hint that the text should be in reStructuredText(reST) markup, even though that's largely irrelevant to the rest of the data model.

To support simple substitution into templates, the as_dict() method returns a dictionary of values that have been converted into string format. We'll look at the processing templates using string.Template later. The type hint reflects the general nature of JSON, where the resulting object will be a dictionary with string keys and values chosen from a small domain of types. In this example, all of the values are strings, and Dict[str, str] could also be used; this seems overly specific, however, so Dict[str, Any] is used to provide future flexibility.

In addition to the essential data values, we've added a few values to help with creating the reST output. The tag_text attribute is a flattened text version of the tuple of tag values. The underline attribute produces an underline string with a length that matches the title string; this helps the reST formatting work out nicely.

We'll also create a blog as a collection of posts. We'll make this collection more than a simple list by including an additional attribute of a title for the collection of posts. We have three choices for the collection design: wrap, extend, or invent a new class. We'll head off some confusion by providing this warning: don't extend a list if you intend to make it persistent.

Extending an iterable object can be confusing
When we extend a sequence class, we might get confused with some serialization algorithms. This may wind up bypassing the extended features we put in a subclass of a sequence. Wrapping a sequence is usually a better idea than extending it.

This encourages us to look at wrapping or inventing. Since the blog posts form a simple sequence, there will be few places for confusion, and we can extend a list. Here's a collection of microblog posts. We've built in a list to create the Blog class:

from collections import defaultdict

class Blog_x(list):

def __init__(self, title: str, posts: Optional[List[Post]]=None) -> None:
self.title = title
super().__init__(posts if posts is not None else [])

def by_tag(self) -> DefaultDict[str, List[Dict[str, Any]]]:
tag_index: DefaultDict[str, List[Dict[str, Any]]] = defaultdict(list)
for post in self:
for tag in post.tags:
tag_index[tag].append(post.as_dict())
return tag_index

def as_dict(self) -> Dict[str, Any]:
return dict(
title=self.title,
entries=[p.as_dict() for p in self]
)

In addition to extending the list class, we've also included an attribute that is the title of the microblog. The initializer uses a common technique to avoid providing a mutable object as a default value. We've provided None as the default value for posts. If posts is None, we use a freshly-minted empty list, []. Otherwise, we use the given value for posts.

Additionally, we've defined a method that indexes the posts by their tags. In the resulting defaultdict, each key is a tag's text. Each value is a list of posts that shares the given tag.

To simplify the use of string.Template, we've added another as_dict() method that boils the entire blog down to a simple dictionary of strings and dictionaries. The idea here is to produce only built-in types that have simple string representations. In this case, the Dict[str, Any] type hint reflects a general approach the return value. Practically, the tile is a str, and the entries are a List[Dict[str, Any]], based on the definition of the Post entries. The extra details don't seem to be completely helpful, so we've left the hint as Dict[str, Any].

In the next section, we'll see how to render blogs and posts.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset