-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8109f03
commit 49cfabe
Showing
5 changed files
with
274 additions
and
244 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,244 +1,3 @@ | ||
import mimetypes | ||
from lxml.etree import Element, CDATA, tostring | ||
from typing import Optional, List, TypeVar, Dict, Any, Union | ||
from datetime import datetime | ||
|
||
import pytz | ||
|
||
__all__ = ('GenRSS', 'Item', 'Enclosure') | ||
|
||
ElementT = TypeVar('ElementT') | ||
RSS_DEFAULT_GENERATOR = f'Generated by genrss for python' | ||
|
||
|
||
def create_element(name: str, text: Any = None, children: List[ElementT] = None, | ||
**kwargs) -> ElementT: | ||
"""Creates xml node with text or children elements. | ||
:param name: Tag name of node with namespace | ||
:param text: Text of node | ||
:param children: Appends elements as child nodes | ||
""" | ||
el = Element(name, **kwargs) | ||
if text: | ||
if isinstance(text, datetime): | ||
text = text.replace(tzinfo=pytz.timezone('GMT')). \ | ||
strftime("%a, %d %b %Y %H:%M:%S %Z") | ||
el.text = text | ||
elif isinstance(children, (list, tuple)): | ||
for child in children: | ||
el.append(child) | ||
return el | ||
|
||
|
||
class Enclosure: | ||
"""Data for enclosure tag for rss. | ||
:param url: Absolute url to file | ||
:param size: File size | ||
:param type: File mime type | ||
""" | ||
|
||
def __init__(self, url: str, size=None, type=None): | ||
self.url = url | ||
self.size = size or 0 | ||
self.type = type or mimetypes.guess_type(self.url)[0] | ||
|
||
def to_element(self) -> ElementT: | ||
"""Returns item element for xml.""" | ||
return create_element('enclosure', url=self.url, length=str(self.size), | ||
type=self.type) | ||
|
||
@staticmethod | ||
def from_dict(data: Dict[str, Union[str, int]]): | ||
"""Makes enclosure data from dict.""" | ||
return Enclosure(data.get('url'), data.get('size'), data.get('type')) | ||
|
||
|
||
class Item: | ||
"""Data for item tag for rss. | ||
:param title: Title of this particular item | ||
:param description: Content for the item. Can contain html but | ||
link and image urls must be absolute path including hostname | ||
:param url: Url to the item. This could be a blog entry | ||
:param guid: A unique string feed readers use to know if an item | ||
is new or has already been seen. If you use a guid never change | ||
it. If you don't provide a guid then your item urls must be unique | ||
:param author: If included it is the name of the item's creator. | ||
If not provided the item author will be the same as the feed | ||
author. This is typical except on multi-author blogs | ||
:param categories: If provided, each array item will be added as a | ||
category element | ||
:param enclosure: An enclosure object | ||
:param pub_date: The date and time of when the item was created. | ||
Feed readers use this to determine the sort order. Some readers | ||
will also use it to determine if the content should be presented | ||
as unread | ||
""" | ||
|
||
def __init__(self, title: str, **kwargs): | ||
self.title: str = title | ||
self.description: str = kwargs.pop('description', '') | ||
self.url: Optional[str] = kwargs.pop('url', None) | ||
self.guid: Optional[str] = kwargs.pop('guid', None) | ||
self.author: Optional[str] = kwargs.pop('author', None) | ||
self.categories: List[str] = kwargs.pop('categories', []) | ||
self.enclosure: Optional[Enclosure] = kwargs.pop('enclosure', None) | ||
self.pub_date: Optional[datetime] = kwargs.pop('pub_date', None) | ||
|
||
if isinstance(self.enclosure, dict): | ||
self.enclosure = Enclosure.from_dict(self.enclosure) | ||
|
||
def to_element(self) -> ElementT: | ||
"""Returns item element for xml.""" | ||
item = create_element('item', children=[ | ||
create_element('title', CDATA(self.title)), | ||
create_element('description', CDATA(self.description)), | ||
]) | ||
|
||
if self.url: | ||
item.append(create_element('link', self.url)) | ||
|
||
item.append(create_element( | ||
'guid', | ||
attrib={ | ||
'isPermaLink': str(bool(not self.guid and self.url)).lower() | ||
}, | ||
text=(self.guid or self.url or CDATA(self.title)) | ||
)) | ||
|
||
if self.author: | ||
item.append(create_element( | ||
'{http://purl.org/dc/elements/1.1/}creator', | ||
CDATA(self.author) | ||
)) | ||
|
||
for category in self.categories: | ||
item.append(create_element('category', CDATA(category))) | ||
if self.enclosure: | ||
item.append(self.enclosure.to_element()) | ||
if self.pub_date: | ||
item.append(create_element('pubDate', self.pub_date)) | ||
|
||
return item | ||
|
||
|
||
class GenRSS: | ||
"""Generates RSS feed of channel. | ||
:param title: Title of your site or feed | ||
:param site_url: Absolute url to the site that the feed is for | ||
:param feed_url: Absolute url to the rss feed | ||
:param description: A short description of feed | ||
:param image_url: Image absolute url for channel | ||
:param author: Author of channel | ||
:param pub_date: Datetime in utc when last item was published | ||
:param copyright: Copyright information for this feed | ||
:param language: The language of the content of this feed. | ||
:param editor: Who manages content in this feed | ||
:param webmaster: Who manages feed availability and technical support | ||
:param generator: Feed generator | ||
""" | ||
|
||
def __init__(self, title: str, site_url: str, feed_url: str, **kwargs): | ||
self.title: str = title | ||
self.site_url: str = site_url | ||
self.feed_url: str = feed_url | ||
self.description: str = kwargs.pop('description', self.title) | ||
self.image_url: Optional[str] = kwargs.pop('image_url', None) | ||
self.author: Optional[str] = kwargs.pop('author', None) | ||
self.pub_date: Optional[datetime] = kwargs.pop('pub_date', None) | ||
self.copyright: Optional[str] = kwargs.pop('copyright', None) | ||
self.language: Optional[str] = kwargs.pop('language', None) | ||
self.editor: Optional[str] = kwargs.pop('editor', None) | ||
self.webmaster: Optional[str] = kwargs.pop('webmaster', None) | ||
self.docs_url: Optional[str] = kwargs.pop('docs_url', None) | ||
self.categories: List[str] = kwargs.pop('categories', []) | ||
|
||
self.items: List[Item] = kwargs.pop('items', []) | ||
self.generator: str = kwargs.pop('generator', RSS_DEFAULT_GENERATOR) | ||
self.root_version: str = '2.0' | ||
self.root_nsmap: Dict[str, str] = { | ||
'atom': 'http://www.w3.org/2005/Atom', | ||
'dc': 'http://purl.org/dc/elements/1.1/' | ||
} | ||
|
||
def item(self, title: str, **kwargs): | ||
"""Adds item to the feed. | ||
An item can be used for recipes, blog entries, project update, log | ||
entry, etc. Your RSS feed can have any number of items. | ||
:param title: Title of this particular item | ||
:param description: Content for the item. Can contain html but | ||
link and image urls must be absolute path including hostname | ||
:param url: Url to the item. This could be a blog entry | ||
:param guid: A unique string feed readers use to know if an item | ||
is new or has already been seen. If you use a guid never change | ||
it. If you don't provide a guid then your item urls must be unique | ||
:param author: If included it is the name of the item's creator. | ||
If not provided the item author will be the same as the feed | ||
author. This is typical except on multi-author blogs | ||
:param categories: If provided, each array item will be added as a | ||
category element | ||
:param enclosure: An enclosure object | ||
:param pub_date: The date and time of when the item was created. | ||
Feed readers use this to determine the sort order. Some readers | ||
will also use it to determine if the content should be presented | ||
as unread | ||
""" | ||
self.items.append(Item(title, **kwargs)) | ||
|
||
def to_element(self) -> ElementT: | ||
"""Returns root element for xml.""" | ||
root = Element('rss', nsmap=self.root_nsmap, version=self.root_version) | ||
channel = create_element('channel', children=[ | ||
create_element('title', CDATA(self.title)), | ||
create_element('description', CDATA(self.description)), | ||
create_element('link', self.site_url), | ||
create_element('{http://www.w3.org/2005/Atom}link', | ||
href=self.feed_url, rel='self', | ||
type='application/rss+xml'), | ||
create_element('generator', self.generator), | ||
create_element('lastBuildDate', datetime.utcnow()) | ||
]) | ||
|
||
if self.image_url: | ||
channel.append(create_element('image', children=[ | ||
create_element('url', self.image_url), | ||
create_element('title', CDATA(self.title)), | ||
create_element('link', self.site_url) | ||
])) | ||
for category in self.categories: | ||
channel.append(create_element('category', CDATA(category))) | ||
if self.pub_date: | ||
channel.append(create_element('pubDate', self.pub_date)) | ||
if self.copyright: | ||
channel.append(create_element('copyright', CDATA(self.copyright))) | ||
if self.language: | ||
channel.append(create_element('language', CDATA(self.language))) | ||
if self.editor: | ||
channel.append(create_element('managingEditor', CDATA(self.editor))) | ||
if self.webmaster: | ||
channel.append(create_element('webMaster', CDATA(self.webmaster))) | ||
if self.docs_url: | ||
channel.append(create_element('docs', self.docs_url)) | ||
|
||
for item in self.items: | ||
if isinstance(item, Item): | ||
channel.append(item.to_element()) | ||
|
||
root.append(channel) | ||
return root | ||
|
||
def xml(self, pretty: bool = False) -> str: | ||
"""Returns the XML as a string. | ||
:param pretty: Pretty print xml | ||
""" | ||
root = self.to_element() | ||
|
||
return tostring(root, pretty_print=pretty, xml_declaration=True, | ||
encoding='UTF-8').\ | ||
decode('utf-8') | ||
from .genrss import GenRSS | ||
from .item import Item | ||
from .enclosure import Enclosure |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import mimetypes | ||
from typing import Dict, Union | ||
|
||
from .utils import ElementT, create_element | ||
|
||
__all__ = ('Enclosure',) | ||
|
||
|
||
class Enclosure: | ||
"""Data for enclosure tag for rss. | ||
:param url: Absolute url to file | ||
:param size: File size | ||
:param type: File mime type | ||
""" | ||
|
||
def __init__(self, url: str, size=None, type=None): | ||
self.url = url | ||
self.size = size or 0 | ||
self.type = type or mimetypes.guess_type(self.url)[0] | ||
|
||
def to_element(self) -> ElementT: | ||
"""Returns item element for xml.""" | ||
return create_element('enclosure', url=self.url, length=str(self.size), | ||
type=self.type) | ||
|
||
@staticmethod | ||
def from_dict(data: Dict[str, Union[str, int]]): | ||
"""Makes enclosure data from dict.""" | ||
return Enclosure(data.get('url'), data.get('size'), data.get('type')) | ||
|
||
|
Oops, something went wrong.