Skip to content

Commit

Permalink
Merge 242f5c6 into 7a59713
Browse files Browse the repository at this point in the history
  • Loading branch information
jacebrowning committed Apr 22, 2016
2 parents 7a59713 + 242f5c6 commit 1fbd6d9
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 63 deletions.
15 changes: 15 additions & 0 deletions .appveyor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
cache:
- env

install:
# Export build paths
- copy C:\MinGW\bin\mingw32-make.exe C:\MinGW\bin\make.exe
- set PATH=%PATH%;C:\MinGW\bin
- make --version
# Install project dependenies
- make depends-ci

build: off

test_script:
- make ci
3 changes: 2 additions & 1 deletion .pep8rc
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

# E501: line too long (checked by PyLint)
# E711: comparison to None (used to improve test style)
ignore = E501,E711
# E712: comparison to True (used to improve test style)
ignore = E501,E711,E712
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,9 @@ $(INSTALLED_FLAG): Makefile setup.py requirements.txt

$(PIP):
$(SYS_PYTHON) -m venv --clear $(ENV)
ifndef APPVEYOR
$(PIP) install --upgrade pip setuptools
endif

# Tools Installation ###########################################################

Expand Down Expand Up @@ -281,25 +283,31 @@ test-unit: depends-ci
$(PYTEST) $(PYTEST_OPTS) $(PACKAGE)
@- mv $(FAILURES).bak $(FAILURES)
ifndef TRAVIS
ifndef APPVEYOR
$(COVERAGE_SPACE) jacebrowning/memegen unit
endif
endif

.PHONY: test-int
test-int: depends-ci
@ if test -e $(FAILURES); then $(PYTEST) $(PYTEST_OPTS_FAILFAST) tests; fi
$(PYTEST) $(PYTEST_OPTS) tests
ifndef TRAVIS
ifndef APPVEYOR
$(COVERAGE_SPACE) jacebrowning/memegen integration
endif
endif

.PHONY: tests test-all
tests: test-all
test-all: depends-ci
@ if test -e $(FAILURES); then $(PYTEST) $(PYTEST_OPTS_FAILFAST) $(PACKAGE) tests; fi
$(PYTEST) $(PYTEST_OPTS) $(PACKAGE) tests
ifndef TRAVIS
ifndef APPVEYOR
$(COVERAGE_SPACE) jacebrowning/memegen overall
endif
endif

.PHONY: read-coverage
read-coverage:
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

An API to generate meme images based solely on requested URLs.

