Merge pull request #5 from icetemple/task-3

feat: add image element
This commit is contained in:
Dmitriy Pleshevskiy 2019-08-21 21:40:26 +03:00 committed by GitHub
commit 08c1c1d6fc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 144 additions and 20 deletions

View file

@ -6,8 +6,9 @@ from lxml.etree import Element, CDATA, tostring
from .utils import create_element, ElementT from .utils import create_element, ElementT
from .item import Item from .item import Item
from .enclosure import Enclosure from .enclosure import Enclosure
from .image import Image
__all__ = ('GenRSS', 'Item', 'Enclosure') __all__ = ('GenRSS', 'Item', 'Enclosure', 'Image')
RSS_DEFAULT_GENERATOR = f'Generated by genrss for python' RSS_DEFAULT_GENERATOR = f'Generated by genrss for python'
@ -21,12 +22,15 @@ class GenRSS:
:param feed_url: Absolute url to the rss feed :param feed_url: Absolute url to the rss feed
:param description: A short description of feed :param description: A short description of feed
:param image_url: Image absolute url for channel :param image_url: Image absolute url for channel
:param image: Image element for channel (it replaces image_url)
:param author: Author of channel :param author: Author of channel
:param pub_date: Datetime in utc when last item was published :param pub_date: Datetime in utc when last item was published
:param copyright: Copyright information for this feed :param copyright: Copyright information for this feed
:param language: The language of the content of this feed. :param language: The language of the content of this feed.
:param editor: Who manages content in this feed :param editor: Who manages content in this feed
:param webmaster: Who manages feed availability and technical support :param webmaster: Who manages feed availability and technical support
:param docs_url: Url to rss documentation
:param categories: List of category names
:param generator: Feed generator :param generator: Feed generator
""" """
@ -36,6 +40,7 @@ class GenRSS:
self.feed_url: str = feed_url self.feed_url: str = feed_url
self.description: str = kwargs.pop('description', self.title) self.description: str = kwargs.pop('description', self.title)
self.image_url: Optional[str] = kwargs.pop('image_url', None) self.image_url: Optional[str] = kwargs.pop('image_url', None)
self.image: Optional[Image] = kwargs.pop('image', None)
self.author: Optional[str] = kwargs.pop('author', None) self.author: Optional[str] = kwargs.pop('author', None)
self.pub_date: Optional[datetime] = kwargs.pop('pub_date', None) self.pub_date: Optional[datetime] = kwargs.pop('pub_date', None)
self.copyright: Optional[str] = kwargs.pop('copyright', None) self.copyright: Optional[str] = kwargs.pop('copyright', None)
@ -44,7 +49,6 @@ class GenRSS:
self.webmaster: Optional[str] = kwargs.pop('webmaster', None) self.webmaster: Optional[str] = kwargs.pop('webmaster', None)
self.docs_url: Optional[str] = kwargs.pop('docs_url', None) self.docs_url: Optional[str] = kwargs.pop('docs_url', None)
self.categories: List[str] = kwargs.pop('categories', []) self.categories: List[str] = kwargs.pop('categories', [])
self.items: List[Item] = kwargs.pop('items', []) self.items: List[Item] = kwargs.pop('items', [])
self.generator: str = kwargs.pop('generator', RSS_DEFAULT_GENERATOR) self.generator: str = kwargs.pop('generator', RSS_DEFAULT_GENERATOR)
self.root_version: str = '2.0' self.root_version: str = '2.0'
@ -93,12 +97,12 @@ class GenRSS:
create_element('lastBuildDate', datetime.utcnow()) create_element('lastBuildDate', datetime.utcnow())
]) ])
if self.image_url: channel_image = self.image
channel.append(create_element('image', children=[ if not channel_image and self.image_url:
create_element('url', self.image_url), channel_image = Image(self.image_url, self.site_url, self.title)
create_element('title', CDATA(self.title)), if isinstance(image, Image):
create_element('link', self.site_url) channel.append(channel_image.to_element())
]))
for category in self.categories: for category in self.categories:
channel.append(create_element('category', CDATA(category))) channel.append(create_element('category', CDATA(category)))
if self.pub_date: if self.pub_date:

