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

Commit

Permalink
Refactor config object (#60)
Browse files Browse the repository at this point in the history
* Refactor config object

* Fix flake8

* Fix .travis.yml

* Fix mypy
  • Loading branch information
c-bata committed Dec 30, 2016
1 parent 6c48d71 commit a8c7679
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 104 deletions.
7 changes: 5 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
sudo: false
language: python
python:
- "3.5"
- "3.6-dev"
- "3.6"
- "nightly"
matrix:
allow_failures:
- python: "nightly"
install: pip install -q coveralls tox-travis
script: tox
after_success:
Expand Down
5 changes: 4 additions & 1 deletion kobin/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from .app import Kobin, Config, current_app, current_config
from .app import (
Kobin, Config,
load_config_from_module, load_config_from_pyfile
)
from .environs import (
request, BaseResponse, Response,
TemplateResponse, JSONResponse, RedirectResponse, HTTPError
Expand Down
60 changes: 29 additions & 31 deletions kobin/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ class Kobin:
This class is a WSGI application implementation.
Create a instance, and run using WSGI Server.
"""
def __init__(self, root_path='.'):
def __init__(self, config=None):
self.router = Router()
self.config = Config(os.path.abspath(root_path))
self.config = config if config else Config()
self.before_request_callback = None
self.after_request_callback = None

Expand Down Expand Up @@ -91,36 +91,34 @@ def __call__(self, environ, start_response):


class Config(dict):
"""This class manages your application configs."""
default_config = {
'BASE_DIR': os.path.abspath('.'),
'TEMPLATE_DIRS': [os.path.join(os.path.abspath('.'), 'templates')],
'DEBUG': False,
}

def __init__(self, root_path, *args, **kwargs):
super().__init__(*args, **kwargs)
self.root_path = root_path
self.update(self.default_config)

self.update_jinja2_environment()

def load_from_pyfile(self, file_name):
file_path = os.path.join(self.root_path, file_name)
module = SourceFileLoader('config', file_path).load_module()
self.load_from_module(module)

def load_from_module(self, module):
configs = {key: getattr(module, key) for key in dir(module) if key.isupper()}
self.update(configs)
self.update_jinja2_environment()

def update_jinja2_environment(self):
try:
"""This class manages your application configs.
"""
def __init__(self, **kwargs):
init_kw = {
'BASE_DIR': os.path.abspath('.'),
'TEMPLATE_DIRS': [os.path.join(os.path.abspath('.'), 'templates')],
'DEBUG': False,
}
init_kw.update(kwargs)
super().__init__(**init_kw)
self._template_env = None

@property
def template_env(self):
if self._template_env is None:
from jinja2 import Environment, FileSystemLoader
self['JINJA2_ENV'] = Environment(loader=FileSystemLoader(self['TEMPLATE_DIRS']))
except ImportError:
pass
self._template_env = Environment(loader=FileSystemLoader(self['TEMPLATE_DIRS']))
return self._template_env


def load_config_from_module(module):
return Config(**{key: getattr(module, key)
for key in dir(module) if key.isupper()})


def load_config_from_pyfile(filepath):
module = SourceFileLoader('config', filepath).load_module()
return load_config_from_module(module)


def current_app():
Expand Down
19 changes: 9 additions & 10 deletions kobin/app.pyi
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from typing import Callable, Dict, List, Tuple, Iterable, TypeVar
from typing import Callable, Dict, List, Tuple, Iterable, TypeVar, Any
from types import ModuleType
from jinja2 import Environment # type: ignore

from .routes import Router
from .environs import BaseResponse
Expand All @@ -15,11 +16,11 @@ WSGIResponse = Iterable[bytes]

class Kobin:
router: Router
config: Config
config: Dict[str, Any]
before_request_callback: Callable[[], None]
after_request_callback: Callable[[BaseResponse], BaseResponse]

def __init__(self, root_path: str = ...) -> None: ...
def __init__(self, config: str = ...) -> None: ...
def route(self, rule: str = ..., method: str = ..., name: str = ...,
callback: ViewFunction = ...) -> ViewFunction: ...
def before_request(self, callback: Callable[[], None]) -> Callable[[], None]: ...
Expand All @@ -31,14 +32,12 @@ class Kobin:


class Config(dict):
root_path: str
default_config: WSGIEnviron

def __init__(self, root_path: str, *args: str, **kwargs: WSGIEnvironValue) -> None: ...
def load_from_pyfile(self, file_name: str) -> None: ...
def load_from_module(self, module: ModuleType) -> None: ...
def update_jinja2_environment(self) -> None: ...
_template_env: Environment

def __init__(self, **kwargs: Dict[str, Any]) -> None: ...
def template_env(self) -> Environment: ...

def load_config_from_pyfile(filepath: str) -> Config: ...
def load_config_from_module(module: ModuleType) -> Config: ...
def current_app() -> Kobin: ...
def current_config() -> Config: ...
8 changes: 3 additions & 5 deletions kobin/environs.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,11 +363,9 @@ class TemplateResponse(BaseResponse):

def __init__(self, filename, status=200, headers=None, charset='utf-8', **tpl_args):
super().__init__(b'', status=status, headers=headers)
from . import current_config
jinja2_env = current_config().get('JINJA2_ENV')
if jinja2_env is None:
raise ImportError('Please install jinja2!')
self.template = jinja2_env.get_template(filename)
from .app import current_config
template_env = current_config().template_env
self.template = template_env.get_template(filename)
self.tpl_args = tpl_args
self.charset = charset

Expand Down
26 changes: 12 additions & 14 deletions tests/test_apps.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
from unittest import TestCase
from kobin import Kobin, Config, Response
from kobin import Kobin, Config, Response, load_config_from_module, load_config_from_pyfile


class KobinTests(TestCase):
Expand Down Expand Up @@ -139,29 +139,27 @@ def test_after_request(self):

class ConfigTests(TestCase):
def setUp(self):
self.root_path = os.path.dirname(os.path.abspath(__file__))
self.base_path = os.path.dirname(os.path.abspath(__file__))

def test_constructor_set_root_path(self):
config = Config(self.root_path)
config.load_from_pyfile('dummy_config.py')
self.assertIn('root_path', dir(config))
def test_constructor(self):
config = Config()
self.assertIn('DEBUG', config.keys())

def test_load_from_module(self):
from tests import dummy_config
config = Config(self.root_path)
config.load_from_module(dummy_config)
config = load_config_from_module(dummy_config)
self.assertIn('UPPER_CASE', config)

def test_load_from_pyfile(self):
config = Config(self.root_path)
config.load_from_pyfile('dummy_config.py')
dummy_config = os.path.join(self.base_path, 'dummy_config.py')
config = load_config_from_pyfile(dummy_config)
self.assertIn('UPPER_CASE', config)

def test_config_has_not_lower_case_variable(self):
config = Config(self.root_path)
config.load_from_pyfile('dummy_config.py')
dummy_config = os.path.join(self.base_path, 'dummy_config.py')
config = load_config_from_pyfile(dummy_config)
self.assertNotIn('lower_case', config)

def test_failure_for_loading_config(self):
config = Config(self.root_path)
self.assertRaises(FileNotFoundError, config.load_from_pyfile, 'no_exists.py')
dummy_config = os.path.join(self.base_path, 'no_exists.py')
self.assertRaises(FileNotFoundError, load_config_from_pyfile, dummy_config)
13 changes: 3 additions & 10 deletions tests/test_environs.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from datetime import timedelta, datetime
import freezegun
from jinja2 import Environment, FileSystemLoader
import os
from unittest import TestCase
from unittest.mock import MagicMock, patch

from kobin import Kobin
from kobin import Kobin, Config
from kobin.environs import (
Request, BaseResponse, Response, JSONResponse, TemplateResponse, RedirectResponse,
_split_into_mimetype_and_priority, _parse_and_sort_accept_header, accept_best_match,
Expand Down Expand Up @@ -306,16 +305,10 @@ def test_constructor_headerlist_with_add_header(self):


class Jinja2TemplateTests(TestCase):
def setUp(self):
self.app = Kobin()
self.app.config['TEMPLATE_DIRS'] = TEMPLATE_DIRS

@patch('kobin.current_config')
@patch('kobin.app.current_config')
def test_file(self, mock_current_config):
""" Templates: Jinja2 file """
mock_current_config.return_value = {
'JINJA2_ENV': Environment(loader=FileSystemLoader(TEMPLATE_DIRS))
}
mock_current_config.return_value = Config(TEMPLATE_DIRS=TEMPLATE_DIRS)
response = TemplateResponse('jinja2.html', var='kobin')
actual = response.body
expected = [b"Hello kobin World."]
Expand Down
26 changes: 0 additions & 26 deletions tests/test_templates.py

This file was deleted.

10 changes: 5 additions & 5 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
[tox]
envlist =
py35
py36
py37
flake8
mypy

[tox:travis]
3.5=py35
3.6-dev=py36,flake8,mypy
3.6=py36,flake8,mypy
nightly=py37

[testenv]
basepython = python3.6
setenv=PYTHONPATH = {toxinidir}:{toxinidir}
deps = -rrequirements/test.txt
commands = python setup.py test

[testenv:py35]
basepython = python3.5
[testenv:py37]
basepython = python3.7

[testenv:flake8]
deps = flake8
Expand Down

0 comments on commit a8c7679

Please sign in to comment.