[![Build Status](http://img.shields.io/travis/jacebrowning/memegen/master.svg)](https://travis-ci.org/jacebrowning/memegen)
[![Coverage Status](http://img.shields.io/coveralls/jacebrowning/memegen/master.svg)](https://coveralls.io/r/jacebrowning/memegen)
Unix: [![Unix Build Status](http://img.shields.io/travis/jacebrowning/memegen/master.svg)](https://travis-ci.org/jacebrowning/memegen)
Windows: [![Windows Build Status](https://img.shields.io/appveyor/ci/jacebrowning/memegen.svg)](https://ci.appveyor.com/project/jacebrowning/memegen)

Metrics: [![Coverage Status](http://img.shields.io/coveralls/jacebrowning/memegen/master.svg)](https://coveralls.io/r/jacebrowning/memegen)
[![Scrutinizer Code Quality](http://img.shields.io/scrutinizer/g/jacebrowning/memegen.svg)](https://scrutinizer-ci.com/g/jacebrowning/memegen/?branch=master)

## Generating Images
Expand Down
24 changes: 15 additions & 9 deletions memegen/domain/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import re
import hashlib
import shutil
from pathlib import Path
import tempfile
import logging

import time
Expand Down Expand Up @@ -109,9 +111,12 @@ def get_path(self, *styles):

for name in (n.lower() for n in (*styles, self.DEFAULT) if n):
for extension in self.EXTENSIONS:
path = os.path.join(self.dirpath, name + extension)
if os.path.isfile(path):
return path
path = Path(self.dirpath, name + extension)
try:
if path.is_file():
return path
except OSError:
continue

return None

Expand Down Expand Up @@ -164,8 +169,8 @@ def validate_meta(self):

def validate_link(self):
if self.link:
flag = os.path.join(self.dirpath, self.VALID_LINK_FLAG)
if os.path.isfile(flag):
flag = Path(self.dirpath, self.VALID_LINK_FLAG)
if flag.is_file():
log.info("Link already checked: %s", self.link)
else:
log.info("Checking link %s ...", self.link)
Expand Down Expand Up @@ -237,9 +242,10 @@ def download_image(url):
if not url or not url.startswith("http"):
return None

# /tmp is detroyed after every Heroku request
path = "/tmp/" + hashlib.md5(url.encode('utf-8')).hexdigest()
if os.path.isfile(path):
path = Path(tempfile.gettempdir(),
hashlib.md5(url.encode('utf-8')).hexdigest())

if path.is_file():
log.debug("Already downloaded: %s", url)
return path

Expand All @@ -254,7 +260,7 @@ def download_image(url):

if response.status_code == 200:
log.info("Downloading %s", url)
with open(path, 'wb') as outfile:
with open(str(path), 'wb') as outfile:
response.raw.decode_content = True
shutil.copyfileobj(response.raw, outfile)
return path
Expand Down
5 changes: 4 additions & 1 deletion memegen/stores/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ def create(self, image):
if self.exists(image) and not self.regenerate_images:
return

ImageModel(image)
try:
ImageModel(image)
except ImportError:
log.warning("Unable to store models on this machine")

image.root = self.root
image.generate()
Expand Down
72 changes: 40 additions & 32 deletions memegen/test/test_domain_template.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# pylint: disable=unused-variable,expression-not-assigned,misplaced-comparison-constant,singleton-comparison

import tempfile
from pathlib import Path
from unittest.mock import patch, Mock

import pytest
Expand All @@ -8,6 +10,10 @@
from memegen.domain import Template


def temp(path):
return Path(tempfile.gettempdir(), path)


def describe_template():

def it_supports_comparison():
Expand All @@ -21,61 +27,63 @@ def it_supports_comparison():

def describe_get_path():

@patch('os.path.isfile', Mock(return_value=True))
@patch('pathlib.Path.is_file', Mock(return_value=True))
def it_returns_default_when_no_style(template):
expect(template.get_path()) == "abc/default.png"
expect(template.get_path()) == Path("abc/default.png")

@patch('os.path.isfile', Mock(return_value=True))
@patch('pathlib.Path.is_file', Mock(return_value=True))
def it_returns_alternate_when_style_provided(template):
expect(template.get_path('Custom')) == "abc/custom.png"
expect(template.get_path('Custom')) == Path("abc/custom.png")

@patch('os.path.isfile', Mock(return_value=True))
@patch('pathlib.Path.is_file', Mock(return_value=True))
def it_returns_default_when_style_is_none(template):
expect(template.get_path(None)) == "abc/default.png"
expect(template.get_path(None)) == Path("abc/default.png")

@patch('os.path.isfile', Mock(return_value=False))
@patch('pathlib.Path.is_file', Mock(return_value=False))
def it_considers_urls_valid_styles(template):
url = "http://example.com"
path = "/tmp/a9b9f04336ce0181a08e774e01113b31"
path = temp("a9b9f04336ce0181a08e774e01113b31")
expect(template.get_path(url)) == path

@patch('os.path.isfile', Mock(return_value=True))
@patch('pathlib.Path.is_file', Mock(return_value=True))
def it_caches_file_downloads(template):
url = "http://this/will/be/ignored"
path = "/tmp/d888710f0697650eb68fc9dcbb976d4c"
path = temp("d888710f0697650eb68fc9dcbb976d4c")
expect(template.get_path(url)) == path

def it_handles_bad_urls(template):
url = "http://invalid"
expect(template.get_path(url)) == None
expect(template.get_path("http://invalid")) == None

def it_handles_invalid_paths(template):
expect(template.get_path("@#$%^")) == None

def describe_path():

def is_returned_when_file_exists(template):
template.root = "my_root"

with patch('os.path.isfile', Mock(return_value=True)):
with patch('pathlib.Path.is_file', Mock(return_value=True)):
path = template.path

assert "my_root/abc/default.png" == path
expect(path) == Path("my_root/abc/default.png")

def is_none_when_no_file(template):
template.root = "my_root"

with patch('os.path.isfile', Mock(return_value=False)):
with patch('pathlib.Path.is_file', Mock(return_value=False)):
path = template.path

assert None is path
expect(path) == None

def describe_default_path():

def is_based_on_lines(template):
assert "foo/bar" == template.default_path
expect(template.default_path) == "foo/bar"

def is_underscore_when_no_lines(template):
template.lines = []

assert "_" == template.default_path
expect(template.default_path) == "_"

def describe_styles():

Expand All @@ -90,12 +98,12 @@ def is_filesnames_of_alternate_images(template):
def describe_sample_path():

def is_based_on_lines(template):
assert "foo/bar" == template.sample_path
expect(template.sample_path) == "foo/bar"

def is_placeholder_when_no_lines(template):
template.lines = []

assert "your-text/goes-here" == template.sample_path
expect(template.sample_path) == "your-text/goes-here"

def describe_match():

Expand All @@ -112,15 +120,15 @@ def describe_validate_meta():
def with_no_name(template):
template.name = None

assert False is template.validate_meta()
expect(template.validate_meta()) == False

def with_no_default_image(template):
assert False is template.validate_meta()
expect(template.validate_meta()) == False

def with_nonalphanumberic_name(template):
template.name = "'ABC' Meme"

assert False is template.validate_meta()
expect(template.validate_meta()) == False

def describe_validate_link():

Expand All @@ -131,13 +139,13 @@ def with_bad_link(template):
with patch('requests.get', Mock(return_value=mock_response)):
template.link = "example.com/fake"

assert False is template.validate_link()
expect(template.validate_link()) == False

@patch('os.path.isfile', Mock(return_value=True))
@patch('pathlib.Path.is_file', Mock(return_value=True))
def with_cached_valid_link(template):
template.link = "example.com"
template.link = "already_cached_site.com"

assert True is template.validate_link()
expect(template.validate_link()) == True

def describe_validate_size():

Expand All @@ -153,28 +161,28 @@ def with_various_dimenions(mock_open, template, dimensions, valid):
mock_img.size = dimensions
mock_open.return_value = mock_img

assert valid is template.validate_size()
expect(template.validate_size()) == valid

def describe_validate_regexes():

def with_missing_split(template):
template.compile_regexes([".*"])

assert False is template.validate_regexes()
expect(template.validate_regexes()) == False

def describe_validate():

def with_no_validators(template):
assert True is template.validate(validators=[])
expect(template.validate([])) == True

def with_all_passing_validators(template):
"""Verify a template is valid if all validators pass."""
mock_validators = [lambda: True]

assert True is template.validate(validators=mock_validators)
expect(template.validate(validators=mock_validators)) == True

def with_one_failing_validator(template):
"""Verify a template is invalid if any validators fail."""
mock_validators = [lambda: False]

assert False is template.validate(validators=mock_validators)
expect(template.validate(validators=mock_validators)) == False
23 changes: 22 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

"""Setup script for MemeGen."""

import sys
import logging

import setuptools

from memegen import __project__, __version__
Expand All @@ -14,6 +17,24 @@
else:
DESCRIPTION = README + '\n' + CHANGELOG


def load_requirements():
"""Exclude specific requirements based on platform."""
requirements = []

for line in open("requirements.txt").readlines():
name = line.split('=')[0].strip()

if sys.platform == 'win32':
if name in ['psycopg2', 'gunicorn']:
logging.warning("Skipped requirement: %s", line)
continue

requirements.append(line)

return requirements


setuptools.setup(
name=__project__,
version=__version__,
Expand All @@ -36,5 +57,5 @@
'Programming Language :: Python :: 3.5',
],

install_requires=open("requirements.txt").readlines(),
install_requires=load_requirements(),
)

0 comments on commit 1fbd6d9

Please sign in to comment.