From 517c74269d97441c57b5fb5f3cf9d35ba97cbc8e Mon Sep 17 00:00:00 2001 From: Dmitriy Pleshevskiy Date: Wed, 21 Aug 2019 21:34:46 +0300 Subject: [PATCH] feat: add image element --- genrss/__init__.py | 20 +++++++++------- genrss/enclosure.py | 11 +++++---- genrss/image.py | 51 +++++++++++++++++++++++++++++++++++++++++ genrss/item.py | 12 +++++++--- tests/conftest.py | 35 +++++++++++++++++++++++++++- tests/test_enclosure.py | 6 ++--- tests/test_image.py | 29 +++++++++++++++++++++++ 7 files changed, 144 insertions(+), 20 deletions(-) create mode 100644 genrss/image.py create mode 100644 tests/test_image.py diff --git a/genrss/__init__.py b/genrss/__init__.py index 0aaa610..5dd3096 100644 --- a/genrss/__init__.py +++ b/genrss/__init__.py @@ -6,8 +6,9 @@ from lxml.etree import Element, CDATA, tostring from .utils import create_element, ElementT from .item import Item 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' @@ -21,12 +22,15 @@ class GenRSS: :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 image: Image element for channel (it replaces image_url) :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 docs_url: Url to rss documentation + :param categories: List of category names :param generator: Feed generator """ @@ -36,6 +40,7 @@ class GenRSS: 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.image: Optional[Image] = kwargs.pop('image', 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) @@ -44,7 +49,6 @@ class GenRSS: 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' @@ -93,12 +97,12 @@ class GenRSS: 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) - ])) + channel_image = self.image + if not channel_image and self.image_url: + channel_image = Image(self.image_url, self.site_url, self.title) + if isinstance(image, Image): + channel.append(channel_image.to_element()) + for category in self.categories: channel.append(create_element('category', CDATA(category))) if self.pub_date: diff --git a/genrss/enclosure.py b/genrss/enclosure.py index cdb698d..139c578 100644 --- a/genrss/enclosure.py +++ b/genrss/enclosure.py @@ -1,9 +1,9 @@ import mimetypes -from typing import Dict, Union +from typing import Dict, Union, TypeVar, Dict from .utils import ElementT, create_element -__all__ = ('Enclosure',) +__all__ = ('Enclosure', 'EnclosureOrDictT') class Enclosure: @@ -24,9 +24,10 @@ class Enclosure: return create_element('enclosure', url=self.url, length=str(self.size), type=self.type) - @staticmethod - def from_dict(data: Dict[str, Union[str, int]]): + @classmethod + def from_dict(cls, data: Dict[str, Union[str, int]]): """Makes enclosure data from dict.""" - return Enclosure(data.get('url'), data.get('size'), data.get('type')) + return cls(**data) +EnclosureOrDictT = Union[Enclosure, Dict] diff --git a/genrss/image.py b/genrss/image.py new file mode 100644 index 0000000..7ddd540 --- /dev/null +++ b/genrss/image.py @@ -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) diff --git a/genrss/item.py b/genrss/item.py index 8c8820d..8410288 100644 --- a/genrss/item.py +++ b/genrss/item.py @@ -1,10 +1,11 @@ from datetime import datetime -from typing import Optional, List +from typing import Optional, List, Union, Dict from lxml.etree import CDATA from .utils import ElementT, create_element -from .enclosure import Enclosure +from .enclosure import Enclosure, EnclosureOrDictT +from .image import Image __all__ = ('Item',) @@ -25,6 +26,7 @@ class Item: :param categories: If provided, each array item will be added as a category element :param enclosure: An enclosure object + :param image: An image 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 @@ -38,11 +40,15 @@ class Item: 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.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) if isinstance(self.enclosure, dict): self.enclosure = Enclosure.from_dict(self.enclosure) + if isinstance(self.image, dict): + self.image = Image.from_dict(self.image) def to_element(self) -> ElementT: """Returns item element for xml.""" diff --git a/tests/conftest.py b/tests/conftest.py index 32fdcf2..668cbaf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,13 @@ 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=[ @@ -21,3 +28,29 @@ def enclosure_tuple(request): ]) def enclosure_dict(request): 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 + ) diff --git a/tests/test_enclosure.py b/tests/test_enclosure.py index de8e6fc..1909506 100644 --- a/tests/test_enclosure.py +++ b/tests/test_enclosure.py @@ -2,13 +2,13 @@ import pytest from genrss import Enclosure -def test_init_fails(): +def test_init_enclosure_fails(): with pytest.raises(TypeError): Enclosure() assert False -def test_init(enclosure_tuple): +def test_init_enclosure(enclosure_tuple): url, size, type = enclosure_tuple enclosure = Enclosure(url, size, type) assert enclosure.url == url @@ -16,7 +16,7 @@ def test_init(enclosure_tuple): 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) assert enclosure.url == enclosure_dict.get('url') assert enclosure.size == enclosure_dict.get('size', 0) diff --git a/tests/test_image.py b/tests/test_image.py new file mode 100644 index 0000000..6132efc --- /dev/null +++ b/tests/test_image.py @@ -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')