Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

TDD rewrite

  • Loading branch information...
commit 3a0369cf07059908b726dd02aeee549723334f54 0 parents
@jpscaletti jpscaletti authored
3  .coverage
@@ -0,0 +1,3 @@
+�}q(U collectorqUcoverage v3.6b1qUlinesq}q(U8/Users/jps/Projects/Lucuma/shake/newclay/clay/helpers.pyq]q(KKKKKKK K KKKKKKKKKKKKKK K!K"K)K*K+K,K-K.K/K0K3K4K5K6K7eU5/Users/jps/Projects/Lucuma/shake/newclay/clay/main.pyq]q (KKKKKK K K KKKKKKKKKKKKKK!K"K$K%K'K(K)K*K+K,K.K/K1K2K3K4K5K7K8K9K<K=K>K?K@KAKCKDKEKFKHKIKJKKKNKOKPKRKSKTKVKWKZK[K]K^K`KaKbKcKeKfKgKiKjKkKmKnKoKpKqKsKtKuKvKwKxKzK{eUB/Users/jps/Projects/Lucuma/shake/newclay/clay/source/notfound.htmlq
+]q (KKKKKK K
+eU9/Users/jps/Projects/Lucuma/shake/newclay/clay/__init__.pyq ]q (K K Keuu.
7 .gitignore
@@ -0,0 +1,7 @@
+~*
+.DS_Store
+*.pyc
+build
+dist
+*.egg*
+
14 AUTHORS.md
@@ -0,0 +1,14 @@
+# Authors
+
+Clay is written and maintained by the Lúcuma Team and various contributors.
+
+
+Project Leader / Developer:
+
+- Juan-Pablo Scaletti <juanpablo@lucumalabs.com>
+
+
+Contributors:
+
+- Muhammad Alkarouri (https://github.com/malkarouri)
+- Alexander Zayats (https://github.com/z4y4ts)
6 CHANGES.md
@@ -0,0 +1,6 @@
+# Clay Changelog
+
+
+## Version 2.0
+
+- Complete TDD rewrite in Flask + awesome Lucuma's libraries.
3  MANIFEST.in
@@ -0,0 +1,3 @@
+include *.py *.txt *.md *.rst Makefile
+recursive-include clay *
+recursive-include tests *
23 Makefile
@@ -0,0 +1,23 @@
+.PHONY: clean clean-pyc test upload
+
+all: clean clean-pyc test
+
+clean: clean-pyc
+ rm -rf build
+ rm -rf dist
+ rm -rf *.egg-info
+ rm -rf tests/res/t
+ find . -name '.DS_Store' -exec rm -f {} \;
+
+clean-pyc:
+ find . -name '*.pyc' -exec rm -f {} \;
+ find . -name '*.pyo' -exec rm -f {} \;
+ find . -name '*~' -exec rm -f {} \;
+
+test:
+ /Users/jps/.virtualenvs/clay/bin/py.test --cov clay tests/
+ rm -rf tests/__pycache__
+
+upload: clean
+ python setup.py sdist upload
+
97 README.md
@@ -0,0 +1,97 @@
+# Clay
+
+**A rapid prototyping tool.**
+
+http://lucuma.github.com/Clay
+
+With Clay you can forget about making changes to dozens of HTML files just because you need to add a link in the footer.
+
+You can also use it to prototype your AJAX-driven application or the responses of sending forms, because it acts like a real server.
+
+
+## Instructions
+
+Run the development server with
+
+ $ clay run
+
+or generate a static version of the site
+
+ $ clay build
+
+
+## Quickstart
+
+
+ $ clay new myappname
+
+will generate a new app container with the following structure::
+
+ myappname
+   ├── source/
+ ├─────── static/
+ ├── README.md
+   └── settings.yml
+
+Inside that folder, run the development server with:
+
+ $ clay run
+
+and your site'll be available at `http://0.0.0.0:8080/`.
+
+Anything you put under `source` will be render as a page. For instance `source/page.html` will be visible at `http://0.0.0.0:8080/page.html`, and `source/foo/bar.json` at `http://0.0.0.0:8080/foo/bar.json`.
+
+To generate a static version of your site, stop the server (with `Control + C`) and run:
+
+ $ clay build
+
+and all the templates will be processed and the result stored inside the `build` folder.
+
+
+## Not just for HTML
+
+Clay can automatically compiles Less, Sass, CleverCSS & CoffeeScript files directly from your `static` dir (Less and CoffeeScript has to be installed in node first).
+
+
+## How to install
+
+Just run
+
+ sudo pip install clay
+
+and you're ready to go.
+
+
+## Templates
+
+The real power of Clay comes by using the Jinja2 template syntax.
+
+You can make a single file, (for instance, your header) and included it many times using:
+
+ {% include "header.html "%}
+
+You can also use a powerful feature called _template inheritance_: inside the `source` folder you'll find a file called `base.html`. This is a page skeleton shared among the rest of HTML templates. You put in there anything you want to be repeated in every page, like the doctype declaration or maybe navigation links and a footer. You change something there and the rest of the pages will be automatically updated. Much more easy than manually search and replace a bunch of files!
+
+The rest of the files, like `index.html`, are composed of **blocks**, like
+
+ {% block title %}Welcome to Clay{% endblock %}
+
+Any content you put *inside* those blocks will be used to fill the same-named blocks in `base.html`. In this case to fill the `<title>` tag.
+
+You can create new blocks for your templates. You can even create new base files, just change in your templates the base that they will use, by updating the line that says:
+
+ {% extends "base.html" %}
+
+You can use more than just HTML: JSON, csv, plain text, etc. Any text-based format will be ok.
+
+Jinja2 templates are much more than just template inheritance. For more advaced features check the [official documentation] (http://jinja.pocoo.org/docs/templates/).
+
+
+---------------------------------------
+***Happy coding!***
+
+
+---------------------------------------
+© by [Lúcuma] (http://lucumalabs.com).<br />
+See `AUTHORS.md` for more details.<br />
+License: [MIT License] (http://www.opensource.org/licenses/mit-license.php).
63 README.rst
@@ -0,0 +1,63 @@
+========
+Clay
+========
+
+**A rapid prototyping tool.**
+
+With Clay you can forget about making changes to dozens of HTML files
+just because you need to add a link in the footer.
+
+You can also use it to prototype your AJAX-driven application or the
+responses of sending forms, because it acts like a real server.
+
+Quickstart
+----------
+
+::
+
+ $ clay new myappname
+
+will generate a new app container with the following structure::
+
+ myappname
+   ├── source/
+ ├─────── static/
+ ├── README.md
+   └── settings.yml
+
+Inside that folder, run the development server with::
+
+ $ clay run
+
+and your site'll be available at ``http://0.0.0.0:8080/``.
+
+Anything you put under ``source`` will be render as a page. For instance,
+``source/page.html`` will be visible at::
+
+ http://0.0.0.0:8080/page.html
+
+and ``source/foo/bar.json`` at::
+
+ http://0.0.0.0:8080/foo/bar.json
+
+
+To generate a static version of your site, stop the server (with
+``Control + C``) and run::
+
+ $ clay build
+
+and all the templates will be processed and the result stored inside the
+``build`` folder.
+
+
+How to install
+--------------
+
+Just run::
+
+ sudo pip install clay
+
+and you're ready to go.
+
+
+Happy coding!
16 clay/__init__.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+"""
+# Clay
+
+A rapid prototyping tool
+
+------------
+© by [Lúcuma] (http://lucumalabs.com).
+See `AUTHORS.md` for more details.
+License: [MIT License] (http://www.opensource.org/licenses/mit-license.php).
+
+"""
+from main import Clay
+
+
+__version__ = '2.0'
56 clay/helpers.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+from datetime import datetime
+import errno
+import io
+import os
+
+from flask import request
+import jinja2
+from werkzeug.local import LocalProxy
+
+
+class Render(object):
+ """A thin wrapper arround Jinja2.
+ """
+
+ default_globals = {
+ 'now': LocalProxy(datetime.utcnow),
+ 'enumerate': enumerate,
+ 'request': request,
+ }
+
+ def __init__(self, loader, **kwargs):
+ kwargs.setdefault('autoescape', True)
+ env = jinja2.Environment(loader=loader, **kwargs)
+ env.globals.update(self.default_globals)
+ self.env = env
+
+ def render(self, tmpl, context=None):
+ context = context or {}
+ return tmpl.render(context)
+
+ def __call__(self, filename, context=None):
+ tmpl = self.env.get_template(filename)
+ return self.render(tmpl, context)
+
+ # def from_string(self, source, context=None):
+ # tmpl = self.env.from_string(source)
+ # return self.render(tmpl, context)
+
+
+def make_dirs(*lpath):
+ path = os.path.join(*lpath)
+ try:
+ os.makedirs(path)
+ except (OSError), e:
+ if e.errno != errno.EEXIST:
+ raise
+ return path
+
+
+def create_file(path, content, encoding='utf8'):
+ if not isinstance(content, unicode):
+ content = unicode(content, encoding)
+ with io.open(path, 'w+t', encoding=encoding) as f:
+ f.write(content)
+
125 clay/main.py
@@ -0,0 +1,125 @@
+# -*- coding: utf-8 -*-
+import mimetypes
+from os.path import isfile, isdir, dirname, join, splitext, split
+
+from flask import Flask, send_file, make_response
+from jinja2 import ChoiceLoader, FileSystemLoader, PackageLoader, TemplateNotFound
+
+from helpers import Render, make_dirs, create_file
+
+
+SOURCE_DIRNAME = 'source'
+BUILD_DIRNAME = 'build'
+TMPL_EXTS = ('.html', '.tmpl')
+HTTP_NOT_FOUND = 404
+
+DEFAULT_HOST = '0.0.0.0'
+DEFAULT_PORT = 5000
+
+
+class Clay(object):
+
+ def __init__(self, root, settings=None):
+ settings = settings or {}
+ self.settings = settings
+
+ if isfile(root):
+ root = dirname(root)
+ self.source_dir = join(root, SOURCE_DIRNAME)
+ self.build_dir = join(root, BUILD_DIRNAME)
+ self.app = self.make_app()
+ self.render = self.make_renderer()
+
+ def get_full_source_path(self, path):
+ return join(self.source_dir, path)
+
+ def get_full_build_path(self, path):
+ return join(self.build_dir, path)
+
+ def get_real_fn(self, path):
+ head, tail = split(path)
+ if tail.endswith('.html'):
+ return tail
+ fn, ext = splitext(tail)
+ return fn
+
+ def guess_mimetype(self, fn):
+ return mimetypes.guess_type(fn)[0] or 'text/plain'
+
+ def normalize_path(self, path):
+ path = path or 'index.html'
+ if isdir(self.get_full_source_path(path)):
+ path = '/'.join([path, 'index.html'])
+ return path
+
+ def make_build_dir(self):
+ if not isdir(self.build_dir):
+ make_dirs(self.build_dir)
+
+
+ def make_app(self):
+ app = Flask('clay')
+ app.debug = True
+ self.add_default_urls(app)
+ self.set_not_found_handler(app)
+ return app
+
+ def make_renderer(self):
+ loader = self.make_jinja_loader()
+ render = Render(loader)
+ return render
+
+ def make_jinja_loader(self):
+ return ChoiceLoader([
+ FileSystemLoader(self.source_dir),
+ PackageLoader('clay', SOURCE_DIRNAME),
+ ])
+
+ def add_default_urls(self, app):
+ app.add_url_rule('/', 'page', self.render_page)
+ app.add_url_rule('/<path:path>', 'page', self.render_page)
+
+ def set_not_found_handler(self, app):
+ @app.errorhandler(HTTP_NOT_FOUND)
+ @app.errorhandler(TemplateNotFound)
+ def page_not_found(error):
+ res = self.render('notfound.html', self.settings)
+ return make_response(res, HTTP_NOT_FOUND)
+
+
+ def render_page(self, path=None):
+ path = self.normalize_path(path)
+
+ if not path.endswith(TMPL_EXTS):
+ return self.send_file(path)
+
+ res = self.render(path, self.settings)
+ response = make_response(res)
+ response.mimetype = self.guess_mimetype(self.get_real_fn(path))
+ return response
+
+ def send_file(self, path):
+ fp = self.get_full_source_path(path)
+ return send_file(fp)
+
+ def get_test_client(self):
+ self.app.testing = True
+ return self.app.test_client()
+
+ def build_page(self, path):
+ self.make_build_dir()
+ content = self.render(path)
+ fp = self.get_full_build_path(path)
+ create_file(fp, content)
+
+ def run(self, host=DEFAULT_HOST, port=DEFAULT_PORT, _test=False):
+ config = {
+ 'host': host or self.settings.get('host', DEFAULT_HOST),
+ 'port': port or self.settings.get('port', DEFAULT_PORT),
+ 'use_debugger': True,
+ 'use_reloader': False,
+ }
+ if _test:
+ return config
+ self.app.run(**config)
+
72 clay/source/notfound.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html public "display of awesomeness">
+<html>
+<head>
+<meta charset="utf-8">
+<title>Page not found</title>
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<link rel="shortcut icon" type="image/x-icon" href="/static/favicon.ico">
+<style>
+@import url(http://fonts.googleapis.com/css?family=Pacifico);
+
+body {
+font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+font-size: 26px;
+line-height: 1.5;
+color: #333;
+background: #fefefe;
+}
+
+div.page {
+margin: 0 auto;
+position:relative;
+width: 600px;
+}
+
+h1 {
+font-family: 'Pacifico', Georgia, serif;
+font-size: 90px;
+font-weight: normal;
+color: #a3312d;
+letter-spacing: -1px;
+line-height: 1;
+margin: 110px 0 50px;
+border: 20px;
+text-align: left;
+}
+
+a, a:visited {
+color: #a3312d;
+border-bottom: 1px dotted;
+text-decoration: none;
+}
+
+a:hover, a:focus, a:active {
+color: #932c28;
+border-bottom: 2px solid;
+margin-bottom: -1px;
+}
+
+.✓ {
+display: block;
+text-align: center;
+margin-top: 90px;
+padding: 5px 0;
+font-size: 12px;
+color: #999;
+}
+</style>
+</head>
+<body>
+<div class="page">
+ <h1>Page not found</h1>
+
+ <p>Sorry, the page you requested may have been moved or deleted.</p>
+ <p>Let's try going back to the <a href="/">home page</a> and look from there.</p>
+</div>
+
+<div class="">
+ Powered by <a href="http://lucuma.github.com/Clay">Clay</a>
+</div>
+<!-- not found -->
+</body>
+</html>
13 requirements.txt
@@ -0,0 +1,13 @@
+# This file collects all required third-party applications that are needed
+# to run this project. Later you can install all these apps in a row
+# using pip. Example:
+#
+# pip install -r requirements.txt
+
+Flask==0.9
+Jinja2==2.6
+Werkzeug==0.8.3
+
+# markdown
+# pygments
+# pyscss
90 setup.py
@@ -0,0 +1,90 @@
+# -*- coding: utf-8 -*-
+import io
+import os
+import re
+from setuptools import setup
+
+
+PACKAGE = 'clay'
+THIS_DIR = os.path.dirname(__file__).rstrip('/')
+
+
+def get_path(*args):
+ return os.path.join(THIS_DIR, *args)
+
+
+def read_from(filepath):
+ with io.open(filepath, 'rt', encoding='utf-8') as f:
+ source = f.read()
+ return source
+
+
+def get_version():
+ data = read_from(get_path(PACKAGE, '__init__.py'))
+ version = re.search(r"__version__\s*=\s*'([^']+)'", data).group(1)
+ return version.encode('utf-8')
+
+
+def find_package_data(root, include_files=None):
+ include_files = include_files or ['.gitignore',]
+ files = []
+ src_root = get_path(root).rstrip('/') + '/'
+ for dirpath, subdirs, filenames in os.walk(src_root):
+ path, dirname = os.path.split(dirpath)
+ if dirname.startswith(('.', '_')):
+ continue
+ dirpath = dirpath.replace(src_root, '')
+ for filename in filenames:
+ is_valid_filename = not (
+ filename.startswith('.') or
+ filename.endswith('.pyc')
+ )
+ include_it_anyway = filename in include_files
+
+ if is_valid_filename or include_it_anyway:
+ files.append(os.path.join(dirpath, filename))
+ return files
+
+
+def find_packages_data(*roots):
+ return dict([(root, find_package_data(root)) for root in roots])
+
+
+def get_requirements():
+ data = read_from(get_path('requirements.txt'))
+ lines = map(lambda s: s.strip(), data.splitlines())
+ return [l for l in lines if l and not l.startswith('#')]
+
+
+def run_tests():
+ import sys, subprocess
+ errno = subprocess.call([sys.executable, 'runtests.py'])
+ raise SystemExit(errno)
+
+
+setup(
+ name = 'Clay',
+ version = get_version(),
+ author = 'Juan-Pablo Scaletti',
+ author_email = 'juanpablo@lucumalabs.com',
+ packages = [PACKAGE],
+ package_data = find_packages_data(PACKAGE, 'tests'),
+ zip_safe = False,
+ url = 'http://github.com/lucuma/Clay',
+ license = 'MIT license (http://www.opensource.org/licenses/mit-license.php)',
+ description = 'A rapid prototyping tool',
+ long_description = read_from(get_path('README.rst')),
+ install_requires = get_requirements(),
+ classifiers = [
+ 'Development Status :: 4 - Beta',
+ 'Environment :: Web Environment',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: MIT License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+ 'Topic :: Software Development :: Libraries :: Python Modules'
+ ],
+ test_suite = '__main__.runtests',
+ entry_points = {'console_scripts': ['clay = clay.manage:main']},
+)
0  tests/__init__.py
No changes.
38 tests/helpers.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+import io
+from os.path import dirname, join, isdir
+import pytest
+
+from clay import Clay
+from clay.helpers import make_dirs, create_file
+
+
+HTTP_OK = 200
+HTTP_NOT_FOUND = 404
+SOURCE_DIR = join(dirname(__file__), 'source')
+BUILD_DIR = join(dirname(__file__), 'build')
+
+
+@pytest.fixture(scope="module")
+def c():
+ root = dirname(__file__)
+ return Clay(root)
+
+
+@pytest.fixture(scope="module")
+def t(c):
+ return c.get_test_client()
+
+
+def get_source_path(path):
+ return join(SOURCE_DIR, path)
+
+
+def get_build_path(path):
+ return join(BUILD_DIR, path)
+
+
+def read_content(path, encoding='utf8'):
+ with io.open(path, 'r', encoding=encoding) as f:
+ return f.read().encode(encoding)
+
64 tests/test_build.py
@@ -0,0 +1,64 @@
+# -*- coding: utf-8 -*-
+import shutil
+
+from tests.helpers import *
+
+
+def setup_module():
+ try:
+ shutil.rmtree(SOURCE_DIR)
+ except OSError:
+ pass
+ try:
+ shutil.rmtree(BUILD_DIR)
+ except OSError:
+ pass
+ make_dirs(SOURCE_DIR)
+
+
+def teardown_module():
+ try:
+ shutil.rmtree(SOURCE_DIR)
+ shutil.rmtree(BUILD_DIR)
+ except OSError:
+ pass
+
+
+def test_build_dir_is_made(c):
+ name = 'test.html'
+ create_file(get_source_path(name), u'')
+ try:
+ shutil.rmtree(BUILD_DIR)
+ except OSError:
+ pass
+ c.build_page(name)
+ assert isdir(BUILD_DIR)
+
+
+def test_make_dirs_wrong():
+ with pytest.raises(OSError):
+ make_dirs('/etc/bla')
+
+
+def test_build_file(c):
+ name = 'main.css'
+ content = """
+ .icon1 { background-image: url('img/icon1.png'); }
+ .icon2 { background-image: url('img/icon2.png'); }
+ .icon3 { background-image: url('img/icon3.png'); }
+ .icon4 { background-image: url('img/icon4.png'); }
+ """
+ create_file(get_source_path(name), content)
+ c.build_page(name)
+ result = read_content(get_build_path(name))
+ assert result.strip() == content.strip()
+
+
+def test_build_page(c):
+ name = 'foo.html'
+ create_file(get_source_path(name), u'foo{% include "bar.html" %}')
+ create_file(get_source_path('bar.html'), u'bar')
+ c.build_page(name)
+ result = read_content(get_build_path(name))
+ assert result.strip() == 'foobar'
+
128 tests/test_main.py
@@ -0,0 +1,128 @@
+# -*- coding: utf-8 -*-
+import shutil
+
+from tests.helpers import *
+
+
+HTML_PAGE = u'<!DOCTYPE html><html><head><title></title></head><body></body></html>'
+TEXT = u'''Je suis belle, ô mortels! comme un rêve de pierre,
+Et mon sein, où chacun s'est meurtri tour à tour,
+Est fait pour inspirer au poète un amour'''
+
+
+def setup_module():
+ try:
+ shutil.rmtree(SOURCE_DIR)
+ except OSError:
+ pass
+ make_dirs(SOURCE_DIR)
+
+
+def teardown_module():
+ try:
+ shutil.rmtree(SOURCE_DIR)
+ except OSError:
+ pass
+
+
+def assert_page(t, name, content=HTML_PAGE, url=None, encoding='utf8'):
+ content = content.encode(encoding)
+ path = get_source_path(name)
+ create_file(path, content, encoding=encoding)
+ url = url or '/' + name
+ resp = t.get(url)
+ assert resp.status_code == HTTP_OK
+ assert content == resp.data
+
+
+def test_setup_with_filename_as_root():
+ assert Clay(__file__)
+
+
+def test_render_page(t):
+ assert_page(t, 'index.html')
+
+
+def test_index_page(t):
+ assert_page(t, 'index.html', url='/')
+
+
+def test_render_sub_page(t):
+ make_dirs(join(SOURCE_DIR, 'sub'))
+ assert_page(t, 'sub/index.html')
+
+
+def test_render_sub_index_page(t):
+ make_dirs(join(SOURCE_DIR, 'sub'))
+ assert_page(t, 'sub/index.html', url='sub')
+
+
+def test_do_not_process_non_html_or_tmpl_files(t):
+ assert_page(t, 'main.js', "/* {% foobar %} */")
+
+
+def test_i18n_filename(t):
+ assert_page(t, 'mañana.txt', TEXT)
+
+
+def test_weird_encoding_of_content(t):
+ assert_page(t, 'iso-8859-1.txt', TEXT, encoding='iso-8859-1')
+
+
+def test_process_template_files(t):
+ content = """
+ {% for i in range(1,5) -%}
+ .icon{{ i }} { background-image: url('img/icon{{ i }}.png'); }
+ {% endfor -%}
+ """
+ expected = """
+ .icon1 { background-image: url('img/icon1.png'); }
+ .icon2 { background-image: url('img/icon2.png'); }
+ .icon3 { background-image: url('img/icon3.png'); }
+ .icon4 { background-image: url('img/icon4.png'); }
+ """
+ path = get_source_path('main.css.tmpl')
+ create_file(path, content)
+ resp = t.get('/main.css.tmpl')
+ assert resp.status_code == HTTP_OK
+ assert resp.mimetype == 'text/css'
+ assert resp.data.strip() == expected.strip()
+
+
+def test_page_with_includes(t):
+ create_file(get_source_path('foo.html'), u'foo{% include "bar.html" %}')
+ create_file(get_source_path('bar.html'), u'bar')
+ resp = t.get('/foo.html')
+ assert resp.status_code == HTTP_OK
+ assert resp.data == 'foobar'
+
+
+def test_notfound_page(t):
+ resp = t.get('/lalalala.html')
+ assert resp.status_code == HTTP_NOT_FOUND
+ assert '<title>Page not found</title>' in resp.data
+
+
+def test_settings_as_template_context():
+ root = dirname(__file__)
+ c = Clay(root, {'who': u'world'})
+ t = c.get_test_client()
+ create_file(get_source_path('hello.html'), u'Hello {{ who }}!')
+ resp = t.get('/hello.html')
+ assert resp.status_code == HTTP_OK
+ assert resp.data == 'Hello world!'
+
+
+def test_can_run(c):
+ config = c.run(_test=True)
+ assert config['use_debugger']
+ assert not config['use_reloader']
+
+
+def test_run_with_custom_host_and_port(c):
+ host = 'localhost'
+ port = 9000
+ config = c.run(host=host, port=port, _test=True)
+ assert host == config['host']
+ assert port == config['port']
+
Please sign in to comment.
Something went wrong with that request. Please try again.