View file

@ -1,9 +1,9 @@
import mimetypes import mimetypes
from typing import Dict, Union from typing import Dict, Union, TypeVar, Dict
from .utils import ElementT, create_element from .utils import ElementT, create_element
__all__ = ('Enclosure',) __all__ = ('Enclosure', 'EnclosureOrDictT')
class Enclosure: class Enclosure:
@ -24,9 +24,10 @@ class Enclosure:
return create_element('enclosure', url=self.url, length=str(self.size), return create_element('enclosure', url=self.url, length=str(self.size),
type=self.type) type=self.type)
@staticmethod @classmethod
def from_dict(data: Dict[str, Union[str, int]]): def from_dict(cls, data: Dict[str, Union[str, int]]):
"""Makes enclosure data from dict.""" """Makes enclosure data from dict."""
return Enclosure(data.get('url'), data.get('size'), data.get('type')) return cls(**data)
EnclosureOrDictT = Union[Enclosure, Dict]

51
genrss/image.py Normal file
View file

@ -0,0 +1,51 @@
from typing import Dict, Union
from lxml.etree import CDATA
from .utils import ElementT, create_element
__all__ = ('Image',)
class Image:
"""The element allows an image to be displayed when aggregators
present a feed.
:param url: Absolute url to the image
:param link: Hyperlink to the website
:param title: Text to display if the image could not be shown
:param description: Specifies the text in the HTML title attribute of the
link around the image
:param width: The width of the image
:param height: The height of the image
"""
def __init__(self, url: str, link: str, title: str, description: str = None,
width: int = None, height: int = None):
self.url = url
self.link = link
self.title = title
self.description = description
self.height = height
self.width = width
def to_element(self) -> ElementT:
"""Returns image element for xml."""
image = create_element('image', children=[
create_element('url', self.url),
create_element('link', self.link),
create_element('title', CDATA(self.title))
])
if self.description:
image.append(create_element('description', CDATA(self.description)))
if self.height:
image.append(create_element('height', self.height))
if self.width:
image.append(create_element('width', self.width))
return image
@classmethod
def from_dict(cls, data: Dict[str, Union[str, int]]):
"""Makes image data from dict."""
return cls(**data)

View file

@ -1,10 +1,11 @@
from datetime import datetime from datetime import datetime
from typing import Optional, List from typing import Optional, List, Union, Dict
from lxml.etree import CDATA from lxml.etree import CDATA
from .utils import ElementT, create_element from .utils import ElementT, create_element
from .enclosure import Enclosure from .enclosure import Enclosure, EnclosureOrDictT
from .image import Image
__all__ = ('Item',) __all__ = ('Item',)
@ -25,6 +26,7 @@ class Item:
:param categories: If provided, each array item will be added as a :param categories: If provided, each array item will be added as a
category element category element
:param enclosure: An enclosure object :param enclosure: An enclosure object
:param image: An image object
:param pub_date: The date and time of when the item was created. :param pub_date: The date and time of when the item was created.
Feed readers use this to determine the sort order. Some readers Feed readers use this to determine the sort order. Some readers
will also use it to determine if the content should be presented will also use it to determine if the content should be presented
@ -38,11 +40,15 @@ class Item:
self.guid: Optional[str] = kwargs.pop('guid', None) self.guid: Optional[str] = kwargs.pop('guid', None)
self.author: Optional[str] = kwargs.pop('author', None) self.author: Optional[str] = kwargs.pop('author', None)
self.categories: List[str] = kwargs.pop('categories', []) self.categories: List[str] = kwargs.pop('categories', [])
self.enclosure: Optional[Enclosure] = kwargs.pop('enclosure', None) self.enclosure: Optional[Enclosure, Dict] = kwargs.pop('enclosure',
None)
self.image: Optional[Image, Dict] = kwargs.pop('image', None)
self.pub_date: Optional[datetime] = kwargs.pop('pub_date', None) self.pub_date: Optional[datetime] = kwargs.pop('pub_date', None)
if isinstance(self.enclosure, dict): if isinstance(self.enclosure, dict):
self.enclosure = Enclosure.from_dict(self.enclosure) self.enclosure = Enclosure.from_dict(self.enclosure)
if isinstance(self.image, dict):
self.image = Image.from_dict(self.image)
def to_element(self) -> ElementT: def to_element(self) -> ElementT:
"""Returns item element for xml.""" """Returns item element for xml."""

