-
-
Notifications
You must be signed in to change notification settings - Fork 4
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
Simon Willison
committed
Aug 21, 2018
0 parents
commit e40ac68
Showing
6 changed files
with
154 additions
and
0 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 |
---|---|---|
@@ -0,0 +1,10 @@ | ||
.venv | ||
__pycache__/ | ||
*.py[cod] | ||
*$py.class | ||
venv | ||
.eggs | ||
.pytest_cache | ||
*.egg-info | ||
build/ | ||
dist/ |
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,34 @@ | ||
language: python | ||
|
||
# 3.6 is listed first so it gets used for the later build stages | ||
python: | ||
- "3.6" | ||
- "3.7-dev" | ||
|
||
before_script: | ||
- pip install -U datasette pip wheel pytest | ||
|
||
script: | ||
- pytest | ||
|
||
cache: | ||
directories: | ||
- $HOME/.cache/pip | ||
|
||
jobs: | ||
include: | ||
- stage: release tagged version | ||
if: tag IS present | ||
language: python | ||
python: 3.6 | ||
script: | ||
- pip install -U pip wheel | ||
deploy: | ||
- provider: pypi | ||
user: simonw | ||
distributions: bdist_wheel | ||
password: ${PYPI_PASSWORD} | ||
on: | ||
branch: master | ||
tags: true | ||
repo: simonw/datasette-render-images |
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,9 @@ | ||
# datasette-render-images | ||
|
||
[![PyPI](https://img.shields.io/pypi/v/datasette-render-images.svg)](https://pypi.org/project/datasette-render-images/) | ||
[![Travis CI](https://travis-ci.com/simonw/datasette-render-images.svg?branch=master)](https://travis-ci.com/simonw/datasette-render-images) | ||
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-json-html/blob/master/LICENSE) | ||
|
||
A Datasette plugin that renders binary blob images with data-uris, using the new `render_cell` plugin hook available in Datasette master - see [issue 352](https://github.com/simonw/datasette/issues/352) for details. | ||
|
||
If a database row contains binary image data (PNG, GIF or JPEG), this plugin will detect that it is an image (using the [imghdr module](https://docs.python.org/3/library/imghdr.html) and render that cell using an `<img src="data:image/png;base64,...">` element. |
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,24 @@ | ||
from datasette import hookimpl | ||
import base64 | ||
import imghdr | ||
import jinja2 | ||
|
||
|
||
@hookimpl | ||
def render_cell(value): | ||
# Only act on byte columns | ||
if not isinstance(value, bytes): | ||
return None | ||
# Only render images < 100kb | ||
if len(value) > 100 * 1024: | ||
return None | ||
# Is this an image? | ||
image_type = imghdr.what(None, h=value) | ||
if image_type not in ("png", "jpeg", "gif"): | ||
return None | ||
# Render as a data-uri | ||
return jinja2.Markup( | ||
'<img src="data:image/{};base64,{}">'.format( | ||
image_type, base64.b64encode(value).decode("utf8") | ||
) | ||
) |
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,27 @@ | ||
from setuptools import setup | ||
import os | ||
|
||
VERSION = "0.1" | ||
|
||
|
||
def get_long_description(): | ||
with open( | ||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "README.md"), | ||
encoding="utf8", | ||
) as fp: | ||
return fp.read() | ||
|
||
|
||
setup( | ||
name="datasette-render-images", | ||
description="Datasette plugin that renders binary blob images using data-uris", | ||
long_description=get_long_description(), | ||
long_description_content_type="text/markdown", | ||
author="Simon Willison", | ||
url="https://github.com/simonw/datasette-render-images", | ||
license="Apache License, Version 2.0", | ||
version=VERSION, | ||
py_modules=["datasette_render_images"], | ||
entry_points={"datasette": ["render_images = datasette_render_images"]}, | ||
install_requires=["datasette"], | ||
) |
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,50 @@ | ||
from datasette_render_images import render_cell | ||
import jinja2 | ||
import pytest | ||
|
||
GIF_1x1 = b"GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\xff\xff\xff!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x01D\x00;" | ||
PNG_1x1 = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x00\x00\x00\x00:~\x9bU\x00\x00\x00\nIDATx\x9cc\xfa\x0f\x00\x01\x05\x01\x02\xcf\xa0.\xcd\x00\x00\x00\x00IEND\xaeB`\x82" | ||
# https://github.com/python/cpython/blob/master/Lib/test/imghdrdata/python.jpg | ||
JPEG = b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00\x01\x00\x01\x00\x00\xff\xdb\x00C\x00\x03\x02\x02\x02\x02\x02\x03\x02\x02\x02\x03\x03\x03\x03\x04\x06\x04\x04\x04\x04\x04\x08\x06\x06\x05\x06\t\x08\n\n\t\x08\t\t\n\x0c\x0f\x0c\n\x0b\x0e\x0b\t\t\r\x11\r\x0e\x0f\x10\x10\x11\x10\n\x0c\x12\x13\x12\x10\x13\x0f\x10\x10\x10\xff\xdb\x00C\x01\x03\x03\x03\x04\x03\x04\x08\x04\x04\x08\x10\x0b\t\x0b\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\xff\xc0\x00\x11\x08\x00\x10\x00\x10\x03\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x16\x00\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\x04\x05\xff\xc4\x00$\x10\x00\x01\x04\x01\x04\x02\x02\x03\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x06\x05\x07\x08\x12\x13\x11"\x00\x14\t12\xff\xc4\x00\x15\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\xff\xc4\x00#\x11\x00\x01\x02\x05\x03\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x11\x03\x04\x05\x06!\x00\x121\x15\x16a\x81\xe1\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x14\xa6\xd2j\x1bs\xc1\xe6\x13\x12\xd4\x95\x1c\xf3\x11c\xe4%e\xbe\xbaZ\xeciE@\xb1\xe5 \xb2T\xa5\x1f\xd2\xca\xb8\xfa\xf2 \xab\x96=\x97l\x935\xe6\x9bw\xd7\xe6m\xa7\x17\x81\xa5W\x1c\x7f\x1c\xeaq\xe2K9\xd7\xe3"S\xf2\x1ai\xde\xd4qJ8\xb4\x82\xe8K\x89*qi\x1e\xcd-!;\xf1\xef\xb9\x1at\xac\xee\xa1Zu\x8e\xd5H\xace[\x85\x8b\x81\x85{!)\x98g\xa9k\x94\xb9IeO\xb9\xc8\x85)\x11K\x81*\xf0z\xd9\xf2<\x80~U\xbe\r\xf6b\xa1@\xcc\xe8\xe6\x9a=\\\xb7C\xb3\xd7zeX\xb1\xd9Q!\x88\xbfd\xb8\xd3\xf1\xc3h\x04)\xc0\xd0\xfe\xbb<\x02\xe0<T\x07\xb4\xbd\xd9{T\xe6\'\xfbn\xdf\x94`\x14\x82b\x13\x8d\xb8R\x98(7\x05\x89ry`\xe42\x89o\xc3\x82\x8e\xa7R\x8c\xea \x8d\xbex\x19\x1f\x07\xad\x7f\xff\xd9' | ||
|
||
|
||
@pytest.mark.parametrize( | ||
"input,expected", | ||
[ | ||
("hello", None), | ||
(1, None), | ||
(True, None), | ||
( | ||
GIF_1x1, | ||
'<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7">', | ||
), | ||
# If it's a unicode string, not bytes, it is ignored: | ||
(GIF_1x1.decode("latin1"), None), | ||
# 1x1 transparent PNG: | ||
( | ||
PNG_1x1, | ||
'<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR4nGP6DwABBQECz6AuzQAAAABJRU5ErkJggg==">', | ||
), | ||
(PNG_1x1.decode("latin1"), None), | ||
# Smallest possible JPEG, from https://github.com/mathiasbynens/small/ | ||
( | ||
JPEG, | ||
'<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAAQABADASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABwQF/8QAJBAAAQQBBAICAwAAAAAAAAAAAQIDBAYFBwgSExEiABQJMTL/xAAVAQEBAAAAAAAAAAAAAAAAAAAABv/EACMRAAECBQMFAAAAAAAAAAAAAAECEQMEBQYhABIxFRZhgeH/2gAMAwEAAhEDEQA/ABSm0mobc8HmExLUlRzzEWPkJWW+ulrsaUVAseUgslSlH9LKuPryIKuWPZdskzXmm3fX5m2nF4GlVxx/HOpx4ks51+MiU/Iaad7UcUo4tILoS4kqcWkezS0hO/HvuRp0rO6hWnWO1UisZVuFi4GFeyEpmGepa5S5SWVPuciFKRFLgSrwetnyPIB+Vb4N9mKhQMzo5po9XLdDs9d6ZVix2VEhiL9kuNPxw2gEKcDQ/rs8AuA8VAe0vdl7VOYn+27flGAUgmITjbhSmCg3BYlyeWDkMolvw4KOp1KM6iCNvngZHwetf//Z">', | ||
), | ||
(JPEG.decode("latin1"), None), | ||
], | ||
) | ||
def test_render_cell(input, expected): | ||
actual = render_cell(input) | ||
assert expected == actual | ||
assert actual is None or isinstance(actual, jinja2.Markup) | ||
|
||
|
||
def test_render_cell_maximum_image_size(): | ||
max_length = 100 * 1024 | ||
max_image = GIF_1x1 + (b"b" * (max_length - len(GIF_1x1))) | ||
rendered = render_cell(max_image) | ||
assert rendered is not None | ||
assert rendered.startswith("<img src") | ||
# Add one byte and it should no longer render | ||
assert None == render_cell(max_image + b"b") |