Skip to content
This repository has been archived by the owner on May 31, 2019. It is now read-only.

Commit

Permalink
Merge pull request #48 from c-bata/develop
Browse files Browse the repository at this point in the history
Refactor templates
  • Loading branch information
c-bata committed Sep 13, 2016
2 parents 0915de1 + e218627 commit 877b9a6
Show file tree
Hide file tree
Showing 7 changed files with 17 additions and 116 deletions.
4 changes: 2 additions & 2 deletions example/app.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from kobin import Kobin, request, response, template
from kobin import Kobin, request, response, render_template
from wsgi_static_middleware import StaticMiddleware

app = Kobin()
Expand All @@ -8,7 +8,7 @@
@app.route('/')
def index():
response.headers.add_header("hoge", "fuga")
return template('hello_jinja2', name='Kobin')
return render_template('hello_jinja2.html', name='Kobin')


@app.route('/user/{name}')
Expand Down
2 changes: 1 addition & 1 deletion example/templates/hello_jinja2.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% extends "base" %}
{% extends "base.html" %}

{% block title %}Hello!{% endblock %}

Expand Down
2 changes: 1 addition & 1 deletion kobin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from .app import Kobin, Config, current_app, current_config
from .environs import request, response
from .templates import template, jinja2_template
from .templates import render_template
from .exceptions import HTTPError
from .routes import redirect
8 changes: 4 additions & 4 deletions kobin/environs.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from typing import Dict, List, Tuple, Any
import http.client as http_client
from urllib.parse import SplitResult
from http.cookies import SimpleCookie
from http.cookies import SimpleCookie # type: ignore
from wsgiref.headers import Headers # type: ignore


Expand Down Expand Up @@ -210,12 +210,12 @@ def set_cookie(self, key: str, value: Any, expires: str=None, path: str=None, **
for k, v in options.items():
if k == 'max_age':
if isinstance(v, timedelta):
v = v.seconds + v.days * 24 * 3600
v = v.seconds + v.days * 24 * 3600 # type: ignore
if k == 'expires':
if isinstance(v, (date, datetime)):
v = v.timetuple()
v = v.timetuple() # type: ignore
elif isinstance(v, (int, float)):
v = v.gmtime(value)
v = v.gmtime(value) # type: ignore
v = time.strftime("%a, %d %b %Y %H:%M:%S GMT", v) # type: ignore
self._cookies[key][k.replace('_', '-')] = v # type: ignore

Expand Down
86 changes: 5 additions & 81 deletions kobin/templates.py
Original file line number Diff line number Diff line change
@@ -1,84 +1,8 @@
from typing import List, Dict, Any, Callable
import os
import functools
from jinja2 import Environment, FileSystemLoader # type: ignore

from .exceptions import HTTPError


def load_file(name: str, directories: List[str]) -> str:
for directory in directories:
if not os.path.isabs(directory):
directory = os.path.abspath(directory) + os.sep
file = os.path.join(directory, name)
if os.path.exists(file) and os.path.isfile(file) and os.access(file, os.R_OK):
return file
# TODO: if not os.access: raise HTTPError(403, "You do not have permission to access this file.")
raise HTTPError(404, "{name} not found.".format(name=name))


class TemplateMixin:
extension = 'html' # type: str
settings = {} # type: Dict[str, Any] # used in parepare()
defaults = {} # type: Dict[str, Any] # used in render()

def __init__(self, name: str, encoding: str='utf8', **settings) -> None:
""" Create a new template. """
from . import current_config # type: ignore
self.name = name
config = current_config() # type: ignore
self.template_dirs = config['TEMPLATE_DIRS'] # type: List[str]
self.filename = self.search(self.name, self.template_dirs) # type: str

self.encoding = encoding
self.settings = self.settings.copy()
self.prepare(**self.settings)

@classmethod
def search(cls, name: str, template_dirs: List[str]) -> str:
""" Search name in all directories specified in lookup. """
filename = '{name}.{ext}'.format(name=name, ext=cls.extension)
return load_file(filename, template_dirs)

def prepare(self, **options):
raise NotImplementedError

def render(self, *args, **kwargs):
raise NotImplementedError


class Jinja2Template(TemplateMixin):
def prepare(self, filters: Dict=None, tests: Dict=None, globals: Dict={}, **kwargs) -> None:
from jinja2 import Environment, FunctionLoader # type: ignore
self.env = Environment(loader=FunctionLoader(self.loader), **kwargs)
if filters:
self.env.filters.update(filters)
if tests:
self.env.tests.update(tests)
if globals:
self.env.globals.update(globals)
self.tpl = self.env.get_template(self.filename)

def render(self, *args, **kwargs) -> str:
for dictarg in args:
kwargs.update(dictarg)
_defaults = self.defaults.copy()
_defaults.update(kwargs)
return self.tpl.render(**_defaults)

def loader(self, name: str) -> str:
if name == self.filename:
fname = name
else:
fname = self.search(name, self.template_dirs)
if not fname:
return # type: ignore
with open(fname, "rb") as f:
return f.read().decode(self.encoding)


def template(template_name: str, **kwargs) -> str:
def render_template(template_name: str, **kwargs) -> str:
""" Get a rendered template as string iterator. """
adapter = kwargs.pop('template_adapter', Jinja2Template)
return adapter(name=template_name).render(**kwargs)

jinja2_template = functools.partial(template, adapter=Jinja2Template)
from . import current_config # type: ignore
env = Environment(loader=FileSystemLoader(current_config()['TEMPLATE_DIRS'])) # type: ignore
return env.get_template(template_name).render(**kwargs)
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ release = register sdist bdist_wheel upload

[flake8]
max-line-length = 120
exclude = venv/*.py,build/*.py,*/__init__.py
exclude = venv/*.py,build/*.py,*/__init__.py,doc/*.py
ignore = F401

[pytest]
Expand Down
29 changes: 3 additions & 26 deletions tests/test_templates.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,16 @@
import os
from unittest import TestCase
from unittest.mock import patch
from kobin.exceptions import HTTPError
from kobin.templates import Jinja2Template, load_file
from kobin.templates import render_template

TEMPLATE_DIRS = [os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates')]


class LoadFileTests(TestCase):
def test_load_file(self):
actual = load_file('jinja2.html', TEMPLATE_DIRS)
expected = os.path.join(TEMPLATE_DIRS[0], 'jinja2.html')
self.assertEqual(actual, expected)

@patch('os.access')
def test_raise_403_do_not_have_access_permission(self, mock_os_access):
mock_os_access.return_value = False
self.assertRaises(HTTPError, load_file,
name='jinja2.html', directories=TEMPLATE_DIRS)

def test_raise_404_because_file_does_not_exist(self):
self.assertRaises(HTTPError, load_file,
name='does_not_exist.html', directories=TEMPLATE_DIRS)

def test_raise_404_because_it_is_not_file(self):
self.assertRaises(HTTPError, load_file,
name='this_is_not_file.html', directories=TEMPLATE_DIRS)
TEMPLATE_DIRS = [os.path.join(os.path.dirname(__file__), 'templates')]


class Jinja2TemplateTests(TestCase):
@patch('kobin.current_config')
def test_file(self, mock_current_config):
""" Templates: Jinja2 file """
mock_current_config.return_value = {'TEMPLATE_DIRS': TEMPLATE_DIRS}
j2 = Jinja2Template(name='jinja2', template_dirs=TEMPLATE_DIRS)
actual = j2.render(var='kobin')
actual = render_template('jinja2.html', var='kobin')
expected = "Hello kobin World."
self.assertEqual(actual, expected)

0 comments on commit 877b9a6

Please sign in to comment.