View file

@ -1,6 +1,13 @@
import pytest import pytest
IMAGE_URL = 'http://s3.smartfridge.me/image.jpg' IMAGE_URL = 'https://s3.smartfridge.me/image.jpg'
SITE_URL = 'https://smartfridge.me/'
SITE_TITLE = 'Smart Fridge'
IMAGE_DESCRIPTION = 'a'*100
IMAGE_HEIGHT = 100
IMAGE_WIDTH = 100
@pytest.fixture(params=[ @pytest.fixture(params=[
@ -21,3 +28,29 @@ def enclosure_tuple(request):
]) ])
def enclosure_dict(request): def enclosure_dict(request):
return request.param return request.param
@pytest.fixture(params=[
pytest.param((None, None, None), id='-/-/-'),
pytest.param((IMAGE_DESCRIPTION, None, None), id='+/-/-'),
pytest.param((IMAGE_DESCRIPTION, 100, None), id='+/+/-'),
pytest.param((IMAGE_DESCRIPTION, 100, 200), id='+/+/+'),
])
def image_tuple(request):
return (IMAGE_URL, SITE_URL, SITE_TITLE) + request.param
@pytest.fixture(params=[
pytest.param(dict(), id='-/-/-'),
pytest.param(dict(description=IMAGE_DESCRIPTION), id='+/-/-'),
pytest.param(dict(description=IMAGE_DESCRIPTION, width=100), id='+/+/-'),
pytest.param(dict(description=IMAGE_DESCRIPTION, width=100, height=100),
id='+/+/+'),
])
def image_dict(request):
return dict(
url=IMAGE_URL,
link=SITE_URL,
title=SITE_TITLE,
**request.param
)

View file

@ -2,13 +2,13 @@ import pytest
from genrss import Enclosure from genrss import Enclosure
def test_init_fails(): def test_init_enclosure_fails():
with pytest.raises(TypeError): with pytest.raises(TypeError):
Enclosure() Enclosure()
assert False assert False
def test_init(enclosure_tuple): def test_init_enclosure(enclosure_tuple):
url, size, type = enclosure_tuple url, size, type = enclosure_tuple
enclosure = Enclosure(url, size, type) enclosure = Enclosure(url, size, type)
assert enclosure.url == url assert enclosure.url == url
@ -16,7 +16,7 @@ def test_init(enclosure_tuple):
assert enclosure.type == (type or 'image/jpeg') assert enclosure.type == (type or 'image/jpeg')
def test_init_from_dict(enclosure_dict): def test_init_enclosure_from_dict(enclosure_dict):
enclosure = Enclosure.from_dict(enclosure_dict) enclosure = Enclosure.from_dict(enclosure_dict)
assert enclosure.url == enclosure_dict.get('url') assert enclosure.url == enclosure_dict.get('url')
assert enclosure.size == enclosure_dict.get('size', 0) assert enclosure.size == enclosure_dict.get('size', 0)

29
tests/test_image.py Normal file
View file

@ -0,0 +1,29 @@
import pytest
from genrss import Image
def test_init_image_fails():
with pytest.raises(TypeError):
Image()
assert False
def test_init_image(image_tuple):
url, link, title, description, width, height = image_tuple
image = Image(url, link, title, description, width, height)
assert image.url == url
assert image.link == link
assert image.title == title
assert image.description == description
assert image.width == width
assert image.height == height
def test_init_image_from_dict(image_dict):
image = Image.from_dict(image_dict)
assert image.url == image_dict.get('url')
assert image.link == image_dict.get('link')
assert image.title == image_dict.get('title')
assert image.description == image_dict.get('description')
assert image.width == image_dict.get('width')
assert image.height == image_dict.get('height')